在 Unity 中,SetPassCall 和 DrawCall 是两个密切相关但含义不同的性能指标。简单来说:
-
SetPassCall:指的是 GPU 渲染状态的切换次数(例如换了一个材质球或 Shader 的某个 Pass)。 -
DrawCall:指的是 CPU 向 GPU 发送绘制命令的次数(每次命令绘制一批图元,如一个网格)。
🔍 详细区别
| 维度 | SetPassCall | DrawCall |
|---|---|---|
| 本质 | 切换渲染管线状态(如混合模式、纹理、着色器参数) | 提交几何体绘制指令 |
| 触发条件 | 当前要绘制的物体使用的材质/Shader Pass与上一个物体不同 | 每次绘制一个 Mesh / 一批 Mesh (如 SRP Batcher 合并后) |
| 开销特点 | 通常比 DrawCall 更昂贵,因为刷新 GPU 内部状态流水线 | 每个 DrawCall 也有 CPU 准备数据、调用 API 的开销 |
| 如何降低 | 按材质排序,让相同材质的物体连续绘制 | 合批(静态/动态批处理、GPU Instancing、SRP Batcher) |
📌 举一个经典的例子
假设有 3 个物体:
-
物体 A:材质 M1(使用 Shader 的 Pass 0)
-
物体 B:材质 M1(使用 Shader 的 Pass 0)
-
物体 C:材质 M2(使用 Shader 的 Pass 0)
按顺序 A → B → C 绘制:
-
绘制 A:
SetPassCall(切换到 M1-Pass0) +DrawCall(绘制 A) -
绘制 B:因为材质还是 M1-Pass0,不会产生新的 SetPassCall ,只有
DrawCall(绘制 B) -
绘制 C:材质变成 M2-Pass0,产生一次新的
SetPassCall+DrawCall
结果:3 个 DrawCall,2 个 SetPassCall。
如果乱序绘制(A → C → B),就会产生 3 个 SetPassCall → 性能更差。
🎯 对性能优化的指导意义
-
SetPassCall 过高 ⇒ 需要按材质/Shader Pass 对渲染队列进行排序(Unity 默认做了,但透明物体需格外注意)。
-
DrawCall 过高 ⇒ 需要合并模型(静态批处理)、启用 GPU Instancing 或 SRP Batcher(URP/HDRP 下非常有效,能大幅减少 SetPassCall 和 DrawCall 两者的 CPU 开销)。
💡 在 Unity 中如何查看
-
Frame Debugger:直观展示每一帧的 SetPassCall 和 DrawCall。
-
Statistics 面板 :显示
Batches(大致等于 DrawCall 数)和Saved by batching(合批节省的次数),但没有直接显示 SetPassCall 数。URP 的 Rendering Debugger 中可以看到SetPass计数。
🧠 先回顾:SetPassCall 为什么昂贵?
SetPassCall 是 GPU 从一个渲染状态(例如混合模式、深度测试、使用的 Shader Pass)切换到另一个状态的指令。每次切换都会导致:
-
GPU 内部流水线刷新,丢失已准备好的并行状态。
-
CPU 需要重新绑定纹理、常量缓冲区、着色器程序等。
SRP Batcher 的核心目标就是大幅减少 SetPassCall,它可以做到:不管场景中有多少种不同材质 (Material) 的物体,只要 Shader 支持 SRP Batcher,每个 Shader Pass 在整个帧中只需要一次 SetPassCall。
⚙️ SRP Batcher 工作原理(简化版)
传统批处理(如静态/动态批处理)试图减少 DrawCall,但无法减少 SetPassCall。而 SRP Batcher 通过 持久化绑定 和 属性分离 来工作:
-
属性分离:
-
Per-Object (每个物体的专属属性):位置、缩放、自定义颜色等存在一个快速更新的常量缓冲区 (UnityPerDraw)。
-
Per-Material (材质共享的属性):纹理、基础颜色、光滑度等存在另一个缓冲区 (UnityPerMaterial)。
-
-
持久化绑定:
-
当 Shader 兼容 SRP Batcher 时,Unity 会在 CPU 端建立数据缓冲区,并把材质属性绑定到 GPU 的固定槽位(slot)。
-
渲染物体时,Unity 只需调用一次
SetPassCall来设定 Shader Pass,然后为每个物体只更新UnityPerDraw数据并发出 DrawCall。材质属性不需要重新上传(除非材质真的改变了)。
-
结果:即使有 1000 个使用不同材质的物体,每个材质也只会产生 1 次 SetPassCall(而不是 1000 次)。
✅ SRP Batcher 对 Shader 的要求(兼容性条件)
要使 Shader 被 SRP Batcher 接纳,必须满足以下条件(否则会自动回退到传统路径,产生大量 SetPassCall):
-
Shader 必须部署在 SRP (URP 或 HDRP)中
内置渲染管线 (Built-in) 不支持 SRP Batcher。
-
所有材质的属性(纹理、float、color 等)必须声明在名为
UnityPerMaterial的 CBUFFER (Constant Buffer) 内-
不能使用传统统一变量(uniform float _XXX)直接暴露。
-
不能使用
MaterialPropertyBlock中的非 CBUFFER 属性?实际上 MaterialPropertyBlock 也可以工作,但每个物体如果修改了相同 CBUFFER 里的属性,就会破坏批次?SRP Batcher 允许 Per-Object 修改材质属性,通过每物体更新UnityPerMaterial的实例数据,依然兼容。但性能略有下降,但仍比传统方式好。
-
-
Shader 中不能使用以下非兼容特性:
-
在顶点着色器中使用
mul(UNITY_MATRIX_MVP, ...)这种旧式宏(现在用UnityObjectToClipPos是安全的)。 -
使用
#pragma multi_compile产生的较多 variant 不影响(SRP Batcher 可以处理)。 -
使用几何着色器或曲面细分着色器(部分情况可以,但官方建议谨慎)。
-
修改渲染状态(
Cull,ZTest等)不影响兼容性。 -
使用
material.GetFloat等脚本频繁修改材质属性 时,只要修改后材质版本不同,批次仍可能分裂,但 SRP Batcher 会按材质实例分组,每组一次 SetPassCall,而不是每个物体一次。
-
-
Shader 必须声明正确的名称空间(适用于手动编写) 。
例如:
cs
CBUFFER_START(UnityPerMaterial)
float4 _MainTex_ST;
half _Metallic;
CBUFFER_END
在 ASE 中生成 SRP Batcher 兼容的 Shader
Amplify Shader Editor 从 1.7.x 版本 开始支持一键生成兼容 SRP Batcher 的 Shader。具体操作:
1. 创建新 Shader 时选择正确的模板
-
在 Unity 中右键 →
Create→Amplify Shader→ 选择URP或HDRP模板(不能选 Built-in 模板)。 -
建议选
Lit(光照)或Unlit。ASE 会自动生成符合 SRP Batcher 规范的代码。
2. 检查 Shader 的主设置(在 ASE 编辑器中)
-
打开你的 Shader 图,点击 空白处 或查看 Node Properties。
-
找到 Shader Settings 部分的
SRP Batcher属性框:-
Enabled:必须勾选(新版本中 URP/HDRP 模板默认勾选)。
-
Check Built-in:保持勾选可以检测非兼容特性并给出警告。
-
3. 确认所有材质节点都位于 "Global" 或 "Property"?
实际上 ASE 会自动将材质属性(Float/Range/Color/Texture 等)放置到 UnityPerMaterial CBUFFER 中,前提是:
-
这些节点的
Type设置为Property(不要误设为Global,除非你明确需要全局变量)。 -
并且节点的
Variable Mode默认就是Create,由 ASE 统一管理。
重要 :如果你手动添加了 Custom Expression 节点且访问了某个 uniform 变量但未通过 CBUFFER 声明,可能会导致 SRP Batcher 失效。尽量使用 ASE 内置节点。
4. 避免使用不兼容的功能
-
不要在 ASE 中启用 Geometry Shader(除非你明确知道并接受失去 SRP Batcher 支持)。
-
尽量不要使用
Custom Material Data功能(如VertexOffset中动态访问材质属性可能导致问题,但这些通常也是兼容的,取决于具体代码)。
5. 构建并测试兼容性
-
生成 Shader 后,在场景中运行,打开 Frame Debugger (
Window → Analysis → Frame Debugger)。 -
勾选 "Enable",观察渲染事件。
-
对于兼容 SRP Batcher 的物体,Frame Debugger 中会显示 "SRP Batch" 标签,并且
SetPass调用次数非常少(每个 Shader Pass 一次)。 -
如果不兼容,会显示 "Fallback" 或 "No SRP Batch"。
⚠️ 常见陷阱与误区
| 误区 | 真相 |
|---|---|
| SRP Batcher 能减少 DrawCall | 不,它主要减少 SetPassCall。DrawCall 数可能不会变少(甚至可能略多),但因为 SetPass 大幅减少,CPU 总体开销下降。 |
| 只要勾选 SRP Batcher 就万事大吉 | 还需要确保 Shader 中没有使用非 CBUFFER 的属性 。比如通过 float4 _MyColor; 直接定义(未包在 CBUFFER_START 里)就会破坏兼容性。ASE 生成的代码通常安全,但如果你混入手动代码或某些老旧节点,可能出问题。 |
| MaterialPropertyBlock 会完全破坏 SRP Batcher | 不完全。如果 MaterialPropertyBlock 修改的是 PerMaterial 缓冲区中的属性,那么每个物体依然需要单独更新该缓冲区,但 SetPassCall 依然只有一次,只是 CPU 更新数据的开销会增加(仍然比传统方式快)。 |
| Shader 变体 (multi_compile) 会破坏 SRP Batcher | 不会。SRP Batcher 能处理 keyword 变体,但切换 keyword 依然可能引起 SetPassCall(因为实际 Shader 代码改变了)。不过通常比传统切换材质更快。 |
📈 性能实测对比(举例)
| 场景 | 传统管线 (Built-in) | SRP Batcher (URP) |
|---|---|---|
| 100 个物体,各不相同的材质 | 100 SetPass + 100 DrawCalls | 1 SetPass + 100 DrawCalls (SetPass 近乎消除) |
| 1000 个物体,10 种材质(每种 100 个实例) | 1000 SetPass(因为未排序) + 1000 DrawCalls | 10 SetPass + 1000 DrawCalls(大幅降低) |
| 使用 MaterialPropertyBlock 每物体改颜色 | 每物体 SetPass(传统模式) + 1000 DC | 1 SetPass + 1000 DC(但每物体更新 per-object 数据) |