Unity SRP自定义渲染管线学习2.2: 合批(Batching) SRP Batcher

文章探讨了自定义渲染管线中合批的概念,解释了大量DrawCall如何影响性能,并介绍了SRPBatcher如何通过减少CPU和GPU间的通信来优化性能。通过调整Shader和开启SRPBatcher,可以简化流程并减少数据传输,但需遵循特定的Shader编写规范。同时,文章提到了MaterialPropertyBlock在处理多颜色对象时的便利性,但可能会导致SRPBatcher失效的问题。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

接下来我们要来学习下自定义渲染管线中的合批,这一节主要学习SRP Batcher

每一次的Draw Call都需要CPU和GPU之间的通信,如果有大量的数据需要从CPU发送到GPU中,那GPU就可能因为等待数据而浪费时间,而CPU会因为忙于发送数据导致无法做其他的事情,所以这两个问题都会导致帧率的降低。在目前我们的做法有点粗暴,一个物体一个Draw Call,这是非常浪费时间的,只是目前我们发送的整体数据量较少,所以还感受不出问题。

我们可以用示例数字来说明这个问题。
整三十个球,同样颜色,按以前的Unity肯定是能合批的,可是现在需要31个Draw Call,通过合批减少的DrawCall数量(Saved by batching)为0,其实就是天空盒一个,剩下的就是30个球的了。
(为什么Clear的DrawCall没了?哈哈,这就是我们之前做ClearRenderTarget的小优化,对于Skybox的Flag不用进行清理也是可以的,具体可以查看《自定义渲染管线基础学习》-”相机的ClearFlags“这一小节)

在这里插入图片描述

自定义渲染管线的合批

合批是合并Draw Call的过程,减少CPU和GPU之间的通信量。
在自定义管线中最简单的实现方法就是直接开启SRP Batcher,SRP Batcher实际上并不是直接减少Draw Call的数量,而是简化了流程,它将材质属性存储在GPU,所以不用每一次Draw Call都发送数据,这样子既能减少CPU和GPU之间的通信,也能减少CPU每一次Draw Call所要做的数据准备工作。这样也相当于减少了Draw Call的数量。
但是想要SRP Batcher生效,我们的Shader编写必须按照规范的统一结构来。
我们查看Shader的面板可以查看SRP Batcher的使用情况,发现有无法使用的提示,”属性没有定义在叫【UnityPerDraw】的cbuffer中“。cbuffer(constant buffer)。
在这里插入图片描述

想要SRP Batcher生效,我们Shader中的所有材质属性不能像之前那样直接定义,必须放在固定的内存缓存中。
之前SRP Batcher不生效的写法

float4 _BaseColor;

我们查看Unlit的Shader,在上面可以看到提示SRP合批不适用的原因
在这里插入图片描述
标准写法
(但是在一些平台上无法支持cbuffer,比如OpenGL ES 2.0)


//使用cbuffer UnityPerMaterial 包起来才能使SRP Batcher生效
//但是在一些平台上无法支持cbuffer,比如OpenGL ES 2.0
cbuffer UnityPerMaterial  
{
    float4 _BaseColor;  //用于Shader中定义颜色属性,名称需相同
};

由于一些平台无法直接支持cbuffer
Core RP Library中通过CBUFFER_START和CBUFFER_END对此做了处理,因此我们可以使用这个来解决问题
使用需要include Common.hlsl

在这里插入图片描述
在这里插入图片描述

#include "Packages/com.unity.render-pipelines.core/ShaderLibrary/Common.hlsl"

最终写法


//CBUFFER_START和CBUFFER_END是CORE RP Library中对cbuffer做的处理,解决部分平台无法支持的问题
CBUFFER_START(UnityPerMaterial)
    float4 _BaseColor;
CBUFFER_END

但仍然还有问题
在这里插入图片描述
我们还需要把别的变量也加到这里


CBUFFER_START(UnityPerDraw)
float4x4 unity_ObjectToWorld;  //每一次绘制GPU设置这个值,然后在一次绘制中的顶点片元函数使用期间值不变
float4x4 unity_WorldToObject;
float4 unity_LODFade;
real4 unity_WorldTransformParams;
CBUFFER_END

然后CustomRenderPipeline.cs中开启SRP合批


    public CustomRenderPipeline()
    {
        GraphicsSettings.useScriptableRenderPipelineBatching = true;  //开启SRP合批
    }

