为什么需要使用录制序列帧?
常规的影视流程要求美术在DCC中进行建模材质K动画渲染等操作。而渲染出来的PV由于工作流不同会和实时引擎中的效果产生巨大的差异。这个可以通过做在引擎和DCC双端做LOOK DEV的开发来规避。而往往项目并没有这么多的人力来做这方面的工业化。因此在实时引擎中录制timeline并输出颜色和序列帧来辅助在后期软件中的制作。而UNITY本身有提供这一需求的解决方案,就是recorder。但recorder对URP的支持非常有限,仅限于颜色的序列帧,例如后期中刚需的深度和objects id, motion vector等等都没有支持。
个人理解,一方面原因是因为URP是一个高度自定义的管线,其渲染流程相较于HDRP是不确定的(延迟原生带一些所需的rt,例如屏幕空间法线,diffuse, specular等)。另一方面则是为了照顾到全平台通用的特性,比较难以实现,因此项目应该基于自己的渲染流程来定制recorder用以实现不同的需求。
Recorder
什么是recorder?Recorder是unity自带的一款能够录制实时游戏序列帧并输出成EXR等序列帧后期软件支持的格式的官方插件。在HDRP中,Recorder的支持比较全面,基本覆盖了后期所需的全部功能。
如下图所示:
而URP这边的AOV是不支持的。(代码中做了判断)
Capture pass
URP中的序列帧输出主要依赖于管线中的capture pass
if (renderingData.cameraData.captureActions != null)
{
m_CapturePass.Setup(sourceForFinalPass);
EnqueuePass(m_CapturePass);
}
这段代码在URP12的universalrenderer.cs中,当我们注册了一个capture actions回调函数时,URP会执行capture pass来捕获序列帧。
那么在哪里注册这个capture action呢?
Input Strategy
注册这个capture actions需要我们自己来实现一个input stragty,他需要继承于CaptureCallbackSRPInputStrategy,在这个类中有一个实现一个add capture commands的函数,来自定义自己的capture action。
注册回调函数
这里可以参考recorder3.03中自带的camerainput类:
protected virtual void AddCaptureCommands(RenderTargetIdentifier source, CommandBuffer cb)
{
if (source == BuiltinRenderTextureType.CurrentActive)
{
var tid = Shader.PropertyToID("_MainTex");
cb.GetTemporaryRT(tid, m_RenderTexture.width, m_RenderTexture.height, 0, FilterMode.Bilinear);
cb.Blit(source, tid);
cb.Blit(tid, m_RenderTexture, copyMaterial);
cb.ReleaseTemporaryRT(tid);
}
else
cb.Blit(source, m_RenderTexture, copyMaterial);
}
从这里就可以看出执行的逻辑,实际就是一个blit函数把当前屏幕的颜色输出到所需的RT上。
有了这个参考,我们就可以根据需求实现自己的AOV,类似这样:
protected override void AddCaptureCommands(RenderTargetIdentifier source, CommandBuffer cb)
{
if (source == BuiltinRenderTextureType.CurrentActive)
{
//....
}
else
{
switch (m_AOVType)
{
case AOVType.Depth:
cb.Blit(source, m_RenderTexture, aovMaterial, AOVType.Depth);
break;
case AOVType.ObjectsId:
cb.Blit(source, m_RenderTexture, aovMaterial, AOVType.ObjectsId);
break;
//......
}
}
}
之后在对应的shader中实现所需的功能,例如绘制objects id, motion vector等。
成功输出之后就可以在对应的文件夹中看到序列帧:
设置对应的RT格式
Inupt strategy中同样也支持根据对应的AOV来设置对应的RT格式来满足后期软件中对于精度的需求。
主要在于实现自己的prepFrameRenderTexture,直接给出一个示例实现:
protected override void PrepFrameRenderTexture(RecordingSession session)
{
if (OutputRenderTexture != null)
{
if (OutputRenderTexture.IsCreated() && OutputRenderTexture.width == OutputWidth && OutputRenderTexture.height == OutputHeight)
return;
ReleaseBuffer();
}
AOVImageRecorderSettings aovImageRecorderSettings = session.settings as AOVImageRecorderSettings;
if (aovImageRecorderSettings == null) return;
var fmtRW = RenderTextureReadWrite.Default;
var fmt = RenderTextureFormat.ARGB32;
switch (aovImageRecorderSettings.AOVType)
{
case AOVType.Depth:
fmtRW = RenderTextureReadWrite.Linear;
fmt = RenderTextureFormat.RFloat;
break;
case AOVType.ObjectsId:
fmtRW = RenderTextureReadWrite.Linear;
fmt = RenderTextureFormat.ARGB32;
break;
//......
}
这里还引出了几个额外需要实现的类,例如aovImageRecorderSettings是一个继承于scriptableObject的类,主要用来存储参数。
UI
为了以Recorder规范的UI显示我们的参数,还需要实现一个aovImageRecorderSettingsEditor用来绘制,这部分完全可以参考recorder中已经实现的一些功能。需要指出,recordSettings是一个中继类,他用以让我们和input类进行沟通,便于我们将设置的参数最终传递到input类中来进行RT的录制。
总结
说的比较融通,主要讲一些大概思路。当然完全可以按照自己的思路来实现。但recorder中实现了各种导出序列帧的工具代码,自己来实现未免周期比较长。利用recorder已有的架构来实现自己的需求是一个不错的方案。
参考资料
https://docs.unity3d.com/Packages/[email protected]/manual/index.html