아래 코드는 blender에서 camera가 obj를 중심으로 circle path로 회전하며, 3D obj에서 2D 이미지를 rendering하는 코드이다. 이미지를 rendering할 때 필요한 함수를 크게 8개로 구성했다.
bound_box_center(target)
대상 객체의 경계 상자(bounding box) 중심을 계산하고, 이 함수는 객체의 모든 꼭짓점을 월드 좌표계로 변환한 후, 이 꼭짓점들의 평균 위치를 구하여 경계 상자의 중심점을 찾는다.
character_center(target)
대상 객체의 경계 상자 중심을 계산하고, 이 중심의 높이(z 좌표)만 이용하고 나머지 x와 y 좌표는 0으로 설정해 캐릭터의 중심점을 정의한다.
bound_box_render(target)
대상 객체의 경계 상자를 시각적으로 표시하기 위한 메시(mesh) 객체를 생성하고, 경계 상자의 모든 꼭짓점을 사용해 와이어프레임(wireframe) 형태의 bound-box를 만든다.
camera_setting(camera, center)
카메라를 특정 위치(center)를 바라보도록 설정한다. "TRACK_TO" 제약 조건을 사용해 카메라가 지정된 빈 객체(empty object)를 향하도록 하고, empty object는 chracter center 또는 bound box center에 배치할 수 있다.
rendering_img(r, d, interval, empty)
카메라를 이용해 지정된 각도와 간격으로 여러 각도에서 렌더링을 수행하며, 주어진 중심점을 중심으로 카메라를 원형 경로로 이동시키면서 이미지를 렌더링한다.
light_setting(point_light_radius, num_lights, target, point_light_intensity, sun_light_intensity)
Scene에 포인트 라이트(point light)와 선 라이트(sun light)를 추가해 대상 객체를 조명한다. point light의 조명 개수를 지정할 수 있고, point light과 sun light의 강도 또한 설정할 수 있다.
background_setting(R, G, B, A)
Scene의 배경 색상을 설정하고, world background의 색상과 투명도를 조정할 수 있다.
material_setting(target, specular_rate, roughness_rate)
대상 객체의 재질(material) 설정을 조정하며, 대상 객체의 재질의 광택(specular, 빛에 반사되는 정도)과 거칠기(roughness) 값을 설정해 재질의 외관을 변경한다.
Python Script Code
import bpy
import math
from mathutils import Vector
###################### bound box center ######################
def bound_box_center(target):
# character's bounding box
bound_box = target.bound_box
# bounding box has 8 corners
# 월드 좌표계에서 경계 상자의 꼭짓점 좌표를 계산
world_vertices = [target.matrix_world @ Vector(corner) for corner in bound_box]
# 꼭짓점 좌표 출력(order: vector(x, y, z))
for idx, vertex in enumerate(world_vertices):
print(f"Corner {idx}: {vertex}")
# sum whole corner's location and divide to 8
char_bound_box_center = sum((Vector(v) for v in bound_box), Vector()) / 8
# bound box center point in world coordinate
world_center = target.matrix_world @ char_bound_box_center
print("bound box center:", world_center)
return world_center
###################### character center ######################
def character_center(target):
# character's bounding box
bound_box = target.bound_box
# bounding box has 8 corners
# 월드 좌표계에서 경계 상자의 꼭짓점 좌표를 계산
world_vertices = [target.matrix_world @ Vector(corner) for corner in bound_box]
# 꼭짓점 좌표 출력(order: vector(x, y, z))
for idx, vertex in enumerate(world_vertices):
print(f"Corner {idx}: {vertex}")
# sum whole corner's location and divide to 8
char_bound_box_center = sum((Vector(v) for v in bound_box), Vector()) / 8
# bound box center point in world coordinate
world_center = target.matrix_world @ char_bound_box_center
char_center = (0, 0, world_center[2]+0.2)
print("character center:", char_center)
return char_center
###################### render bound box ######################
def bound_box_render(target):
# define bound box name
bound_box_name = target.name + "_BoundingBox"
# 기존 BoundingBox가 존재하는지 검사하고 삭제
if bound_box_name in bpy.data.objects:
bpy.data.objects.remove(bpy.data.objects[bound_box_name], do_unlink=True)
# 대상 객체의 Bounding Box의 8개 꼭짓점을 월드 좌표계로 변환
world_vertices = [target.matrix_world @ Vector(corner) for corner in target.bound_box]
# make mesh data - include (vertex, edge, face)
mesh_data = bpy.data.meshes.new(bound_box_name)
# make mesh object - include object's location, rotation, scale, translation
mesh_obj = bpy.data.objects.new(bound_box_name, mesh_data)
# 씬에 메시 객체 추가
bpy.context.collection.objects.link(mesh_obj)
# 메시 데이터에 꼭짓점(버텍스)와 면(페이스) 추가
edges = [(0, 1), (1, 2), (2, 3), (3, 0),
(4, 5), (5, 6), (6, 7), (7, 4),
(0, 4), (1, 5), (2, 6), (3, 7)]
faces = []
mesh_data.from_pydata(world_vertices, edges, faces)
# 메시 업데이트
mesh_data.update()
# 생성된 메시 객체를 선택하고 원본 객체의 위치로 이동
mesh_obj.select_set(True)
bpy.context.view_layer.objects.active = mesh_obj
# 뷰포트에서 경계 상자를 볼 수 있도록 객체의 디스플레이 타입 설정
mesh_obj.display_type = 'WIRE'
###################### camera setting ######################
def camera_setting(camera, center):
# 카메라가 Bounding Box or Character의 중심을 바라보게 설정
# add empty object at center
empty_obj = bpy.data.objects.get('CenterEmpty')
if empty_obj:
bpy.data.objects.remove(empty_obj, do_unlink=True)
bpy.ops.object.empty_add(type='PLAIN_AXES', location=center)
empty = bpy.context.object
empty.name = 'CenterEmpty'
# TRACK_TO 제약 조건 추가
# 기존에 'TRACK_TO' 제약 조건이 있다면 제거
track_constraints = [c for c in camera.constraints if c.type == 'TRACK_TO']
for c in track_constraints:
camera.constraints.remove(c)
# 새 'TRACK_TO' 제약 조건 추가(track to: 카메라가 특정 대상을 바라보도록 강제합니다)
track_to = camera.constraints.new(type='TRACK_TO')
track_to.target = empty
# 카메라가 대상을 바라보는 조건 정의
track_to.up_axis = 'UP_Y'
track_to.track_axis = 'TRACK_NEGATIVE_Z'
return empty
###################### rendering ######################
def rendering_img(r, d, interval, empty):
# 렌더링 설정
bpy.context.scene.render.image_settings.file_format = 'PNG'
render = bpy.context.scene.render
render.resolution_x = 1024
render.resolution_y = 1024
if empty == empty1:
# 원의 중심, 반지름, 각도 단위
circle_center = empty1.location
file_name = 'bound_box'
else:
circle_center = empty2.location
file_name = 'character'
radius = r
degrees = d
# 각도마다 카메라 위치 계산 및 렌더링
for angle in range(0, degrees, interval): # 18도 간격으로 회전
radians = math.radians(angle)
# 원의 경로 상에서 카메라의 위치 계산
x = circle_center[0] + radius * math.cos(radians)
y = circle_center[1] + radius * math.sin(radians)
z = circle_center[2]
# 카메라 위치 설정
camera.location = (x, y, z)
# 렌더링을 위한 파일 경로 설정
bpy.context.scene.render.filepath = f'C:/Users/user/Desktop/blender_python/circle_path/camera_circle_path/{file_name}_center/image_{angle}.png'
# 렌더링 실행
bpy.ops.render.render(write_still=True)
###################### light setting ######################
def light_setting(point_light_radius, num_lights, target, point_light_intensity, sun_light_intensity):
# 씬 내의 모든 객체를 순회
for obj in bpy.context.scene.objects:
# 객체가 라이트인 경우
if obj.type == 'LIGHT':
# 라이트의 유형이 포인트(Point) 또는 선(Sun)인 경우
if obj.data.type == 'POINT' or obj.data.type == 'SUN':
# 해당 라이트 객체 삭제
bpy.data.objects.remove(obj, do_unlink=True)
# 360도를 라이트의 수로 나누어 각 라이트의 각도를 계산
angle_step = 2 * math.pi / num_lights
for i in range(num_lights):
# 각 라이트의 위치 계산
angle = angle_step * i
x = target.location.x + point_light_radius * math.cos(angle)
y = target.location.y + point_light_radius * math.sin(angle)
z = target.location.z + 1.0 # 높이 조정
# 포인트 라이트 생성
bpy.ops.object.light_add(type='POINT', location=(x, y, z))
point_light = bpy.context.object
point_light.data.energy = point_light_intensity # 포인트 라이트의 강도 설정
# 선(Sun) 라이트 추가
bpy.ops.object.light_add(type='SUN', location=(target.location.x, target.location.y, target.location.z + 20))
sun_light = bpy.context.object
sun_light.data.energy = sun_light_intensity # 선 라이트의 강도 설정
sun_light.rotation_euler = (math.radians(45), math.radians(30), math.radians(0)) # 선 라이트의 방향 조정
###################### background setting ######################
def background_setting(R, G, B, A):
# 월드 설정에 접근
world = bpy.context.scene.world
if world.use_nodes:
# 노드 유형이 'BACKGROUND'인 노드를 찾기
bg_node = next(node for node in world.node_tree.nodes if node.type == 'BACKGROUND')
# 노드의 색상을 흰색으로 설정
bg_node.inputs['Color'].default_value = (R, G, B, A) # RGBA, A는 투명도를 나타냄
# 배경 밝기를 설정 (기본값은 1)
bg_node.inputs['Strength'].default_value = 1
###################### specular & roughness setting ######################
def material_setting(target, specular_rate, roughness_rate):
# Armature 오브젝트 찾기
target_obj = bpy.data.objects.get(target.name)
mat = None
# Armature 오브젝트가 존재하는지 확인
if target_obj:
# Armature 오브젝트의 자식 오브젝트들 순회
for child_obj in target_obj.children:
# 자식 오브젝트가 Mesh 타입인 경우
if child_obj.type == 'MESH':
print(child_obj.name) # Erin
if not child_obj.data.materials:
# 재질이 없는 경우 새 재질 생성 및 할당
mat = bpy.data.materials.new(name="New_Material")
child_obj.data.materials.append(mat)
else:
# 첫 번째 재질을 가져옴
mat = child_obj.data.materials[0]
print(mat)
# 재질이 'Principled BSDF' 셰이더 노드를 사용하는지 확인
if mat and mat.use_nodes and mat.node_tree.nodes:
nodes = mat.node_tree.nodes
principled = next((node for node in nodes if node.type == 'BSDF_PRINCIPLED'), None)
# Specular와 Roughness 값 설정
if principled and 'IOR Level' in principled.inputs:
principled.inputs['IOR Level'].default_value = specular_rate
if principled:
principled.inputs['Roughness'].default_value = roughness_rate
else:
print("can't find Principled BSDF shader node")
else:
print("material has no node tree")
# 카메라 객체
camera = bpy.data.objects['Camera']
# 렌더링할 대상 객체
target = bpy.data.objects['Casual_F_0039']
world_center = bound_box_center(target)
char_center = character_center(target) # bound box center의 height만 가져오기
print(world_center)
print(char_center)
bound_box_render(target)
# 포인트 라이트를 배치할 원의 반지름
point_light_radius = 5
# 원 주위에 배치할 포인트 라이트의 수
num_lights = 5
point_light_intensity = 200
sun_light_intensity = 1
light_setting(point_light_radius, num_lights, target, point_light_intensity, sun_light_intensity)
R, G, B, A = 1, 1, 1, 0
background_setting(R, G, B, A)
specular_rate = 1
roughness_rate = 0.2
material_setting(target, specular_rate, roughness_rate)
r = 4
d = 360
interval = 18
empty1 = camera_setting(camera, world_center)
print(empty1.location)
rendering_img(r, d, interval, empty1)
empty2 = camera_setting(camera, char_center)
print(empty2.location)
rendering_img(r, d, interval, empty2)
위 코드는 아래 사진처럼 blender의 python script에 붙여 넣어서 원하는 rendering 결과물을 얻을 수 있다.
(단, 객체는 사용자가 지정해야 함)
Output1
- bound box를 Camera center로 설정할 경우
Output2
- character를 Camera center로 설정할 경우
'실습 & 활동 > Computer vision' 카테고리의 다른 글
[Objaverse] 데이터셋 다운로드 (0) | 2024.03.25 |
---|---|
[PTAM] 실습 (0) | 2024.03.20 |
[Blender] Image to Video (PNG → mp4) (0) | 2024.03.12 |
[Blender] Camera spiral path python script 작성 (2) | 2024.03.12 |
Instant NeRF & Gaussian Splatting을 이용한 3D reconstruction 실습 (0) | 2024.03.04 |