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
定义:Create
是 ScriptableRenderFeature
或 ScriptableRenderPass
中的一个方法,它在 渲染特性 或 渲染 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