Unity自定义渲染通道的入门报告:深入解析五个关键函数

Unity版本:2022.3.60f1

打开里面是这个内容

using UnityEngine;
using UnityEngine.Rendering;
using UnityEngine.Rendering.Universal;

public class LearnningFeature : ScriptableRendererFeature
{
    
    class CustomRenderPass : ScriptableRenderPass
    {
        
        // This method is called before executing the render pass.
        // It can be used to configure render targets and their clear state. Also to create temporary render target textures.
        // When empty this render pass will render to the active camera render target.
        // You should never call CommandBuffer.SetRenderTarget. Instead call <c>ConfigureTarget</c> and <c>ConfigureClear</c>.
        // The render pipeline will ensure target setup and clearing happens in a performant manner.
        public override void OnCameraSetup(CommandBuffer cmd, ref RenderingData renderingData)
        {
        }

        // Here you can implement the rendering logic.
        // Use <c>ScriptableRenderContext</c> to issue drawing commands or execute command buffers
        // https://docs.unity3d.com/ScriptReference/Rendering.ScriptableRenderContext.html
        // You don't have to call ScriptableRenderContext.submit, the render pipeline will call it at specific points in the pipeline.
        public override void Execute(ScriptableRenderContext context, ref RenderingData renderingData)
        {
        }

        // Cleanup any allocated resources that were created during the execution of this render pass.
        public override void OnCameraCleanup(CommandBuffer cmd)
        {
        }
    }

    CustomRenderPass m_ScriptablePass;

    /// <inheritdoc/>
    public override void Create()
    {
        m_ScriptablePass = new CustomRenderPass();

        // Configures where the render pass should be injected.
        m_ScriptablePass.renderPassEvent = RenderPassEvent.AfterRenderingOpaques;
    }

    // Here you can inject one or multiple render passes in the renderer.
    // This method is called when setting up the renderer once per-camera.
    public override void AddRenderPasses(ScriptableRenderer renderer, ref RenderingData renderingData)
    {
        renderer.EnqueuePass(m_ScriptablePass);
    }
}


分别解释五个函数:

CustomRenderPass中的五个个自带函数。

1:OnCameraSetup

就像文档里说的那样,此方法由渲染器在渲染摄像机之前调用 如果需要配置渲染目标及其清除状态,并创建临时渲染目标纹理,请覆盖此方法。 如果渲染通道未覆盖此方法,则此渲染通道将渲染到活动摄像机的渲染目标。

说人话就是,渲染摄像头在最开始,处理的一切行为,在这一步,就要你自己先处理好你想要什么,到底要做什么,做这件事的前提有没有都准备好。

一般流程图都是。

预定义----》OnCameraSetup里自己准备----》Execute中进行操作----》意识到缺了什么---》回到

OnCameraSetup----》Execute接着操作。

这里主要先讲明白两个输入:

1:命令缓冲区(cmd)

简单来说,cmd 是一个 命令的集合,这些命令在渲染流程中执行,允许你将一组渲染指令组合起来并批量执行,而不需要每一条命令都单独执行。

它的作用是:

  • 存储渲染命令(例如,设置渲染目标、清除缓冲区、绘制模型等)。

  • 允许你控制何时以及如何执行这些命令。

  • 在渲染过程中,可以把这些命令加入到渲染管线的不同阶段。

最简单的话来说,就是当有一个渲染需求前,我们可以在cmd这个需求中填入所有的指令命令。这样cmd就会暂时储存下来,之后再集中释放。

2:渲染数据(类型)

我就直接把定义贴上来:

public struct RenderingData
{
    internal CommandBuffer commandBuffer;

    public CullingResults cullResults;

    public CameraData cameraData;

    public LightData lightData;

    public ShadowData shadowData;

    public PostProcessingData postProcessingData;

    public bool supportsDynamicBatching;

    public PerObjectData perObjectData;

    public bool postProcessingEnabled;
}

就一个储存数据结构的结构体,它包含了当前帧渲染所需的所有信息。

那么有什么用呢?

