当前位置: 首页 > news >正文

Unity GPU 合批优化详解

Unity GPU 合批优化详解

从 Draw Call 到 Static Batching、Dynamic Batching、GPU Instancing、SRP Batcher,再到 Profiler 和 Frame Debugger 验证。

这份文档的目标不是让你背概念,而是让你能看懂 Unity 为什么卡、该用哪种优化、怎么证明优化真的有效。

先说结论

Unity 渲染优化里经常听到一句话:

减少 Draw Call。

但真正准确的说法应该是:

减少 CPU 给 GPU 下命令的成本,同时不要把 GPU、内存、显存、剔除效率搞坏。

也就是说,Draw Call 少不一定就快,Draw Call 多也不一定就慢。你要先知道瓶颈在 CPU 还是 GPU,然后选对方法。

本文重点补充 3 个最容易被忽略的点:

  1. 底层原理:CPU、Render Thread、GPU、Draw Call、Batch、SetPass 到底是什么关系。
  2. Unity 实操:Static Batching、Dynamic Batching、GPU Instancing、SRP Batcher 分别适合什么,怎么开,什么时候不要用。
  3. 性能验证:怎么用 Stats、Profiler、Frame Debugger 判断合批有没有成功,以及优化后是不是真的变快。

1. 一帧画面是怎么出来的

玩家看到一帧画面,大概经历这条链路:

游戏逻辑 ↓ CPU 更新世界状态 ↓ CPU 准备渲染数据 ↓ Render Thread / 渲染系统组织绘制命令 ↓ CPU 把命令提交给图形 API / 驱动 ↓ GPU 执行绘制 ↓ 屏幕显示

1.1 CPU 做什么

CPU 更像游戏的“调度员”。

它负责:

  • 玩家输入。
  • 角色移动。
  • AI。
  • 物理。
  • 动画状态。
  • UI 逻辑。
  • 场景物体显隐。
  • 告诉 GPU 这一帧要画什么。

比如你的 RPG 场景里有:

玩家 敌人 NPC 房子 地面 树 UI 小地图

CPU 要先判断这些东西当前在哪里、是否可见、用什么材质、用什么 Shader,然后再组织绘制命令。

1.2 GPU 做什么

GPU 更像“绘图工厂”。

它负责:

  • 顶点变换。
  • 光照计算。
  • 阴影。
  • 贴图采样。
  • 像素填充。
  • 后处理。
  • 最终输出画面。

如果 GPU 很忙,通常会看到:

GPU Usage 高 GPU Frame Time 高 降低分辨率以后 FPS 明显提升 关闭阴影/后处理以后 FPS 明显提升

如果 CPU 很忙,通常会看到:

CPU Main Thread 高 Render Thread 高 Batches 很多 SetPass Calls 很多 场景物体很多但 GPU 并不满

2. Draw Call、Batch、SetPass 是什么

2.1 Draw Call

Draw Call 是:

CPU 让 GPU 绘制一次几何体的命令。

比如:

画这棵树 画这个箱子 画这个敌人 画这个按钮

每发一次绘制命令,都有 CPU 端开销。

开销包括:

  • 检查渲染状态。
  • 切换 Mesh。
  • 切换 Material。
  • 切换 Shader。
  • 上传参数。
  • 设置贴图。
  • 组织命令。

所以 Draw Call 多了,CPU 会忙。

2.2 Batch

Batch 可以理解成:

Unity 最终提交给底层渲染的一批绘制任务。

很多时候你会把 Batch 和 Draw Call 近似理解成同一类指标。

Unity Game 窗口右上角Stats里常看到:

Batches: 1200 SetPass calls: 300 Tris: 1.5M Verts: 2.2M

这里的Batches越多,通常 CPU 渲染提交压力越大。

但它不是唯一指标。

例如:

Batches: 20 Tris: 20M

Draw Call 很低,但是三角面太多,GPU 也可能很卡。

2.3 SetPass

SetPass 可以理解成:

切换一次 Shader Pass / 材质渲染状态。

它通常比普通 Draw Call 更贵。

比如:

