美术资源优化
从网格、动画、纹理到 AssetBundle,降低美术资源对内存与加载的影响。
学习目标
- 能根据用途调整网格、纹理、动画的导入设置,并解释每项对内存与 CPU 的影响
- 能说出 Resources 文件夹的三大陷阱,并改写成 AssetBundle 或 Addressables 的按需加载写法
- 能回答:一张 4096×4096 的 UI 图标以 RGBA32 导入、未限制 Max Size,在 1024 屏幕上实际占多少内存?你会改哪三项设置?
原书边界:Chapter 4
Packt 第三版把本章命名为 Optimizing Your Art Assets。官方目录列出的核心范围如下;这些条目共同构成本章的一对一边界:
- Audio:纳入本章实验与验收,不拆到别章重复计数。
- Texture files:纳入本章实验与验收,不拆到别章重复计数。
- Mesh and animation files:纳入本章实验与验收,不拆到别章重复计数。
- Asset bundles and resources:纳入本章实验与验收,不拆到别章重复计数。
从源资源、导入设置、平台压缩、运行时驻留到卸载建立完整资产生命周期,而不是只看包体。Addressables 可替代部分旧式 AssetBundle/Resources 工作流,但压缩格式、Read/Write、副本与依赖生命周期仍需逐项验证。 因此,本章既保留 2019/2020 语境,也明确标出迁移到现代 Unity 时哪些是稳定原理、哪些只是版本相关接口。
包体不大,内存却爆了
上一章你把画面提交开销压下去了,帧率却还是在切场景时卡住。打开内存统计一看——贴图占了几百兆,模型又占了一大块,一切关卡就要黑屏两三秒。问题往往不在玩法逻辑,而在资源进门时没瘦身:源文件很大,进游戏仍是原尺寸;每个模型都多留了一份可改副本;启动时把整柜东西全搬进客厅。
这章要解决的,是美术资源从进门到搬入整条链上的浪费:模型、动作、贴图、声音怎么瘦身;为什么「随手塞进万能抽屉」是个坑;怎么把加载尖峰摊到多帧。测量工具已经能看内存曲线——接下来需要的是在导入面板里改对开关,而不是再堆一个后处理脚本。
没有这些优化会怎样?手机上先因内存不足闪退,电脑上则是加载黑屏、切关卡顿、安装包膨胀——玩家还没见到你的合批成果,就已经被资源拖垮了。
像搬家:箱子越大,车越难装
想象你要把一屋子家具搬进新房。原样搬(不压缩、不拆箱)每辆车只能装一件大床;提前折叠、打木架能一车装十件;把暂时不用的家具锁在仓库、进门再拉,比全堆客厅省地方得多。
美术资源优化就是在装车前决定:这件家具体积能不能缩小、要不要留一份可改的副本、是不是进门才从仓库拉出来。
网格:压缩精度,关掉可写副本
↡在 Model Import Settings 中降低顶点数据精度以节省 GPU 内存,运行时由 GPU 解压,略增 CPU。 把顶点位置、法线等精度从 32 位降到 16 位或更低——肉眼难辨的差异,换显著的显存节省。移动平台常用 Medium 或 High;主角高精度模型可 Off 或 Low。
↡导入时是否在内存保留一份 CPU 可写的网格副本;开启后内存约翻倍,仅运行时修改 mesh 时需要。
是更隐蔽的内存杀手:默认应关闭。只有你要在代码里改
mesh.vertices、做运行时变形、或 MeshCollider 烘焙前需要可读网格时才开。
导入设置对照
// Model Import Settings(Inspector)
// Mesh Compression: Off
// Read/Write Enabled: ✓
// 结果:12MB 网格 + 12MB 可写副本 = 24MB/模型// Model Import Settings
// Mesh Compression: Medium(移动)/ Low(PC 主角)
// Read/Write Enabled: ✗(纯渲染)
// Optimize Mesh: ✓
// 结果:~4MB 只读 GPU 网格动画:关键帧精度与压缩
↡Animation Import Settings 中降低关键帧浮点精度、启用 Optimal 键减少冗余关键帧,以缩小 .anim 与运行时曲线内存。
对角色海尤其重要。Keyframe Reduction 去掉肉眼不可见的冗余关键帧;Rotation Error / Position Error / Scale Error 控制允许误差(通常 0.5° / 0.5cm 级即可)。
// Animation Import Settings
// Anim Compression: Off
// Rotation Error: 0
// 结果:Mocap 原样保留,.anim 数 MB,运行时曲线占内存// Animation Import Settings
// Anim Compression: Optimal
// Rotation Error: 0.5
// Position Error: 0.5
// Scale Error: 0.5
// 结果:体积降 50–80%,肉眼难辨Humanoid 角色还可勾选 Optimize Game Objects,去掉运行时不需要的骨骼节点,减少 Avatar 与绑定开销——适合远处 NPC 与过场里不特写的手指骨骼。
纹理:格式、尺寸与 mipmap
↡按目标平台选择 GPU 原生压缩格式(如 ASTC、ETC2、BC7),显著降低显存占用。 决定每像素占多少字节。RGBA32 未压缩约 4 B/px;ASTC 6×6 约 0.36 B/px——同分辨率可差一个数量级。
↡导入时限制纹理最大边长,超过则下采样;按用途设 512/1024/2048 而非一律 4096。 是 UI 图标最常见的浪费:源图 4096×4096,屏幕上只显示 128×128,却仍按 4K 占显存。
↡预生成逐级减半的纹理副本,远处物体采样低级,减少闪烁并降低带宽;约增加 33% 内存。 对 3D 场景纹理应开启;UI、Sprite Atlas、法线贴图(部分管线)常关闭。
按用途分档
| 用途 | Max Size | Mipmap | 格式建议 |
|---|---|---|---|
| 3D 场景漫反射 | 1024–2048 | ✓ | ASTC/BC7 |
| UI 图标 | 256–512 | ✗ | ASTC 或 RGBA Crunched |
| 字体图集 | 512–1024 | ✗ | Alpha8 / ASTC |
| 远景天空盒 | 2048 | ✓ | 压缩格式 |
音频:压缩格式简介
↡按播放类型选择压缩格式与加载方式:背景音乐流式加载,短音效解压进内存。 影响包体与运行时内存,但比纹理简单:
在 Memory Profiler 里搜 AudioClip,核对是否有意外解压的大文件。
Resources 陷阱 vs AssetBundle
↡Assets/Resources 下资源构建时全部打进包内,启动时建立索引,Resources.Load 无法卸载。 曾是快速原型手段,现代项目应极力避免膨胀使用。
↡按包输出资源文件,运行时异步加载,可用 Unload 释放内存,支持热更与分包。 (或 Unity Addressables 封装层)才是可控的按需加载方案。
// Assets/Resources/Prefabs/Enemy.prefab
var go = Resources.Load<GameObject>("Prefabs/Enemy");
// 无法卸载;文件夹越大启动越慢;字符串路径易拼错var req = AssetBundle.LoadFromFileAsync(path);
yield return req;
var bundle = req.assetBundle;
var assetReq = bundle.LoadAssetAsync<GameObject>("Enemy");
yield return assetReq;
var prefab = assetReq.asset as GameObject;
// 不用时 bundle.Unload(false);异步加载:把尖峰摊开
↡用 LoadSceneAsync / LoadFromFileAsync 等 API 把 IO 与反序列化分摊到多帧,避免单帧主线程阻塞。
解决切场景、拉大 AssetBundle 时的卡顿——同步 LoadScene 或 Resources.Load
大资源会在一帧内完成反序列化,Profiler 出现 CPU 尖峰。
// 一帧内完成 — Memory Profiler 尖峰 + 掉帧
SceneManager.LoadScene("HugeLevel");
var bundle = AssetBundle.LoadFromFile(path);
var prefab = bundle.LoadAsset<GameObject>("Boss");IEnumerator LoadLevelAsync() {
var op = SceneManager.LoadSceneAsync("HugeLevel");
op.allowSceneActivation = false;
while (op.progress < 0.9f) {
bar.fillAmount = op.progress;
yield return null;
}
op.allowSceneActivation = true;
}场景激活前用 allowSceneActivation = false 停在 90% 进度,让你播完 loading 动画再切入——避免「进度条满了还黑半秒」。
动手:美术资源审计四步
下面把「内存高 / 加载卡」从测量到改设置走通。每步示意图对应当前关注点——建议打开 Memory Profiler 与一个 Batches 已优化但仍内存告急的场景同步操作。
猜一猜:把一百个敌人的 Read/Write 全关掉、纹理 Max Size 从 4096 改 1024,Memory Profiler 里 Mesh 与 Texture2D 谁降得更多?
① Memory Profiler:谁占大头?
打开 Memory Profiler(或 Profiler Memory 模块),按 Texture2D / Mesh / AnimationClip / AudioClip 排序。记下前三名类型与单个最大资源——通常是未压纹理或未关 Read/Write 的网格。
容易踩的坑
小结
- Mesh:Compression 省显存;Read/Write 默认关,仅运行时改 mesh 时开
- Animation:Optimal 压缩 + 合理 Error 阈值;NPC 可 Optimize Game Objects
- Texture:按平台选 ASTC/ETC2;按用途限 Max Size;3D 开 mipmap、UI 常关
- Audio:长音频 Streaming + Vorbis;短音效 ADPCM,忌大文件 Decompress On Load
- Resources 全量进包且不可卸;用 AssetBundle/Addressables + 异步加载 控内存与尖峰
练习
问题 1(改设置型) 在项目中找一张大于 2048 的纹理和一个大 FBX,记录改前 Memory Profiler 中对应条目大小。将纹理 Max Size 改为用途合理值、格式改为平台压缩;网格关 Read/Write 并开 Medium Compression。Reimport 后复测并填表。
问题 2(问答型) Resources 与 AssetBundle 在「能否卸载」「启动影响」「包体控制」上各差在哪?
问题 3(计算型) 1024×1024 RGBA32 含 mipmap 约多少 MB?若改为 ASTC 6×6 同尺寸含 mipmap 大约多少?UI 图标只需 256 显示,Max Size 设 256 对 RGBA32 约多少 MB?
名词解释
本章出现的专业名词,用大白话再讲一遍。
- Mesh Compression(网格压缩)
降低顶点精度省 GPU 内存,略增解压 CPU。详见 MeshImportDiagram。
- Read/Write Enabled
保留 CPU 可写网格副本,内存约翻倍;仅运行时改 mesh 时开启。详见 MeshImportDiagram highlight readwrite。
- Animation Compression(动画压缩)
Optimal 键减少 + 误差阈值缩小 .anim。详见动画 CodeTabs。
- Texture Format(纹理格式)
平台压缩格式决定每像素字节。详见 TextureImportDiagram highlight format。
- Max Size(最大尺寸)
导入时限制最大边长。详见 TextureImportDiagram highlight maxsize。
- Generate Mip Maps(生成多级渐远纹理)
预生成逐级减半副本供远处采样。详见 MipmapPyramidDiagram。
- Audio Compression
按长短音频选 Streaming/Vorbis 或 ADPCM。详见音频 Callout。
- Resources 文件夹
全量进包、启动索引、不可卸载。详见 ResourcesAssetBundleDiagram mode resources。
- AssetBundle
按包加载与卸载的资源包。详见 ResourcesAssetBundleDiagram。
- 异步加载(Async Loading)
LoadSceneAsync 等分帧加载。详见 AsyncLoadDiagram 与 CodeTabs。