ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

移动端GPU带宽优化:纹理与后处理两大发烫元凶的排查与优化实战

移动端GPU带宽优化:纹理与后处理两大发烫元凶的排查与优化实战 前几天线上反馈说手机发烫严重我第一反应是查CPU逻辑和shader复杂度结果GPU计数器打开一看两个数据高得离谱纹理带宽占用、render target读写量。做图形优化的都懂发热这件事算力有时反而不是重点数据搬运才是大头。纹理和后处理就是图形渲染里搬运量最大的两个惯犯这期发烫优化系列第4篇就把这两个家伙彻底拆开讲清楚。这篇内容适合正在做Unity、Unreal移动端项目的渲染工程师、性能优化人员或者刚入手移动端图形优化、想搞明白“为什么帧率看着不低但手机就是烫”的开发者。我会从带宽和能耗的关系讲起接着把纹理和后处理各自的搬运路径、优化手法、真机排查经验全部摆出来最后附上我踩过的坑和可复用的操作清单。1. 先搞清楚发烫和“带宽”到底是什么关系1.1 移动端GPU的能耗大头是搬数据不是算数据很多人有个误区觉得GPU发热大是因为跑了很多复杂的shader指令。真实情况不完全是这样。移动GPU的功耗可以粗略拆成三块计算单元的功耗、数据搬运的功耗、固定功能单元的功耗。其中数据搬运这块在移动端占比高得吓人。原理很简单数据从内存搬到GPU、从GPU写回内存每一次跨越片上的SRAM/L2到片外DRAM的传输消耗的能量比在GPU核心内做一次浮点乘加高一个数量级。业内有个粗略数字一次32位浮点乘加约消耗0.2pJ而从LPDDR4上搬运1bit数据大约要消耗几pJ到十几pJ取决于访问路径。也就是说省下一次对片外纹理的读取远比省下几条算术指令划算。这也是很多低端芯片GPU算力够但一跑复杂场景就烫手的原因——瓶颈在带宽不在FLOPs。明白这个你再看发烫问题思路就会从“降低算法复杂度”转向“降低数据搬运量”。纹理和后处理这两个模块恰好就是移动端带宽消耗大户里的前两名所以说是“搬运量最大的两个惯犯”一点都不冤枉。1.2 一个公式看懂为什么帧率没跌但温度上去了很多人用CPU帧时间、GPU帧时间来找瓶颈找半天发现都正常但手机就是热。我建议你直接看带宽相关计数器。先记住这个估算公式每帧带宽消耗 Σ(纹理采样次数 × 纹理单次读取量) Σ(每pass渲染目标读写量 × pass数) 顶点数据读取量 其他举几个直观的数字。一张1080p的RGBA8纹理1920×1080×4字节约8.29MB。一个全屏后处理pass读一遍写一遍就是16.6MB的搬运量。如果你的后处理有5个pass那一帧就要耗掉83MB。60帧每秒就是4.98GB/s的带宽——注意这只是后处理一个环节还没算场景里所有物体采样的纹理量。对比一下主流移动SoC的片外内存带宽大概是20-50GB/s看起来还有余量但实际上不可能100%打满而且除了GPUCPU也在用同一块内存。当整体带宽占用超过60%功耗就明显上去了温度自然跟着飙高。所以发烫不一定代表你的渲染“算”得很多而是“搬”得太多。2. 纹理显卡里最大的数据搬运工2.1 纹理格式选错带宽直接翻倍纹理这个东西最坑的地方在于它太不起眼了。美术同学拖一张图进引擎默认可能就是RGBA8888格式也就是每个像素4字节。一个2048×2048的贴图单张就16MB采集一次就是16MB的带宽消耗。如果场景里同时有几十张贴图在帧内被采样再叠加mipmap级别切换、各向异性过滤的多级采样数字一下就爆炸了。所以纹理优化的第一刀一定是砍格式。移动端最主流的替代方案是ASTCARM推出的自适应可扩展纹理压缩格式当然ETC2在兼容性上也很稳。ASTC和ETC2的本质都是用有损压缩的方式把每像素的位数压下来。比如ASTC 4x4块格式大约8bit/像素ASTC 6x6块格式大约3.56bit/像素ASTC 8x8块格式只有2bit/像素。对比RGBA8888的32bit/像素压缩率是4倍到16倍不等。这不是说格式压了画面就一定会糊。ASTC的压缩质量跟纹理内容强相关它对渐变色、高频噪点、UI文字的表现差异很大。实操经验是UI图标和文字尽量用ASTC 4x4或者干脆用ETC2图片内容平滑的漫反射贴图可以大胆用ASTC 6x6甚至8x8。法线贴图要小心压缩带来的法线方向误差会影响光照质量建议保留率更高的格式或者用专门的法线压缩格式。2.2 mipmap、tt streaming与一个容易被忽略的陷阱格式之外mipmap是另一个关键点。mipmap链本质上是用少量额外内存约额外的三分之一换取采样时更低的带宽消耗。一颗全分辨率纹理如果在远处被缩小时采样没有mipmap纹理单元会做多次采样再插值等于一次shader采样吃掉好几倍的带宽和cache效率。开了mipmap之后硬件会直接选择合适分辨率的层级采样量和带宽直线下降。但mipmap也不是万能的。mipmap需要在运行时生成或随资源打包纹理内存会增大30%左右同时如果美术没注意mipmap的偏置设置远处纹理可能糊掉。我的习惯是场景里会缩小显示的纹理一律开mipmap全屏UI、图集、粒子贴图不强制开。再说纹理流送Texture Streaming。这个机制的思路是只把当前摄像头可见范围内需要的纹理加载进GPU内存离开视野就卸载或降级到低分辨率版本。在开放世界、大地图项目里它是控制显存和带宽的杀手锏。但它的坑在于如果流送加载的粒度太粗或者加载速度跟不上玩家视角切换会出现贴图突然变糊pop-up的尴尬场面。实际项目中我给每个纹理设置了几档优先级主角附近、UI、关键交互物体优先级最高远景装饰性物体优先级最低这样可以把流送带宽峰值削掉很大一块。还有个容易被忽略的陷阱UI纹理。很多人觉得UI就是一张图能占多少带宽。其实在复杂界面里UI的纹理宽高可能都接近屏幕大小而且UI往往在每一帧都会全屏重绘。如果你把UI图集设为RGBA8888那一次界面切换的带宽可能比你整个3D场景还高。我在两个项目里都遇到类似问题把UI图集从RGBA8888切成ASTC 4x4后界面切换时功耗降了不小一截。2.3 纹理优化的具体操作清单实操时我会先跑一轮资源分析确认当前项目的纹理格式分布、mipmap使用率、纹理总内存再按下面的顺序做调整批量把漫反射贴图和颜色贴图转为ASTC 6x6或8x8具体看画质目标法线和UI格式单独设定。清理重复纹理和未使用纹理别让资源管理器里堆一堆没人引用的“孤儿”贴图。将有缩放需求的模型贴图全部开启mipmap并设置合适的Aniso等级移动端建议2x或4x即可太高会额外增加采样量。开启纹理流送并设置好各资源优先级。检查UI图集压缩格式和尺寸都单独做配置。做完这些单看带宽数字纹理这块通常能降30%-50%发热改善非常明显。3. 后处理一片一片全屏数据搬运3.1 后处理为什么是带宽大户后处理的本质就是“全屏操作”把当前画面作为输入读一遍算完再写回去。每增加一个pass就是一次全屏读加一次全屏写。以1080p为例如果每像素4字节每pass的读写量就是16.6MB。一个典型的移动端后处理链Bloom需要降采样、径向模糊、升采样和合成往往要4-7个pass再加上Color Grading、Vignette、DoF总pass数轻轻松松上两位数。这些pass加起来一帧的带宽可能高达几百MB。在PC上这点带宽没什么压力因为PC显卡的显存带宽动不动就是几百GB每秒甚至TB级别。但在移动端SoC和内存共用一个总线这数据一多整个系统的功耗和发热就上去了。我在有的项目里见过光是后处理链路的带宽就能占整体GPU带宽的40%以上比场景里所有模型的纹理采样加起来还高。所以说后处理是另一个“搬运惯犯”名副其实。3.2 移动端Tile-Based渲染为什么单独写回这么贵移动GPU和PC GPU架构不一样主流移动GPUArm Mali、Qualcomm Adreno都是基于Tile-Based RenderingTBR或者更进一步的Tile-Based Deferred RenderingTBDR。简单说GPU把屏幕切成一个个小块tile每个tile在片上的高速存储器里完成运算再一次性写回主存。这样做的好处是正常3D渲染时颜色、深度这些中间结果大多留在片上的tile内存里不用频繁写回片外DRAM节省了大量带宽。但后处理却会破坏这种高效路径。后处理需要把之前的渲染结果作为输入纹理这意味着前一个pass的渲染目标必须写回到片外内存下一个pass再从片外内存读回来。读写之间Tile内存的优势完全被绕过了。尤其是多pass后处理链路每多一个pass就多一次全屏读和全屏写这跟TBR省带宽的初衷完全相反。所以在移动端上优化后处理的核心不是追求算法多“炫”而是拼谁能用更少的pass达到视觉目标。能合并的pass就合并能用半分辨率处理的地方就不要用全分辨率能跳过的pass就不要留着。3.3 是不是所有后处理都必须做全屏操作看清成本再决定我见过不少项目美术摆出一套PC上常用的后处理链Bloom、SSAO、Motion Blur、DoF全开。在PC上这些效果确实能拉满氛围但在移动端这就是发热炸弹。比如Bloom降采样、模糊、升采样、合成。每多一级模糊就是一次全屏读写。如果做5级模糊这就是至少5个全屏读写pass。SSAO要在半分辨率甚至四分之一分辨率下做深度采样然后还要空间模糊一次再合成到主画面。计算量不大但读写量很大。DoF通常基于半分辨率计算再加模糊和合成也是一串pass。Motion Blur需要读深度和运动向量然后做多次采样或多次pingpong模糊成本更高。不是说这些效果不能用而是要做取舍。移动端我的默认策略是后处理链控制在3个pass以内能半分辨率就半分辨率能合并到一个pass里做就合并。Bloom用降采样方式实现DoF和SSAO在主机平台再单独评估。3.4 后处理优化操作清单在真机上做后处理优化时我一般按下面几步走先记录当前默认后处理管线里的pass数量和每pass的输入输出分辨率算出理论带宽成本。根据项目美术风格逐项评估每个pass的视觉权重砍掉收益低但成本高的pass。把可以合并在同一个pass里完成的效果组合起来用一次全屏shader算出多个结果比如把Color Grading和Vignette合并。大量使用半分辨率渲染。物体的全屏效果在1/2分辨率下通常肉眼差距较小但带宽直接降到原来的1/4。用Temporal Filtering或降采样上采样的组合来弥补半分辨率带来的画质损失。在真机上对比帧时间和温度记录不同后处理组合的数据。实测下来把Bloom从全分辨率改成半分辨率再把Color Grading合并进Bloom的合成pass里后处理带宽能砍50%甚至更多温度下降明显。4. 实操过程在项目中定位和解决这两个惯犯4.1 用帧捕获工具和GPU计数器定位瓶颈空谈理论没有用关键还是得在真实项目里定位。我会先用三样东西做初步诊断Unity的Frame Debugger查看每一帧的draw call和每个render pass对应的纹理尺寸与格式。RenderDoc或Xcode的GPU帧捕获工具看每个pass的纹理输入输出说明。真机Profiler比如Snapdragon Profiler或Mali Offline Compiler配合硬件计数器直接查看带宽占用和着色器周期。定位思路很简单先看后处理pass列表数pass数量看看有没有全分辨率的大pass然后看纹理资源总内存和每帧纹理采样量重点标注RGBA8888格式和未开mipmap的贴图。这两个方向几乎每次都能抓到“惯犯”。实际操作里我遇到过一种很隐蔽的情况某些后处理pass在Frame Debugger里看起来是半分辨率但Unity或引擎可能为了适配某些效果平台在内部把RT尺寸恢复成了全分辨率导致我估算的带宽翻倍。这个问题后来是通过查看硬件计数器发现的所以我的经验是不要只依赖引擎界面一定要跑到硬件计数器上验证。4.2 一次完整的优化记录从前到后砍带宽我拿最近一个项目举例。项目用Unity渲染管线是URPUniversal Render Pipeline场景是偏写实的城市街区后处理链原本是Bloom - Color Grading - Depth of Field - Vignette。第一步我用Frame Debugger数了一下Bloom是5个pass降采样2个、模糊2个、合成1个Color Grading 1个passDoF 3个passVignette 1个pass总共10个pass。1080p分辨率下这10个pass的理论带宽大概就是10 × 16.6MB 166MB每帧60帧就是近10GB/s。这个数字已经足够把手机烫到不行。第二步我做方案调整。Vignette直接合并进Color Grading的pass里DoF砍成1个半分辨率passBloom保留但全改成半分辨率并且把模糊pass从2个降到1个用双线性采样的移位模拟效果。改完之后总pass数从10个降到4个且其中3个都是半分辨率。第三步纹理上我把主场景里的贴图批量转成ASTC 6x6UI图集切成ASTC 4x4然后把远处建筑、地面等不太重要的贴图开了mipmap和纹理流送。调完以后在骁龙中端机型上实测GPU带宽占用从优化前的约12GB/s降到约5.5GB/s帧时间从33ms降到24ms机身温度从稳定47度降到42度左右。画面效果上美术反馈在手机屏幕上几乎看不出差异反而因为帧率更稳观感更顺滑了。4.3 工具和配置不同引擎的落地差异Unity和Unreal在优化操作上有一些平台差异但底层逻辑完全一致。Unity项目里最优先的是改纹理导入设置。Texture Import Settings里纹理格式从RGBA Compressed切换到ASTC并且勾选合适的Compression Quality比例就是ASTC 6x6、8x8等。URP里后处理可以按平台设定配置文件在Quality设置里可以配置不同画质档位的Bloom强度、DoF开关等。还有一个关键点URP的Render Scale可以直接改全场景的渲染分辨率从1.0拉到0.8带宽减少非常明显配合后处理半分辨率使用发热曲线会很漂亮。Unreal项目里Texture Settings里可以设置Texture Compression Settings移动端一般选Arm ASTC并且在ViewPort或项目设置里关掉过度依赖带宽的屏幕空间效果。Unreal的Post Process Volume里有很多效果需要逐项调整Bloom的Method在移动端可以考虑默认或较简单的方案Screen Space Ambient Occlusion 在移动端建议关掉Motion Blur视情况降级。一个容易被忽略的设置是“Render Target缓存策略”。有些引擎会默认把渲染目标保留在LDR格式驱动可能转成HDR导致带宽上升。在移动端如果不需要HDR管线建议明确使用LDR或有限的HDR范围避免隐形格式转换。4.4 在真机上记录温度与帧时间怎么算优化有效带宽优化有没有效不能只看unity里的Profiler真机温度才是最终判断。我的测试方法很简单选同一台真机关掉后台应用屏幕亮度固定为80%摘下保护壳。加载同一个场景设定统一的测试路线跑图30秒战斗30秒地图界面30秒。使用PerfDog或Snapdragon Profiler记录帧时间、CPU/GPU占用、温度和功耗曲线。所有优化前后必须在同一时长内记录数据最好连续跑2轮避免第一轮GPU频率爬升和热降频带来的误差。我特别提醒大家优化后的测试要等手机冷却到初始温度再开始不然冷却不够数据还是偏高你会误判优化无效。我踩过这个坑有一版优化改完上了真机觉得没变化后来才发现是手机没冷却滤波器已经把频率降下来了。等手机回到室温再测数据才正常。另外温度下降通常滞后于带宽下降可能需要跑3-5分钟才能看到稳定温度。只看前30秒的温度数据基本不能反映真实效果。5. 常见问题与排查技巧实录5.1 问题速查表我把这几年项目里遇到的高频问题整理成一张表方便对照排查现象主要原因排查思路与解法界面切换时发烫加剧UI图集格式为RGBA8888UI全屏重绘带宽大检查UI图集压缩格式切到ASTC 4x4或ETC2压缩UI图集体积远处贴图糊、闪烁纹理未开mipmap硬件采样需要多级插值为可缩放纹理开启mipmap检查Anisotropic Filtering等级打开Bloom后温度骤升Bloom全分辨率多pass带宽成本高改成半分辨率Bloom合并模糊pass使用降采样上采样方案后处理链路很长但画面提升不明显做了很多看不见的效果比如SSAO、DoF堆叠卸载或降低这些效果用更轻量的替代方案纹理流送导致贴图突然变糊流送粒度太粗、优先级没设置设置资源优先级摄像机视角切换前预加载关键纹理特定场景掉帧严重但看不出问题场景里存在大量大面积透明物体或UI叠加层用Frame Debugger逐pass查看检查透明排序和overdraw优化后带宽没降多少引擎内部偷偷升级了RT格式用硬件计数器确认检查渲染目标是否被转成HDR5.2 三个独家经验和排查技巧第一检查后处理pass的成本时不要只看pass数量要看每个pass的输入输出尺寸。两个都是全屏pass1080p和540p的成本差4倍这种差距在计算时最容易忽略。建议把所有pass列成表格逐项标注分辨率和格式。第二纹理带宽优化里最容易被忽视的是“采样次数”。一张纹理就算压缩到ASTC 6x6如果每个shader里采样了8次带宽还是飙升。所以看纹理问题不能只看格式还要看shader里到底用了几次采样指令。水晶、金属、水面材质尤其容易踩坑一次shader里连续采样高光贴图、环境贴图、法线贴图带宽翻几倍是家常便饭。第三后处理可以用Profiler对比不同方案的带宽但不要只盯着平均值。要注意峰值带宽因为发热和非连续帧卡顿往往由瞬时峰值引起。比如每帧60fps平均带宽可能看似还好但某些镜头里Bloom和DoF同开峰值冲到15GB/s以上瞬间把手机拉热。这种场景下做个简单的带宽上限控制或者在高风险镜头下主动降低后处理分辨率能压住波动。写在最后这两个惯犯真的可以提前预防每次做优化挖到纹理和后处理我都会感叹一句如果项目策划阶段就把纹理格式规范和后处理预算定下来后期会省太多事。但现实里大多数项目的渲染管线和资源规范都要等到发烫、卡顿出现才想起来补课所以这块的经验才显得格外值钱。我个人这几年最深刻的感受是优化发烫不是把某个特效关掉那么简单而是要先理解每个特效背后到底搬了多少数据。纹理和后处理之所以被称为惯犯就是因为它俩几乎贯穿每一帧的渲染流程而且数据量很大。不管你是做Unity还是Unreal移动端还是主机模拟器带宽意识必须时刻在线。最后分享一个小技巧日常开发时可以养成一个习惯——每周抽10分钟跑一次硬件的带宽计数器看看数字有没有异常波动。别等到玩家反馈手机烫手了才回头排查。很多时候发热问题在前端开发阶段就能被摁死在摇篮里而做到这一切只需要你多看一眼纹理格式和pass列表。
返回列表