物体 A 用 URP Lit 材质 物体 B 用 Unlit 材质 物体 C 用另外一个带透明的 Shader

Unity 可能要不断切换渲染状态。

这种切换会让 CPU 和渲染线程变忙。

所以优化时不要只看 Batches,也要看:

SetPass Calls Render Thread Camera.Render DrawRenderers

3. 为什么合批能优化性能

合批的本质:

把多个能一起画的物体,尽量变成更少的绘制提交。

3.1 不合批

假设场景里有 1000 个石头:

CPU: 画石头 1 CPU: 画石头 2 CPU: 画石头 3 ... CPU: 画石头 1000

可能产生:

1000 Batches

3.2 合批以后

如果这些石头能合批:

CPU: 这一批石头一起画

可能变成:

10 Batches

CPU 提交命令次数减少,Render Thread 压力下降。

3.3 一个快递例子

不合批:

1000 个包裹 跑 1000 趟快递站

合批:

1000 个包裹装进几个大箱子 跑几趟就送完

但注意:

如果你为了合批,把所有东西塞进一个超级大箱子,搬不动了,也会慢。

对应 Unity 里就是:

Draw Call 降了 但是 Mesh 太大 显存变多 剔除变差 GPU 更忙

所以优化不是盲目合批,而是选合适的批。


4. Unity 能不能合批,看哪些条件

Unity 合批时最关心这些东西:

4.1 Mesh

Mesh 是模型数据。

包括:

顶点 法线 切线 UV 颜色 骨骼权重 三角面索引

不同合批方式对 Mesh 要求不同。

例如:

  • GPU Instancing 通常要求 Mesh 相同。
  • Static Batching 不要求 Mesh 完全相同,但要求静态且材质兼容。
  • Dynamic Batching 对 Mesh 顶点数量限制很严格。

4.2 Material

材质是合批最容易踩坑的地方。

看起来一样,不代表能合。

比如:

Tree_Mat Tree_Mat

可以尝试合批。

但如果你复制了材质:

Tree_Mat_01 Tree_Mat_02

哪怕两个材质参数完全一样,Unity 也可能认为它们是两个不同材质,导致不能合批。

4.3 Shader

Shader 不一样,通常不能合。

例如:

URP/Lit URP/Unlit

这两个渲染流程不同,不能随便合在一起。

4.4 Texture

贴图不一样,也经常破坏合批。

例如 100 个 UI 图标:

icon_1.png icon_2.png icon_3.png

如果每张图都独立成材质,Batch 会很多。

更好的做法是:

把小图打进同一个 Atlas 用同一个 UI 材质

4.5 Transform

位置、旋转、缩放也会影响合批。

尤其 Dynamic Batching。

比如非统一缩放:

scale = (1, 2, 1)

可能导致动态合批失败。

4.6 Lightmap

静态场景常用 Lightmap。

但如果物体使用不同 Lightmap,或者 Lightmap 参数不同,也可能被分成不同批次。

例如:

房子 A 使用 Lightmap 0 房子 B 使用 Lightmap 1

它们可能不能放进同一个批次。

4.7 Render Queue 和透明排序

透明物体很麻烦。

因为透明物体通常需要按照距离从远到近排序。

例如:

玻璃 水面 半透明特效

这些东西不能像不透明物体那样随便重排顺序,所以合批空间会小很多。


5. Static Batching 静态合批

5.1 一句话理解

把不会动的场景物体提前整理到一起,减少运行时绘制提交。

适合:

房子 墙 地面 道路 石头 固定树木 固定栏杆 固定装饰物

不适合:

玩家 敌人 NPC 会移动的平台 会旋转的机关 会动态隐藏显示很多次的物体

5.2 Unity 怎么开启

项目设置:

Edit → Project Settings → Player → Other Settings → Static Batching

场景物体:

选中 GameObject → Inspector → Static 勾选

常用是勾:

Batching Static

如果你直接勾Static总开关,可能还会包含:

Lightmap Static Navigation Static Occluder Static Occludee Static

这些不是每个物体都应该全开。

5.3 底层大概做了什么

