아래 코드는 blender에서 camera가 obj를 중심으로 spiral path로 회전하며, 3D obj에서 2D 이미지를 rendering하는 코드이다. [Blender] Camera circle path python script 작성과 중복되는 함수가 여러개 존재한다. 하지만, camera가 spiral path를 따르기 때문에 rendering_img와 관련한 함수 위주로 참고하면 좋을 것 같다.
character_center(target)
대상 객체의 경계 상자 중심을 계산하고, 이 중심의 x와 z값만 이용하고 y 좌표는 0으로 설정해 캐릭터의 중심점을 정의한다.
camera_setting(camera, center)
먼저 기존에 CenterEmpty라 명명된 빈 객체가 있으면 제거하고, 대상의 중심 위치에 새로운 empty object를 추가한다. 그런 다음, 카메라에 TRACK_TO 제약 조건을 추가해 empty object를 추적하도록 설정한다. empty object는 (character_center[0], 0, 0)에 배치할 수 있다.
rendering_img(r, d, interval, empty)
카메라를 이용해 지정된 각도와 간격으로 여러 각도에서 렌더링을 수행하며, 주어진 중심점을 중심으로 카메라를 나선형 경로로 이동시키면서 이미지를 렌더링한다. 그리고 현재 코드는 camera의 높이가 증가하더라도 object를 수평하게 바라보게 구현한 코드이기 때문에 track to 제약이 걸린 empty object의 위치도 같이 증가하도록 구현했다.
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의 강도 또한 설정할 수 있다.
rendering_video(r, d, interval, height_increment, frame_per_second, empty)
연속된 이미지 대신 하나의 비디오 파일을 생성하는 코드이다.
material_setting(target, specular_rate, roughness_rate)
대상 객체의 재질(material) 설정을 조정하며, 대상 객체의 재질의 광택(specular, 빛에 반사되는 정도)과 거칠기(roughness) 값을 설정해 재질의 외관을 변경한다.
Python Script Code
import bpy
import math
from mathutils import Vector
###################### 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 = (world_center[0], 0, world_center[2])
print("character center:", char_center)
return char_center
###################### 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
if track_to:
# 카메라가 대상을 바라보는 조건 정의
track_to.up_axis = 'UP_Y'
track_to.track_axis = 'TRACK_NEGATIVE_Z'
return empty
###################### 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)) # 선 라이트의 방향 조정
###################### 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")
###################### rendering img ######################
def rendering_img(r, d, interval, height_increment, empty):
# 렌더링 설정
bpy.context.scene.render.image_settings.file_format = 'PNG'
render = bpy.context.scene.render
render.engine = 'BLENDER_EEVEE'
render.resolution_x = 1920
render.resolution_y = 1080
current_height = 0 # 시작 높이(angle에 따라 달라질 예정)
circle_center = empty.location
file_name = 'character'
radius = r
degrees = d
order = 0
# 각도마다 카메라 위치 계산 및 렌더링
for angle in range(0, degrees, interval): # 6도 간격으로 회전
radians = math.radians(angle)
# 원의 경로 상에서 카메라의 위치 계산
x = circle_center[0] + radius * math.cos(radians)
y = circle_center[1] + radius * math.sin(radians)
z = circle_center[2] + current_height
# 카메라 위치 설정
camera.location = (x, y, z)
# empty의 Z 좌표를 업데이트
# 카메라가 최대한 target과 수평을 이루도록 하기 위해 track to 제약이 걸린 empty의 범위를 angle이 증가할 때마다 똑같이 증가시켜주기
empty.location.z += height_increment
# 렌더링을 위한 파일 경로 설정
bpy.context.scene.render.filepath = f'C:/Users/user/Desktop/blender_python/spiral_path/camera_spiral_path/ex/{order}.png'
# 렌더링 실행
bpy.ops.render.render(write_still=True)
current_height += height_increment/4 # camera z location: 6도마다 높이 증가
order += 1 # PNG name
###################### rendering video ######################
def rendering_video(r, d, interval, height_increment, frame_per_second, empty):
# 렌더링 설정
render = bpy.context.scene.render
render.engine = 'CYCLES' # 렌더러 설정 (예: 'BLENDER_EEVEE', 'CYCLES')
render.resolution_x = 1920 # 해상도 X
render.resolution_y = 1080 # 해상도 Y
render.resolution_percentage = 100 # 해상도 백분율
render.fps = frame_per_second # 프레임 속도 (FPS)
render.image_settings.file_format = 'FFMPEG' # 파일 포맷
render.ffmpeg.format = 'MPEG4' # 비디오 포맷
render.ffmpeg.codec = 'H264' # 코덱
render.ffmpeg.constant_rate_factor = 'MEDIUM' # 품질 설정
# 프레임 계산
total_frames = int(d / interval)
bpy.context.scene.frame_start = 1
bpy.context.scene.frame_end = total_frames
current_height = 0 # 시작 높이(angle에 따라 달라질 예정)
circle_center = empty.location
radius = r
degrees = d
# 렌더링을 위한 파일 경로 설정
bpy.context.scene.render.filepath = f'C:/Users/user/Desktop/blender_python/spiral_path/camera_spiral_path/Scene1/video.mp4'
# 각도마다 카메라 위치 계산 및 렌더링
for frame in range(1, total_frames+1): # 6도 간격으로 회전
radians = math.radians(frame*interval)
# 원의 경로 상에서 카메라의 위치 계산
x = circle_center[0] + radius * math.cos(radians)
y = circle_center[1] + radius * math.sin(radians)
z = circle_center[2] + current_height
# 카메라 위치 설정
camera.location = (x, y, z)
camera.keyframe_insert(data_path="location", frame=frame)
# empty의 Z 좌표를 업데이트
# 카메라가 최대한 target과 수평을 이루도록 하기 위해 track to 제약이 걸린 empty의 범위를 angle이 증가할 때마다 똑같이 증가시켜주기
empty.location.z += height_increment
current_height += height_increment # 6도마다 0.05씩 높이 증가
# 애니메이션 렌더링
bpy.ops.render.render(animation=True)
# 카메라 오브젝트가 애니메이션 데이터를 가지고 있는지 확인
if camera.animation_data:
# 애니메이션 데이터가 있으면, 액션을 삭제
camera.animation_data_clear()
# 카메라 객체
camera = bpy.data.objects['Camera']
# 렌더링할 대상 객체
target = bpy.data.objects['Casual_F_0036']
char_center = character_center(target) # bound box center의 height만 가져오기
print(char_center)
print(char_center[2])
r = 2
d = 360 * 2
interval = 6
height_increment = (char_center[2] * 2 + 0.1) / 120
frame_per_second = 30
center = (char_center[0], 0, 0)
empty = camera_setting(camera, center)
print(empty.location)
# 포인트 라이트를 배치할 원의 반지름
point_light_radius = 5
# 원 주위에 배치할 포인트 라이트의 수
num_lights = 8
point_light_intensity = 500
sun_light_intensity = 1
light_setting(point_light_radius, num_lights, target, point_light_intensity, sun_light_intensity)
specular_rate = 0.2
roughness_rate = 0.5
material_setting(target, specular_rate, roughness_rate)
rendering_img(r, d, interval, height_increment, empty)
#rendering_video(r, d, interval, height_increment, frame_per_second, empty)
print('success')
위 코드는 아래 사진처럼 blender의 python script에 붙여 넣어서 원하는 rendering 결과물을 얻을 수 있다.
(단, 객체는 사용자가 지정해야 함)
Output Image
Output Video
'실습 & 활동 > Computer vision' 카테고리의 다른 글
[Objaverse] 데이터셋 다운로드 (0) | 2024.03.25 |
---|---|
[PTAM] 실습 (0) | 2024.03.20 |
[Blender] Image to Video (PNG → mp4) (0) | 2024.03.12 |
[Blender] Camera circle path python script 작성 (0) | 2024.03.12 |
Instant NeRF & Gaussian Splatting을 이용한 3D reconstruction 실습 (0) | 2024.03.04 |