Rederizar com imagem de fundo no Blender

E ai pessoal,

Eu estou tentando renderizar alguns modelos 3D no Blender só que não entendo nada de 3D nem de Blender. Fui pesquisando e vendo exemplos e consegui montar um script que faz o que eu quero, mas falta um detalhe, adicionar uma imagem ao fundo do modelo renderizado, os exemplos que achei são de versões antigas, a api mudou para o Blender 2.80.
Alguém ai manja de Blender e tem alguma ideia de como fazer isso?

olá,

você pode fazer isso de duas formas, usar um plano com a imagem que você quer utilizar

ou

renderiza com o BG transparente. => menu de render => Film => marca a opção transparent

Muito obrigado,
Você sabe o comando em python que cria o plano com imagem pro blender 2.80, só consigo achar exemplos para versões antigas que não funcionam mais.
Como plano B, vou olhar como coloca a BG transparente em python, parece ser a opção mais simples, depois coloco o plano de fundo com outro script.

não sei nada de programação kkkkkk
faço de forma tradicional mesmo, criando o objeto e aplicando textura.

tem um add-on que gera o plano assim que você arrasta a imagem para o blender

Vlw. É que tenho que renderizar milhares de vistas de vários objetos, ai acho difícil de fazer isso pela interface.

Consegui fazer renderizando com fundo transparente e adicionando o fundo com a biblioteca PIL em Python. Seria mais prático fazer tudo logo no Blender mas funcionou bem assim.

Se puder compartilhar o código seria muito interessante, tenho interesse em automação para o Blender!

Colei o código ai, é uma coleção de funções que peguei prontas na internet com algumas poucas que criei.
Ele carrega o objeto, rotaciona e centraliza o objeto. Então cria uma câmera que é rotacionada e elevada no entorno do objeto para múltiplas renderizações. Além disso, tem uma função que calcula as bounding boxes do objeto, já que o objetivo dessa renderização é treinar modelos de detecção de objetos

import bpy
import sys
import os
import math
from mathutils import Vector
import mathutils
from os.path import join as pjoin
import random

def get_center(o):
    local_bbox_center = 0.125 * sum((Vector(b) for b in o.bound_box), Vector())
    global_bbox_center = o.matrix_world @ local_bbox_center
    
    return global_bbox_center

def clear():
    # Select objects by type
    for o in bpy.context.scene.objects:
        o.select_set(True)
    # Call the operator
    bpy.ops.object.delete()
    

def update_camera(camera, focus_point=mathutils.Vector((0.0, 0.0, 0.0)), distance=10.0):
    """
    Focus the camera to a focus point and place the camera at a specific distance from that
    focus point. The camera stays in a direct line with the focus point.

    :param camera: the camera object
    :type camera: bpy.types.object
    :param focus_point: the point to focus on (default=``mathutils.Vector((0.0, 0.0, 0.0))``)
    :type focus_point: mathutils.Vector
    :param distance: the distance to keep to the focus point (default=``10.0``)
    :type distance: float
    """
    looking_direction = camera.location - focus_point
    rot_quat = looking_direction.to_track_quat('Z', 'Y')

    camera.rotation_euler = rot_quat.to_euler()
    new_position = camera.location + rot_quat @ mathutils.Vector((0.0, 0.0, distance))
    camera.location = new_position
    
    return new_position


def create_lamp(point, energy=1000):
    lamp_data = bpy.data.lights.new(name="Lamp", type='POINT')
    lamp_data.energy = energy
    lamp_object = bpy.data.objects.new(name="Lamp", object_data=lamp_data)
    bpy.context.collection.objects.link(lamp_object)
    bpy.context.view_layer.objects.active = lamp_object
    lamp_object.location = point
    
    return lamp_object

def load_stl(filepath):
    bpy.ops.import_mesh.stl(filepath=filepath)
    obj = bpy.context.object
    
    return obj

def center_rotate_obj(obj, angles):
    bpy.context.scene.cursor.location = get_center(obj)
    bpy.ops.object.origin_set(type='ORIGIN_CURSOR')
    obj.location=(0,0,0)
    obj.rotation_euler = angles
    