原来:

House Mesh Rock Mesh Fence Mesh Road Mesh

静态合批后,Unity 会把这些静态物体的顶点数据整理成更大的批次。

它的核心思想是:

既然这些东西不会动 那就提前把它们的世界坐标算好 运行时少做事

5.4 优点

优点很直接:

减少 CPU 渲染提交压力 适合大场景固定物体 不需要每帧重新合并

5.5 缺点

最大的缺点是内存。

假设你有 100 个路灯,原本都共享同一个 Mesh:

Lamp Mesh x 1 100 个 Transform

静态合批可能会把它们的顶点按世界空间复制整理。

结果:

内存增加 显存增加 加载时间增加

所以 Static Batching 不是无脑全开。

5.6 RPG 场景例子

你的MainCity这类城市场景,可以这样分:

适合 Static Batching:

城墙 地砖 房屋主体 固定木箱 固定摊位 固定灯笼架 石阶 桥 栏杆

不适合 Static Batching:

玩家 Player Enemy NPC 任务交互点高亮 可破坏木箱 会开关的门 会摇摆的旗帜 粒子特效

5.7 常见错误

错误 1:

把 Player 勾成 Static

后果:

移动、光照、剔除、导航都可能出问题

错误 2:

整个场景所有东西全 Static

后果:

内存上涨 加载慢 动态物体异常

错误 3:

材质乱七八糟,每个房子一个材质

后果:

勾了 Static,但合批效果很差

更好的做法:

同类建筑尽量共用材质 贴图尽量做 Atlas

6. Dynamic Batching 动态合批

6.1 一句话理解

Unity 每帧在 CPU 上临时把一些小 Mesh 拼起来画。

适合:

很小的低模道具 简单小物件 少量小特效模型

不适合:

角色 复杂敌人 高模建筑 大场景物体 大量动态单位

6.2 为什么它会消耗 CPU

Dynamic Batching 不是免费优化。

它每帧要做:

检查哪些物体能合 把顶点从本地空间转换到世界空间 重新整理顶点数据 提交给 GPU

如果模型很小,这点 CPU 开销可能值得。

如果模型稍微复杂一点,就会变成:

为了减少 Draw Call CPU 反而更累

6.3 顶点限制

常见说法是:

Dynamic Batching 适合 300 顶点以下的小 Mesh

更准确一点:

它和顶点属性数量有关

顶点属性包括:

Position Normal UV Tangent Color

属性越多,能动态合批的顶点数越少。

例如:

一个只有 Position + UV 的小面片 可能容易合 一个带 Normal + Tangent + 多套 UV 的模型 顶点不多也可能失败

6.4 例子

可以考虑 Dynamic Batching:

金币:80 顶点 小石子:60 顶点 小草片:40 顶点

不建议:

玩家:8000 顶点 敌人:6000 顶点 建筑:20000 顶点

6.5 什么时候关掉也没问题

如果你项目里:

URP + SRP Batcher 大量中高模 大量角色 GPU Instancing 已经处理重复物体

Dynamic Batching 的价值可能不高。

甚至可能因为 CPU 每帧处理合批导致更慢。

实际项目里要用 Profiler 测,不要凭感觉。


7. GPU Instancing

7.1 一句话理解

同一个 Mesh、同一个 Material 的大量物体,让 GPU 自己复制很多份。

适合:

大量同种树 大量草 大量石头 大量子弹 大量同款士兵 大量同款敌人

不适合:

每个物体 Mesh 都不同 每个物体 Material 都不同 普通 SkinnedMeshRenderer 角色 需要复杂独立材质逻辑的单位

7.2 传统方式

1000 棵树:

CPU: 画树 1 CPU: 画树 2 CPU: 画树 3 ... CPU: 画树 1000

7.3 Instancing 方式

CPU 只告诉 GPU:

Mesh = Tree Material = Tree_Mat Instances = [ position 1, position 2, position 3, ... ]

GPU 自己在 Vertex Shader 里读取每个实例的位置、旋转、缩放,然后画出来。

7.4 Unity 怎么开启

