Unreal Python Sequencer 批量渲染总结

本文章转载自 智伤帝的个人博客 - 原文链接

前言

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

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

手动操作

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

  其实操作起来不难,打开 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 代码

# -*- coding: utf-8 -*-
"""
渲染 sequencer 的画面
选择 LevelSequence 批量进行渲染
"""

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

__author__ = 'timmyliang'
__email__ = '[email protected]'
__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 目前还是无解。

你可能感兴趣的:(Unreal Python Sequencer 批量渲染总结)