看这段代码:

 public override void Execute(ScriptableRenderContext context, ref RenderingData renderingData)
        {
            var draw = CreateDrawingSettings(shaderTag, ref renderingData, renderingData.cameraData.defaultOpaqueSortFlags);
            draw.overrideMaterial = setting.material;
            draw.overrideMaterialPassIndex = 0;

            //获取主光源方向,并转换到相机空间
            var visibleLight = renderingData.cullResults.visibleLights[0];
            Vector2 lightDirSS = renderingData.cameraData.camera.worldToCameraMatrix * (visibleLight.localToWorldMatrix.GetColumn(2));
            setting.material.SetVector("_LightDirSS", lightDirSS);

            CommandBuffer cmd = CommandBufferPool.Get("DrawHairShadow");
            context.ExecuteCommandBuffer(cmd);
            context.DrawRenderers(renderingData.cullResults, ref draw, ref filtering);
        }

这是知乎大佬流朔在进行阴影编写时的代码。

其中Execute函数可以认为整个脚本中最重要的执行函数,是做事情做最多的。

我们看这段代码,设计renderingData的有

 var visibleLight = renderingData.cullResults.visibleLights[0];

            Vector2 lightDirSS = renderingData.cameraData.camera.worldToCameraMatrix * (visibleLight.localToWorldMatrix.GetColumn(2));

这段话仔细读一读,就会发现,是在获取数据中的可见光,并且是标记为0的可见光。同时设计了一个向量,从当前帧中得到摄像机的数据,并且对摄像机的数据进行矩阵转换,转化到摄像机空间。

关于worldToCameraMatrix函数如何实现,这里就不再详细解释,有兴趣的可以自己查文档。

所以,从这个例子可以看到,renderingData数据结构是包含了每一帧具体地所有信息。

爱怎么做,做什么,找cmd去。

小结:

cmd:代表传话员,你可以对cmd发出各种要求。cmd替你传话

renderingdata:代表现在手头上有的所有素材,怎么折腾,要做什么,是你自己的事。

2:Execute

上文已经说过,Execute是主要进行操作的函数。具体要做什么,都在这里写就行

下面是文档定义:执行通道。这是自定义渲染发生的地方。

先搞清楚输入的是什么,分别是ScriptableRenderContext和RenderingData,RenderingData已经说过,这里不再复述。

1:ScriptableRenderContext数据结构

先理解文档定义:

定义自定义渲染管道使用的状态和绘制命令。

在定义自定义 RenderPipelin 时,您可以使用 ScriptableRenderContext 来计划状态更新和绘制命令并将其提交给 GPU。

RenderPipeline.Render 方法实现通常会剔除渲染管道不需要为每个摄像机渲染的对象

这是什么意思?

按笔者的理解是:

手法,或者说是能力。

继续从流朔大佬的例子中进行思考

context.ExecuteCommandBuffer(cmd);

context.DrawRenderers(renderingData.cullResults, ref draw, ref filtering);

涉及到ScriptableRenderContext在Execute中只有这一部分,调用了两个函数,

防止忘了。execute部分继续贴在这里

public override void Execute(ScriptableRenderContext context, ref RenderingData renderingData)
        {
            var draw = CreateDrawingSettings(shaderTag, ref renderingData, renderingData.cameraData.defaultOpaqueSortFlags);
            draw.overrideMaterial = setting.material;
            draw.overrideMaterialPassIndex = 0;

            //获取主光源方向,并转换到相机空间
            var visibleLight = renderingData.cullResults.visibleLights[0];
            Vector2 lightDirSS = renderingData.cameraData.camera.worldToCameraMatrix * (visibleLight.localToWorldMatrix.GetColumn(2));
            setting.material.SetVector("_LightDirSS", lightDirSS);

            CommandBuffer cmd = CommandBufferPool.Get("DrawHairShadow");
            context.ExecuteCommandBuffer(cmd);
            context.DrawRenderers(renderingData.cullResults, ref draw, ref filtering);
            cmd.Clear();
        }

