解释光照贴图Atlas爆炸的原因与拆分策略
解读
面试官抛出“光照贴图Atlas爆炸”并不是想听“图太大”这种表层答案,而是考察候选人对Unity烘焙管线、GPU纹理压缩、移动端内存预算、场景分块策略的综合理解。国内项目普遍追求“一套资源跑全平台”,Atlas爆炸往往在中后期才暴露,直接体现性能与包体双重崩溃,因此回答必须落地到“如何拆、拆多少、拆完怎么保证效果一致”。
知识点
-
Atlas爆炸的本质:Unity在烘焙时会把所有静态物体的光照贴图拼成一张或几张大Atlas,当静态物体数量、精度、多场景叠加超过GPU最大纹理尺寸(移动端普遍4096×4096)或内存预算(中低端机单张纹理<100 MB)时,Atlas被迫拆成多张,导致:
- SetPass Call翻倍(材质相同但Lightmap索引不同)
- 包体激增(多张2048→4096空白填充)
- 加载时间指数级上升(IO+解压)
- 显存碎片化(Android纹理层对齐浪费)
-
国内常见诱因:
- 场景“All-in-One”烘焙,忽略Unity 2021以后默认开启的Packed Vector导致精度需求翻倍
- 为了省DrawCall把不同Shader Variant的物体强行合并到同一Atlas,结果一张图里出现金属+透明+植被,压缩格式只能选RGBA32
- 多人协作时Lighting Settings资产未锁定,美术A把Texels Per Unit从30改成100,重新烘焙后Atlas瞬间×3
- 使用SceneManager.LoadSceneAdditive加载子关卡,但子关卡自带Lightmap,Unity不会自动合并,导致内存里同时存在Atlas_0、Atlas_1…Atlas_N
-
拆分策略:
- 预算倒推法:根据目标机型最大允许单张Lightmap内存(如低端机50 MB)反推最大分辨率,Android ETC2_RGBA8下1像素=1 byte,则50 MB≈7324×7324,取整到4096×4096,剩余空间做留白;超出即拆
- GPU Instance化拆分:把相同材质且光照方向一致的物体抽离成独立Atlas,开启Graphics.DrawMeshInstanced,用MaterialPropertyBlock传入LightmapST,这样Atlas拆成N张也不会增加DrawCall
- 场景分块+Lightmap Parameters Overlay:在Unity 2022 LTS里给每个子场景挂独立的Lighting Settings,勾选Auto Generate OFF,用Lightmapping.Enlighten异步烘焙,通过Lighting Data Asset Remap把多张Atlas绑定到对应场景,运行时按World Bounds动态加载,内存峰值从“全场景”降为“可视块”
- UV2通道拆分:对超大型物件(如地铁整车)手动拆UV2岛,保证每岛<Atlas 1/4面积,Unity烘焙时会自动分配到不同Atlas,避免“一图带全车”
- 压缩格式分层:iOS ASTC 6×6、Android ETC2_RGB8、PC DXT5,拆分后允许每张Atlas选最优格式,总体包体反而下降;注意Android 10以下ETC2不支持非2次幂,需把Atlas拆成1024×1024的整数倍
答案
“Atlas爆炸”是Unity烘焙时把过多静态物体光照塞进一张纹理,导致尺寸超限、内存溢出、SetPass Call激增的现象。国内项目常见诱因是单场景烘焙、精度随意上调、Additive加载子关卡未合并。拆分策略分三步:先按目标机50 MB内存预算倒推最大4096×4096,超出即拆;再用GPU Instance化+独立Lightmap Parameters保证拆后DrawCall不增;最后用场景分块+异步烘焙+World Bounds动态加载,把内存峰值压到“可视块”级别,同时按平台选压缩格式,包体与帧率双达标。
拓展思考
如果项目使用URP+GPU Driven Pipeline,传统Atlas拆分反而破坏Batch。可改用Virtual Texture Stack把Lightmap拆成128×128页,运行时通过Feedback Renderer按需加载,内存占用恒定30 MB,但需自定义Lightmap VT Shader并解决Android Tile Memory Align问题。国内头部厂商已在开放世界手游落地,面试时可作为“下一代方案”抛出,体现前瞻性。