前言

  最近我的学弟找我咨询关于 Unreal Sequencer 渲染输出的问题。
  之前没有折腾过这个一块,于是就跟进了一下,顺便学习 Sequencer 的序列输出。

  另外最近另一个师弟也研究了差不多的问题,发了一篇 B站专栏 , 在这里推荐一下 链接

手动操作

  在自动化操作渲染之前,需要先搞清楚怎么手动操作 Sequencer 进行渲染。

alt

  其实操作起来不难,打开 Unreal 的定序器,点击上面的 Render 图标打开 Render Movie Setting
  然后配置好渲染设置就可以点击渲染,就可以将影片批量渲染出来。

自动化操作

  下面就是将手动操作转为 Python 的自动操作。
  具体的操作脚本其实可以参考官方的脚本,在官方 SequencerScripting 插件里面有渲染相关的 Python 脚本。
  安装了 Unreal 引擎之后可以根据地址查找 \Engine\Plugins\MovieScene\SequencerScripting\Content\Python
  sequencer_examples 就有输出的 Python 代码,不需要自己查文档研究怎么搭建代码。
  参照 render_sequence_to_movie 的代码即可输出。

  其中比较坑的点在于 OnRenderMovieStopped 的 delegate
  接入 Python 回调需要一个 global 函数才可以,否则执行完成的回调函数不会触发。
  官方的案例是放到最外层执行的,如果不凑巧回调函数写在函数里面,就需要利用 global 关键字解决这个问题。


  官方案例还没能实现一个需求,就是批量将不同 Sequence 同时渲染出来。
  然而 render_movie 这个函数是不阻塞的,如果使用 for 循环会一直把所有的 render_movie 持续执行。
  所以这里进行渲染需要通过回调来实现逐个渲染的调用。

Python 代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
# -*- coding: utf-8 -*-
"""
渲染 sequencer 的画面
选择 LevelSequence 批量进行渲染
"""

from __future__ import division
from __future__ import print_function
from __future__ import absolute_import

__author__ = 'timmyliang'
__email__ = '820472580@qq.com'
__date__ = '2020-07-14 21:57:32'

import unreal

import os
import subprocess
from functools import partial

def alert(msg):
unreal.SystemLibrary.print_string(None,msg,text_color=[255,255,255,255])

def render(sequence_list,i,output_directory="C:/render",output_format="{sequence}"):

# NOTE 如果超出数组则退出执行
if i >= len(sequence_list):
# NOTE 输出完成 打开输出文件夹的路径
subprocess.call(["start","",output_directory], creationflags=0x08000000,shell=True)
return

# NOTE 获取当前渲染序号下的 LevelSequence
sequence = sequence_list[i]

# NOTE 配置渲染参数
settings = unreal.MovieSceneCaptureSettings()
path = unreal.DirectoryPath(output_directory)
settings.set_editor_property("output_directory",path)
settings.set_editor_property("output_format",output_format)
settings.set_editor_property("overwrite_existing",True)
settings.set_editor_property("game_mode_override",None)
settings.set_editor_property("use_relative_frame_numbers",False)
settings.set_editor_property("handle_frames",0)
settings.set_editor_property("zero_pad_frame_numbers",4)
settings.set_editor_property("use_custom_frame_rate",True)
settings.set_editor_property("custom_frame_rate",unreal.FrameRate(24, 1))

# NOTE 渲染大小
w,h = 1280,720
settings.set_editor_property("resolution",unreal.CaptureResolution(w,h))

settings.set_editor_property("enable_texture_streaming",False)
settings.set_editor_property("cinematic_engine_scalability",True)
settings.set_editor_property("cinematic_mode",True)
settings.set_editor_property("allow_movement",False)
settings.set_editor_property("allow_turning",False)
settings.set_editor_property("show_player",False)
settings.set_editor_property("show_hud",False)

# NOTE 设置默认的自动渲染参数
option = unreal.AutomatedLevelSequenceCapture()
option.set_editor_property("use_separate_process",False)
option.set_editor_property("close_editor_when_capture_starts",False)
option.set_editor_property("additional_command_line_arguments","-NOSCREENMESSAGES")
option.set_editor_property("inherited_command_line_arguments","")
option.set_editor_property("use_custom_start_frame",False)
option.set_editor_property("use_custom_end_frame",False)
option.set_editor_property("warm_up_frame_count",0.0)
option.set_editor_property("delay_before_warm_up",0)
option.set_editor_property("delay_before_shot_warm_up",0.0)
option.set_editor_property("write_edit_decision_list",True)
# option.set_editor_property("custom_start_frame",unreal.FrameNumber(0))
# option.set_editor_property("custom_end_frame",unreal.FrameNumber(0))

option.set_editor_property("settings",settings)
option.set_editor_property("level_sequence_asset",unreal.SoftObjectPath(sequence.get_path_name()))

# NOTE 设置自定义渲染参数
option.set_image_capture_protocol_type(unreal.CompositionGraphCaptureProtocol)
protocol = option.get_image_capture_protocol()
# NOTE 这里设置 Base Color 渲染 Base Color 通道,可以根据输出的 UI 设置数组名称
passes = unreal.CompositionGraphCapturePasses(["Base Color"])
protocol.set_editor_property("include_render_passes",passes)
# protocol.set_editor_property("compression_quality",100)

# NOTE 设置全局变量才起作用!
global on_finished_callback
on_finished_callback = unreal.OnRenderMovieStopped(
lambda s:render(sequence_list,i+1,output_directory,output_format))
unreal.SequencerTools.render_movie(option,on_finished_callback)


def main(output_directory="C:/render",output_format="{sequence}"):
# NOTE 获取当前选择的 LevelSequence
sequence_list = [asset for asset in unreal.EditorUtilityLibrary.get_selected_assets() if isinstance(asset,unreal.LevelSequence)]

if not sequence_list:
alert(u"请选择一个 LevelSequence")
return

if not os.access(output_directory, os.W_OK):
alert(u"当前输出路径非法")
return
elif not os.path.exists(output_directory):
# NOTE 路径不存在则创建文件夹
os.makedirs(output_directory)
elif os.path.isfile(output_directory):
# NOTE 如果传入文件路径则获取目录
output_directory = os.path.dirname(output_directory)

render(sequence_list,0,output_directory,output_format)


if __name__ == "__main__":
main()

  选择 Sequencer 执行上面的脚本,就可以自动批量输出 Sequencer 了。

总结

  理论上 Python 调用蓝图方法做到的功能, 蓝图应该也可以做到的。
  但是经过我的测试,我发现 蓝图 的 get_image_capture_protocol 返回值是 基类。
  导致无法获取 CompositionGraphCaptureProtocol 这个类
  也就无法设置 include_render_passes 的值了,这样导致蓝图输出会将所有通道输出,而不能实现单一通道的输出。


  另外这一次没有制作 GUI ,我还在纠结使用 Qt 还是 Unreal 原生的界面。
  Unreal使用 Editor Utility 创建的 UI 是二进制 uasset ,无法向前兼容 Unreal 版本。
  UI的功能响应上也没有 Qt 成熟。
  但是无论如何,Unreal 的 UMG 是原生体验,嵌入样式各方面都比较舒服的。
  虽然 Qt 可以写一套 Qss 来解决样式问题,但是在 Unreal 中实现 Dock 目前还是无解。