이 문서는 무비 렌더 큐(Movie Render Queue) 의 후속 툴인 무비 렌더 그래프(Movie Render Graph) 로 스크립팅하는 방법을 다룹니다. 무비 렌더 그래프(MRG)는 MRQ 와 유사한 기능 세트를 제공하지만, 그래프 에디터에서 환경설정 파일을 지원합니다. 큐와 실행기의 기능은 두 시스템에서 같지만, 실제 무비 환경설정(Movie Config) 및 렌더링(Rendering)은 이제 서로 다른 환경설정 및 함수를 사용하는 두 시스템으로 나뉘었습니다.
실행 가능한 모든 스크립트와 추가 예시는 /Engine/Plugins/MovieScene/MovieRenderPipeline/ Content/Python/ 폴더의 MovieGraphEditorExample.py 및 MovieGraphEditorExampleHelpers.py 에서 찾을 수 있으며, 당분간 이 강좌 끝에 첨부되어 있을 것입니다. 이 문서에서는 Python 코드의 코드 스니펫을 참조하여 유의해야 할 무비 렌더 그래프의 새 콘셉트에 대해 논의하겠습니다.
전제조건
단순 자동화 예시
가장 단순하게 같은 샷을 자동화된 방식으로 반복해서 렌더링하려면 무비 파이프라인 큐(Movie Pipeline Queue)에서 렌더 작업을 특정 세팅으로 저장하면 됩니다. 큐는 렌더 작업을 정리하여 프리셋 세팅 및 작업 프로퍼티를 적용함으로써 렌더 작업 제작 및 관리를 간소화합니다.
무비 렌더 창(Movie Render Window)에서 렌더링할 작업을 추가하거나 큐를 로드합니다. 아직 하지 않았다면 세팅 열에서 '그래프로 대체(Replace with Graph)'를 선택하고 생성된 무비 그래프 세팅을 선택합니다. 무비 렌더 큐 창의 '무비 그래프 변수(Movie Graph Variables)' 섹션 아래에 노출된 'Custom Output Resolution' 변수가 보일 것입니다.
무비 그래프 환경설정 UAsset 생성하기
우선 예시 코드의 실행을 도울 예시 그래프를 생성합니다. 환경설정은 다음 스니펫을 실행하여 생성할 수 있는 디폴트 무비 렌더 그래프 에셋을 기반으로 합니다. 이는 /Game/MyTests/IntermediateConfig 에 그래프를 배치합니다.
import MovieGraphCreateConfigExample
MovieGraphCreateConfigExample.CreateIntermediateConfig()
아니면 콘텐츠 브라우저에서 우클릭하고 '시네마틱(Cinematics) > 무비 렌더 그래프 환경설정(Movie Render Graph Config)'을 선택한 뒤 다음 2개의 변경을 따라 수동으로 생성할 수도 있습니다.
-
'Global Output Settings' 노드에서 노드를 우클릭하고 컨텍스트 메뉴의 체크박스를 선택하여
OutputResolution을 노출합니다. -
'Global Output Settings' 노드의
OutputResolution핀을 우클릭하고 '변수로 승격(Promote to Variable)'을 선택하여 출력 해상도를 변수로 노출합니다.
그래프의 모습은 다음과 비슷할 것입니다.
스크립트를 사용하면 시각적으로 모든 노드가 얽힐 것이며, 따로 떨어뜨리면 됩니다)
사용자 노출 파라미터 수정하기
이제 블루프린트에서와 마찬가지로 새 무비 렌더 그래프를 통해 프로퍼티별 오버라이드를 사용자 파라미터로 노출하여 작업의 디테일 패널에서 직접 오버라이드할 수 있습니다. 이전에는 이렇게 하려면 C++에서 추가 코드 관리 및 에디터 리빌딩을 필요로 하는 자체 MoviePipelineExecutorJob 배리언트를 생성해야 했습니다. 또한 실행자 작업(Executor Job) 정의를 한 가지 버전만 가질 수 있었습니다. 이는 서로 다른 렌더 유형에 노출된, 다양한 어트리뷰트가 필요한 대규모 프로젝트에 적절하지 않았습니다. 아티스트는 그래프를 보고 프로퍼티가 수정될 때 어떤 경로를 거쳤는지 쉽게 확인할 수 있습니다.
다음 스니펫에서는 작업의 무비 렌더 큐 패널에 표시되는 노출된 변수 CustomOutputRes 를 오버라이드할 것입니다.
def set_user_exposed_variables(job:unreal.MoviePipelineExecutorJob):
"""Find user exposed variable CustomOutputResolution and modify"""
graph = job.get_graph_preset()
variables = graph.get_variables()
if not variables:
print("No variables are exposed on this graph, expose 'CustomOutputResolution' to test this example")
for variable in variables:
if variable.get_member_name() == "CustomOutputRes":
# 그래프 또는 서브그래프에서 변수 할당을 구함
variable_assignment = job.get_or_create_variable_overrides(graph)
# 새 값 설정
variable_assignment.set_value_serialized_string(variable,
unreal.MovieGraphLibrary.named_resolution_from_profile("720p (HD)").export_text())
# 오버라이드 토글 활성화
variable_assignment.set_variable_assignment_enable_state(variable, True)
토론:
우선 그래프 프리셋을 가져오고 렌더링된 이미지의 렌더 해상도를 오버라이드하기 위해 그래프에서 노출시켰던 'CustomOutputResolution'을 찾을 때까지 반복작업합니다. 새 CustomOutputResolution 어트리뷰트를 새 해상도 '720p'로 설정한 뒤 변수 상태를 활성화하여 오버라이드가 완전히 적용되게 합니다.
이를 큐에 있는 작업에서 테스트하려면 레퍼런스(예: 큐의 첫 번째 항목)를 작업으로 가져오고 이 함수를 실행합니다.
#큐 서브시스템을 가져와서 현재 큐에 액세스
subsytem = unreal.get_editor_subsystem(unreal.MoviePipelineQueueSubsystem)
pipeline_queue = subsytem.get_queue()
#첫 번째 작업 구하기
job = pipeline_queue.get_jobs()[0]
#함수를 테스트하기, 사용자 노출 변수가 변경됨
set_user_exposed_variables(job)
코드가 성공적이라면 Custom Output Resolution 이 720p(HD) - 1280x720로 설정될 것입니다.
그래프 노드 세팅의 디폴트 파라미터 수정:
오버라이드 관리 시에는 그래프 변수를 노출하고 이를 그래프 로직에 연결하는 것을 권장하지만, 노드 자체에서 세팅을 직접 오버라이드해야 하는 상황도 있을 수 있습니다. 다시 말해 사용자 변수를 노출하지 않았지만 디폴트 값을 수정하고 싶을 수도 있습니다.
@staticmethod
def set_global_output_settings_node(job:unreal.MoviePipelineExecutorJob):
'''
이 예시는 Global Output Settings 노드를 수정하여 디폴트 값을 편집하는
방법을 보여줍니다. 노출된 값을 오버라이드하는 데 관심이 있다면
작업별 오버라이드에 더 적절한 워크플로인 'set_user_exposed_variables'를
참조하세요.
참고: 이는 실제 공유 그래프 에셋을 수정하고 더티 상태로 만듭니다.
'''
# 작업에서 출력 세팅 노드를 위해 검색할 그래프 에셋을 구함
graph = job.get_graph_preset()
# Globals 출력 노드를 구함
globals_pin_on_output = graph.get_output_node().get_input_pin("Globals")
# Output Settings 노드가 Globals 핀에 연결되어 있다고 가정함
output_settings_node = globals_pin_on_output.get_connected_nodes()[0]
if not isinstance(output_settings_node, unreal.MovieGraphGlobalOutputSettingNode):
unreal.log("This example expected that the Global Output Settings node is plugged into the Globals Node")
return
output_settings_node.set_editor_property("override_output_resolution", True)
# 출력 해상도를 오버라이드함
output_settings_node.set_editor_property("output_resolution",
unreal.MovieGraphLibrary.named_resolution_from_profile("720p (HD)"))
이 예시에서는 처음에 'Globals' 노드의 입력 핀을 엔트리 포인트로 사용하고, 연결된 노드가 이 예시에서 생성한 MovieGraphGlobalOutputSettingNode 라고 가정했습니다.
이 세팅 오버라이드 예시에 집중하기 위해 Globals(출력 노드)에 대한 입력을 Global Output Settings 노드로 가정했습니다.
Global Output Settings 노드 세팅을 테스트하기 위해 다음 스니펫을 실행하여 'Global Output Settings' 노드에서 해상도를 설정할 수 있습니다.
#큐 서브시스템을 가져와서 현재 큐에 액세스
subsytem = unreal.get_editor_subsystem(unreal.MoviePipelineQueueSubsystem)
pipeline_queue = subsytem.get_queue()
#첫 번째 작업 구하기
job = pipeline_queue.get_jobs()[0]
#함수를 테스트하기, 사용자 노출 변수가 변경됨
set_global_output_settings_node(job)
무비 그래프 트래버설
다음은 그래프 세팅을 트래버스하여 뎁스 우선 검색으로 모든 노드를 방문하는 방법의 예시입니다. 다음 예시에서는 Globals 노드 핀을 시작 포인트로 사용하여 모든 노드를 찾을 때까지 오른쪽에서 왼쪽으로 검색합니다.
graph = unreal.load_asset("/Game/YourGraph.YourGraph")
output_node = graph.get_output_node()
visisted = set()
def dfs(node, visisted=None):
visited.add(node.get_name())
print(node)
#별개의 노드는 서로 다른 수의 입력 노드 및 이름을 지님
if isinstance(node, unreal.MovieGraphSubgraphNode) or isinstance(node, unreal.MovieGraphOutputNode):
pins = [node.get_input_pin("Globals"), node.get_input_pin("Input")]
elif isinstance(node, unreal.MovieGraphBranchNode):
pins = [node.get_input_pin("True"), node.get_input_pin("False")]
elif isinstance(node, unreal.MovieGraphSelectNode):
pins = [node.get_input_pin("Default")]
else:
pins = [node.get_input_pin("")]
#발견한 핀을 반복작업함
for pin in pins:
if pin:
for neighbour in pin.get_connected_nodes():
dfs(neighbour, visited)
dfs(output_node)
그래프를 아래 이미지와 같이 만들고 노드를 트래버스한 다음 '핀'을 사용하여 프로세스할 다음 노드를 찾습니다. 서로 다른 노드 유형은 명명된 핀을 가질 수 있으므로, 'True' 및 'False' 핀 모두를 반복적으로 검색해야 하는 MovieGraphBranchNode 같은 특정 노드 유형이 나올 경우, 이러한 프로퍼티의 목록을 유지해야 합니다. 단일 입력이 있는 노드는 핀 “”에서 빈 스트링만 갖습니다.
그래프 트래버설은 더 적은 코드로 보다 쉽게 노드를 방문할 수 있도록 다음 릴리스에서 초점을 맞춘 영역 중 하나가 될 것입니다.
그래프 작업의 추가 예시는 Content/Python/MovieGraphCreateConfigExample.py 안에서 찾을 수 있습니다.
추가 무비 렌더 그래프 예시
첨부된 두 Python 파일은 추가 기능 및 사용 사례와 주석을 포함합니다. 둘 다 임포트하여 로직을 살펴볼 수 있습니다. MovieGraphEditorExample.py 는 자동화를 생성하는 완전히 스크립팅된 예시를 제공하며, MovieGraphEditorExample.py 파일에서는 재사용 가능한 함수를 찾을 수 있습니다.
# Copyright Epic Games, Inc. All Rights Reserved.
# 무비 렌더 그래프로 스크립팅을 사용하는 방법을 보여주는 예시
import unreal
import MovieGraphCreateConfigExample as graph_helper
import MovieGraphEditorExampleHelpers
# 사용법:
# - 프로젝트에서 활성화하려면 'Python 에디터 스크립트 플러그인'이 필요합니다.
# main() 함수에서 무비 수정 그래프를 로드 및 수정하고,
# 노출된 변수를 덮어쓰고, 무비 렌더 그래프와 관련된 기타 작업을
# 수행하는 예시를 찾을 수 있습니다.
#
# '로드할 에셋(Assets to load)' 섹션이
# 무비 렌더 큐 및 무비 그래프 환경설정 에셋을 가리키도록 변경하세요.
'''
가비지 컬렉션 중 UObject를 유지하기 위한 Python Globals입니다.
완료 시 수동으로 삭제해야 합니다.
'''
subsystem = None
executor = None
def render_queue(queue_to_load:unreal.MoviePipelineQueue=None,
graph_to_load: unreal.MovieGraphConfig=None):
"""
이 예시는 다음을 수행하는 방법을 보여줍니다.
- 큐 로드 또는 현재 큐 사용
- 그래프 환경설정을 프리셋으로 설정
- 단순 실행자 완료 콜백 추가
"""
# 무비 파이프라인 큐와 상호작용할 서브시스템 구하기
subsystem = unreal.get_editor_subsystem(unreal.MoviePipelineQueueSubsystem)
# 제공된 경우 제공된 큐 에셋을 로드하고, 아니면 활성 큐를 사용
if queue_to_load:
if subsystem.load_queue(queue_to_load, prompt_on_replacing_dirty_queue=False):
unreal.log("Loaded specified queue")
# 활성 큐에 대한 레퍼런스를 구함
pipeline_queue = subsystem.get_queue()
if not pipeline_queue.get_jobs():
unreal.log("There are no jobs in the Queue.")
return
# 작업마다 작업의 그래픽 프리셋, 값 수정 등
# 작업 파라미터를 수정하는 것으로 시작할 수 있음
for job in pipeline_queue.get_jobs():
# 이 예시에 대한 작업 그래프 환경설정으로 작업 중인지 확인할 것
if not job.get_graph_preset():
unreal.log("A Graph Config needs to be specified for this example)")
return
if graph_to_load:
job.set_graph_preset(graph_to_load)
# 작업 컬렉션 예시는 다음에서 찾을 수 있음
MovieGraphEditorExampleHelpers.advanced_job_operations(job)
# globals 키워드를 사용하여 실행자가 글로벌 범위에 속함을 나타냄으로써
# 작업이 렌더링을 마친 뒤 가비지 컬렉션되는 것을 방지함
global executor
executor = unreal.MoviePipelinePIEExecutor(subsystem)
# 렌더 작업이 끝나면 콜백 on_queue_finished_callback을 실행
executor.on_executor_finished_delegate.add_callable_unique(
MovieGraphEditorExampleHelpers.on_queue_finished_callback
)
# 렌더링 시작, UI에서 '렌더(Render)'를 누르는 것과 유사함
subsystem.render_queue_with_executor_instance(executor)
def allocate_render_job(config_to_load: unreal.MovieGraphConfig=None):
'''
렌더링 시작 전 새 작업 할당 후 해당 작업의 파라미터를 채웁니다.
'''
# 현재 큐를 구함
subsytem = unreal.get_editor_subsystem(unreal.MoviePipelineQueueSubsystem)
pipeline_queue = subsytem.get_queue()
# 기존 작업을 삭제하여 현재 큐를 지움
pipeline_queue.delete_all_jobs()
job = pipeline_queue.allocate_new_job(unreal.MoviePipelineExecutorJob)
# 작업 파라미터를 오버라이드하려면 다음을 확인할 것
# MovieGraphEditorExampleHelpers.set_job_parameters(job)
if config_to_load:
job.set_graph_preset(config_to_load)
subsystem = unreal.get_editor_subsystem(unreal.MoviePipelineQueueSubsystem)
executor = unreal.MoviePipelinePIEExecutor(subsystem)
subsystem.render_queue_with_executor_instance(executor)
def render_queue_minimal():
'''이는 이미 작업이 할당된 큐를 렌더링하는 방법에 대한 MVP 예시입니다.
오버라이드를 다룬 보다 구체적인 예시는 render_queue 함수에서 찾을 수 있습니다.
'''
subsystem = unreal.get_editor_subsystem(unreal.MoviePipelineQueueSubsystem)
executor = unreal.MoviePipelinePIEExecutor(subsystem)
subsystem.render_queue_with_executor_instance(executor)
def main():
"""큐를 렌더링하는 방법(render_queue)과 처음에 큐 생성 없이 작업을 처음부터
생성하는 방법을 보여주고 있습니다. (allocate_render_job)
이 예시들을 독립적으로 실행하세요.
"""
# 'Example'이라는 그래프 에셋을 /Game/MyTests/IntermediateConfig에 생성함
# 이는 출력 해상도를 사용자 변수로 노출함
created_graph = graph_helper.CreateIntermediateConfig()
# 저장된 큐 에셋 실행자 콜백 추가(Add Executor Callbacks)로 큐를 렌더링하고
# 무비 렌더 큐 에셋을 전달하거나 무비 렌더 큐에 작업이 있는지 확인할 수 있음
render_queue()
# 이 함수는 새 MoviePipelineJob을 할당하여 큐에 작업을 생성함
# 또한 config_to_load를 전달하여 그래프 환경설정을 세팅하고 렌더링을 시작할 수 있음
allocate_render_job()
if __name__ == "__main__":
unreal.log("Check the main() function for examples")
MovieGraphEditorExample.py
# Copyright Epic Games, Inc. All Rights Reserved.
#
#
# 무비 렌더 그래프의 헬퍼 함수, 각 함수는 스태틱 메서드로
# 독립된 개별 함수의 테스트를 돕습니다. 이 모듈은
# MovieGraphEditorExample.py에서 사용됩니다.
import unreal
@staticmethod
def on_queue_finished_callback(executor: unreal.MoviePipelineExecutorBase, success: bool):
"""실행자가 모든 작업의 렌더링을 끝낸 뒤 호출됨
실행인자:
success (bool): 모든 작업이 성공적으로 완료되면 true, 작업에서
오류(유효하지 않은 출력 디렉터리 등)가 발생하거나
사용자가 작업을 취소(ESC 누름)하면 false입니다.
executor (unreal.MoviePipelineExecutorBase): 이 큐를 실행하는 실행자입니다.
"""
unreal.log("on_queue_finished_callback Render completed. Success: " + str(success))
@staticmethod
def set_global_output_settings_node(job:unreal.MoviePipelineExecutorJob):
'''
이 예시는 Global Output Settings 노드를 수정하여 디폴트 값을 편집하는
방법을 보여줍니다. 노출된 값을 오버라이드하는 데 관심이 있다면
작업별 오버라이드에 더 적절한 워크플로인 'set_user_exposed_variables'를
참조하세요.
참고: 이는 실제 공유 그래프 에셋을 수정하고 더티 상태로 만듭니다.
'''
# 작업에서 출력 세팅 노드를 위해 검색할 그래프 에셋을 구함
graph = job.get_graph_preset()
# Globals 출력 노드를 구함
globals_pin_on_output = graph.get_output_node().get_input_pin("Globals")
# Output Settings 노드가 Globals 핀에 연결되어 있다고 가정함
output_settings_node = globals_pin_on_output.get_connected_nodes()[0]
if not isinstance(output_settings_node, unreal.MovieGraphGlobalOutputSettingNode):
unreal.log("This example expected that the Global Output Settings node is plugged into the Globals Node")
return
output_settings_node.set_editor_property("override_output_resolution", True)
# 출력 해상도를 오버라이드함
output_settings_node.set_editor_property("output_resolution",
unreal.MovieGraphLibrary.named_resolution_from_profile("720p (HD)"))
@staticmethod
def set_job_parameters(job:unreal.MoviePipelineExecutorJob):
"""이 함수는 작업 파라미터가 설정 및 수정되는 방법을 보여줍니다. set_editor_property
메서드를 사용하여 변경사항이 큐를 더티 상태로
표시하게 합니다.
실행인자:
job (unreal.MoviePipelineExecutorJob): 수정할 파이프라인 작업입니다.
"""
job.set_editor_property("sequence", unreal.SoftObjectPath('/Game/Levels/shots/shot0010/shot0010.shot0010'))
job.set_editor_property("map", unreal.SoftObjectPath('/Game/Levels/Main_LVL.Main_LVL'))
job.set_editor_property("job_name", "shot0010")
job.set_editor_property("author", "Automated.User")
job.set_editor_property("comment", "This comment was created through Python")
@staticmethod
def set_user_exposed_variables(job:unreal.MoviePipelineExecutorJob):
"""사용자 노출 변수 CustomOutputResolution을 찾아서 수정합니다.
실행인자:
job (unreal.MoviePipelineExecutorJob): 수정할 그래프 프리셋을 찾기 위해 사용할
파이프라인 작업입니다.
"""
graph = job.get_graph_preset()
variables = graph.get_variables()
if not variables:
print("No variables are exposed on this graph, expose 'CustomOutputResolution' to test this example")
for variable in variables:
if variable.get_member_name() == "CustomOutputRes":
# 그래프 또는 서브그래프에서 변수 할당을 구함
variable_assignment = job.get_or_create_variable_overrides(graph)
# 새 값 설정
variable_assignment.set_value_serialized_string(variable,
unreal.MovieGraphLibrary.named_resolution_from_profile("720p (HD)").export_text())
# 오버라이드 토글 활성화
variable_assignment.set_variable_assignment_enable_state(variable, True)
@staticmethod
def duplicate_queue(pipeline_queue:unreal.MoviePipelineQueue):
"""
인터랙티브 세션에서는 큐를 복제하는 것이 좋습니다.
원본을 수정하는 대신 큐 에셋 사본을 수정하려는 경우 특히 그렇습니다.
실행인자:
queue (unreal.MoviePipelineQueue): 복제하고자 하는 큐입니다.
"""
new_queue = unreal.MoviePipelineQueue()
new_queue.copy_from(pipeline_queue)
pipeline_queue = new_queue
return pipeline_queue
@staticmethod
def advanced_job_operations(job:unreal.MoviePipelineExecutorJob):
"""
현재 작업에서 다음 함수를 실행하는 래퍼 함수입니다.
- set_job_parameters
- set_user_exposed_variables
- set_global_output_settings_node
실행인자:
job (unreal.MoviePipelineJob): 현재 프로세스된 큐 작업입니다.
"""
if not job.get_graph_preset():
unreal.log("This Job doesn't have a graph type preset, add a graph preset to the job to test this function")
return
# Author/Level/LevelSequence와 같은 작업 파라미터 설정
set_job_parameters(job)
# 그래프 환경설정에서 사용자 노출 변수 설정
set_user_exposed_variables(job)
# 실제 그래프 노드에서 어트리뷰트를 직접 설정함,
# 디폴트 값을 설정하는 것과 같음
set_global_output_settings_node(job)
@staticmethod
def traverse_graph_config(graph:unreal.MovieGraphConfig):
"""뎁스 우선 검색을 사용하여 'Globals' 핀에서 시작하는 모든 노드를 검색하고,
모든 노드가 소진될 때까지 왼쪽으로 이동하는 방법을
보여줍니다.
실행인자:
graph (unreal.MovieGraphConfig): 작업할 그래프입니다.
"""
visited = set()
def dfs(node, visisted=None):
visited.add(node.get_name())
# 노드는 서로 다른 수의 수집해야 할 입력 노드 및 이름을 가질 수 있음
if isinstance(node, unreal.MovieGraphSubgraphNode) or isinstance(node, unreal.MovieGraphOutputNode):
pins = [node.get_input_pin("Globals"), node.get_input_pin("Input")]
elif isinstance(node, unreal.MovieGraphBranchNode):
pins = [node.get_input_pin("True"), node.get_input_pin("False")]
elif isinstance(node, unreal.MovieGraphSelectNode):
pins = [node.get_input_pin("Default")]
else:
pins = [node.get_input_pin("")]
# 발견한 핀을 반복작업함
for pin in pins:
if pin:
for neighbour in pin.get_connected_nodes():
dfs(neighbour, visited)