本篇文章整理了URP管线使用中的一些简单的心得记述
1.使用ScriptableRendererFeature自定义渲染特性
在内建(Build-in)管线中可以使用CommandBuffer并添加到摄像机上来实现自定义的特性。在URP管线中,处理方法变成了RendererFeature
RendererFeature不需要绑定到相机;而是挂载到渲染器(如ForwardRenderer)的设置里。
在Project面板点击右键Create/Rendering/Universal Render Pipeline/Renderer Feature可以创建Renderer Feature模板。
模板中Feature带有一个CustomRenderPass的嵌套类;并且在AddRenderPasses函数中被添加进pass队列。
ScriptableRenderPass可以指定需要的渲染步骤,如:
m_ScriptablePass.renderPassEvent = RenderPassEvent.AfterRenderingOpaques;
由于一个效果往往需要多个pass不同阶段处理;这样的方式显然比较友好。
在之前内建的渲染管线中;CommandBuffer只能执行预先设定好的一些步骤,这样多少有些不方便。在自定义管线中CommandBuffer变成了立即执行,
现在可以在ScriptableRenderPass中直接通过context来立即执行CommandBuffer:
public override void Execute(ScriptableRenderContext context, ref RenderingData renderingData) { CommandBuffer cmd = CommandBufferPool.Get("MyCommandBuffer"); //CommandBuffer的操作 context.ExecuteCommandBuffer(cmd); CommandBufferPool.Release(cmd); }
此外,标记Profiling后,可在FrameDebugger中直接查看标记Profiling的对象:
{ ProfilingSampler mProfilingSampler = new ProfilingSampler("Test1"); CommandBuffer cmd = CommandBufferPool.Get("Test1 Cmd"); using (new ProfilingScope(cmd, mProfilingSampler)) { MeshRenderer meshRenderer = Resources.Load<MeshRenderer>("TestModel"); cmd.DrawRenderer(meshRenderer, meshRenderer.sharedMaterial); context.ExecuteCommandBuffer(cmd); CommandBufferPool.Release(cmd); } }
而打开了管线设置中的Debug Level后;可以通过这个参数看见更多的调试信息
如UniversalRenderPipeline.cs的RenderSingleCamera方法里:
static void RenderSingleCamera(...)
{
...
asset.debugLevel >= PipelineDebugLevel.Profiling ? ...
这段代码在勾选这个设置后可以在FrameDebugger内显示不同的相机名。
2.使用DrawRenderers进行大批量绘制
在内建管线中,通常使用CommandBuffer.DrawRenderer来绘制一些指定的对象,
不过绘制对象一多这样做就不太方便。
在URP的自定义pass中可以使用ScriptableRenderContext上下文里的DrawRenderers接口进行批量绘制,
它可以拿到当前相机的剔除结果(CullingResults),通过FilteringSettings参数再进行一次过滤。
FilteringSettings里还可以设置renderingLayerMask,renderingLayerMask可在MeshRenderer、SkinnedMeshRenderer
等渲染器组件中设置,独立于旧的Layer。
借助传入的DrawingSettings,RenderStateBlock参数可指定是否写入Stencil、是否写深度等信息,最终完成绘制,如:
context.DrawRenderers(renderingData.cullResults, ref drawingSettings, ref m_FilteringSettings, ref m_RenderStateBlock);
如果要绘制的内容第一次相机剔除时没有,也可以再进行一次相机剔除:
context.Cull(...)
使用新的结果来绘制。
以前抓取uGUI的绘制内容较为困难,现在也可以用这种办法把UI分成几部分绘制,并且控制每一部分是否写入指定RT了,
并且还可以通过自定义的Feature和context.DrawRenderers把UI绘制的步骤单独挪出来自行控制(但是Stencil会丢掉,酌情使用)。
还可以先修改RenderTarget再执行context.DrawRenderers绘制,这样就可以把内容批量绘制到指定RT上:
public override void Execute(ScriptableRenderContext context, ref RenderingData renderingData) { CommandBuffer cmd = CommandBufferPool.Get("tempRt"); int tmpRt = Shader.PropertyToID("_TempRt"); cmd.GetTemporaryRT(tmpRt, mDesc); cmd.SetRenderTarget(tmpRt); context.ExecuteCommandBuffer(cmd); context.DrawRenderers(...); CommandBufferPool.Release(cmd); }
(这里的RenderTexture应该在Configure中绑定)
注意,内建管线中使用CommandBuffer时可以直接填写-1、-2等宽高参数获得1/2,1/3等大小的RenderTexture,在自定义管线中
不再可用。
更多的绘制方法可以参考Render Objects(Runtime/RendererFeatures/RenderObjects.cs)或
DrawObjectsPass.cs(Runtime/Passes/DrawObjectsPass.cs)的做法。
3.相较内建管线的优化
在URP中单个对象支持的最大灯光数量是8盏;光照处理的操作在一个pass中完成。
虽然有数量限制;但这样整个场景的Batches数量得到了控制,这一点体现较为明显。
而阴影方面目前只能支持主光源平行光的阴影和聚光灯的阴影(v7.3.1)。
其次就是SRP Batches等技术,这方面暂未深入了解。