Unity性能优化
更新: 10/12/2026 字数: 0 字 时长: 0 分钟
Reference:
性能检测
Statics 窗口
在 Game 窗口的右上角可以打开 Statics 小窗,他不是很详细,但是很简单和方便。
在 BRP 下的示例:

Graphics
- CPU 耗时:31.7ms
- GPU 耗时:29.2ms
- 帧率:31.5fps (31.7ms)
渲染一帧的耗时是二者取低,即受 CPU 耗时影响。但实际上忽略了 CPU 和显示画面所花时间,实际帧率要更低。
Graphics
- Batches: 30003
- Saved by batching: 0
- SetPass calls: 6
示例中仅有一个方向光,渲染的目标为多个正方体,共有10000个点,所以看来每个点都渲染了3次。一次在深度通道,一次为阴影投射,一次为最终图形。
在 URP 下,渲染速度有显著提升。如下图所示,批处理次数降低为 BRP 的

有两种技术可以提升渲染效率:
- Dynamic Batching:将 mesh 的多个小顶点合并成一个大 mesh,再一次性提交给 GPU
- GPU Instancing:把实例数据(矩阵等)打包发给 GPU,以让 GPU 在单次 dc 中重复渲染使用同个 material 的 mesh。
然而 dynamic batching 因为无法作用与 shadow map,所以在 urp 中效果不如 gpu instancing。 在 material 资产中可以勾选 Enable GPU Instancing 来启用 GPU 实例化。
TIP
现在 Unity 更推荐用 SRP Batcher(URP 默认开启),它比两者都更现代,对材质兼容性要求也更高一些。它的核心是:让相同 Shader 变体的物体之间,CPU 几乎不用重复设置材质属性(把数据留在 GPU 上),从而大幅降低每个 Draw Call 的 CPU 开销。
Frame Debugger
Statics 窗口只能告知结果而不能告知原因,对于渲染过程的细节可以通过 Frame Debugger 查看。启用 Frame Debugger 会暂停你的游戏。
Profiler
使用 Profiler 获取更多关于 CPU 的细节。

CPU 使用情况的默认底部视图是时间线(Timeline)。它可视化了一帧中各个部分分别花费了多少时间。可以看到,每一帧都从 PlayerLoop 开始,而它大部分时间都花在调用 RunBehaviourUpdate 上。你可以选中时间线上的某个区块,查看它的完整名称以及持续的毫秒数。
在最初的玩家循环段之后,会出现一小段 EditorLoop,然后是另一段玩家循环,负责这一帧的渲染部分——也就是 CPU 告诉 GPU 该做什么。这些工作会分配到主线程(main thread)、渲染线程(render thread)以及几个 Job 工作线程上。
渲染部分结束后(如果使用的是 URP,此时渲染线程可能还在忙碌),会再出现一段编辑器循环,之后下一帧就开始了。线程有时看起来会跨越帧边界,这是因为 Unity 可以在渲染线程还没完成的时候,就先在主线程上启动下一帧的更新循环,从而利用并行性。
如果你对线程的精确时间不感兴趣,可以通过左侧的下拉列表,把 Timeline 视图切换成 Hierarchy 视图。Hierarchy 会把相同的数据以可排序的列表形式显示出来。这个视图能更方便地看出哪些部分最耗时,以及内存分配发生在哪里。
Profiler 清楚地表明,编辑器给我们的应用增加了大量额外开销。因此,在应用独立运行时进行性能分析会有用得多。为此,我们需要构建(Build)应用,并且专门为调试而构建。可以在 Build Settings 窗口中配置构建方式,通过 File / Build Settings... 打开。然后勾选 Development Build 和 Autoconnect Profiler 选项。等 Player 完全启动后,再切回 Unity 看 Profiler。
如果没自动连上:
- 打开 Profiler 窗口
- 点左上角的 Target Selection(下拉菜单)
- 手动选你的 Player
还是不行就再 Build and Run 一次,或者关掉多余的 Editor 实例。
游戏内的帧率显示
有时我们只想获得帧率的信息,有时游戏在 Editor 之外运行。此时我们需要自己实现一个小窗口来显示帧率。
[SerializeField]
TextMeshProUGUI display;
[SerializeField, Range(0.1f, 2f)]
float sampleDuration = 1f; // 采样时间
int frames;
float duration;
void Update () {
float frameDuration = Time.unscaledDeltaTime;
frames += 1;
duration += frameDuration;
if (frameDuration < bestDuration) {
bestDuration = frameDuration;
}
if (frameDuration > worstDuration) {
worstDuration = frameDuration;
}
if (duration >= sampleDuration) {
// 更新帧率显示
display.SetText(
"FPS\n{0:0}\n{1:0}\n{2:0}",
1f / bestDuration,
frames / duration,
1f / worstDuration
);
// 重置计数器
frames = 0;
duration = 0f;
bestDuration = float.MaxValue;
worstDuration = 0f;
}
}