材质上:

选中 Material → Inspector → Enable GPU Instancing

代码里:

material.enableInstancing=true;

7.5 不要用 renderer.material 乱改材质

这是新手最常踩的坑。

错误写法:

renderer.material.color=Color.red;

renderer.material会在运行时实例化一份材质。

原本:

100 个敌人共用 Enemy_Mat

变成:

Enemy_Mat_Instance_1 Enemy_Mat_Instance_2 Enemy_Mat_Instance_3 ...

结果:

材质不再相同 合批被破坏 内存也增加

更推荐:

usingUnityEngine;publicsealedclassEnemyColorSetter:MonoBehaviour{privatestaticreadonlyintBaseColorId=Shader.PropertyToID("_BaseColor");[SerializeField]privateRenderertargetRenderer;privateMaterialPropertyBlockpropertyBlock;privatevoidAwake(){propertyBlock=newMaterialPropertyBlock();}publicvoidSetColor(Colorcolor){targetRenderer.GetPropertyBlock(propertyBlock);propertyBlock.SetColor(BaseColorId,color);targetRenderer.SetPropertyBlock(propertyBlock);}}

这样可以避免复制材质。

注意:

MaterialPropertyBlock 对 GPU Instancing 很常用 但在某些 SRP Batcher 场景下可能让 SRP Batcher 失效 最终要用 Frame Debugger 验证

7.6 DrawMeshInstanced 例子

如果你想自己批量画一堆相同 Mesh,可以用:

usingSystem.Collections.Generic;usingUnityEngine;publicsealedclassSimpleInstancedRocks:MonoBehaviour{[SerializeField]privateMeshrockMesh;[SerializeField]privateMaterialrockMaterial;[SerializeField]privateintcount=500;[SerializeField]privateVector2area=newVector2(50f,50f);privatereadonlyList<Matrix4x4>matrices=newList<Matrix4x4>(1023);privatevoidStart(){rockMaterial.enableInstancing=true;for(inti=0;i<count;i++){floatx=Random.Range(-area.x,area.x);floatz=Random.Range(-area.y,area.y);Vector3position=newVector3(x,0f,z);Quaternionrotation=Quaternion.Euler(0f,Random.Range(0f,360f),0f);Vector3scale=Vector3.one*Random.Range(0.8f,1.2f);matrices.Add(Matrix4x4.TRS(position,rotation,scale));}}privatevoidUpdate(){constintmaxInstanceCount=1023;for(inti=0;i<matrices.Count;i+=maxInstanceCount){intbatchCount=Mathf.Min(maxInstanceCount,matrices.Count-i);Graphics.DrawMeshInstanced(rockMesh,0,rockMaterial,matrices.GetRange(i,batchCount));}}}

上面这个例子好懂,但GetRange每帧会产生 GC。

更好的写法是提前准备数组:

usingUnityEngine;publicsealedclassNoGcInstancedRocks:MonoBehaviour{privateconstintMaxInstanceCount=1023;[SerializeField]privateMeshrockMesh;[SerializeField]privateMaterialrockMaterial;[SerializeField]privateintcount=500;[SerializeField]privateVector2area=newVector2(50f,50f);privateMatrix4x4[]matrices;privatevoidAwake(){count=Mathf.Min(count,MaxInstanceCount);matrices=newMatrix4x4[count];rockMaterial.enableInstancing=true;for(inti=0;i<count;i++){Vector3position=newVector3(Random.Range(-area.x,area.x),0f,Random.Range(-area.y,area.y));Quaternionrotation=Quaternion.Euler(0f,Random.Range(0f,360f),0f);Vector3scale=Vector3.one*Random.Range(0.8f,1.2f);matrices[i]=Matrix4x4.TRS(position,rotation,scale);}}privatevoidUpdate(){Graphics.DrawMeshInstanced(rockMesh,0,rockMaterial,matrices,count);}}

实际大型项目里,如果实例数量特别多,会继续考虑:

DrawMeshInstancedIndirect GPU Culling ComputeBuffer DOTS / Entities Graphics

