Unity SetPassCall和DrawCall的区别是什么

在 Unity 中,SetPassCallDrawCall 是两个密切相关但含义不同的性能指标。简单来说:

  • 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 通过 持久化绑定属性分离 来工作:

  1. 属性分离

    • Per-Object (每个物体的专属属性):位置、缩放、自定义颜色等存在一个快速更新的常量缓冲区 (UnityPerDraw)。

    • Per-Material (材质共享的属性):纹理、基础颜色、光滑度等存在另一个缓冲区 (UnityPerMaterial)。

  2. 持久化绑定

    • 当 Shader 兼容 SRP Batcher 时,Unity 会在 CPU 端建立数据缓冲区,并把材质属性绑定到 GPU 的固定槽位(slot)。

    • 渲染物体时,Unity 只需调用一次 SetPassCall 来设定 Shader Pass,然后为每个物体只更新 UnityPerDraw 数据并发出 DrawCall。材质属性不需要重新上传(除非材质真的改变了)。

结果:即使有 1000 个使用不同材质的物体,每个材质也只会产生 1 次 SetPassCall(而不是 1000 次)。

✅ SRP Batcher 对 Shader 的要求(兼容性条件)

要使 Shader 被 SRP Batcher 接纳,必须满足以下条件(否则会自动回退到传统路径,产生大量 SetPassCall):

  1. Shader 必须部署在 SRP (URP 或 HDRP)中

    内置渲染管线 (Built-in) 不支持 SRP Batcher。

  2. 所有材质的属性(纹理、float、color 等)必须声明在名为 UnityPerMaterial 的 CBUFFER (Constant Buffer) 内

    • 不能使用传统统一变量(uniform float _XXX)直接暴露。

    • 不能使用 MaterialPropertyBlock 中的非 CBUFFER 属性?实际上 MaterialPropertyBlock 也可以工作,但每个物体如果修改了相同 CBUFFER 里的属性,就会破坏批次?SRP Batcher 允许 Per-Object 修改材质属性,通过每物体更新 UnityPerMaterial 的实例数据,依然兼容。但性能略有下降,但仍比传统方式好。

  3. Shader 中不能使用以下非兼容特性

    • 在顶点着色器中使用 mul(UNITY_MATRIX_MVP, ...) 这种旧式宏(现在用 UnityObjectToClipPos 是安全的)。

    • 使用 #pragma multi_compile 产生的较多 variant 不影响(SRP Batcher 可以处理)。

    • 使用几何着色器或曲面细分着色器(部分情况可以,但官方建议谨慎)。

    • 修改渲染状态(Cull, ZTest 等)不影响兼容性。

    • 使用 material.GetFloat 等脚本频繁修改材质属性 时,只要修改后材质版本不同,批次仍可能分裂,但 SRP Batcher 会按材质实例分组,每组一次 SetPassCall,而不是每个物体一次。

  4. 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 中右键 → CreateAmplify Shader → 选择 URPHDRP 模板(不能选 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 数据)
相关推荐
电子云与长程纠缠2 小时前
UE5 GameFeature创建与使用
开发语言·学习·ue5·游戏引擎
moonsims3 小时前
AiBrainLink:无人化系统异构连接架构-多执行体、多链路(5G+自组网)、多业务流(控制、遥测、视频、文件)透明传输、多对多控制
unity·游戏引擎
小贺儿开发4 小时前
Unity3D 年会抽奖工具(附体验链接)
数据库·unity·excel·人机交互·工具·抽奖·互动
旧物有情4 小时前
Unity性能优化之合批,什么是合批?
unity·性能优化·游戏引擎
天人合一peng1 天前
Hololens2 发布debug调试安装至hololens2
unity·xr
Yuk丶1 天前
UE4 与 UE5:技术差异深度解析
c++·ue5·游戏引擎·ue4·游戏程序·虚幻
l1t1 天前
DeepSeek总结的Delta 成长记:写入、Unity Catalog 和时间旅行
数据库·人工智能·unity
年少无知且疯狂1 天前
【Unity】Mirror网络框架
unity
顾温1 天前
协程结束——实测
开发语言·unity·c#