终于适用了
在这里插入图片描述
但不知道为什么我的面板还是没有变化
在这里插入图片描述
但是FrameDebuger里面已经可以看到效果了
我们可以看到SRP Batch
但我们可以看到上面显示着,Draw Calls 30,所以实际上SRP Batch并不是真正的合批,仍然会有30次Draw Calls,是减少了发送到GPU的数据量
在这里插入图片描述
到了这里我自己有点懵了
没用SRP之前的有合批么?
除了要勾静态的静态合批外,原来的动态合批是怎么回事?
动态合批需要在PlayerSetting中勾选Dynamic Batch功能,然后也有许多限制
但是它的合批是真的合批
在这里插入图片描述

多种颜色的合批

我们增加不同颜色的材质赋予这些球,但是我们发现实际上SRP的Batch并不会增加,因为数据缓存在了GPU上,每一次DrawCall只需要包含内存位置的偏移数据就好了。
对于SRP的使用有一个限制是每个材质的memory layout(内存布局?)必须是一样的,因此在这里所有的球我们都使用了同一个Shader,而这些材质都只包含了一个颜色属性。Unity不会去详细的比较的材质的memory layout,而是简单的根据相同的Shader变体去合并Draw Calls。
在这里插入图片描述

在上面的例子中,为了有5种颜色,我们创建了5个材质球,但如果要十几种颜色或者更多,我们总不能去创建那么多的材质球。如果我们能直接设置每个对象的颜色,那就会方便很多。
我们通过MaterialPropertyBlock实现这个功能。


using UnityEngine;
[DisallowMultipleComponent]
public class PerObjectMaterialProperties : MonoBehaviour
{
    static int baseColorId = Shader.PropertyToID("_BaseColor"); //使用Id的方式去设置属性会更高效
    static MaterialPropertyBlock block;
    [SerializeField]
    Color baseColor = Color.white;
    void Awake()
    {
        OnValidate();  //OnValidate只在编辑器下会被调用,打包后我们得自己调用
    }
    //OnValidate在编辑器下,在组件加载和组件修改时会被调用
    void OnValidate()
    {
        if (block == null)
        {
            block = new MaterialPropertyBlock();
        }
        block.SetColor(baseColorId, baseColor);
        GetComponent<Renderer>().SetPropertyBlock(block);
    }
}

我们可以看到虽然我们对这些球只使用了一个材质球,但是颜色却是不一样的,是通过组件修改单个对象的颜色
在这里插入图片描述
但是,我们看到SRP Batcher不生效了,这样子虽然方便了,但是性能不行了
在这里插入图片描述

