Unity-SRP Batcher 原理与使用
对 SRP Batcher 的个人理解整理。之前的笔记比较零散,现在系统梳理一下,顺便验证是否有理解偏差。
1. 概述:SRP Batcher 是什么
SRP Batcher 是 Unity Scriptable Render Pipeline(URP / HDRP)中的一个 CPU 端优化机制。它不会减少实际的 DrawCall 数量,但能显著降低每个 DrawCall 的 CPU 开销——特别是渲染状态切换和 Buffer 绑定这部分。
一句话概括:用 GPU 内存换 CPU 状态切换时间。
核心思想是:把低频变化的数据(材质属性)持久化缓存在 GPU 上,CPU 在材质不变的情况下只需要更新物体级别的数据(MVP 矩阵等),省去了大量重复的 Buffer 绑定和状态设置。
2. 如何使用
2.1 在 URP Asset 中启用
- 选中 URP 配置文件(UniversalRenderPipelineAsset)
- 在 Advanced 部分勾选 SRP Batcher
- 较新的 URP 版本中默认是开启的

2.2 Shader 中声明 CBuffer
SRP Batcher 要求材质属性声明在名为 UnityPerMaterial 的 CBuffer 中:
1 | |
Unity 内置的 Lit / Unlit Shader 已经按此规范声明,但如果你写自定义 Shader,必须手动加上 CBUFFER_START(UnityPerMaterial) / CBUFFER_END,否则 SRP Batcher 不会生效,Unity 会 fallback 到传统渲染路径。
3. 核心原理:CPU 与 GPU 的数据分离架构
高频变化的数据(物体级别:MVP 矩阵、Color 等)和低频变化的数据(全局设置、材质固有属性)在 GPU 上做物理分离。
SRP Batcher 在底层维护了两个概念上的 Buffer 区域:
| Buffer | 内容 | 更新频率 |
|---|---|---|
| PerBuffer(单个 CBuffer 槽位) | 同一 Shader Variant 共享的材质属性(_BaseColor、_Smoothness 等) | 仅材质切换时更新 |
| PerDraw(引擎内部维护的 Buffer) | 每个物体的独有数据(MVP 矩阵、Lightmap UV、Instance ID、_BaseColor 等) | 每帧按物体更新,高频 |
3.1 具体工作流程
- 准备阶段(CPU):将材质数据上传到 GPU 的持久 Buffer 中,原地保留
- 渲染阶段:当遍历可见物体列表时:
- 如果物体使用的 Shader Variant 和上一个物体相同 → 跳过所有材质属性上传,只更新 PerDraw 数据(矩阵等),直接发 Draw
- 如果 Shader Variant 变了 → 更新 PerBuffer 中的材质属性,再发 Draw
也就是说,CPU 在材质不变的情况下,只更新物体级别的数据,不切换渲染状态,也不重新绑定 Buffer。
3.2 架构对比
传统渲染(无 SRP Batcher):
1 | |
有 SRP Batcher:
1 | |
关键区别:在同一材质批量渲染时,SRP Batcher 省去了每物体都要做的 Buffer 绑定和属性上传。
4. 为什么材质属性必须在一个 CBuffer 里?
这是 SRP Batcher 高效工作的重要前提,有两个原因:
4.1 固定内存布局
引擎需要保证 GPU Buffer 的内存布局在运行时是确定且固定的。如果 Shader 属性分散在多个 CBuffer 里:
- 引擎无法保证多个 Buffer 之间的布局关系是固定的
- 可能需要运行时逐个绑定多个 Buffer,或不断重组布局
- PerDraw 数据也无法干净地分离出来
4.2 内存拷贝效率
如果所有材质属性在一个 CBuffer 里,CPU 可以用一次内存拷贝(memcpy)把整个块送上 GPU。分散到多个 CBuffer 意味着多次单独的提交操作,效率低得多。
4.3 SRP 中定义的标准 CBuffer 布局
SRP 预定义了三个标准 CBuffer,SHader 中应严格遵循:
| CBuffer 名称 | 用途 | 生命周期 |
|---|---|---|
UnityPerMaterial |
材质级属性(颜色、贴图、参数) | 材质切换时更新 |
UnityPerDraw |
物体级属性(矩阵、LM UV、Instance ID) | 每物体更新,SRP 自动管理 |
UnityInstancing |
GPU Instancing 相关数据 | Instancing 启用时使用 |
自定义 Shader 中若遗漏 CBUFFER_START(UnityPerMaterial)/CBUFFER_END,SRP Batcher 会检测到 CBuffer 布局不兼容,自动退化为非 batched 渲染。
5. SRP Batcher vs GPU Instancing
这是两个容易混淆的优化,需要明确区分:
| SRP Batcher | GPU Instancing | |
|---|---|---|
| 目标 | 降低每个 DrawCall 的 CPU 开销 | 减少 DrawCall 数量 |
| DrawCall 数量 | 不变 | 显著减少(N 合 1) |
| 同一 Mesh 要求 | ❌ 不需要 | ✅ 需要相同 Mesh |
| 同一 Material 要求 | ✅ 需要同一 Shader Variant | ✅ 需要完全相同的材质 |
| 适用场景 | 场景中大量不同 Mesh 但使用同一 Shader | 大量相同 Mesh 的物体(草、树、粒子) |
| 原理 | 持久化 GPU Buffer,跳过重复绑定 | 一次性提交多个 Instance 数据 |
两者可以共存。 Unity 在 SRP 中会先尝试 Instancing(如果能合批),退而求其次走 SRP Batcher(同 Shader 不同 Mesh),最后 fallback 到传统 Draw。
6. 如何在 Frame Debugger 中查看
开启 Frame Debugger(Window → Analysis → Frame Debugger),点击一个 DrawCall:
- Batching 显示为 “SRP Batch” → SRP Batcher 生效
- 显示 “DrawDynamic” 或 “SetPassCall” → 没有命中 SRP Batcher

