1. 动态合批的核心原理与价值
动态合批(Dynamic Batching)是Unity引擎中一项关键的渲染优化技术。简单来说,它就像把一堆零散的快递包裹打包成一个集装箱运输——原本需要多次运输的货物,现在只需一次就能完成。具体到渲染流程中,Unity会在CPU端将多个小型可移动物体的顶点数据合并,再一次性提交给GPU处理。
这个过程的本质是减少Draw Call数量。每次Draw Call都是CPU向GPU发出的绘制指令,涉及材质切换、状态设置等准备工作。实测数据显示,在移动设备上,Draw Call数量超过100就可能引发性能问题。动态合批通过将多个物体的绘制合并为单个Draw Call,能显著降低CPU开销。
动态合批特别适合处理场景中大量重复的小型物体,比如:
- 散落的金币/道具
- 战场上的子弹/弹幕
- 森林中的小石块/草丛
但要注意,动态合批与静态合批有本质区别:
- 动态合批:运行时CPU实时合并顶点数据,适合移动物体
- 静态合批:提前合并静态物体数据,适合固定场景元素
2. SRP/URP中的动态合批配置
2.1 基础配置步骤
在SRP/URP管线中配置动态合批比内置管线更直观。以URP 12.x版本为例:
全局开关:
Edit > Project Settings > Player > Other Settings > Dynamic Batching这个总开关控制整个项目的动态合批功能
URP管线专属设置:
- 打开URP资源文件(通常名为
UniversalRP-HighQuality) - 在
Advanced栏目下找到Dynamic Batching选项 - 建议同时开启
SRP Batcher以获得最佳性能
- 打开URP资源文件(通常名为
注意:SRP Batcher和动态合批是互补技术,前者优化材质提交效率,后者减少Draw Call数量
2.2 配置界面详解
URP 2021 LTS版本后的配置界面有所变化:
| 配置项 | 推荐值 | 作用说明 |
|---|---|---|
| Dynamic Batching | ✅开启 | 基础开关 |
| SRP Batcher | ✅开启 | 提升材质处理效率 |
| GPU Instancing | 按需开启 | 大量重复模型时使用 |
实测案例:在一个包含500个移动箱子的场景中,仅开启动态合批可使Draw Call从500降至120,配合SRP Batcher后进一步降至80。
3. 动态合批的硬性条件与避坑指南
3.1 顶点数量限制
Unity官方文档说明动态合批的顶点属性不能超过900个,但实际测试发现:
- 内置管线:严格遵循900顶点属性限制
- URP管线:可支持到约1500顶点属性(视Shader复杂度而定)
计算顶点属性的公式:
顶点属性数 = 顶点数 × 每个顶点的属性数量例如使用包含位置、法线、UV0的Shader时:
- 每个顶点包含:position(3) + normal(3) + uv(2) = 8个属性
- 最大顶点数 = 900 / 8 ≈ 112个
3.2 材质一致性要求
必须使用完全相同的材质实例。常见误区包括:
- 修改了材质的某个参数(如颜色值)
- 使用
MaterialPropertyBlock动态修改属性 - 材质实例虽然参数相同但内存地址不同
解决方法:
// 错误做法:创建新材质实例 renderer.material.color = Color.red; // 正确做法:使用共享材质 public Material sharedMaterial; ... renderer.sharedMaterial = sharedMaterial;3.3 光照贴图问题
使用光照贴图的物体需要特别注意:
- 必须指向相同的光照贴图位置
- 光照贴图UV必须完全一致
- 建议对动态物体禁用光照贴图
可以通过检查器查看:
Mesh Renderer > Lighting > Lightmap Index3.4 多Pass着色器限制
多Pass着色器(如标准着色器)会导致:
- 只有第一个Pass能被合批
- 后续Pass会产生额外Draw Call
- 解决方案:改用单Pass着色器或自定义Shader
4. 性能分析与实战决策
4.1 Profiler数据解读
使用Unity Profiler分析时重点关注:
Render Statistics:
- Batches:合批后的总批次数
- Saved by batching:被合批节省的Draw Call数
CPU耗时:
RenderLoop.Draw耗时变化- 动态合批本身带来的CPU开销
实测数据对比(中端移动设备):
| 场景规模 | 无合批 | 仅动态合批 | 动态+SRP Batcher |
|---|---|---|---|
| 100个箱子 | 6.2ms | 3.8ms | 2.1ms |
| 500个子弹 | 崩溃 | 8.4ms | 5.7ms |
4.2 项目适配建议
根据项目类型选择策略:
适合开启动态合批的情况:
- 大量小规模移动物体(顶点数<200)
- 物体使用简单Shader(属性少)
- 中低端移动设备项目
建议关闭动态合批的情况:
- 物体顶点数普遍较大(>300)
- 使用复杂Shader(如PBR多贴图)
- 高端PC/主机项目(SRP Batcher更高效)
5. 高级优化技巧
5.1 混合使用多种合批技术
最佳实践组合:
- 静态大场景:Static Batching
- 动态小物体:Dynamic Batching
- 重复模型:GPU Instancing
- 材质管理:SRP Batcher
配置示例:
// 运行时动态切换合批策略 void OptimizeForPlatform() { if (SystemInfo.graphicsDeviceType == GraphicsDeviceType.Vulkan) { GraphicsSettings.useScriptableRenderPipelineBatching = true; GraphicsSettings.dynamicBatching = false; } else { GraphicsSettings.dynamicBatching = true; } }5.2 Shader优化策略
使Shader更易合批的技巧:
- 减少顶点属性(如去掉切线数据)
- 避免使用
worldPos等需要逐顶点计算的属性 - 简化变体数量(减少Shader关键字)
示例Shader修改:
// 原版(不易合批) struct Attributes { float4 positionOS : POSITION; float3 normalOS : NORMAL; float4 tangentOS : TANGENT; float2 uv : TEXCOORD0; float2 uv2 : TEXCOORD1; }; // 优化版(更易合批) struct Attributes { float4 positionOS : POSITION; float2 uv : TEXCOORD0; };6. 常见问题排查指南
当发现合批效果不理想时,按以下步骤排查:
检查合批状态:
- 使用Frame Debugger查看每帧的Draw Call
- 选中物体查看Inspector中的"Batching"信息
典型错误信息与解决方案:
| 错误提示 | 可能原因 | 解决方案 |
|---|---|---|
| "Different materials" | 材质实例不同 | 使用sharedMaterial |
| "Too many vertices" | 顶点超标 | 简化模型或分拆 |
| "Multi-pass shader" | 复杂Shader | 改用单Pass着色器 |
| "Lightmap mismatch" | 光照图不一致 | 统一光照图设置 |
- 内存监控:
- 动态合批会导致约10%的额外内存占用
- 使用Profiler的Memory模块监控
Dynamic Batch项
在最近的一个2D手游项目中,我们通过动态合批将Draw Call从350降至90,同时配合以下优化:
- 标准化所有精灵的材质
- 控制单个合批组的精灵数量在50以内
- 禁用非必要的光照计算 最终在千元机上实现了稳定的60FPS渲染。