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__ = '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 了。

<video src="G:/repo/_blog/source/post_img/2f3a9b95/02.mp4" autoplay="autoplay" loop="loop" style="width: 100%; height:100%;"></video>

总结

  理论上 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 目前还是无解。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 213,254评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,875评论 3 387
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,682评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,896评论 1 285
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,015评论 6 385
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,152评论 1 291
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,208评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,962评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,388评论 1 304
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,700评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,867评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,551评论 4 335
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,186评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,901评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,142评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,689评论 2 362
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,757评论 2 351