之前做项目的过程中,有一部分工作是在UE4里制作输出小短片。由于要完成的量比较大,所以研究了一些批渲染的方法。
逻辑上跟以前在maya里用batch render差不多,不过UE4这边的设置相对繁琐一点点。
本文讲解了在不打开UE4软件的前提下,批量输出预先设置好的sequence序列 的方法。
文章内容粗浅简单,权当抛砖引玉了。
注意:本文调用的是"/Script/MovieSceneCapture.AutomatedLevelSequenceCapture",也就是相当于4.24及以前版本中,在sequence面板里点击render moive一样。 跟4.25更新的movie render queue无关。
为什么要用cmd做批渲染:
我们已经知道UE4.25中更新了非常好用的movie render queue工具,质量高而且方便自定义和输出。 奈何UE更新速度之迅猛,应该有不少项目还没切到4.25 ,还用不了render queue。
另外,在cmd指令里配置多任务,编辑效率和方便性我个人用下来是感觉比render queue高的,而且可以通过开动小脑筋实现简单的分布式渲染。让局域网内的多台机器同时处理一批序列。比如说我一共要渲20个序列,有四台肉鸡,那么可以每台肉鸡自动分5个去渲。
所以,总的来说,研究下怎么用cmd做批渲染,依然有用武之地;而且灵活性便利性都不错。
用cmd打开UE4地图:
我们先从基本的来——打开UE4项目里的某一张地图。
要打开某张地图的话,首先我们需要知道的信息是:
UE4Editor.exe 文件的位置
想要打开的地图所在的项目位置
所要打开的地图位置
比如说:
UE4Editor路径 —— "D:\Program Files\UE_4.24\Engine\Binaries\Win64\UE4Editor.exe"
项目路径—— "D:\Projects\UnrealProjects\renderMovieTest\renderMovieTest.uproject"
地图路径—— "D:\Projects\UnrealProjects\Test\Content\Maps\testLaunch"
其中UE4的项目路径可以用相对路径表示,为—— /Game; 所以地图路径可以写为“/Game/Maps/testLaunch”
然后我们新建一个txt文本文件,在文本中写入
"D:\Program Files\UE_4.24\Engine\Binaries\Win64\UE4Editor.exe" D:\Projects\UnrealProjects\renderMovieTest\renderMovieTest.uproject /Game/maps/shot0010
这样一串文本,ctrl+s 保存一下;再把txt文件的后缀改成bat。再双击运行这个bat,你就可以看到我们的UE4启动了,并且正在打开我们指定好的那张图。目标达成。
聪明的朋友可能会在琢磨一件事,就是为什么UE4Editor路径 加了双引号,而另外两个路径没有加双引号呢?
因为cmd命令,基本上是在玩字符串,每一条字符串就是一条command,而在cmd里输入的绝大多数字符,都会被理解成字符串,不管你有没有加 单引号 或者 双引号。而字符串的分隔就是用空格或者回车的。
也就是说 D:\Projects\UnrealProjects\renderMovieTest\renderMovieTest.uproject 这一条路径,在cmd里实际上是一个字符串,是一条command,而D:\Program Files\UE_4.24\Engine\Binaries\Win64\UE4Editor.exe 因为路径里有空格,所以会被理解成两条字符串—— D:\Program 和Files\UE_4.24\Engine\Binaries\Win64\UE4Editor.exe。
这当然不是我们想要的结果,我们需要把这两条字符串当成一个command来处理,这个时候就需要用双引号把这个路径括起来,这样,其中的空格也会理解成字符串的一部分。
也就是说,任何没有空格的一条字符串,你都可以不加双引号(也可以加)。而如果出现了空格,就必须加双引号。
用cmd渲染一个序列:
在上一节打开一张地图的基础上,我们只要继续加一些指令就可以渲染了。
其中必须要加的一条是: -MovieSceneCaptureType="/Script/MovieSceneCapture.AutomatedLevelSequenceCapture"
这条可以让我们使用渲染相关的各种cmd指令。
然后需要定义需要渲染的sequence文件路径,这里需要加上-LevelSequence=:
-LevelSequence="/Game/Sequence/testRende02"
另外我们还需要指定输出序列的文件夹位置,这里需要加上 -MovieFolder=:
-MovieFolder="D:\Projects\UnrealProjects\Test"
最后还有两个必须指令:
-game 和 -noLoadingScreen
按照官方文档说,必须加-noLoadingScreen ,不然会渲染失败:
而经我测试,-game也是必须的。另,感谢 @X Tesla 的提醒,这里的-noLoadingScreen的n需要小写。加一句跌坑录:那个noLoadingScreen,no的n要小写,大写N是不能渲染的。
到目前为止,我们的cmd 代码大概变成了这样:
"D:\Program Files\UE_4.24\Engine\Binaries\Win64\UE4Editor.exe" D:\Projects\UnrealProjects\renderMovieTest\renderMovieTest.uproject /Game/maps/shot0010 -MovieSceneCaptureType="/Script/MovieSceneCapture.AutomatedLevelSequenceCapture" -LevelSequence="/Game/Sequences/Shot0010" -MovieFolder="D:\Projects\UnrealProjects\renderMovieTest\outputs" -NoLoadingScreen -game
双击运行即可渲染序列了,并且界面也和正常的启动editor界面不同。直接会开始渲染序列,渲完整个窗口就都关了。
渲染设置:
上一节我们能够渲染出序列了。但是渲染输出的格式啊、尺寸啊、帧率啊、质量啊 这些常用设置,都没有提到。而这些设置,肯定是渲染过程中时常需要调试必不可少的。
这些设置的方法也跟前文类似,都是 - 加一个关键词来设置的,这些关键词大部分能在文档中找到:
-ResX=2560 -ResY=1440 -RenderOffScreen -ForceRes -MovieFormat=PNG -MovieQuality=100 -notexturestreaming -MovieCinematicMode=yes -MovieWarmUpFrames=01 -NoScreenMessages -VSync
我们在之前的基础上再继续添加这些设置,来满足我们的渲染需求。
一定要记住,每条字符串之间用空格隔开,一条完整字符串中,千万别打空格!
比如-MovieQuality=100 字符之间一定不能有空格,不能按照一般编程习惯打成 -MovieQuality = 100
这些设置中,可能有一些需要拿出来讲解一下:
-VSync
会开启垂直同步,让画面不会出现上下撕裂的问题,略微降低渲染效率,但由于我们是输出序列,不用理会帧率的问题,永远要用。
-NoTextureStreaming
关闭流送贴图池的;当UE4渲染一帧画面时,加载的显存大于你手动设置的流送贴图池上限时,会有很多贴图会被加载为非常小的尺寸,贴图在最终画面里会看起来非常模糊。而将其关闭以后,会忽略相关设置,每次都等所有贴图按照高精度加载完成之后再渲染。这个在游戏里会造成卡顿,因为我们渲序列,依然不用在意,永远加上这一条。
-MovieWarmUpFrames=##
设置暖场时间,当你的渲染文件里,有特效、解算的东西时,是很有必要设置的。会在渲染之前先让特效和解算文件先跑你设置的帧数,让特效解算都到位了,再渲染。这是以前离线渲染经常需要设置的东西,多加留心。
-NoScreenMessages
你总不想你们游戏内,程序员的打印的字符信息出现在渲染画面上吧。还有什么,你的灯光、反射采集球需要烘焙这些提示,你不可能想渲到最终画面上吧。所以永远加上这一条。
-CaptureFramesInHDR
渲染成exr格式的高动态图片。当你需要无损图片时,应当使用相关设置。
-CustomRenderPasses="RenderPassName"
这个是设置渲染通道用的,什么diffuse、reflection、velocity之类的。我知道很多搞影视渲染的朋友看到这个眼睛要发光,因为熟悉的工作流程似乎可以在UE4里文艺复兴了(包括曾经的自己)。
但我现在非常不推荐大家用这种输出多pass再到合成软件里面调的流程,首先在UE4里这个流程就不够健壮,想要对标影视的流程,需要二次开发,超麻烦。另外,以前的合成流程,主要是为了解决离线渲染太慢,一些小改动需要重新渲染的问题。现在UE4里渲染快得飞起,所见即所得,为啥不直接都在UE4里调到位呢?
记住,UE4里 post process就是你的nuke,几乎没有什么是nuke能做,后处理不能做的。所以,输出exr图片不是为了分pass,唯一的情况是你的画面里有较多过度细腻的暗部层次,这种层次在导出8位图片的时候,会被吃掉,导出浮点exr图片,可以保留足够的原始信息在你后面输出最终剪辑视频的时候,能提高画质。
-RenderOffScreen
最后要介绍的这一条,却是非常关键的一条,并且是在官方帮助文档上没有写的一条。按照前面的所有讲解,都设置好以后,你可以非常方便地渲染出一个序列,但是渲染的过程中会弹出一个窗口,显示你渲染的过程;然而在我们确定渲染内容没问题时,我们是不需要看到这个窗口的内容的,这条指令可以让你的渲染变成真正的后台渲染,没有任何窗口,只看到序列帧在文件夹里一个个创建(cool,这非常批渲染)。
除此之外,还有非常关键的一点,按照官方帮助文档的说法, -ForceRes 可以让你渲染出超过你显示器尺寸的序列:比如你显示器是1920*1080,加上 -ForceRes 可以渲染出2560*1440的序列。而在使用过程中,我发现这是错误的,就算加上 -ForceRes,你渲染的序列,依然会被卡在你的显示器尺寸上,即为1920*1080,没法更大。只有再加上-RenderOffScreen,才能让你突破显示器像素尺寸极限,这条乃非常重要的踩坑经验。
用蓝图调整游戏内质量:
然后有很多游戏内需要用 console command设置的质量指令,官方文档是说写在 Game/Config配置文件里。但在项目制作里,这种做法很容易影响到别的不该被影响的地方,所以我个人是用蓝图的形式,放在每个需要渲染的map中的。
官方文档也有一些设置建议,可以参考上一节的链接。
我的质量配置蓝图大概是这么设置的。将需要调节的参数暴露出去,于是每个地图里的质量,都可以单独设置。
另外,在蓝图中,Excute Console Command 节点,接受的字符串,不需要等号连接;也就是说,不需要用 r.MotionBlurQuality=4 这种,而是直接用 r.MotionBlurQuality 4(中间一个空格)这种。所以我上面append str节点,A里面填的字符串,最后都有一个空格。最后执行的时候,如下图所示:
渲染多个序列前置技能:
前面已经把渲染一个序列的方法都讲到位了。而我们用cmd去批渲染,关键还是要在渲染很多任务的时候,才有价值。
你当然可以把前面所有的内容复制一遍,修改一下需要渲染的地图名和Sequence名,但这也太搓了。看着就难受,并且对于渲染任务较多的时候,极容易出错。
所以这个时候,我们就必须真的拿cmd写代码了。这里稍微科普一点cmd的语法,这是想要写的优雅必须掌握的基本技能。怕很多朋友不熟悉,所以这里多叨扰一些。包括的内容是:设置变量
for循环
算数表达式
局部变量问题
设置变量:
cmd中也可以设置变量,使用关键词set,于是按照一般的变成思路,我们会写成:
set varA='test'
echo varA
@pause
(echo可以理解成打印print,@pause会让窗口执行完不关闭,可以观察结果)
然而令人遗憾的是,打印出来的是我们的变量名,而不是变量的赋值:
因为cmd里,varA并不是变量,而是字符串"varA”,要把他当变量打印,需要用%把他围起来:
set varA='test'
echo %varA%
@pause
这样就对了,但是打印出来的是'test'而不是我们一般想象的test字符串,是因为,cmd里打的绝大多数字符,都强制是字符串,包括单引号,也是字符串的一部分,所以我们赋予一段字符串给变量,不需要用引号扩起来:
set varA=test
echo %varA%
@pause
这样才对。
虽然这个语法感觉怪怪的,但习惯起来也不算特别艰难。
for循环:
光设置变量还不行,我们要渲染多个序列,少不了要用for循环。
比如我们在一个数组['a','b','c','d']中对元素逐个打印,我们可以这样写:
@echo off
set listA=a b c d
for %%s in (%listA%) do (
echo %%s
)
@pause
其中@echo off是屏蔽系统执行过程中的一些打印信息,这样,我们就可以只看到我们自己用echo打印的信息。
cmd中没有list那种东西,但我们可以用空格隔开字符串,赋予给一个变量,把这个变量当list用。
for循环中的变量,需要两个%来修饰。其余的都应该很好理解。
算数表达式:
当我们在for循环内部,需要做一些额外处理的时候,比如,我们要在上一节的abcd后面加后缀,并且后缀数字逐个增长,输出成a_v01,b_v02,c_v03,d_v04这样。
我们需要掌握一些额外的知识。首先是变量的数值增加。
一般语言我们直接用:
b = 3
b = b+2
这样,就可以让b = 5,但在cmd里,我们直接这样写,会得到:
因为,b+2会被理解成字符串,而不是加法操作,如果要让cmd理解这是加法操作,我们需要在设置变量的时候,加一个算术表达式的标识符 /A
set b=3
set /A b=b+2
echo %b%
@pause
这样就对了。
局部变量问题:
那么按照我们之前的目标,开始在循环内给字符串加后缀,很容易写出:
@echo off
set listA=a b c d
set postfix=1
for %%s in (%listA%) do (
echo %%s_v0%postfix%
set /A postfix=postfix+1
)
@pause
注意一下 ) do ( ,都前后都有空格。
另外要注意一下%%s_v%postfix,看起来像乱码一样,这就是cmd的毛病了;我们得拆开来看,首先%%s是for循环内的遍历变量,比如说第二次循环时,%%s是b,而%postfix%是我们定义的后缀变量,第二次循环中理论上应该是2,而这两个变量中间有一个_v0,这其实会被理解成字符串;最终cmd会把三个字符串连在一起,于是应该输出成b_v02。
而我们打印出来一看,却和预想的不一样:
每次打印的时候,postfix变量都是1,并没有按我们想象的每次都 +1 。
这是因为cmd的机制问题,%postfix%其实一直取的是外部变量,也就是永远是1;要想把他当内部变量用,我们需要加一串东西:setlocal enabledelayedexpansion
并且,用!postfix!的形式取内部变量,可以参考这篇文章:
最后我们把代码改成:
@echo off
set listA=a b c d
set postfix=1
setlocal enabledelayedexpansion
for %%s in (%listA%) do (
echo %%s_v0!postfix!
set /A postfix=!postfix!+1
)
@pause
目标达成,终于掌握了输出多个序列的前置技能点了。
渲染多个序列脚本编写:
首先,我们把不会变的东西都设置成变量:
set editorPath="D:\Program Files\UE_4.24\Engine\Binaries\Win64\UE4Editor.exe"
set projectPath=D:\Projects\UnrealProjects\renderMovieTest\renderMovieTest.uproject
set mapDir=/Game/maps/
set seqArg=-LevelSequence=/Game/Sequences/
set arguments=-game -MovieSceneCaptureType="/Script/MovieSceneCapture.AutomatedLevelSequenceCapture" -MovieFrameRate=60 -noLoadingScreen -ResX=3840 -ResY=2160 -ForceRes -MovieFormat=PNG -MovieQuality=100 -notexturestreaming -MovieCinematicMode=yes -MovieWarmUpFrames=01 -NoScreenMessages -VSync -RenderOffScreen
set outputPath=-MovieFolder=D:\Projects\UnrealProjects\renderMovieTest\outputs\
然后,把需要批处理的任务设置成数组(一般我为了简化管理,会把seq的名字和map的名字设置一样,所以不需要额外定义一个seqToRender):
set mapToRender=Shot0010 Shot0020 Shot0030 Shot0040
set virsion=v01
最后,for循环伺候:
setlocal enabledelayedexpansion
for %%s in (%mapToRender%) do (
set curOutputPath=%outputPath%virsion
set renderCommand=%editorPath% %projectPath% %mapPath%%%s %seqArg%%%s %arguments% !curOutputPath!
if exist !curOutputPath! (
echo !curOutputPath!
echo exist,ignore this rendering
) else (
echo notExist!curOutputPath!
echo Rendering ThisSequence
!renderCommand!
echo RenderFinish
)
)
其中,做了一个if else的判断: 判断当前输出文件夹是否存在,如果存在,表示可能别的机器已经渲染过这个序列了,于是忽略,如果不存在,那么没人渲过,我来渲染。
注意一下 ) else ( ,else的前后都要有空格。
这样,就可以搞简单的人肉分布式渲染了。
最后又加了一点逻辑判断,完整的示例代码。
@echo off
set editorPath="D:\Program Files\UE_4.24\Engine\Binaries\Win64\UE4Editor.exe"
set projectPath=D:\Projects\UnrealProjects\renderMovieTest\renderMovieTest.uproject
set mapDir=/Game/maps/
set seqArg=-LevelSequence=/Game/Sequences/
set arguments=-game -MovieSceneCaptureType="/Script/MovieSceneCapture.AutomatedLevelSequenceCapture" -MovieFrameRate=60 -noLoadingScreen -ResX=3840 -ResY=2160 -ForceRes -MovieFormat=PNG -MovieQuality=100 -notexturestreaming -MovieCinematicMode=yes -MovieWarmUpFrames=01 -NoScreenMessages -VSync -RenderOffScreen
set outputPath=-MovieFolder=D:\Projects\UnrealProjects\renderMovieTest\outputs\
set mapToRender=Shot0010 Shot0020 Shot0030 Shot0040
set virsion=v01
setlocal enabledelayedexpansion
for %%s in (%mapToRender%) do (
set "_temp="
set curOutputPath=%outputPath%virsion
set renderCommand=%editorPath% %projectPath% %mapPath%%%s %seqArg%%%s %arguments% !curOutputPath!
if exist !curOutputPath! (
echo !curOutputPath!
echo exist,ignore this rendering
for /f %%a in ('dir /b !curOutputPath!') do set _temp=%%a
if {!_temp!}=={} (
echo folderEmpty Rendering ThisSequence
!renderCommand!
echo renderFinish
) else (
echo Folder is Not Empty and Ignored
)
) else (
echo notExist!curOutputPath!
echo Rendering ThisSequence
!renderCommand!
echo RenderFinish
)
)
文章内容比较简单,为了照顾大部分可能不太懂的朋友,写得相对详细一点,见谅。
也许有后续。