class Box:

    dim_x = 1
    dim_y = 1

    def __init__(self, min_x, min_y, max_x, max_y, dim_x=dim_x, dim_y=dim_y):
        self.min_x = min_x
        self.min_y = min_y
        self.max_x = max_x
        self.max_y = max_y
        self.dim_x = dim_x
        self.dim_y = dim_y

    @property
    def x(self):
        return round(self.min_x * self.dim_x)

    @property
    def y(self):
        return round(self.dim_y - self.max_y * self.dim_y)

    @property
    def width(self):
        return round((self.max_x - self.min_x) * self.dim_x)

    @property
    def height(self):
        return round((self.max_y - self.min_y) * self.dim_y)

    def __str__(self):
        return "<Box, x=%i, y=%i, width=%i, height=%i>" % \
               (self.x, self.y, self.width, self.height)

    def to_tuple(self):
        if self.width == 0 or self.height == 0:
            return (0, 0, 0, 0)
        return (self.x, self.y, self.width, self.height)
    
    
def camera_view_bounds_2d(scene, cam_ob, me_ob):
    """
    Returns camera space bounding box of mesh object.

    Negative 'z' value means the point is behind the camera.

    Takes shift-x/y, lens angle and sensor size into account
    as well as perspective/ortho projections.

    :arg scene: Scene to use for frame size.
    :type scene: :class:`bpy.types.Scene`
    :arg obj: Camera object.
    :type obj: :class:`bpy.types.Object`
    :arg me: Untransformed Mesh.
    :type me: :class:`bpy.types.Mesh´
    :return: a Box object (call its to_tuple() method to get x, y, width and height)
    :rtype: :class:`Box`
    """

    mat = cam_ob.matrix_world.normalized().inverted()
    #me = me_ob.to_mesh(scene, True, 'PREVIEW')
    me = me_ob.to_mesh()
    me.transform(me_ob.matrix_world)
    me.transform(mat)

    camera = cam_ob.data
    frame = [-v for v in camera.view_frame(scene=scene)[:3]]
    camera_persp = camera.type != 'ORTHO'

    lx = []
    ly = []

    for v in me.vertices:
        co_local = v.co
        z = -co_local.z

        if camera_persp:
            if z == 0.0:
                lx.append(0.5)
                ly.append(0.5)
            # Does it make any sense to drop these?
            #if z <= 0.0:
            #    continue
            else:
                frame = [(v / (v.z / z)) for v in frame]

        min_x, max_x = frame[1].x, frame[2].x
        min_y, max_y = frame[0].y, frame[1].y

        x = (co_local.x - min_x) / (max_x - min_x)
        y = (co_local.y - min_y) / (max_y - min_y)

        lx.append(x)
        ly.append(y)

    min_x = clamp(min(lx), 0.0, 1.0)
    max_x = clamp(max(lx), 0.0, 1.0)
    min_y = clamp(min(ly), 0.0, 1.0)
    max_y = clamp(max(ly), 0.0, 1.0)

    #bpy.data.meshes.remove(me)

    r = scene.render
    fac = r.resolution_percentage * 0.01
    dim_x = r.resolution_x * fac
    dim_y = r.resolution_y * fac

    return Box(min_x, min_y, max_x, max_y, dim_x, dim_y)


def clamp(x, minimum, maximum):
    return max(minimum, min(x, maximum))

def custom2yolo(data):
    """Convert yolo format to voc, both in relative coordinates."""
    yolo = []
    bbox_width = float(data[3]) 
    bbox_height = float(data[4])
    x0 = float(data[1])
    y0 = float(data[2])
    yolo.append(data[0])
    yolo.append(x0 + (bbox_width / 2))
    yolo.append(y0 + (bbox_height / 2))
    yolo.append(bbox_width)
    yolo.append(bbox_height)
    
    return yolo

def convertbb(bb):
    return bb[0] / 800.0, bb[1]/600.0, bb[2]/800.0, bb[3]/600.0