常见的导致 SRP Batcher 退出的原因:
- Shader 中材质属性没有放在
CBUFFER_START(UnityPerMaterial)/CBUFFER_END内 - 使用了 Multi-Pass Shader
- Shader 包含了 Procedural Instancing(
#pragma instancing_options procedural),且没有正确处理 - 材质使用了不同 Shader Variant(例如关键字状态不同)
7. 优缺点与限制
优点
- 显著降低 CPU 开销:在大量同 Shader 物体的场景下,CPU 端的渲染提交耗时可以降低 50%~80%
- 无需合 Mesh:不需要像 Static Batching 那样合并模型,不同 Mesh 也可以受益
- 透明集成:开启后对项目无侵入,Shader 符合规范即可自动生效
缺点与限制
| 限制 | 说明 |
|---|---|
| 仅 SRP 管线 | 不适用于 Built-in Render Pipeline |
| 同 Shader Variant 要求 | 即使同一 Shader,关键字状态不同也不会 batch |
| Multi-Pass 不支持 | 使用了多 Pass 的 Shader 会禁用 SRP Batcher |
| CBuffer 布局必须一致 | 所有 Variant 的 UnityPerMaterial CBuffer 声明必须完全相同 |
| GPU 内存占用 | 材质属性常驻 GPU,多材质时会有额外的显存开销 |
8. 总结
- 本质:CPU 端的提交优化,不减少 DrawCall 数量
- 原理:把材质数据持久化缓存在 GPU,省去重复的 Buffer 绑定和状态设置
- 代价:牺牲 GPU 内存,换来 CPU 状态切换时间的降低
- 关键:Shader 中声明
CBUFFER_START(UnityPerMaterial)/CBUFFER_END,且保证所有 Variant 的 CBuffer 布局一致 - 优势场景:大量不同 Mesh 但使用同一种 Shader 的场景(如开放世界的建筑、物件)
- 与 Instancing 关系:互补,可共存
对比你的原始笔记,主要补充的内容:
- 完整的 CBuffer 体系(UnityPerDraw / UnityInstancing)说明
- 与 GPU Instancing 的详细对比
- Frame Debugger 验证方法
- SRP Batcher 工作流程图解
- 优缺点和限制清单
- 针对”优化 SetPassCall” 说法的更加精确的描述