开场从理论懂了到手上能做小王理解了动态批处理的原理但一动手就懵“道理我懂了可代码到底怎么写怎么设置才能触发合批怎么验证成没成有没有能跑起来的完整案例让我照着做一遍”老鸟说“好光讲原理是纸上谈兵。今天用几个完整可运行的案例从’触发合批’到’意外断批’再到’对比各方案’让你手上真正会做” 第一幕案例一——触发动态批处理场景设定案例: 场景里放100个小方块(会旋转,是动态的) 目标: 让它们动态批处理成1个DrawCall代码生成100个小方块usingUnityEngine;publicclassDynamicBatchDemo:MonoBehaviour{publicMaterialsharedMat;// 关键:共享的同一材质publicintcount100;voidStart(){for(inti0;icount;i){// 创建小方块(Cube顶点少,满足顶点限制)GameObjectcubeGameObject.CreatePrimitive(PrimitiveType.Cube);// 随机位置cube.transform.positionnewVector3(Random.Range(-10f,10f),Random.Range(-10f,10f),Random.Range(-10f,10f));// ⭐关键:所有方块用同一个材质cube.GetComponentMeshRenderer().sharedMaterialsharedMat;// 挂个旋转脚本,让它动起来cube.AddComponentRotator();}}}// 让方块旋转的脚本publicclassRotator:MonoBehaviour{voidUpdate(){transform.Rotate(Vector3.up,50f*Time.deltaTime);}}关键点解读✅ 触发动态批处理的要点 1. GameObject.CreatePrimitive(Cube) → Cube顶点少(24个),满足顶点限制⭐ 2. sharedMaterial sharedMat → 所有方块用同一个材质⭐ → 用sharedMaterial不用material! (material会创建实例导致断批) 3. 物体在动(旋转) → 是动态物体,适合动态批处理开启动态批处理的设置✅ 项目设置里开启 Edit → Project Settings → Player → Other Settings → Rendering → 勾选 Dynamic Batching ↓ (内置管线用这个;URP要在Asset里设置)验证用 Frame Debugger 看结果运行后打开Frame Debugger ✅ 成功合批: 看到 Draw Dynamic 一次画了多个方块 DrawCall数量远小于100 ❌ 没合批: 100个方块 100个DrawCall 选中看提示原因 第二幕案例二——意外断批的坑坑1用了 material 而非 sharedMaterial// ❌ 错误示范:导致断批!voidBadExample(GameObjectcube){// material会为每个物体创建材质实例!cube.GetComponentMeshRenderer().materialsharedMat;// ↑ 每个物体现在有独立材质实例 → 无法合批!}// ✅ 正确:用sharedMaterialvoidGoodExample(GameObjectcube){cube.GetComponentMeshRenderer().sharedMaterialsharedMat;// ↑ 共享同一材质 → 能合批}关键区别 .material → 访问时创建独立实例(断批!) .sharedMaterial → 共享的原材质(能合批) ↓ 这是最常见的断批坑!坑2运行时改颜色导致断批// ❌ 错误:改material.color产生实例,断批voidBadChangeColor(GameObjectcube){cube.GetComponentMeshRenderer().material.colorColor.red;// ↑ 访问material并改属性 → 独立实例 → 断批!}想给不同物体不同颜色,又不想断批? → 见后面MaterialPropertyBlock方案 (但那个是配合Instancing的) ↓ 动态批处理下,想合批就得完全相同的材质坑3物体顶点太多// ❌ 用高面数模型,超过顶点限制voidBadHighPoly(){// 假设这是个几千顶点的复杂模型GameObjectmodelInstantiate(highPolyPrefab);// ↑ 顶点太多,超过动态批处理限制// 根本无法合批!}✅ 动态批处理只给低顶点物体 Cube、Quad、简单道具... 高模 → 用Instancing或静态批处理生动理解这些坑断批的坑像拼单时的意外 坑1(material): 明明想一起点,系统却 给每人开了独立订单(实例化) 坑2(改颜色): 一改口味就变独立订单 坑3(顶点多): 东西太大,超过拼单限额 ↓ 每个坑都让本能拼单变各点各的! 第三幕案例三——对比 GPU Instancing场景1000个相同的树需求: 渲染1000棵相同的树(相同网格材质) 问题: 动态批处理搞不定(树顶点多数量大) 方案: GPU Instancing更合适!方案A动态批处理(不适合)// ❌ 1000棵树用动态批处理// 问题:树顶点多,超限;就算合也是CPU变换1000棵的顶点// → 慢!方案BGPU Instancing(推荐)⭐usingUnityEngine;publicclassInstancingDemo:MonoBehaviour{publicMeshtreeMesh;// 树的网格publicMaterialtreeMat;// 开启Instancing的材质Matrix4x4[]matrices;// 每棵树的变换矩阵voidStart(){intcount1000;matricesnewMatrix4x4[count];// 准备1000棵树的位置/旋转/缩放for(inti0;icount;i){Vector3posnewVector3(Random.Range(-50f,50f),0,Random.Range(-50f,50f));QuaternionrotQuaternion.identity;Vector3scaleVector3.one;matrices[i]Matrix4x4.TRS(pos,rot,scale);}}voidUpdate(){// ⭐一次调用画1000棵树!(GPU Instancing)Graphics.DrawMeshInstanced(treeMesh,0,treeMat,matrices);// ↑ 1个DrawCall画1000棵!// GPU自己处理每棵的位置}}材质要开启 Instancing✅ 材质设置 选中材质 → Inspector → 勾选 Enable GPU Instancing ↓ Shader也要支持Instancing (URP的Lit等内置Shader默认支持)为什么 Instancing 更适合GPU Instancing vs 动态批处理 动态批处理: CPU变换每个顶点 → 1000棵树×顶点数 → CPU爆炸! GPU Instancing: CPU只传1000个矩阵(位置数据) GPU自己复用同一网格,画1000次 → CPU轻松,GPU高效! ↓ 大量相同物体 → Instancing完胜!生动理解两者区别动态批处理像把1000个模型手动摆好拼一起 CPU累死(变换所有顶点) Instancing像给GPU一张图纸1000个坐标 照这图纸,在这1000个位置各画一个 GPU自己批量复制! CPU只给坐标,轻松! ↓ 相同物体大量重复 → Instancing! 第四幕案例四——MaterialPropertyBlock 不断批改属性需求Instancing 下让每个物体不同颜色问题: 想要1000个物体各自不同颜色 但改material.color会断批 方案: 用MaterialPropertyBlock Instancing代码实现usingUnityEngine;publicclassInstancingColorDemo:MonoBehaviour{publicMeshmesh;publicMaterialmat;// 开启InstancingMatrix4x4[]matrices;Vector4[]colors;MaterialPropertyBlockprops;// 属性块voidStart(){intcount500;matricesnewMatrix4x4[count];colorsnewVector4[count];for(inti0;icount;i){matrices[i]Matrix4x4.TRS(newVector3(Random.Range(-20f,20f),0,Random.Range(-20f,20f)),Quaternion.identity,Vector3.one);// 每个物体不同的随机颜色colors[i]newColor(Random.value,Random.value,Random.value,1);}// ⭐用PropertyBlock设置每实例颜色propsnewMaterialPropertyBlock();props.SetVectorArray(_Colors,colors);}voidUpdate(){// 一次画500个,各自不同颜色,还是1个DrawCall!Graphics.DrawMeshInstanced(mesh,0,mat,matrices,matrices.Length,props);}}关键Shader 要支持每实例属性// Shader里声明每实例的属性(简化示意) UNITY_INSTANCING_BUFFER_START(Props) UNITY_DEFINE_INSTANCED_PROP(float4, _Color) UNITY_INSTANCING_BUFFER_END(Props) // frag里用每实例的颜色 half4 frag(v2f i) : SV_Target { UNITY_SETUP_INSTANCE_ID(i); float4 col UNITY_ACCESS_INSTANCED_PROP(Props, _Color); return col; }生动理解 PropertyBlockMaterialPropertyBlock像给拼单的每个人贴便签 不改材质本身(不断批) 只是给每个实例贴张便签说明差异 → 这个红色 那个蓝色 ↓ 材质还是同一个(能合批/Instancing) 但每个物体又能不同! ↓ 既要合批,又要个性化 → 用它! 第五幕完整案例对比总结三种方案对比(结合案例)案例 最佳方案 原因 ────────────────────────────────────────────────── 100个小方块(会动) 动态批处理 顶点少,同材质 1000棵相同树 GPU Instancing 相同网格,数量大 静止的建筑群 静态批处理 不动,可预处理 500个不同色实例 Instancing 既合批又个性化 PropertyBlock URP各种物体通用优化 SRP Batcher 不同材质也优化 ──────────────────────────────────────────────────决策代码逻辑(伪代码)// 如何选择批处理方案的思路voidChooseBatchingStrategy(){if(物体静止不动)用静态批处理();// 空间换时间elseif(大量相同网格数量很大)用GPUInstancing();// 最适合重复物体elseif(小物体顶点少同材质)用动态批处理();// 补充方案elseif(URP/HDRP项目)依赖SRPBatcher();// 现代管线首选}️ 第六幕实战验证流程完整验证步骤// 验证合批效果的调试辅助publicclassBatchStatsMonitor:MonoBehaviour{voidOnGUI(){// 显示当前渲染统计(编辑器)// 实际用Stats面板和Frame Debugger看GUILayout.Label(查看方式:);GUILayout.Label(1. Game视图Stats面板看Batches);GUILayout.Label(2. Frame Debugger看合批详情);GUILayout.Label(3. Profiler看CPU渲染耗时);}}验证清单✅ 验证合批是否生效 1. Game视图 → Stats面板 看 Batches 和 Saved by batching → Saved越多,合批越成功 2. Frame Debugger → 看DrawCall数量 → 看 Dynamic Batch / Instanced 3. Profiler → 对比开启前后CPU渲染耗时 → 真的降了才算成功! ↓ 数据验证,别想当然!⚠️ 第七幕案例中的常见错误错误对照表// ❌ 错误1: 用material断批renderer.materialmat;// ✅ 正确: 用sharedMaterialrenderer.sharedMaterialmat;// ❌ 错误2: 高模用动态批处理// (顶点超限,合不了)// ✅ 正确: 高模用Instancing或静态// ❌ 错误3: 改material.color断批renderer.material.colorColor.red;// ✅ 正确: 用MaterialPropertyBlockvarpropsnewMaterialPropertyBlock();props.SetColor(_Color,Color.red);renderer.SetPropertyBlock(props);// ❌ 错误4: 忘记开启Instancing// (材质没勾Enable GPU Instancing)// ✅ 正确: 材质勾选 Shader支持// ❌ 错误5: 每帧new数组(GC!)voidUpdate(){Matrix4x4[]mnewMatrix4x4[1000];// 每帧GC!}// ✅ 正确: 数组缓存,复用✅ 实战检查清单动态批处理案例 □ 用了sharedMaterial而非material⭐ □ 物体顶点数够少 □ 没在运行时改material属性 □ 项目设置开了Dynamic Batching Instancing案例 □ 材质勾了Enable GPU Instancing □ Shader支持Instancing □ 用DrawMeshInstanced/自动Instancing □ 数组缓存了不每帧new PropertyBlock案例 □ 用PropertyBlock而非改material □ Shader声明了每实例属性 验证 □ 看了Stats面板Batches □ 用Frame Debugger确认合批 □ 用Profiler验证耗时降了 一句话总结批处理落地的关键代码要点动态批处理用sharedMaterial不是material、物体顶点要少、项目设置开启 Dynamic Batching——适合小的、同材质的动态物体。GPU Instancing用Graphics.DrawMeshInstanced 材质勾选 Enable GPU Instancing一次调用画大量相同网格——适合大量重复物体树、草。MaterialPropertyBlock想让实例各自不同如颜色又不断批用 PropertyBlock 贴便签而非改 material——既合批又个性化。验证三件套Stats 面板看 Batches、Frame Debugger 看合批详情、Profiler 验证耗时真降了核心口诀动态批用sharedMaterial顶点要少大量相同用Instancing改属性用PropertyBlock别用material会断批StatsFrameDebuggerProfiler三重验证 代码要点速查表场景代码要点陷阱触发动态批处理sharedMaterial 小物体别用material改属性不断批MaterialPropertyBlock别改material.color大量相同物体DrawMeshInstanced材质要勾Instancing缓存数组数组复用别每帧new(GC)验证合批StatsFrameDebugger别想当然 一句话记住核心sharedMaterial保合批material会断批大量相同用 Instancing要个性化用 PropertyBlock写完一定用 Frame Debugger Profiler 验证 延伸从代码看批处理本质【所有批处理代码,本质都在做一件事】 减少CPU告诉GPU画东西的次数! sharedMaterial → 让引擎认为是一样的→能合并 DrawMeshInstanced → 一句话画一千个 PropertyBlock → 合并但保留差异 ↓ 核心都是: 让GPU一次多画点 让CPU少喊几次 ↓ 理解这个本质, 所有合批API的用法都是它的变体!