<think>好的,我现在要解决用户的问题:在Unity的URP渲染管线中,如何让一个摄像机只渲染UI,另一个摄像机渲染其他内容。我需要先回顾一下URP的基本知识,然后思考如何配置多个摄像机以及它们的层和渲染设置。 首先,用户可能对URP中的摄像机堆叠不太熟悉。URP允许通过Render Type和层来管理不同摄像机的渲染内容。记得URP中的每个摄像机可以分配不同的Render Type,比如Overlay,这样可以将多个摄像机的输出叠加起来。 接下来,用户需要分开UI和其他物体的渲染。通常UI会放在一个单独的层,比如UI层,而其他物体放在默认层或其他层。可能需要设置摄像机的Culling Mask,使其只渲染特定层。 然后,考虑URP的渲染器设置。可能需要创建或修改URP Asset,添加不同的Render Features来指定渲染顺序和层。比如,前向渲染器中可以设置不同的事件,如AfterRenderingOpaques或BeforeRenderingTransparents,来安排UI的渲染时机。 还要注意摄像机的Clear Flags和Depth设置,确保UI摄像机在正确的位置渲染,不会覆盖其他内容。可能需要调整摄像机的Depth值,使UI摄像机在其他摄像机之后渲染,这样UI会显示在最上层。 另外,检查UI元素的Canvas组件设置,确保其Render Mode是Screen Space - Camera,并正确指定到UI摄像机。这样UI元素会正确跟随该摄像机,而不会干扰其他摄像机的渲染。 可能的错误点包括层设置不正确,导致摄像机没有正确渲染目标层;Render Type未正确配置,导致摄像机输出不叠加;或者Depth值设置错误,导致渲染顺序混乱。 总结步骤应该是:创建两个摄像机,分别设置它们的Culling Mask和Render Type,配置URP Asset中的Renderer,调整Depth和Clear Flags,并确保Canvas正确关联到UI摄像机。最后测试验证是否UI和其他物体分别由不同摄像机渲染,且没有互相干扰。</think>在Unity URP渲染管线中实现摄像机分层渲染(一个专用于UI,另一个渲染场景),可通过以下完整流程实现: --- ### **核心原理** URP通过**Camera Stacking**技术实现多摄像机成渲染,关键配置要素: 1. **Render Type** 设置(Base/Overlay) 2. **Culling Mask** 图层过滤 3. **Renderer优先级** 控制 --- ### **分步操作指南** #### **第一步:创建专用摄像机** 1. **场景摄像机**(渲染游戏物体) - 重命名为"MainSceneCamera" - 属性设置: ```csharp Clear Flags: Skybox Culling Mask: Everything ➔ 取消勾选UI层 Render Type: Base Depth: 0 ``` 2. **UI摄像机**(专用于UI渲染) - 重命名为"UI_Camera" - 属性设置: ```csharp Clear Flags: Depth only Culling Mask: 只勾选UI层 Render Type: Overlay Depth: 1 ``` #### **第二步:配置URP Asset渲染器** 1. 打开URP配置文件(UniversalRenderPipelineAsset) 2. 在Renderer列表中添加**两个Renderer**: - **场景渲染器**: ```csharp Name: SceneRenderer Opaque Layer Mask: Default, TransparentFX Overrides: 保持默认 ``` - **UI渲染器**: ```csharp Name: UIRenderer Opaque Layer Mask: UI Overrides: 勾选"UI Override" (自定义RenderFeature) ``` 3. 创建**UI Renderer Feature**: ```csharp using UnityEngine.Rendering.Universal; public class UIRendererFeature : ScriptableRendererFeature { class CustomRenderPass : ScriptableRenderPass { /* 实现渲染逻辑 */ } public override void Create() { /* 初始化 */ } public override void AddRenderPasses(...) { /* 添加渲染阶段 */ } } ``` #### **第三步:设置Canvas渲染模式** 1. 选择所有UI Canvas: ```csharp Render Mode: Screen Space - Camera Render Camera: 拖入UI_Camera Plane Distance: 100 (确保在场景物体后方) ``` #### **第四步:层级管理(关键)** 1. 创建专用层级: - 打开Layer设置(Layer > Add Layer) - 新建"UI"层和"Scene"层 2. 分配对象层级: - 所有UI元素分配到UI层 - 场景物体分配到Scene层 --- ### **深度调试技巧** 1. **可视化渲染顺序**: ```csharp // 在摄像机脚本中添加调试代码 void OnPreRender() { Debug.Log($"{name} 开始渲染,深度值:{depth}"); } ``` 2. **帧调试器验证**: - 打开Window > Analysis > Frame Debugger - 检查每个Draw Call所属的Renderer --- ### **常见问题解决方案** | 问题现象 | 解决方法 | |---------|----------| | UI穿透场景物体 | 检查UI摄像机的Clear Flags是否为Depth only | | 场景物体出现在UI层 | 确认场景物体未分配到UI层 | | 渲染顺序错乱 | 调整Camera.depth值,确保UI摄像机深度更高 | | UI闪烁 | 在URP配置中设置"Render Passes"顺序为Scene ➔ UI | --- ### **性能优化建议** 1. **动态处理设置**: ```csharp // 在URP配置中启用 GraphicsSettings → URP Global Settings → Enable Dynamic Batching: True Enable SRP Batcher: True ``` 2. **分辨率控制**: - 为UI摄像机单独设置分辨率比例: ```csharp UI_Camera.GetComponent<UniversalAdditionalCameraData>().renderScale = 0.75f; ``` --- ### **高级应用:多UI层渲染** 若需要更复杂的UI分层(如HUD、弹窗分离): 1. 创建多个Overlay摄像机(UI_Camera_HUD、UI_Camera_Popup) 2. 设置不同的Depth值(HUD:2, Popup:3) 3. 在URP Asset中为每个UI层创建独立Renderer --- 通过上述配置,可精准控制不同摄像机负责的渲染内容,实现场景与UI的完全分离渲染。建议在Quality Settings中锁定帧率,避免因渲染顺序导致的视觉异常。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值