但初学阶段先理解 GPU Instancing 就够了。

7.7 RPG 项目例子

假设你后面场景里有:

100 个同款小怪 300 个同款资源点 2000 株草 500 棵同款树

选择建议:

同款草、树、石头 → GPU Instancing 敌人普通 SkinnedMeshRenderer → 先别强行 Instancing 敌人武器/掉落物等静态 Mesh → 可以考虑 Instancing

角色类对象更复杂,因为通常涉及:

骨骼动画 蒙皮 装备 材质变化 血条 状态特效

不要一上来就为了 Instancing 把角色系统搞复杂。

先保证:

敌人数量合理 Animator 不滥用 LOD 做好 Update 控制好 对象池做好

8. SRP Batcher

8.1 一句话理解

在 URP/HDRP 里,减少材质和 Shader 参数切换的 CPU 成本。

它和 GPU Instancing 不是同一个东西。

GPU Instancing 解决:

大量相同 Mesh + 相同 Material 的绘制提交问题

SRP Batcher 解决:

大量使用兼容 Shader 的不同物体,切换材质参数时 CPU 太忙的问题

8.2 适合什么

适合:

URP 项目 大量不同 Mesh 大量不同 Material Shader 兼容 SRP Batcher 场景中物体较多

例如 RPG 城市场景:

房子 A 房子 B 墙 石头 道路 木箱 摊位 灯笼

这些东西 Mesh 不一样,不能靠 GPU Instancing 全解决。

但如果 Shader 兼容 SRP Batcher,CPU 设置材质参数的成本会明显下降。

8.3 怎么开启

URP 项目一般在 URP Asset 中开启:

Project 窗口找到 UniversalRenderPipelineAsset → Inspector → Advanced → SRP Batcher

不同 Unity 版本位置可能略有差异,找不到就搜索:

SRP Batcher

8.4 Shader 怎么兼容

一般 Unity 官方 URP Lit、URP Unlit 都是兼容的。

自定义 Shader 要注意:

材质属性要放在 UnityPerMaterial CBUFFER 里 不要乱写每材质常量缓冲

如果 Shader 不兼容,Frame Debugger 里可能会看到 SRP Batcher 没有生效。

8.5 SRP Batcher 和 MaterialPropertyBlock

很多项目会用 MaterialPropertyBlock 给不同物体设置颜色。

比如:

敌人受击变红 NPC 头顶高亮 任务目标描边

这种写法很方便,但要注意:

MaterialPropertyBlock 可能让该 Renderer 走不了 SRP Batcher

不是说不能用,而是:

少量使用没问题 大量使用要验证

如果你有 5000 个草要变色,更适合考虑 GPU Instancing 的实例属性。

如果只是玩家锁定的 1 个 NPC 高亮,用 MaterialPropertyBlock 很正常。


9. 四种合批方式对比

技术解决什么适合不适合主要风险
Static Batching静态物体减少绘制提交建筑、地面、固定道具会移动的物体内存、显存增加
Dynamic Batching小 Mesh 每帧临时合并低顶点小物件角色、大模型、大量复杂物体CPU 额外开销
GPU Instancing大量相同物体草、树、石头、同款道具Mesh/Material 各不相同的物体材质实例化会破坏
SRP Batcher降低 SRP 材质切换成本URP/HDRP 大量物体Built-in 管线、不兼容 Shader自定义 Shader 不兼容

9.1 选择口诀

固定不动的大场景:

Static Batching

大量重复的同款物体:

GPU Instancing

URP 项目里很多不同物体:

SRP Batcher

很小很简单的动态物体:

Dynamic Batching

UI:

Canvas 拆分 + 图集 + 减少材质切换

10. UI 合批也很重要

很多人只看 3D 场景,忽略 UI。

但 Unity UI 也会产生 Batch。

10.1 UI 为什么会打断合批

UI 合批受这些影响:

不同 Canvas 不同材质 不同贴图 不同 Mask 不同字体材质 不同渲染顺序 频繁 Layout 重建

10.2 常见 UI 错误

错误 1:

所有 UI 都放一个巨大 Canvas

