尧图精选

移动端发热优化:纹理压缩与后处理带宽优化实战

🕒 发布时间:2026/9/15 3:14:32 📁 来源:尧图网络
做过移动端性能优化的朋友应该能感同身受你费尽心思优化了 DrawCall、改好了合批、压完了网格体量结果一进游戏跑五分钟机身照样烫得能煎鸡蛋。这时候你才猛然意识到真正把芯片热量拉起来的往往是那些不起眼的“搬运动作”——数据在内存和GPU之间的传输。而在所有传输数据里纹理和后处理是搬运量最大的两个惯犯这一篇我们就专门来收拾它们。“发烫优化系列”前面几篇讲了 CPU 侧的耗时点、渲染管线的常规瓶颈这次我们换一个视角从 GPU 的带宽和功耗角度来分析。你手里的设备之所以发热、降频、卡顿很大程度上不是计算量爆了而是“搬了太多不该搬的东西”。纹理和后处理恰恰是这条搬运河上的两艘超载货轮。这篇会把它俩的搬运量怎么算、怎么砍、怎么在保证画质的前提下把温度和功耗降下来全部拆开讲清楚适合正在做 Unity、UE 移动端项目或者对渲染性能有优化需求的开发者参考。1. 先搞清楚“搬运量”是什么为什么它是发热的元凶1.1 芯片发热和“搬运”之间的关系我们通常觉得手机发烫是因为 CPU 或者 GPU 在疯狂计算频率拉得高所以烫。这个说法对但不全对。真正让芯片温度失控的除了高负载计算还有大量的内存读写操作。你可以把芯片想象成一个中央厨房计算单元是大厨纹理数据和各种中间结果是食材内存是仓库。大厨再厉害如果连续不断有人从仓库搬食材进厨房再搬半成品出去这个“搬运”过程本身就会消耗大量体力——也就是功耗。在 GPU 里这个“搬运”消耗的功耗非常可观。移动端 SoC 的内存带宽是有限的LPDDR5 之类的内存虽然频率高但带宽依然是稀缺资源而且每次内存访问都要付出能量代价。同一个 GPU 核心做一次浮点运算和读一次纹理数据后者的能耗可能是前者的好多倍。所以当你的游戏里到处是高分辨率纹理、动不动就做全屏后处理采样GPU 的功耗会迅速拉满温度上来之后触发降频帧率就跟着崩了。这也是为什么很多项目在 PC 上跑得飞快一上手机就原形毕露。PC 内存带宽宽裕显卡功耗大一点无所谓散热也扛得住。但手机不行芯片面积小、被动散热带宽就那么几条车道搬运量一大立刻发烫。所以我们做移动端优化最核心的思路就是压缩“搬运量”让 GPU 少搬数据这比单纯压计算量见效更快。1.2 为什么偏偏是纹理和后处理成了“惯犯”可能有朋友要问网格、顶点数据、骨骼动画也有搬运量为什么不抓它们原因很简单纹理和后处理在“单位时间内搬运的数据量”和“搬运频率”上都远远超过其他数据。先看纹理。游戏场景里纹理从开机到关机全程都在被采样每一次渲染都要从显存/内存里读取纹素过绘制率高的时候一个像素可能采样好几遍。而且纹理数量多场景少说几十张、多则几百张加起来的体积轻松突破几百 MB。当 GPU 需要随机读取这些数据时带宽压力非常大。再看后处理。每做一个全屏特效都意味着把整张屏幕的内容当成数据读出来处理完再写回去。一个 Bloom 光是降采样、模糊、合成就要跑好几遍全屏读写搬运量是几何级数上涨。再加上抗锯齿、调色、景深、运动模糊随便一叠带宽占用直接爆表。更麻烦的是纹理和后处理还会互相放大问题纹理分辨率越高后处理采样时读的数据就越多后处理 RT 分辨率越高处理纹理信息时搬运量越大。这两个惯犯通常结伴作案。所以把它们放在一起讲是因为优化思路是相通的——都是想办法减少像素数据的搬运次数和搬运体量。2. 纹理一张贴图是怎么把带宽吃光的2.1 纹理的搬运量怎么算从不压缩到压缩的数量级差距很多人觉得“纹理大就是内存占用高”其实纹理真正要命的是带宽占用。一张纹理只要在渲染时被采样它的数据就会在内存和 GPU 缓存之间反复搬运。我们来算一笔账。一张 1024x1024 的 RGBA32 未压缩纹理单张大小是 1024 x 1024 x 4 4MB。听起来不夸张是吧但 GPU 一个帧里会对它采样多少次一个角色模型可能有 20000 个三角形每个像素都会采样基础贴图、法线贴图、高光贴图屏幕上一帧就要采样几十万次甚至更多。而每个采样点都可能要从 4MB 的数据里读取信息如果纹理没有做压缩带宽消耗是灾难性的。同样一张 1024x1024 纹理如果用 ETC2 压缩4bpp也就是每像素 4 比特大小降到 512KB如果用 ASTC 8x8 块约 2bpp更是压到 256KB。你想想同样的贴图GPU 要搬运的数据量相差 8 到 16 倍发热量的差距能不大吗所以评论区里我经常强调移动端纹理压缩不是可选项是标配谁不做谁就等着烤手。这里顺手提一个很多团队踩过的坑开发期在 PC 上用 BC7 格式跑得没问题打到手机包忘记转换结果 Android 上一堆 BC 格式纹理根本没法用或者内存暴涨。Unity 里 Android 默认纹理格式如果没有明确指定有时会出这种问题。做打包之前一定检查每个平台的 Texture Override 设置该上 ASTC 就上 ASTC别让 PC 的选项把移动端坑了。2.2 纹理压缩选型ETC2 和 ASTC 怎么选移动端现在主流纹理压缩格式就两个方向ETC2 和 ASTC。ETC2 是 OpenGL ES 3.0 之后所有移动 GPU 都支持的格式兼容性非常好Unity 和 UE 在 Android 上默认也推荐它。它的压缩率是 4:1RGBA 支持硬件解码开销很小。缺点是质量上限一般特别是大压缩比需求时不太够用。ASTC 是新一代压缩格式支持从 4x4 到 12x12 的块大小选择压缩比从 8:1 到 20:1 都能灵活控制。好处是质量可控同样体积下视觉损失比 ETC2 小坏处是它要求 GPU 支持 ASTC目前主流移动 GPU 基本都支持但一些低端老设备可能需要适配回退。我个人的经验是如果项目最低适配设备比较新直接用 ASTC选 6x6 或 8x8 块大小作为默认视觉几乎无损体积和带宽都很友好。如果还要兼容两年前的旧设备那就 ETC2 兜底但尽量把大尺寸纹理做成 ASTC 加一张 ETC2 备用。这里有个细节容易忽略ASTC 的块大小选择不是“越压缩越好”。块越大压缩比越高带宽越低但质量也越差。尤其对法线贴图压缩块太大会出现明显的带状瑕疵。实践里我习惯法线贴图用 ASTC 6x6颜色贴图用 ASTC 8x8粗糙度、金属度这类低频信息用 10x10 也没问题。你可以按类型区分配置别一张配置打全场。2.3 Mipmap不是“画质优化”而是“发热优化”很多刚入门的朋友以为 Mipmap 是为了让远处纹理更清晰或者减少闪烁其实它最大的价值是降低带宽消耗。Mipmap 会为一张纹理生成一连串预过滤的小尺寸纹理当物体离相机远时GPU 会自动选择更小的层级采样读的数据量就少很多。举个例子一面墙离相机 30 米远如果没有 MipmapGPU 仍然会去读最高分辨率的纹理然后做缩小滤波。它读的还是那 4MB 的数据但最终屏幕上这个物体可能只占了 100 个像素等于为了 100 个像素搬了 4MB 的数据浪费得离谱。开了 Mipmap 之后GPU 会选择大约 64x64 的层级只需要读几 KB搬运量直接少了几个数量级。所以我经常说Mipmap 是移动端“性价比最高”的发热优化手段没有之一。它能同时减少显存带宽、缓存压力、功耗而且几乎不损失最终画质。代价只是纹理内存增加约三分之一因为要存各个层级这点开销跟省下的带宽相比完全可以忽略。设置上要注意两点一是在导入纹理时勾选 Generate Mip Maps二是设置好 Mipmap 偏移和最大级别避免远处物体还在采样过大的层级。Unity 里可以通过 TextureImporter 设置 mipmapBias 或者在 Shader 里通过 textureLod 控制想精细管理时可以按材质分类调整。2.4 容易被忽略的纹理细节尺寸上限、格式、采样器纹理优化并不只是选个压缩格式那么简单还有几个细节经常被忽略。第一是纹理最大尺寸Max Size。很多美术同学习惯把资源从外包拿过来直接用原始尺寸 4096 甚至 8192。在移动端这个尺寸绝大多数情况完全没有必要。UI 可以压到 512 或 1024角色贴图 2048 顶天了场景大纹理 2048 足够除非做盖亚那种超大环境才需要 4096。你压一半尺寸带宽直接降到四分之一这是肉眼可见的收益。第二是纹理格式。除了压缩格式还要注意是否用了高精度格式。HDR 相关纹理用 RGBA16F 可以理解普通 Diffuse 贴图如果用了 RGBA16F 或 RGBA32F 就属于资源浪费了。法线贴图尽量用 BC5/ASTC 专用格式不要用 RGBA32 硬存不然搬运量翻倍。第三是采样模式。Clamp 和 Repeat 的选择会影响纹理缓存命中率。纹理采样时 GPU 有颜色缓存和纹理缓存如果采样模式合适能够提高命中率减少实际内存读取。这个属于锦上添花但积少成多。还有一个容易踩的坑纹理图集Texture Atlas太大。图集能减少 DrawCall这是对的但超大图集意味着当你需要一个很小的区域时GPU 也可能把整个大页读进缓存。做 UI 图集时不要无脑合并成 4096尽量拆成多个 1024 或 2048 的图集按界面用途分块对缓存友好得多。3. 后处理一次全屏特效到底搬了多少数据3.1 后处理链路里的 RT 搬运后处理是另一个“搬运量”大户这里很多人没有概念觉得 Bloom 不就是一遍模糊吗错。一个标准的 Bloom 流程最少是这样的先把主相机画面拷贝到 RT1读主图像、写 RT1然后对 RT1 做降采样生成不同 Mip 的亮部 RT每次降采样都是一次全屏读写再做横、纵两个方向的高斯模糊又是多次全屏读写最后把所有亮部模糊结果叠加回主画面又一次全屏读写。你数一下这一套流程跑下来一个普通分辨率下光是全屏读写的次数就能上两位数。每次全屏读写代表什么以 1080p 为例一张 RGBA16F 的 RT大小是 1920x1080x8 ≈ 16.6MB。一次读写读 写就是大约 33MB 的数据搬运。如果 Bloom 跑了 10 次全屏操作那就是 330MB 的搬运量。这还只是 Bloom 一个特效再加 AO、抗锯齿、色调映射、暗角一次后处理管线轻松搬运超过 1GB 数据。可以这么说每多一个全屏特效你就在给手机加热一分。移动端优化后处理的核心就是把“每帧被全屏读写处理的数据量”压下来能半分辨率就不全分辨率能合并 pass 就不拆开跑能不用的特效直接关掉。3.2 半分辨率处理与 pass 合并半分辨率处理是当前移动端后处理优化的黄金策略。很多后处理效果对分辨率并不敏感比如 Bloom、景深、运动模糊它们本质是低频信息在半分辨率下做和全分辨率下做视觉差异极小。所以做法很简单先把主画面降采样成半分辨率然后在半分辨率上做所有重特效最后再升采样回全分辨率合成。这个策略能让后处理纹理的带宽消耗直接降到四分之一。举例来说Bloom 在 1080p 下如果全分辨率做每次全屏读写 33MB半分辨率则是 960x540RT 大小变成约 4.1MB一次读写约 8.2MB四倍差距非常直观。pass 合并同样重要。比如 Bloom 的横纵模糊很多引擎是拆成两个 pass 跑的先横向模糊写一张 RT再纵向模糊读出来。这两个 pass 中间还涉及一张中间 RT 的分配和释放。如果某个平台支持硬件双线性过滤你可以把横纵合并成一个 pass用 4 次纹理采样实现 9 个像素的模糊减少一次全屏读写。别小看省掉的这一次一个 bloom 流程省一次全屏读写就是几十 MB 的带宽收益。另外要注意后处理 RT 的分配要复用不要每帧动态创建。频繁分配、释放 RT 会造成内存碎片和带宽开销。Unity 里可以用临时 RT 池或者直接用 RenderTexture 的全局复用方案。我自己在项目里会维护一个按分辨率分级的 RT 池后处理需要的中间 RT 都从池里取用完归还实测内存抖动和 GC 都明显改善。3.3 移动端后处理的取舍方案在移动端做后处理我一直主张“按需裁剪”。不是所有 PC 端的后处理效果都能搬到手机上必须按设备分级配置。以 Bloom 为例。完整版 Bloom 效果好但贵移动端可以用两个优化替代方案。第一个是“模拟 Bloom”用一个预模糊的亮部贴图去叠加只要一张低分辨率 RT跑两个 pass 就完事效果虽然没那么细腻但胜在便宜。第二个是拿 Bloom 的关键参数打折把迭代层数从 5 层减到 3 层降采样倍数从 2x 改成 4x视觉损失不大带宽省一半以上。另一方面抗锯齿在移动端也是个搬运大户。MSAA 在移动端虽然硬件支持但更高采样倍数的 MSAA 带宽消耗惊人。我推荐移动端优先用 FXAA 或 TAA 的轻量版或者直接用引擎自带的移动端抗锯齿方案。如果你上了 MSAA 4x再加上 HDR 后处理带宽会呈指数级上涨发热几乎压不住。还有一个经验很多游戏的后处理不是实时算出来的是“贴图预烘焙”的。比如调色、暗角这些比较静态的效果直接用一张 LUT 贴图和一张 Mask 贴图叠加跑一次全屏 shader 就够。后处理里凡是能用贴图预计算的就不要实时计算这是移动端优化绕不开的思路。4. 实操流程我是怎么定位和压掉这两个惯犯的4.1 先量化再动手抓取带宽数据优化之前先要量化不然就是瞎猜。移动端抓 GPU 带宽有几个常用工具Unity 的 Frame Debugger 和 Profiler 能看渲染状态和大致耗时Xcode 的 Instruments 里 GPU 计数器可以抓 AGX GPU 的带宽数据Android 上用 Snapdragon Profiler 或者 Mali Offline Compiler 能看具体硬件的带宽指标和指令数。我的习惯是先用 Profiler 看 RT 的分配数量、纹理的采样数量手动算一遍每个 pass 的带宽估算圈定嫌疑对象。然后在真机上跑一遍看 GPU 频率和温度曲线确认发热瓶颈。这一步能避免你做无用功——比如你花大力气压缩了纹理结果发现瓶颈在 CPU 侧的 draw submission那优化方向就错了。这里要注意不要在编辑器里看性能数据。编辑器里咱们的显卡是台式机级别带宽宽裕得很跑出的画面流畅不代表手机流畅。所有性能数据必须以真机为准而且一定要覆盖中低端设备。高端机可能跑起来根本不烫但中端机 5 分钟就降频卡顿你的优化目标是保住低端机体验。4.2 纹理优化清单从大到小逐项排查我理了一个纹理优化的排查清单按优先级排序第一步检查全局纹理导入设置。把所有非 UI 纹理统一设置为 ASTC 6x6 或 8x8UI 纹理按屏幕尺寸设置为合适的图集大小关闭不必要的 2048 以上纹理。第二步检查纹理的 Max Size。逐张过一遍场景纹理凡是远景可见度不高的大纹理把 Max Size 降到 1024角色、武器等关注度高的资源才保留 2048。第三步确认所有低频、背景、地表纹理都开启了 Mipmap并且正确设置了 Mipmap 偏移。同时检查 Shader 里有没有关闭 Mipmap 的写法比如某些自定义 Unlit Shader 用了 tex2Dbias会导致 mipmap 失效。第四步检查纹理格式是否有误用。法线贴图不要存成 RGBA32金属度、粗糙度贴图不要用 RGBA16F能用 R8 的就不要用 RGBA8。Unity 里可以给单通道纹理设置合适的格式省内存又省带宽。第五步检查运行时有没有动态创建大纹理。比如某些 UI 框架会动态生成模糊背景如果每次打开界面都创建一张全屏 RT这是隐藏的带宽炸弹建议改成预烘焙或复用缓存。第六步用 Profiler 对比优化前后的帧耗时和温度。这一步比较关键记录下来方便后续排查回归。4.3 后处理优化清单砍 pass、降分辨率、预烘焙后处理的优化我按这个思路执行第一步梳理整个后处理管线把所有特效列出来标出必须保留、可以降级、可以直接删除三类。移动端不是每个特效都要上优先保留色彩分级和抗锯齿其他见机行事。第二步把 Bloom、DOF、AO 这类重特效全部切成半分辨率执行。这里有个技巧做降采样时不要直接粗暴缩一半要先用一个轻量平均 shader 做一次 2x2 降采样或者在降采样时保留阈值信息避免亮部细节丢失。第三步合并 pass。能在一个 shader 里完成的效果不要拆开能用 Blit 完成的操作不要让 CPU 参与。尽量让后处理链路的 RT 数量最小化比如 Bloom 的中间 RT 可以直接复用。第四步考虑用引擎的现成后处理方案做裁剪。Unity 的 URP 后处理栈里每个 feature 都有质量等级Bloom 有 Low/Medium/High 三档低档下迭代层数和降采样倍数都会自动降低非常省心。UE 的话可以把 Post Process Volume 的 Bloom 降到最低 Setting 级。第五步真机验证。看一眼优化后的画面有没有明显劣化同时测量温度。如果温度还是高继续压直到找到画质和发热的平衡点。5. 常见问题与排查技巧实录5.1 优化完画面变糊这是最常见的问题。通常是因为纹理 Max Size 压得太过或者后处理半分辨率没有正确处理。纹理方面角色脸部、UI、关键道具这类视觉敏感资源不要压得太狠留 2048场景远处模糊一点通常玩家感知不强。后处理方面如果 Bloom 半分辨率后亮部虚成一片检查是不是降采样 shader 没有做阈值提取处理好亮部阈值就能保住光晕轮廓。5.2 压缩后出现色块或边缘毛刺ASTC 压缩块太大时容易出现明显的色块和边缘毛刺尤其是法线贴图和 UI 上带细字体的纹理。遇到这种情况把该纹理单独设置为 ASTC 4x4 或 6x6牺牲一点压缩比换取质量。UI 纹理建议单独配置为 ASTC 4x4 甚至 ETC2因为 UI 上文字锐利度很重要8x8 块会糊。5.3 后处理在真机和编辑器表现不一致我在项目里碰到过几次编辑器里 Bloom 效果很好真机上要么没效果要么过爆。排查后发现是 HDR 设置和颜色空间不一致导致的。编辑器默认可能打开了 HDR真机上没开或者 gamma 和 linear 空间不同导致效果差异。后处理链路要注意 RT 格式和相机设置的统一特别是 HDR 开关要跟编辑器保持一致。另一个坑是部分移动 GPU 对半精度浮点支持不完整RGBA16F 在后处理链路上精度不够真机会有色阶断层这时要么升级 RT 格式要么优化算法避免极端颜色。5.4 说一点关于“后处理”这个概念的题外话搜索“后处理”的时候你会发现这个词在好几个行业都有完全不同的意思。比如数控加工里有“后处理”是指生成机床代码的配置AI 目标检测里也有非极大值抑制之类的“后处理”流程甚至三维重建的纹理贴图也有自己的“后处理”阶段。我这里说的“后处理”特指游戏渲染管线里的画面后处理Post-Processing也就是对渲染好的画面做滤镜、模糊、合成等操作。大家在网上搜资料的时候注意区分别找到完全不相干的方向去。这个系列的内容核心是“发烫优化”所以我们会一直围绕实时渲染、手机 GPU、带宽、功耗这条主线。至于 AI 后处理、数控后处理那些属于另一个完全不同的领域这里就不展开了。5.5 一份快速自检的避坑清单最后给大家一份我在项目里常用的避坑清单照着查能省很多时间检查项正确做法常见错误纹理压缩格式Android 用 ASTCiOS 用 ASTC 或 PVRTC沿用 PC 的 BC7 导致移动端异常纹理最大尺寸按资源类型分别限制 512/1024/2048全场景统一用 4096Mipmap低频、远景、大纹理必须开为了省内存关闭 Mipmap法线贴图格式用 ASTC/BC5 专用格式用 RGBA32 存法线后处理分辨率重特效半分辨率执行全分辨率跑 Bloom/DOFRT 复用使用 RT 池复用中间 RT每帧创建销毁 RT后处理 pass 数能合并就合并一个 Bloom 拆十几个 passUI 图集按界面拆分控制在 1024/2048无脑合并成 4096 大图这份清单不是万能药但可以帮你快速覆盖 80% 的纹理和后处理发热问题。剩下 20% 就要结合具体项目的渲染管线和设备适配情况去定位了。6. 最后聊一点我的实际体会优化纹理和后处理这件事我踩过最大的坑就是“一上来就动方案”。之前有个项目美术同学辛苦做的次世代风格场景我上来就把所有纹理压成 ASTC 8x8后处理全关了结果画面肉眼可见的“降级”美术同事差点没把我拉黑。后来我才意识到优化的核心不是把特效全砍掉而是在保证画面表现的前提下把每个效果的“成本”压到最低。纹理可以分主次角色和场景关键物件保持高质量远处和低频的物件压一压后处理可以分级Bloom 半分辨率跑DOF 在特定镜头才开效果上观众根本察觉不到。我自己的习惯是先把 Profiler 跑起来用一个固定场景固定镜头记录优化前的帧耗时、温度和功耗基线然后每次只改一个维度比如先压缩纹理格式再开 Mipmap再降后处理分辨率每步都记录变化。这样你能清楚地知道哪个操作带来了多大的收益后续回归也好定位问题。别一次把所有优化都堆上去到时候出了问题你根本不知道是哪一步引起的。再补充一个容易被忽略的小点纹理和后处理的优化效果在设备温度高的时候体现得最明显。手机在低负载状态下功耗差异不大但只要负载拉升到降频临界点你做没做带宽优化直接决定了设备是稳定 60 帧还是 30 帧上下跳。所以测试的时候别只测刚开机那几分钟最好拿一台用了几小时、已经有点热的设备跑一段战斗场景看看优化后的帧率曲线稳不稳。稳得住说明你的搬运量压到位了还在跳那就继续查纹理采样和 RT 读写惯犯通常藏不住。
上一篇/下一篇内容由系统自动关联 返回资讯列表 →