基于recorder实现在URP中捕获序列帧

为什么需要使用录制序列帧?

常规的影视流程要求美术在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的支持比较全面,基本覆盖了后期所需的全部功能。
如下图所示:
HDRP中的AOV

而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等。
capture pass

成功输出之后就可以在对应的文件夹中看到序列帧:

设置对应的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

猜你喜欢

转载自blog.csdn.net/jianfei_zhou/article/details/129328601