콜백 소개
콜백은 렌더 작업에 프리/포스트 로직을 추가하기에 좋은 방법입니다. 무비 렌더 그래프(Movie Render Graph)에는 작업/샷 실행 전후로 실행할 스크립트를 처리하는 콜백 시스템이 있습니다. 이를 통해 렌더에 추가 로직을 더할 수 있습니다.
새 렌더 그래프에는 'Execute Script Node'라는 노드가 있어 콜백을 더 쉽고 명확하게 만들 수 있습니다. 이전 콜백은 프로젝트 세팅에서 실행자의 일부로만 지원됐지만, 이제는 그래프 환경설정 내에서 지원됩니다.
콜백은 MovieGraphScriptBase를 서브클래스화하고 데디케이티드 콜백에 대한 함수를 오버라이드하여 생성할 수 있습니다.
다음 스니펫은 언리얼 전용 데코레이터 @unreal.uclass() 를 사용하여 UObject 핸들링 시스템에 대한 클래스를 태깅함으로써 인식되고 Execute Script Node에서 레퍼런스될 준비가 되도록 만드는 방법을 보여줍니다.
@unreal.uclass()
class CallbackOverrides(unreal.MovieGraphScriptBase):
def _post_init(self):
print("Callback Overrides Class")
코드 스니펫을 실행하면 'CallbackOverrides' UClass를 스크립트 실행 노드의 항목 메뉴에서 선택할 수 있습니다.
개별 함수는 데코레이터 @unreal.ufunction(override=True) 을 사용하여 오버라이드되며, 이는 우리가 기본 함수의 오버라이드에 관심이 있음을 나타냅니다.
작업 시작 시 콜백:
이는 개별 프로세스가 시작된 뒤 작업 초반에 호출됩니다. 실행될 작업에 대한 레퍼런스를 제공하며, 무비 그래프 환경설정은 작업에 대한 레퍼런스를 통해 이용 가능합니다. 콜백에서 제공되는 작업과 무비 렌더 환경설정은 모두 큐에서 지정된 원본의 복제입니다. 즉 수정된 사항이 여러 작업에서 공유될 수도 있는 원본 에셋에 영향을 미칠 걱정은 하지 않아도 됩니다.
@unreal.uclass()
class CallbackOverrides(unreal.MovieGraphScriptBase):
@unreal.ufunction(override=True)
def on_job_start(self, in_job_copy):
super().on_job_start(in_job_copy)
print("This is run before the render starts")
작업 종료 시 콜백:
이는 무비 그래프 파이프라인(Movie Graph Pipeline) 끝에서 종료 직전에 호출됩니다. 이 지점에서 모든 이미지 데이터가 디스크에 기록됩니다. 이 함수는 성공 시와 취소 시 모두 호출됩니다. 두 번째 파라미터에는 '성공' 어트리뷰트가 있습니다. 이 어트리뷰트를 체크하여 작업이 성공했는지, 아니면 환경설정 오류 또는 사용자 입력으로 인해 취소됐는지 확인할 수 있습니다.
@unreal.ufunction(override=True)
def on_job_finished(self, in_job_copy, in_output_data):
super().on_job_start(in_job_copy, in_output_data)
print("This is run after the render jobs are all finished")
#샷 렌더 레이어 데이터에 액세스하는 방법을 보여주는 샘플 코드
for shot in in_output_data.graph_data:
for layerIdentifier in shot.render_layer_data:
unreal.log("render layer: " + layerIdentifier.layer_name)
for file in shot.render_layer_data[layerIdentifier].file_paths:
unreal.log("file: " + file)
샷 시작 시 콜백:
@unreal.ufunction(override=True)
def on_shot_start(self, in_job_copy, in_shot_copy):
super().on_shot_start(in_job_copy, in_shot_copy)
print("This is run before every shot rendered")
작업 시작과 유사하지만 샷이 구성되기 전에 호출되며, 개별 샷의 시작 시에 상호작용해야 하는 경우 유용합니다.
샷 종료 시 콜백:
@unreal.ufunction(override=True)
def on_shot_finished(self, in_job_copy, in_shot_copy, in_output_data):
super().on_shot_finished(in_job_copy, in_shot_copy, in_output_data)
print("This is called after every shot is finished rendering")
작업 종료와 비슷하지만, 각 샷 종료 시마다 호출됩니다. 출력 데이터는 이 콜백이 이루어지는 샷의 데이터만 포함합니다.
샷별 콜백 활성화:
on_shot_start 및 on_shot_finished 콜백은 명시적으로 활성화했을 때만 호출됩니다. 렌더가 샷이 끝날 때마다 스톨되며 콜백을 호출하기 전에 모든 파일이 디스크에 기록될 때까지 기다리므로 렌더 시간이 느려질 수 있기 때문입니다.
다음 두 가지 방법으로 샷별 쓰기를 활성화할 수 있습니다.
-
Global Output Settings 노드에서 '샷별 디스크 쓰기 비우기(Flush Disk Writes Per Shot)'를 체크합니다.
-
콜백을 정의할 때 이와 같은 방식으로
is_per_shot_callback_needed함수를 오버라이드하여 활성화할 수 있습니다.@unreal.ufunction(override=True) def is_per_shot_callback_needed(): return True
영구적으로 Python UClass 정의하기:
콜백 클래스를 등록하면 현재 세션에서만 지속됩니다. 커스텀 UClass를 영구 등록하려면 임포트 구문을 /Content/Python/init_unreal.py 파일 중 하나에 추가해야 합니다. 클래스를 이 Python 파일에서 임포트하면 스타트업 시마다 실행되므로 영구적으로 이용할 수 있습니다.
실행자에서의 콜백:
작업별/샷별 프리/포스트 콜백 덮어쓰기를 허용하는 그래프의 Script 노드 외에도 실행자도 자체적으로 콜백 오버라이드를 허용합니다. 가장 유용한 것은 모든 작업 완료 후 마지막 단계인 on_executor_finished_delegate 이며, 이는 다른 애플리케이션에 작업 완료를 알릴 때 좋습니다. Execute Script 노드에 설명된 것과 유사하게 작동하는 다른 콜백도 있지만, 프리/포스트 작업/샷 콜백을 무비 렌더 그래프에서 Execute Script 노드의 일부로 유지할 것을 권장합니다.
def on_queue_finished_callback(executor: unreal.MoviePipelineExecutorBase, success: bool):
print("Run before a Render job starts: ")
subsystem_executor.on_individual_job_started_delegate.add_callable_unique(on_individual_job_started_callback)
전체 예시는 /Engine/Plugins/MovieScene/MovieRenderPipeline/ Content/Python/ 의 MovieGraphScriptNodeExample.py 파일에서 볼 수 있으며, 당분간 이 문서에 첨부되어 있을 것입니다.
# Copyright Epic Games, Inc. All Rights Reserved.
import unreal
# 이 예시는 Execute Script 노드에 필요한 프리/포스트 샷
# 작업 콜백을 오버라이드하는 UClass 생성 예시를 보여줍니다. register_callback_class()를
# 실행하면 그래프의 Execute Script 노드 디테일에 'CallbackOverrides'라는
# 이름의 오브젝트가 표시됩니다. 이 오브젝트는 이용 가능한 작업/샷 콜백을
# 기본 구문으로 대체합니다.
# 사용법:
#
# import MovieGraphScriptNodeExample
# MovieGraphScriptNodeExample.register_callback_class()
#
# 위의 함수를 실행한 뒤, 이 UObject를
# 무비 그래프 환경설정의 Execute Script 노드에서 선택할 수 있게 됩니다.
#
# 위 스니펫을 /Content/Python/__init__ py 파일 어디든 추가하여
# 이 UObject를 엔진 스타트업 시 영구적으로 이용 가능하게 할 수 있습니다.
def register_callback_class():
"""이 헬퍼 함수는 CallbackOverrides라는 샘플 UClass를 생성합니다.
이 Uclass는 MovieGraphsScriptBase 함수를 오버라이드하여
무비 그래프 Execute Script 노드 내 작업 및 샷 전/후의
개별 콜백이 어떻게 실행되는지 보여줍니다.
이 함수를 호출하여 CallbackOverrides를 등록하면
Execute Script 노드에서 선택할 수 있게 됩니다.
"""
@unreal.uclass()
class CallbackOverrides(unreal.MovieGraphScriptBase):
@unreal.ufunction(override=True)
def on_job_start(self, in_job_copy):
super().on_job_start(in_job_copy)
unreal.log("This is run before the render starts")
@unreal.ufunction(override=True)
def on_job_finished(self, in_job_copy:unreal.MoviePipelineExecutorJob,
in_output_data:unreal.MoviePipelineOutputData):
super().on_job_finished(in_job_copy, in_output_data)
unreal.log("This is run after the render jobs are all finished")
for shot in in_output_data.graph_data:
for layerIdentifier in shot.render_layer_data:
unreal.log("render layer: " + layerIdentifier.layer_name)
for file in shot.render_layer_data[layerIdentifier].file_paths:
unreal.log("file: " + file)
@unreal.ufunction(override=True)
def on_shot_start(self, in_job_copy:unreal.MoviePipelineExecutorJob,
in_shot_copy: unreal.MoviePipelineExecutorShot):
super().on_shot_start(in_job_copy, in_shot_copy)
unreal.log(" This is run before every shot rendered")
@unreal.ufunction(override=True)
def on_shot_finished(self, in_job_copy:unreal.MoviePipelineExecutorJob,
in_shot_copy:unreal.MoviePipelineExecutorShot,
in_output_data:unreal.MoviePipelineOutputData):
super().on_shot_finished(in_job_copy, in_shot_copy, in_output_data)
unreal.log(" This is called after every shot is finished rendering")
@unreal.ufunction(override=True)
def is_per_shot_callback_needed(self):
"""
이 함수를 오버라이드하고 true를 반환하면 샷별 디스크 비우기가 활성화되며
이는 Global Output Settings 노드에서 샷별 디스크 쓰기 비우기를
켜는 것과 동일한 효과입니다.
"""
return True