后果:

聊天消息刷新一下 整个 Canvas 可能都要重建

错误 2:

每个图标都是独立贴图

后果:

UI Batches 增加 材质/贴图切换增加

错误 3:

大量使用 Mask、Shadow、Outline

后果:

额外绘制和材质变化

10.3 RPG 项目 UI 拆分建议

例如你的游戏里有:

MainUI GameUI ChatUI ToastUI SettingsUI

可以按变化频率拆:

静态 HUD Canvas: 玩家头像 名字 固定按钮 动态 HUD Canvas: 血条 蓝条 小地图玩家点 弹窗 Canvas: 设置 聊天 登录 Toast Canvas: 提示飘字

这样一个小地图点移动,不会导致整个主界面跟着大重建。

10.4 聊天界面例子

聊天 UI 里常见问题:

消息一多 滚动列表卡 LayoutGroup 每帧重排 ContentSizeFitter 频繁计算 文字生成 Mesh

优化建议:

消息 Item 用对象池 只刷新可见范围 减少 ContentSizeFitter 嵌套 文字高度缓存 新消息来了再刷新布局 不要每帧 ForceUpdateCanvases

Canvas.ForceUpdateCanvases()不是不能用,但不要每帧用。

它适合:

打开界面时需要立刻拿到布局尺寸 特殊一次性布局计算

不适合:

Update 每帧调用 滚动时每帧调用 聊天刷新时无脑调用多次

11. 怎么判断合批成功

11.1 Game 窗口 Stats

打开:

Game 窗口 → Stats

重点看:

Batches SetPass calls Tris Verts

例子:

优化前:

Batches: 1800 SetPass calls: 650 Tris: 1.2M

优化后:

Batches: 350 SetPass calls: 120 Tris: 1.2M

这说明 CPU 渲染提交压力可能下降了。

但还要看 Profiler,不要只看 Stats。

11.2 Profiler 看 CPU 还是 GPU

打开:

Window → Analysis → Profiler

先看:

CPU Usage Rendering GPU Usage Memory UI

如果 CPU Rendering 高:

重点看 Batches、SetPass、Render Thread

如果 GPU 高:

重点看阴影、后处理、分辨率、Overdraw、三角面、贴图采样

11.3 Frame Debugger

打开:

Window → Analysis → Frame Debugger

点击 Enable。

你可以一条一条看 Unity 这一帧画了什么。

没合批时可能看到:

Draw Mesh Tree Draw Mesh Tree Draw Mesh Tree Draw Mesh Tree

合批成功可能看到:

Draw Mesh Tree x 100 Render Mesh (instanced) SRP Batch

Frame Debugger 最重要的作用是:

告诉你为什么没合批。

常见原因:

Different Material Different Shader Keywords Different Lightmap Different Render Queue Different Shadow Pass Object is not static Material property block

11.4 正确测试流程

不要一边改一边凭感觉。

建议流程:

1. 记录优化前数据 2. 只改一个优化点 3. 进入同一个场景同一个视角 4. 记录优化后数据 5. 对比 CPU、GPU、Batches、SetPass、内存 6. 确认没有画面错误

记录表可以这样写:

项目优化前优化后是否变好
FPS4258
Batches1600420
SetPass500130
CPU Main Thread18ms11ms
Render Thread12ms5ms
GPU Time9ms9ms基本不变
Memory1.2GB1.35GB变高,需要接受或继续优化

12. 实战案例 1:MainCity 场景

假设你的MainCity有:

房子 80 个 地砖 300 块 摊位 40 个 树 200 棵 灯笼 120 个 箱子 100 个 玩家 1 个 敌人 20 个 NPC 30 个

12.1 错误做法

每个房子一个材质 每个树一个材质 所有东西都勾 Static 玩家也 Static 敌人也 Static UI 全放一个 Canvas

可能结果:

Batches 很高 内存也高 动态物体异常 UI 刷新卡

12.2 推荐做法

建筑:

共享材质 勾 Batching Static 考虑 Lightmap

地面:

合并贴图或使用统一材质 静态 必要时分块,避免一个超级大 Mesh

