开场一个复制粘贴大军的奇迹小王要做一片草地需要10000 棵草…用静态合批内存爆炸复制1万份网格用动态合批CPU 累死每帧算1万份顶点老鸟淡定地说“这种大量相同的物体用GPU Instancing——一道命令GPU 帮你画1万棵”“叮”1万棵草DrawCall 只有1帧率丝滑小王震惊“GPU 是怎么做到用一道命令画1万个的” 第一幕GPU Instancing 是什么一句话定义GPU Instancing 用同一份网格数据 告诉GPU画很多次每次位置不同 一道命令画出成百上千个 关键 - 网格只有1份(不复制!) - 一道DrawCall - GPU自己画N遍和合批的本质区别合批(静态/动态)的思路 把N个物体的网格焊成1个大网格 ↓ 1个大网格1次DrawCall GPU Instancing的思路 根本不合并网格 就1份小网格 ↓ 告诉GPU: 拿这份网格画1000遍 位置我给你一张清单 ↓ GPU照着清单画1000个生动比喻盖章 vs 拼接合批 把1000张纸拼成一大张 (网格被复制拼接费内存) GPU Instancing 一个印章盖1000下 (网格就1份印章位置不同) ↓ 印章(网格)只有一个 但能盖出1000个图案(实例) 超级省 第二幕核心原理——网格共用数据分离关键洞察什么相同什么不同1000棵草 【相同的部分】 网格形状(草的模型) → 只存1份 【不同的部分】 每棵草的位置、旋转、缩放 可能还有颜色 ↓ 这些差异打包成一个数组数据是怎么组织的GPU Instancing 的数据结构 网格数据(1份): 草的顶点、三角形... 实例数据(N份很小): 实例0: 位置(0,0), 旋转0°, 绿色 实例1: 位置(1,0), 旋转15°, 黄绿 实例2: 位置(2,0), 旋转30°, 深绿 ... 实例999: ... ↓ GPU: 拿网格 逐个套用实例数据 → 画出来图解传统方式(1000次DrawCall): CPU: 画草0! → GPU CPU: 画草1! → GPU ...1000次... GPU Instancing(1次DrawCall): CPU: 拿这份草网格 按这张1000条的位置清单 画1000遍! → GPU(1次) ↓ GPU: 收到内部循环画1000个 为什么这样超省省在哪 1. 内存省网格只1份(不复制) 2. DrawCall省1次搞定 3. CPU省只下1次命令 ↓ 三省合一专治大量相同物体 第三幕GPU 内部怎么画N个顶点着色器的实例IDGPU画每个实例时有个编号 instance ID (实例ID): 0,1,2,...,999 顶点着色器里 我现在画的是第几个实例? ↓ 根据实例ID从数组取对应数据 第5个实例它的位置是(5,0) ↓ 把网格顶点移到那个位置 ↓ 画出第5棵草图解 GPU 内部循环GPU拿到指令后 for (instanceID 0; instanceID 1000; instanceID) { 取出 instanceID 对应的位置/颜色 用这份网格 这个位置 画出这一个实例 } ↓ GPU硬件层面高速循环 1000个实例飞速画完生动理解实例ID就像工厂流水线编号 同一个模具(网格) ↓ 1号位装A零件的位置数据 → 产出产品1 2号位装B零件的位置数据 → 产出产品2 ... ↓ 模具不变只换配置单(实例数据) 批量生产️ 第四幕如何使用 GPU Instancing方式1材质勾选最简单适合场景里手动摆放的相同物体 第1步选中材质 第2步Inspector勾选 ☑ Enable GPU Instancing 第3步完事 Unity自动把用这个材质的 相同网格物体实例化渲染材质界面示意┌──────────────────────────────┐ │ Material: GrassMaterial │ ├──────────────────────────────┤ │ Shader: Standard │ │ ... │ │ ☑ Enable GPU Instancing ←勾 │ └──────────────────────────────┘方式2代码批量绘制最强大// 适合程序生成的大量物体(草地/粒子)publicclassGrassRenderer:MonoBehaviour{publicMeshgrassMesh;// 草的网格publicMaterialgrassMat;// 支持实例化的材质Matrix4x4[]matrices;// 存所有草的变换voidStart(){// 生成1000棵草的位置matricesnewMatrix4x4[1000];for(inti0;i1000;i){Vector3posnewVector3(Random.Range(-50,50),0,Random.Range(-50,50));// 记录每棵草的位置/旋转/缩放matrices[i]Matrix4x4.TRS(pos,Quaternion.identity,Vector3.one);}}voidUpdate(){// 一次绘制1000棵草Graphics.DrawMeshInstanced(grassMesh,// 网格(1份)0,// subMesh索引grassMat,// 材质matrices);// 1000个位置数组}}DrawMeshInstanced 的威力Graphics.DrawMeshInstanced 一次调用 - 传入1份网格 - 传入1000个变换矩阵 ↓ GPU画1000个实例 ↓ 1次DrawCall! ⚠️注意一次最多1023个 超过要分批调用方式3DrawMeshInstancedIndirect超大量适合几万、几十万个物体 数据直接放GPU缓冲区 连CPU准备数组的开销都省了 ↓ 用于超大规模草地、森林、粒子 (较进阶需要ComputeBuffer) 第五幕让每个实例各不相同问题都一样太假了1000棵一模一样的草 位置不同但完全相同 ↓ 看起来很假(复制粘贴感) 想要 每棵草颜色略不同、大小略不同 更自然解决Per-Instance 数据GPU Instancing支持每实例不同数据 不只位置不同 还能每个实例 - 不同颜色 - 不同大小 - 不同参数 ↓ 用 MaterialPropertyBlock 传递代码示例每实例不同颜色voidUpdate(){// 准备每个实例的颜色数组Vector4[]colorsnewVector4[1000];for(inti0;i1000;i){// 每棵草随机深浅绿色colors[i]newColor(0,Random.Range(0.5f,1f),0,1);}// 用MaterialPropertyBlock传递MaterialPropertyBlockblocknewMaterialPropertyBlock();block.SetVectorArray(_Color,colors);Graphics.DrawMeshInstanced(grassMesh,0,grassMat,matrices,1000,block);// ↑ 传入每实例数据}Shader 里接收每实例数据// 支持实例化的Shader需要特殊声明 // 声明每实例属性 UNITY_INSTANCING_BUFFER_START(Props) UNITY_DEFINE_INSTANCED_PROP(float4, _Color) UNITY_INSTANCING_BUFFER_END(Props) fixed4 frag(v2f i) : SV_Target { // 取出当前实例的颜色 UNITY_SETUP_INSTANCE_ID(i); float4 color UNITY_ACCESS_INSTANCED_PROP( Props, _Color); return color; } 第六幕GPU Instancing vs 其他合批核心区别对比静态合批合并网格(复制,费内存)适合不动 动态合批每帧合并(费CPU)适合会动小物体 GPU Instancing不合并(网格共用)适合大量相同 GPU Instancing独特优势 ✓ 网格不复制(超省内存) ✓ 支持大量物体(上万) ✓ 物体可以动(每帧更新矩阵即可) ✓ 每实例可不同(颜色/大小)什么时候必用 GPU Instancing✓ 草地(成千上万棵草) ✓ 森林(大量树木) ✓ 弹幕(海量子弹) ✓ 人群(大量相似NPC) ✓ 碎石/杂物(大量装饰) ↓ 特征大量 相同网格 相同材质完整对比表静态合批 动态合批 GPU Instancing ───────────────────────────────────────────── 核心思路 合并网格 每帧合并 共用网格画多次 网格复制 是(费内存) 临时 否(省内存!) 物体数量 中等 少 超大(上万) 能否动 否 能 能(更新矩阵) CPU开销 低 高 低 内存开销 高 低 低 需相同网格 否 否 是! 每实例差异 有限 有限 灵活(颜色等) 第七幕GPU Instancing 的坑坑1网格必须完全相同❌ 限制 只有相同网格 相同材质才能实例化 不同的网格(草和树) 无法在同一批实例化 ↓ 每种网格单独一批✅ 理解 GPU Instancing 同一个印章盖多下 印章(网格)必须是同一个 不同印章要分开盖坑2Shader 必须支持❌ 问题 Shader没写实例化支持代码 ↓ 勾了Enable GPU Instancing也没用✅ 解决 - 用Unity内置支持实例化的Shader - 或Shader里加实例化宏 UNITY_INSTANCING_BUFFER等坑3数量限制❌ DrawMeshInstanced 一次最多1023个 超过怎么办✅ 解决 - 分批调用(每1023个一批) - 或用DrawMeshInstancedIndirect(无此限制)坑4以为万能什么都实例化❌ 误区 实例化好全用它 ↓ 少量物体也用反而没必要 或不同网格硬凑无法实例化✅ 正确认知 GPU Instancing专治大量相同 少量物体或各不相同的 用其他方式 第八幕验证 GPU InstancingFrame Debugger 观察Window → Analysis → Frame Debugger 看到 Draw Mesh (instanced) 显示 instance count: 1000 ↓ 说明1000个实例1次画完了Stats 面板Game视图 → Stats Batches降低 DrawCall极低 即使物体上万Batches也很小实战案例场景10000棵草 不用实例化 - 10000 DrawCall - 帧率: 5 FPS 用GPU Instancing - 约10次DrawCall(分批) - 内存: 网格只1份 - 帧率: 60 FPS ✅ 万棵草丝般顺滑✅ GPU Instancing 检查清单适用判断 □ 物体数量大(几十上百甚至上万) □ 网格完全相同 □ 材质相同 设置 □ 材质勾选Enable GPU Instancing □ 或代码用DrawMeshInstanced □ Shader支持实例化 每实例差异(可选) □ 需要不同颜色/大小吗 □ 用MaterialPropertyBlock传递 □ Shader里正确读取实例数据 验证 □ Frame Debugger看到instanced □ instance count正确 □ 超1023的分批了吗 □ 帧率/内存提升了吗 一句话总结GPU Instancing 的核心不合并网格网格就存1份告诉GPU用这份网格画N遍位置我给你清单GPU 一道命令内部循环画出成千上万个。优势网格不复制(超省内存)、支持海量物体、物体能动、每实例还能不同颜色大小。专治大量相同网格相同材质的场景草地、森林、弹幕、人群核心口诀网格存一份位置给清单一道命令画千军同网格才能实例化 四大优化技术终极全景技术核心思路最适合网格静态合批合并网格不动场景物体复制动态合批每帧合并少量会动小物体临时GPU Instancing共用网格画多次大量相同物体1份SRP Batcher优化材质切换URP/HDRP通用不合并 组合拳威力场景静物 → 静态合批草地森林 → GPU Instancing现代管线 → SRP Batcher 打底三管齐下DrawCall 压到极低 延伸GPU Instancing 的进阶应用【超大规模渲染】 DrawMeshInstancedIndirect ComputeShader(GPU计算位置) ComputeBuffer(数据全在GPU) ↓ 几十万草木CPU几乎零负担 ↓ 《原神》《塞尔达》级别的 超大开放世界植被 就靠这类技术