本文整理自视频:《Unity 性能优化》系列课程第零节——项目创建与总览。本文聚焦如何快速认识一个陌生 Unity 项目,以及如何在优化开始前建立可重复、可比较的性能基线。
文中的性能、内存与包体数字均为视频示例项目的优化前观测值,只用于记录该案例的初始状态,不代表 Unity 移动端项目的通用性能预算。
本文整理自视频:《Unity性能优化》第壹节——静态资源优化(1):Audio 导入设置检查与优化。
本文重点讨论 Unity 音频资源的导入设置、UPR AssetChecker 检查流程,以及如何在内存、CPU、包体和音质之间取得平衡。
文中的检查数量、优化结果和具体阈值均来自视频示例项目。除非特别说明,不应将这些数据理解为 Unity 的通用规则。
先定位瓶颈,再动手优化。 优化没有被卡住的环节,等于白费力气,甚至可能因增加复杂度而变慢。
性能优化不是"把所有东西都做快",而是找到当前限制帧率的那一环,针对性地解决它。所以任何优化都应从测量(Profiling) 开始,而非凭直觉。
一、黄金法则:先测量,再优化
帧率不达标时,先用 Profiler 判断瓶颈类型:
瓶颈类型与对应工具
| 瓶颈 | 典型现象 | 常用工具 |
|---|---|---|
| CPU 瓶颈 | GPU 在等 CPU 喂数据;主线程满 | Unity Profiler、Tracy、火焰图 |
| GPU 瓶颈(计算/填充) | GPU 满载、CPU 空闲 | RenderDoc、Nsight、PIX、GPU 时间轴 |
| 带宽瓶颈 | 算力没满但卡在读写 | 厂商工具的带宽计数器 |
| 内存/GC | 卡顿、爆显存、频繁 GC | Memory Profiler |
| 加载 | 关卡切换/流式时卡顿 | Profiler 时间线、IO 分析 |
提示
GC 的 Stop-The-World 机制会在游戏中产生帧时间尖峰(hitching),即便平均帧率良好。关键优化思路不是"加速回收",而是尽量不产生垃圾 —— 通过消除每帧的堆分配,让 GC 无事可做。
预备:托管内存 vs 原生内存(GC 只管前者)
理解 GC 前,先分清 Unity 的两个内存世界 —— GC 只管其中一个。
Shader 变体爆炸(Shader Variant Explosion) 是现代游戏引擎渲染中最棘手的工程问题之一。一个功能丰富的材质 Shader,为了支持各种开关(有无法线贴图、有无阴影、光源类型、雾效、蒙皮……),往往需要编译出成千上万甚至上百万个变体。
核心关注两个问题:
- 变体爆炸为什么会发生、带来什么代价。
- 特化常量(Specialization Constants) 如何解决它,以及其他引擎常用的应对手段。
提示
一张 1024×1024 的 RGBA32 贴图占 4 MB 显存。一个场景成百上千张贴图,显存和带宽瞬间爆炸——GPU 纹理压缩正是为此而生,它用块压缩实现实时随机硬件解码。
一、共同基础:块压缩
无论 BC 还是 ASTC,底层思想一致:
四个关键性质:
| 性质 | 含义与价值 |
|---|---|
| 定块 | 切成固定小块(BC 恒 4×4 纹素;ASTC 可变) |
| 定长 | 每块压成固定字节数 → 压缩率可预测、内存可预算 |
| 随机访问 | 采样任意纹素只需解码它所在的一个块,常数时间 —— 硬件采样的硬需求 |
| 有损 | 固定比特预算 → 丢弃信息 |
核心思想
游戏的纹理总量可达数十上百 GB,远超显存。虚拟纹理借鉴虚拟内存的思想:
把一张(或全部)巨大纹理在逻辑上视为完整的,但物理上只把当前帧真正采样到的小块(page/tile)驻留显存,其余留在磁盘/内存按需换入。显存占用与纹理总量解耦。
类比操作系统的虚拟内存:逻辑地址空间很大,物理内存有限,靠页表 + 缺页换入。
一、关键结构
| 结构 | 作用 |
|---|---|
| 页(Page / Tile) | 把大纹理切成固定小块(如 128×128),流式与驻留的最小单位 |
| 页表(Page Table / Indirection Texture) | 记录"逻辑页 → 物理页缓存中的位置"的映射,本身是一张小的间接纹理 |
| 物理纹理(Physical Texture) | 显存中一块固定大小的图集,存放当前驻留的页 |