树:

同款树多 → GPU Instancing 固定少量树 → Static Batching 也可以 风吹动画树 → 不要直接 Static

玩家、敌人、NPC:

不 Static 使用对象池 Animator 控制数量 LOD 远距离降低更新频率

UI:

GameUI、ChatUI、ToastUI 分 Canvas 小地图点只在数据变化时刷新 聊天列表用回收/对象池

12.3 预期效果

优化前:

Batches: 2500 SetPass calls: 700 CPU Main Thread: 22ms Render Thread: 15ms

优化后:

Batches: 500 SetPass calls: 160 CPU Main Thread: 13ms Render Thread: 6ms

这类提升主要来自:

材质复用 Static Batching GPU Instancing UI Canvas 拆分

13. 实战案例 2:5000 棵树

13.1 错误做法

5000 个 GameObject 每个树都 Instantiate 每个树 renderer.material 单独改颜色 每棵树一个材质实例

结果:

Batches 高 内存高 CPU 高 可能还有 GC

13.2 改进做法

同一个 Tree Mesh 同一个 Tree Material Material 开 Enable GPU Instancing 颜色差异用实例属性 远处树用 LOD 或 Impostor

如果树完全不动,也可以对一部分使用 Static Batching。

但如果树有风动画:

不要直接把会动的树干/树叶 Static

可以考虑:

GPU Instancing + Shader 风动画

14. 实战案例 3:敌人和战斗

假设你后面做单机战斗:

EnemyPoint 生成 20 个 Enemy Enemy 有 Animator Enemy 会巡逻 Enemy 会攻击 Enemy 有血条

不要第一步就追求敌人 GPU Instancing。

优先级应该是:

1. 敌人对象池 2. Animator 参数不要每帧乱 Set 3. AI 逻辑分帧/降频 4. 远距离敌人降低更新频率 5. 血条 UI 只在变化时刷新 6. 同款武器/掉落物/特效再考虑 Instancing

为什么?

因为角色性能常见瓶颈不只在 Draw Call。

还可能在:

Animator SkinnedMeshRenderer 骨骼数量 AI Update 寻路 碰撞检测 UI 血条 特效

所以敌人战斗优化不能只盯着合批。


15. 常见误区

15.1 误区:Draw Call 越少越好

不绝对。

如果你把整个城市合成一个超级大 Mesh:

Batches 降了

但可能:

视锥剔除变差 看不见的东西也被画 内存暴涨 加载变慢

正确做法:

按区域、材质、可见性合理分块

15.2 误区:勾了 Static 就一定合批

不一定。

还要看:

材质 Shader Lightmap Renderer 状态 是否真的 Static

15.3 误区:物体长一样就能合批

不一定。

两个物体看起来一样,但如果:

材质不是同一个引用 Shader Keyword 不同 贴图不是同一个 Lightmap 不同 Render Queue 不同

都可能不能合。

15.4 误区:GPU Instancing 能解决所有重复物体

不一定。

GPU Instancing 喜欢:

同 Mesh 同 Material 差异用实例数据表达

如果每个物体都有完全不同材质、不同 Shader、不同动画,它就不适合。

15.5 误区:Dynamic Batching 一定是优化

不一定。

Dynamic Batching 会消耗 CPU。

如果你的瓶颈本来就在 CPU,它可能让情况更糟。


16. 项目优化检查清单

16.1 场景物体

检查:

固定建筑是否 Static 动态物体是否误 Static 同类物体是否共用材质 贴图是否可以 Atlas 远处物体是否有 LOD 看不见的区域是否能被剔除

16.2 材质

检查:

有没有大量 xxx_Instance 材质 有没有代码使用 renderer.material 是否能改为 sharedMaterial 或 MaterialPropertyBlock Shader Keyword 是否过多 透明材质是否太多

16.3 UI

检查:

Canvas 是否按变化频率拆分 聊天列表是否对象池 小地图是否每帧无脑刷新 是否每帧 ForceUpdateCanvases 图片是否使用图集 字体材质是否统一 Mask 是否过多