ExecuteCommandBuffer(cmd);

通过context告诉cmd,可以提交命令了。

之后是调用DrawRenderers函数,

关于DrawRenderers函数。

官方定义为:计划一组可见对象的绘制,并选择性地覆盖 GPU 的渲染状态。

从我的理解来看,就是渲染哪些应该渲染的,渲染哪些不应该渲染的。具体便不再细说。

值得一提的DrawRenderers三个输入

cullingResults:剔除结果,这个例子中没有任何操作,默认按Unity摄像机进行剔除。

drawingSettings:画好了,关于这个部分。

CreateDrawingSettings:

具体看定义:

 public DrawingSettings CreateDrawingSettings(ShaderTagId shaderTagId, ref RenderingData renderingData, SortingCriteria sortingCriteria)

    {

        return RenderingUtils.CreateDrawingSettings(shaderTagId, ref renderingData, sortingCriteria);

    }

虽然这玩意不开源,但还是能看懂要的是什么,shaderTagId,渲染数据,renderingData.cameraData.defaultOpaqueSortFlags,它从renderingData对象中获取相机数据(cameraData),然后使用该相机的默认不透明物体排序标志(defaultOpaqueSortFlags)。这些标志用于控制不透明物体的渲染顺序。从摄像机看,怎么样,就是要怎么样的顺序。

filteringSettings:渲染排序,没什么好说的。

小结:

Execute的重点还是ScriptableRenderContext函数,许多操作完成后都是通过这个函数进行通知CMD。

3:OnCameraCleanup

定义:在完成渲染摄像机时调用。您可以使用此回调释放创建的任何资源 通过此渲染 传递需要清理的 camera 完成渲染后。 将为相机堆栈中的所有相机调用此方法。

就是在渲染完成后释放在前面过程中创建的临时变量呗。

下面是一个具体例子

public override void OnCameraCleanup(CommandBuffer cmd)
{
    // 假设在 Execute 或 OnCameraSetup 中创建了一个临时的渲染目标纹理
    if (_temporaryRenderTexture != null)
    {
        // 清理临时渲染目标纹理
        cmd.ReleaseTemporaryRT(_temporaryRenderTexture);
        _temporaryRenderTexture = null;
    }

   
}

 作用就是节省开销,提高性能,还是很重要的。

4:Create

定义CreateScriptableRenderFeatureScriptableRenderPass 中的一个方法,它在 渲染特性渲染 Pass 被创建时调用。

从刚才的例子来看:

public override void Create()
{
    m_ScriptablePass = new CustomRenderPass(setting);  // 创建渲染 Pass(传递渲染参数和初始化)
    m_ScriptablePass.renderPassEvent = setting.passEvent;  // 设置渲染 Pass 的事件时机
}

 和OnCameraSetup的区别:

OnCameraSetup是每一帧调用一次,而Create函数只调用一次,所以,通常只处理不会改变的参数。

比如:初始化渲染 Pass、设置渲染事件时机、传递配置参数。

5:AddRenderPasses

这个相对来说就比较简单了。

就像字面名字一样,添加渲染pass。

定义:

在渲染器中注入一个或多个Pass。

从我的理解来看:AddRenderPasses 方法的核心作用是将自定义的渲染 Pass 插入到渲染管线中。这是渲染管线扩展的基础,因为它使得我们可以插入自己定义的渲染操作,在渲染过程中执行特定的任务。

这最终让我们的处理在得到实现。从流朔大佬的代码来看:

 public override void AddRenderPasses(ScriptableRenderer renderer, ref RenderingData renderingData)
    {
        if (setting.material != null)
            renderer.EnqueuePass(m_ScriptablePass);
    }

如果存在材质,就把这个渲染添加到渲染队列里。确实是这样。

相当于最后的收尾工作。

借鉴:

https://zhuanlan.zhihu.com/p/416577141

Unity - Scripting API: CommandBuffr

Method Execute | Universal RP | 15.0.7

Method OnCameraCleanup | Universal RP | 15.0.7

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值