Unity 批处理与合批
Unity 批处理与 Draw Call 优化 - 静态合批、动态合批、GPU Instancing 与 SRP Batcher
这四种技术都为"降低 Draw Call 开销"服务,但机制、适用对象、减少的东西完全不同,是 Unity 里最容易混淆的一组概念。
一、先理解 Draw Call 为什么贵
一次 Draw Call,CPU 的开销不只是"喊 GPU 画",更多在每次绘制前的准备:绑定材质/着色器状态、设置该物体的常量(变换矩阵、材质参数)、绑定缓冲、调用图形 API。物体一多,CPU 忙于逐物体 setup 与提交,成为瓶颈。
四种技术从两个不同角度缓解它:
关键认知:前三者想办法减少 Draw Call 的数量;SRP Batcher 不减数量,而是让每次 Draw Call 变便宜。这是它和其它三者最本质的区别。
二、静态合批(Static Batching)
原理
把共享同一材质的静态(不移动) 网格,合并成一个大网格(共享顶点缓冲)。合并在构建时或运行时(StaticBatchingUtility.Combine)完成。渲染时一次绑定大缓冲,按材质分组绘制,省去逐物体的状态设置与变换准备。
不同的静态网格(同材质) ──合并──► 一个大网格(共享顶点缓冲)
[椅子][桌子][柜子] [椅子+桌子+柜子 合体]要求与代价
| 项 | 说明 |
|---|---|
| 适用对象 | 静态物体(勾选 Batching Static),不能移动 |
| 网格 | 可以是不同网格 |
| 材质 | 需同材质才能合进同一 Draw Call |
| 主要收益 | 减少 Draw Call 与逐物体 CPU 开销 |
| 主要代价 | 内存:合并网格会复制顶点数据,显著增加内存/包体 |
| 开关 | Player Settings > Static Batching;物体 Static 标志 |
三、动态合批(Dynamic Batching)
原理
对小网格,Unity 每帧在 CPU 上把它们的顶点变换到世界空间,合并进一个 Draw Call。
要求与代价
| 项 | 说明 |
|---|---|
| 适用对象 | 可移动的小网格 |
| 网格 | 需很小:约 ≤ 900 个顶点属性(如 位置+法线+UV 三属性 → ≤ 300 顶点) |
| 材质 | 需同材质 |
| 其它限制 | Lightmap 索引不同、多 Pass 着色器、负缩放等会打断合批 |
| 主要代价 | 每帧 CPU 变换顶点的成本可能超过省下的 Draw Call 成本 → 常得不偿失 |
现状:动态合批(网格)收益有限、限制多,URP 里对网格默认不推荐/关闭;主要仍用于粒子、线、精灵等。优先考虑 GPU Instancing 或 SRP Batcher。
四、GPU Instancing(GPU 实例化)
原理
用同一网格 + 同一材质画很多份:一个 Draw Call 画 N 个实例,每个实例的数据(变换矩阵、per-instance 属性)通过数组传给 GPU,由 SV_InstanceID 索引。
同一个网格(同材质)画很多份:
[树] × 1000 → 一个 Draw Call,GPU 按 instanceID 取各自变换要求与代价
| 项 | 说明 |
|---|---|
| 适用对象 | 大量相同物体(树、石头、子弹、人群),可移动 |
| 网格 | 必须同一网格 |
| 材质 | 必须同一材质 |
| 着色器 | 需支持实例化:#pragma multi_compile_instancing + UNITY_INSTANCING 宏;材质勾选 Enable GPU Instancing |
| per-instance 变化 | 通过 MaterialPropertyBlock 传逐实例属性(如不同颜色) |
| 与静态合批区别 | 静态合批合并不同网格;实例化是同网格多份 |
五、SRP Batcher
原理(与前三者不同!)
SRP Batcher 不减少 Draw Call 数量,而是降低每次 Draw Call 的 CPU 成本。它按 着色器变体(shader variant) 分批,而非按材质:
- 同一 shader variant 的物体(即使材质、贴图不同)能走一条快速路径。
- 每个材质的属性以持久 CBUFFER 常驻 GPU 内存;每次绘制只更新逐物体数据(变换等),省去最贵的"材质/着色器状态重设"。
要求与特点
| 项 | 说明 |
|---|---|
| 管线 | 仅 SRP(URP / HDRP),Built-in 不支持 |
| 着色器 | 需 SRP Batcher 兼容(用 UnityPerMaterial CBUFFER 组织材质属性) |
| 默认 | URP/HDRP 默认开启 |
| 减少的是 | 每次 Draw Call 的 CPU 开销(非数量) |
| 与实例化 | 单次绘制上二者互斥:SRP Batcher 兼容时优先走 SRP Batcher,该物体不走 Instancing |
断批边界:一个 SRP Batch = 同一 PSO
一个 "SRP Batch" 是一串共享同一 GPU 管线状态(PSO)、可连续廉价提交的 draw。它不是把多个 draw 合成一个,而是让每个 draw 便宜。同一 shader variant 是必要条件,但不充分 —— 渲染状态也必须相同,否则断批:
| 批内变化 | 存储位置 | 影响 |
|---|---|---|
| 材质属性(颜色/贴图/float) | UnityPerMaterial CBUFFER | 换 CBUFFER 偏移 → 不断批 |
| 逐物体变换 | 逐物体大缓冲 | 换偏移 → 不断批 |
| 渲染状态(Blend/ZWrite/ZTest/Cull/Stencil…) | PSO | 必须换 PSO → 断批 |
心智模型:一个 SRP Batch = 同一 PSO(= shader variant + 渲染状态) 下的连续 draw。变体相同只保证了 PSO 的一半,渲染状态也必须相同。
常见坑:若 shader 把渲染状态暴露为材质可调(如
Cull [_Cull]、ZWrite [_ZWrite]、Blend [_SrcBlend] [_DstBlend]),则两个材质哪怕用同一 shader、同一变体,只要这些值不同 → 渲染状态不同 → PSO 不同 → 断批。Unity 标准 Lit 就大量使用这类可调渲染状态。
如何确认:用 Frame Debugger 查看每个 SRP Batch 及其断批原因(Batch cause)(如 different render state / shader keywords / shader)。想批得更长,就让同类物体的渲染状态保持一致;半透明因需按距离排序、易与不同混合态交错,天然更容易断批。
六、四者核心区别(本文重点)
| 维度 | 静态合批 | 动态合批 | GPU Instancing | SRP Batcher |
|---|---|---|---|---|
| 减少什么 | Draw Call 数量 | Draw Call 数量 | Draw Call 数量 | 每次 Draw 的 CPU 成本(数量不变) |
| 网格要求 | 不同网格可 | 小网格 | 必须同网格 | 任意 |
| 材质要求 | 同材质 | 同材质 | 同材质 | 同 shader variant(材质可不同) |
| 可否移动 | 否(静态) | 是 | 是 | 是 |
| 主要代价 | 内存(复制顶点) | CPU 每帧变换顶点 | 需同网格同材质 | 需 SRP + 兼容着色器 |
| 管线 | 通用 | 通用 | 通用 | 仅 SRP |
| 典型场景 | 静态场景道具 | 少(多被取代) | 大量相同物体 | URP/HDRP 通用基础 |
一句话记忆:
- 静态合批:不同网格、同材质、静态 → 合成一个大网格(费内存)。
- 动态合批:小网格、同材质、可动 → CPU 每帧拼顶点(常不划算)。
- GPU Instancing:同网格、同材质、多份 → 一次画 N 个。
- SRP Batcher:同 shader、材质可不同 → 每次 Draw 变便宜(不减数量)。
七、它们如何协同 / 优先级
同一物体可能符合多种条件,Unity 有大致的应用优先级(以官方版本行为为准):
- 静态合批先应用于静态物体。
- 对 SRP 兼容物体,SRP Batcher 通常优先于 GPU Instancing(单次绘制二者互斥)。
- 动态合批排在最后,多数情况已被 SRP Batcher / Instancing 取代。
八、选型决策
- URP/HDRP 项目:先确保着色器 SRP Batcher 兼容(这是基础盘);大量相同物体再叠 GPU Instancing;大量静态道具用静态合批(权衡内存)。
- Built-in 项目:主要靠静态合批 + GPU Instancing;动态合批仅对小网格且确认划算时用。
九、与 GPU Resident Drawer / BRG 的关系
这四者是传统的批处理手段。Unity 6 的 GPU Resident Drawer 更进一步 —— 基于 BatchRendererGroup(BRG),把实例数据常驻 GPU、做 GPU 驱动的实例化与剔除,是这条路线的现代演化:
| 层级 | 手段 |
|---|---|
| 降低单次 Draw 成本 | SRP Batcher |
| 减少 Draw 数量 | 静态/动态合批、GPU Instancing |
| GPU 驱动、数据常驻 | GPU Resident Drawer(BRG) |
总结
Unity 提供了四种主要的批处理与合批技术,分别从不同维度优化 Draw Call 开销。静态合批通过合并不同网格(同材质、静态物体)减少 Draw Call 数量,代价是复制顶点数据导致内存增加。动态合批将小网格每帧在 CPU 上合并,但其 CPU 开销常大于收益,已逐步被替代。GPU Instancing用同一网格同材质一次绘制大量实例,适合大量重复物体。SRP Batcher不减少 Draw Call 数量,而是通过将材质属性持久驻留 GPU CBUFFER 来降低每次 Draw Call 的 CPU 成本(仅限 SRP 管线)。实际项目中应根据管线类型和场景特点组合使用:URP/HDRP 项目以 SRP Batcher 为基础,叠加 GPU Instancing 和静态合批;Built-in 项目主要依赖静态合批和 GPU Instancing。
附:核心概念速查
| 概念 | 一句话 |
|---|---|
| 静态合批 | 不同网格同材质合成大网格,静态,费内存 |
| 动态合批 | 小网格同材质每帧 CPU 拼顶点,常不划算 |
| GPU Instancing | 同网格同材质一次画 N 份 |
| SRP Batcher | 同 shader variant,材质属性驻留 GPU,每次 Draw 变便宜(不减数量) |
| MaterialPropertyBlock | 给实例化传逐实例属性 |
| UnityPerMaterial | SRP Batcher 兼容着色器的材质属性 CBUFFER |
| 优先级 | 静态合批 → SRP Batcher(优先于实例化)→ 动态合批 |