16.4 战斗角色

检查:

敌人是否对象池 Animator 参数是否变化时才 Set AI 是否每帧全量计算 血条是否只在血量变化时刷新 远距离敌人是否降频 特效是否对象池

16.5 验证

每次优化后记录:

FPS CPU Main Thread Render Thread GPU Time Batches SetPass calls Tris Verts GC Alloc Memory

17. 最后总结

可以用这张表记住:

场景优先方案
固定建筑、道路、墙Static Batching
大量同款树、草、石头GPU Instancing
URP 下大量不同物体SRP Batcher
很小的低模动态物体Dynamic Batching
聊天、背包、HUDCanvas 拆分 + 图集 + 对象池
玩家、敌人、NPC对象池 + LOD + 动画/AI 降频

一句话:

合批不是目的,稳定帧率才是目的。

真正的优化流程应该是:

发现卡 ↓ Profiler 判断 CPU 还是 GPU ↓ Frame Debugger 看渲染提交 ↓ 只改一个优化点 ↓ 重新测试 ↓ 记录数据

这样做,你就不会靠感觉优化,也不会把项目越改越乱。

http://www.jsqmd.com/news/1100280/

相关文章:

  • 市场正规的画册设计公司口碑
  • 互联网医院系统实现诊疗服务的闭环管理
  • MiMo免费体验金
  • WebRTC远程屏幕共享:浏览器直连桌面的终极解决方案
  • Python爬虫经典案例013:爬虫数据存储方案MongoDB——文档型数据库的数据管理艺术
  • 零基础谷歌收录排查问题:外贸站常见5个坑
  • Temperature:AI 的“脑洞旋钮”
  • 成教 / 专升本论文不会写?笔墨 AI 流程化引导,零基础也能搭好论文框架
  • 七大排序算法全解析:从插入到三路快排,手把手带你掌握核心思想与实战陷阱
  • Obsidian+AI+飞书:搭建一个会自进化的知识库
  • 货架图像识别系统需要哪些核心能力?从5层链路拆解技术选型
  • 独立站搭建平台有哪些?外贸官网、跨境商城和开源方案对比
  • 计算机Java毕设实战-基于 SpringBoot 的棋牌馆收银计费管理系统的设计与实现 基于 SpringBoot 的棋牌室会员消费管理系统【完整源码+LW+部署说明+演示视频,全bao一条龙等】
  • GHelper终极指南:如何让华硕笔记本性能翻倍,告别臃肿控制中心
  • 2026智能门锁行业白皮书:42%投诉增长背后的核心消费警示
  • ParsecVDisplay虚拟显示器终极指南:5分钟搭建Windows高性能虚拟显示系统
  • 【 Godot 4 学习笔记】Blender到Godot4
  • VASP四大输入文件详解:POSCAR、POTCAR、KPOINTS、INCAR
  • Linux内核开发入门:从零构建内核模块与实验环境
  • 【课程设计/毕业设计】基于 SpringBoot 的棋牌室日常营业监管系统的设计与实现 基于 SpringBoot 的休闲棋牌服务管理系统【附源码、数据库、万字文档】
  • Flutter 应用加固方法 从 Dart 混淆到 IPA 层面的保护方案
  • MATLAB实战:用fitdist函数搞定风光数据Weibull和Beta分布拟合(附完整代码)
  • Python爬虫经典案例003:正则表达式精通指南——文本数据的精准提取技巧
  • 资本热捧灵巧手,估值逼近宇树!是“宁德时代”还是被本体厂商围剿?
  • 城市空气质量改善优选雾森系统 吸附悬浮浮尘净化园区空气环境
  • 域名能解析但网站打不开?六层排查比反复重启更快
  • 深圳机器人热潮来袭:越疆科技冲击创业板,“八大金刚”融资引关注
  • NL2SQL 在复杂数仓里为什么不稳?从语义建模看数据问答架构
  • 龙芯平台Jenkins部署实战:从Docker镜像构建到CI/CD流水线搭建
  • AI Agent开发实战:从零构建具备工具调用与记忆能力的智能体