def render_scene_bb(scene, cam_ob, obj, obj_number, prefix):
    
    scene.render.resolution_x = 800
    scene.render.resolution_y = 600
    bpy.context.scene.render.engine = 'CYCLES'
    bpy.context.scene.render.image_settings.file_format='PNG'
    bpy.context.scene.render.film_transparent = True
    bpy.context.scene.render.image_settings.color_mode = 'RGBA'
    bpy.data.scenes['Scene'].render.filepath = prefix + '.png'
    bpy.ops.render.render(write_still=True)
    
    
    filepath = prefix + '.txt'
    coords = camera_view_bounds_2d(scene, cam_ob, obj).to_tuple()
    coords = list(convertbb(coords))
    coords.insert(0, obj_number)
    coords = custom2yolo(coords)
    print(coords)
    with open(filepath, "w") as file:
        #file.write("%f %f %f %f\n" % convertbb(camera_view_bounds_2d(scene, cam_ob, obj).to_tuple()))
        file.write("%i %f %f %f %f\n" % tuple(coords))


##########################################################################################################

is_test = True
root_dir = '/path/to/root'
parts_folder = pjoin(root_dir, 'stl_models')
output_folder = pjoin(root_dir, 'rendered_images')

random.seed(234)


parts = [
            {
            'name': 'tray', 
            'path': pjoin(parts_folder, 'tray.STL'),
            'rot': (0, 0, 0),
            'dists': [280, 560, 1000]
            },
            {
            'name': 'dosing_nozzle', 
            'path': pjoin(parts_folder, 'dosing_nozzle.STL'),
            'rot': (math.pi/2, 0, 0),
            'dists': [200, 400, 600]
            },
            {
            'name': 'button_pad', 
            'path': pjoin(parts_folder, 'button_pad.STL'),
            'rot': (0, 0, 0),
            'dists': [600, 900, 1200]
            },
            {
            'name': 'part1', 
            'path': pjoin(parts_folder, 'part1.STL'),
            'rot': (-math.pi/2, 0, 0),
            'dists': [140, 280, 460]
            },
            {
            'name': 'part3',
            'path': pjoin(parts_folder, 'part3.STL'),
            'rot': (math.pi/2, 0, 0),
            'dists': [140, 280, 460]
            },
            {
            'name': 'part2', 
            'path': pjoin(parts_folder, 'part2.STL'),
            'rot': (math.pi/2, 0, 0),
            'dists': [140, 280, 460]
            }
        ]
 
for obj_number, part in enumerate(parts):
       
    clear()
    scene = bpy.context.scene

    #load and position mesh
    obj = load_stl(part['path'])
    center_rotate_obj(obj, part['rot'])

    energy = 10**6

    cam = bpy.data.cameras.new("Camera")
    cam_ob = bpy.data.objects.new("Camera", cam)
    bpy.context.collection.objects.link(cam_ob)
    scene.camera = cam_ob
    
    scene.camera.data.clip_end = 10000

    

    #write_bounds_2d(filepath, scene, cam_ob, obj, frame_start, frame_end)
    
    distances = part['dists']
    if is_test:
        vert_angles = (0, math.pi/6, math.pi/3)[:1]
        rot_angles = [2*math.pi/8 * x for x in range(8)][:1]
    else:
        vert_angles = (0, math.pi/6, math.pi/3)
        rot_angles = [2*math.pi/8 * x for x in range(8)]

    prefix = pjoin(output_folder, part['name'])
    i = 0
    n = 0
    for dist in distances:
        n += 1
        for vert_angle in vert_angles:
            for rot_angle in rot_angles:
                
                z = dist * math.sin(vert_angle)
                d = dist * math.cos(vert_angle)
                x = d * math.cos(rot_angle)
                y = d * math.sin(rot_angle)
                
                l_z = 10 * math.sin(vert_angle)
                l_d = 10 * math.cos(vert_angle)
                l_x = l_d * math.cos(rot_angle)
                l_y = l_d * math.sin(rot_angle)
                
                cam_ob.location = (x, y, z)
                pos = update_camera(cam_ob, Vector((0, 0, 0)))
                lamp = create_lamp(Vector((0, 0, 0)), energy)
                lamp.location = pos + Vector((l_x, l_y, l_z))
                fprefix = pjoin(prefix, str(i))
                
                render_scene_bb(scene, cam_ob, obj, obj_number, fprefix)
                
                i += 1
``
1 Curtida