ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

GPU Instancing 实战:一句话画一万个,怎么开怎么用全讲透

GPU Instancing 实战:一句话画一万个,怎么开怎么用全讲透 开场一个一万棵草让 CPU 崩溃的难题小王要做一片草地一万棵草。他先试了动态批处理——直接卡爆“一万棵草DrawCall 爆炸CPU 直接跪了老鸟说这种’大量相同物体’要用GPU Instancing……可这玩意儿到底怎么开启、怎么用是勾个选项就行还是要写代码有几种用法”老鸟说“GPU Instancing 就是为’大量相同物体’而生的神器一次 DrawCall 画上万个相同网格CPU 几乎不累开启方式有好几种从’勾选项’到’写代码’都有今天把每种用法手把手教你” 第一幕GPU Instancing 是什么核心定义GPU Instancing(GPU实例化) 用一次DrawCall画大量相同网格的物体 ↓ 关键: 网格相同、材质相同,但 每个实例可以有不同的 位置/旋转/缩放(和少量其他属性) ↓ CPU只传每个实例的差异数据 GPU自己复制网格,画出所有实例!为什么它高效普通渲染1000棵树: CPU发1000个DrawCall 每个都传网格数据 → CPU累死 GPU Instancing: CPU发1个DrawCall 一个1000个实例的变换数组 GPU拿着同一个网格,画1000次 ↓ CPU超轻松,GPU高效批量!生动理解GPU Instancing像盖章印刷 普通渲染: 一张张手画1000棵树(累) Instancing: 刻一个树的印章(网格) 给GPU一张1000个盖章位置的清单 GPU咔咔咔盖1000个章! ↓ 一个模子,批量复制,飞快!和其他批处理的定位动态批处理: 小的、不同的动态物体(CPU拼顶点) 静态批处理: 静止物体(预处理合并) GPU Instancing: 大量相同网格⭐ ↓ Instancing专治: 树、草、石头、子弹、 敌人群、粒子...大量重复的东西! 第二幕方式一——材质勾选(最简单)⭐开启步骤✅ 最简单的开启方式 1. 选中材质 2. Inspector里找到 Enable GPU Instancing 3. 勾选它! ↓ 搞定!用这个材质的相同物体 会自动尝试Instancing前提条件✅ 要生效需满足 1. Shader支持Instancing (URP的Lit、Unlit等内置Shader都支持) 2. 物体用相同网格 相同材质 3. 物体是普通的MeshRenderer (场景里摆放的GameObject) ↓ 勾选满足条件 → 自动Instancing适用场景✅ 这种方式适合 场景里手动摆放/Instantiate的 大量相同物体 (如摆了一堆相同的石头、树) ↓ 最省事,勾一下就行!生动理解勾选方式材质勾选像告诉系统:这批可以盖章印 勾上Enable GPU Instancing 跟Unity说这材质的相同物体, 请用盖章方式批量画 ↓ Unity自动识别相同的,批量处理! 第三幕方式二——代码 DrawMeshInstanced适用场景✅ 当物体不是场景GameObject, 而是纯数据驱动渲染时 (如程序生成的大量草、子弹、特效) ↓ 用代码直接调用Instancing绘制! 不需要创建GameObject!基础代码usingUnityEngine;publicclassGrassInstancing:MonoBehaviour{publicMeshgrassMesh;// 草的网格publicMaterialgrassMat;// 开启Instancing的材质Matrix4x4[]matrices;// 每个实例的变换voidStart(){intcount1000;// ⚠️单次上限1023!matricesnewMatrix4x4[count];for(inti0;icount;i){Vector3posnewVector3(Random.Range(-50f,50f),0,Random.Range(-50f,50f));QuaternionrotQuaternion.Euler(0,Random.Range(0,360f),0);Vector3scaleVector3.one;matrices[i]Matrix4x4.TRS(pos,rot,scale);}}voidUpdate(){// ⭐一次画1000棵草!Graphics.DrawMeshInstanced(grassMesh,0,grassMat,matrices);}}关键限制单次1023个⚠️ DrawMeshInstanced单次最多画1023个! ↓ 超过要分批: 10000棵草 → 分成10次调用 每次≤1023个分批处理代码voidDrawManyInstances(Matrix4x4[]allMatrices){inttotalallMatrices.Length;// 假设10000intbatchSize1023;for(inti0;itotal;ibatchSize){intcountMathf.Min(batchSize,total-i);// 取这一批的子数组Matrix4x4[]batchnewMatrix4x4[count];System.Array.Copy(allMatrices,i,batch,0,count);Graphics.DrawMeshInstanced(grassMesh,0,grassMat,batch);}}⚠️ 注意: 上面每帧new数组会GC! 实际应预先分好批,缓存数组复用!生动理解代码方式DrawMeshInstanced像直接下达印刷指令 不用摆GameObject 直接跟GPU说: 拿这个网格(草),照这1000个位置(矩阵), 给我批量印出来! ↓ 纯数据驱动,超高效,适合海量物体! 第四幕方式三——DrawMeshInstancedIndirect(超大量)适用场景✅ 当数量巨大(几万、几十万) 甚至实例数据在GPU上生成时 ↓ 用Indirect版本: 实例参数放在GPU缓冲(ComputeBuffer) 连画多少个都由GPU决定! ↓ 适合: 超大规模草地、粒子、 GPU剔除后的渲染简化示例usingUnityEngine;publicclassMassiveGrass:MonoBehaviour{publicMeshmesh;publicMaterialmat;publicintinstanceCount100000;// 十万!ComputeBufferargsBuffer;// 绘制参数ComputeBufferpositionBuffer;// 实例位置数据uint[]argsnewuint[5]{0,0,0,0,0};voidStart(){// 设置绘制参数args[0]mesh.GetIndexCount(0);// 索引数args[1](uint)instanceCount;// 实例数argsBuffernewComputeBuffer(1,args.Length*sizeof(uint),ComputeBufferType.IndirectArguments);argsBuffer.SetData(args);// 准备实例位置数据(传到GPU)Vector4[]positionsnewVector4[instanceCount];for(inti0;iinstanceCount;i){positions[i]newVector4(Random.Range(-100f,100f),0,Random.Range(-100f,100f),1);}positionBuffernewComputeBuffer(instanceCount,sizeof(float)*4);positionBuffer.SetData(positions);mat.SetBuffer(_Positions,positionBuffer);}voidUpdate(){// ⭐一次调用画十万个!Graphics.DrawMeshInstancedIndirect(mesh,0,mat,newBounds(Vector3.zero,Vector3.one*1000),argsBuffer);}voidOnDestroy(){argsBuffer?.Release();positionBuffer?.Release();}}关键点✅ Indirect版本特点 - 数量几乎无上限 - 实例数据在GPU缓冲 - 参数(画多少)也可GPU动态定 - 配合Compute Shader做GPU剔除 ↓ ⚠️ 复杂度高,ComputeBuffer要记得Release!生动理解 IndirectIndirect像全自动印刷厂 普通DrawMeshInstanced: 你告诉印刷机印1000个(CPU定数量) Indirect: 印刷机自己的系统(GPU)决定印多少! 连数量都在GPU算(如剔除后剩多少) ↓ 超大规模、GPU驱动的终极方案! 第五幕让实例各不相同(每实例属性)需求不同实例不同颜色Instancing默认所有实例材质属性一样 → 但想让每个实例不同颜色怎么办? ↓ 用MaterialPropertyBlock传每实例数据!代码实现usingUnityEngine;publicclassColoredInstances:MonoBehaviour{publicMeshmesh;publicMaterialmat;Matrix4x4[]matrices;MaterialPropertyBlockprops;voidStart(){intcount500;matricesnewMatrix4x4[count];Vector4[]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(){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) half4 frag(v2f i) : SV_Target { UNITY_SETUP_INSTANCE_ID(i); // 关键 // 取当前实例的颜色 float4 col UNITY_ACCESS_INSTANCED_PROP( Props, _Color); return col; }生动理解每实例属性每实例属性像同款盖章不同颜色墨水 网格相同(同一个印章) 但每个盖章用不同颜色墨水(每实例颜色) ↓ 还是批量盖章(Instancing不断) 但每个又能不一样! ↓ 既高效又有变化! 第六幕三种方式对比与选择对比表方式 数量 用法 场景 ────────────────────────────────────────────────────── 材质勾选 中等 最简单 场景摆放的相同物体 DrawMeshInstanced ≤1023/次 写代码 程序生成的大量物体 DrawMeshInstanced- 几乎无限 复杂 超大规模、GPU驱动 Indirect (草海、GPU剔除) ──────────────────────────────────────────────────────选择建议✅ 怎么选 场景里摆的相同物体(石头堆) → 材质勾选,最省事 程序生成、几百到几千个(子弹、敌人) → DrawMeshInstanced 超大规模、几万几十万(草海、森林) → DrawMeshInstancedIndirect 配合GPU剔除 ↓ 按数量和场景选!⚠️ 第七幕使用注意点注意1Shader 必须支持⚠️ 不是所有Shader都支持Instancing! ↓ ✅ 检查 - URP内置Shader(Lit/Unlit)默认支持 - 自定义Shader要加Instancing支持代码 #pragma multi_compile_instancing注意2性能不一定总赢⚠️ Instancing也有开销(准备实例数据) 物体数量少时,可能不如普通渲染 ↓ ✅ 适合大量相同物体 少量(几个)没必要用注意3数组不要每帧 new// ❌ 每帧new,GC暴增!voidUpdate(){Matrix4x4[]mnewMatrix4x4[1000];// 坏!}// ✅ 缓存数组,复用Matrix4x4[]cached;voidStart(){cachednewMatrix4x4[1000];}voidUpdate(){/* 用cached */}注意4ComputeBuffer 要 Release// ⚠️ Indirect用的ComputeBuffer// 必须手动释放,否则内存泄漏!voidOnDestroy(){argsBuffer?.Release();positionBuffer?.Release();}注意5验证是否真的Instancing了✅ 用Frame Debugger确认 看到 Draw Mesh (instanced) → 成功!一次画了多个 ↓ 没生效就查Shader支持、材质设置️ 第八幕实战——完整草地方案需求做一片5000棵草的草地 要求: 高性能、每棵草朝向随机完整代码usingUnityEngine;publicclassGrassField:MonoBehaviour{publicMeshgrassMesh;publicMaterialgrassMat;// 勾了InstancingpublicintgrassCount5000;publicfloatfieldSize50f;// 分批(每批1023)Matrix4x4[][]batches;voidStart(){intbatchSize1023;intbatchCountMathf.CeilToInt((float)grassCount/batchSize);batchesnewMatrix4x4[batchCount][];intcreated0;for(intb0;bbatchCount;b){intsizeMathf.Min(batchSize,grassCount-created);batches[b]newMatrix4x4[size];for(inti0;isize;i){Vector3posnewVector3(Random.Range(-fieldSize,fieldSize),0,Random.Range(-fieldSize,fieldSize));QuaternionrotQuaternion.Euler(0,Random.Range(0,360f),0);batches[b][i]Matrix4x4.TRS(pos,rot,Vector3.one);}createdsize;}}voidUpdate(){// 每帧画所有批次(数组已缓存,无GC)foreach(varbatchinbatches){Graphics.DrawMeshInstanced(grassMesh,0,grassMat,batch);}}}✅ 要点 - 预先分好批,缓存数组(无GC) - 每帧只调用绘制(不重新生成数据) - 材质勾了Instancing、Shader支持 ↓ 5000棵草,极少DrawCall,CPU轻松!✅ GPU Instancing 使用检查清单开启方式 □ 会材质勾选Enable GPU Instancing⭐ □ 会用DrawMeshInstanced代码 □ 知道有DrawMeshInstancedIndirect 前提条件 □ Shader支持Instancing □ 相同网格相同材质 □ 自定义Shader加了multi_compile_instancing 每实例属性 □ 会用MaterialPropertyBlock传每实例数据 □ Shader里用UNITY_ACCESS_INSTANCED_PROP 注意事项 □ 知道DrawMeshInstanced单次≤1023 □ 数组缓存不每帧new(避免GC) □ ComputeBuffer记得Release □ 用Frame Debugger验证真Instancing了 选型 □ 场景摆放→材质勾选 □ 程序生成→DrawMeshInstanced □ 超大规模→Indirect 一句话总结GPU Instancing 怎么用它用一次 DrawCall 画大量相同网格专治树、草、子弹、敌人群这类大量重复物体。三种开启方式① 材质勾选 “Enable GPU Instancing”最简单适合场景摆放的相同物体② 代码Graphics.DrawMeshInstanced适合程序生成单次上限 1023 个超过要分批③DrawMeshInstancedIndirect超大规模几万几十万实例数据放 GPU 缓冲配合 GPU 剔除。想让每个实例不同如颜色用 MaterialPropertyBlock 传每实例数据Shader 里用 UNITY_ACCESS_INSTANCED_PROP 读取。关键注意Shader 必须支持自定义 Shader 加#pragma multi_compile_instancing、数组别每帧 newGC、ComputeBuffer 要 Release、用 Frame Debugger 验证真的 Instancing 了核心口诀Instancing一次画一堆相同物体材质勾选最简单代码DrawMeshInstanced上限1023要分批超大量用Indirect每实例差异用PropertyBlockShader要支持数组缓存别GC GPU Instancing 使用速查表方式数量难度适用材质勾选中⭐最易场景摆放物体DrawMeshInstanced≤1023/次中程序生成物体DrawMeshInstancedIndirect几乎无限高超大规模/GPU驱动PropertyBlock-中每实例差异化 一句话记住核心场景摆放勾材质、程序生成用 DrawMeshInstanced记得分批 1023、超大规模用 Indirect。Shader 必须支持、数组要缓存、Buffer 要释放——写完一定用 Frame Debugger 看到 “instanced” 才算成功 延伸Instancing 的进阶方向【GPU Instancing的进阶玩法】 基础Instancing → 更强的组合 ① GPU剔除(Compute Shader): GPU算哪些实例可见 → 只画可见的 → DrawMeshInstancedIndirect动态数量 ② LOD: 远处实例用低模,近处高模 → 分组Instancing ③ 动画: 顶点动画烘焙到纹理(VAT) → 大量动画角色也能Instancing! ④ 结合SRP Batcher: URP下两者配合优化 ↓ 从静态草地到十万动画大军 Instancing是大规模渲染的核心!
返回列表