尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

Unity移动端锁帧优化:拿帧率换发热余量的工程实践

Unity移动端锁帧优化:拿帧率换发热余量的工程实践 1. 锁帧这件事远不止改一个数字那么简单移动端项目做到中后期发热和续航几乎一定会成为绕不开的坎。尤其是中重度游戏跑满 60 帧的时候手机背面烫得能煎蛋玩家玩十分钟就开始掉帧、降亮度体验反而比稳定 30 帧还差。这时候很多团队的第一反应就是锁帧——把 Application.targetFrameRate 改成 30或者干脆开垂直同步。但真上手之后你会发现锁帧带来的收益和副作用完全不成正比有的项目锁完帧温度只降了两度有的项目锁完帧画面反而更卡了还有的项目锁帧之后低端机直接变成幻灯片。这篇是发烫优化系列的第七篇专门聊拿帧率换发热余量这件事。核心思路很朴素主动放弃一部分帧率上限换取更平稳的帧生成时间和更低的持续功耗。但锁帧这两个字背后涉及的东西比想象中多——锁在哪一层、锁多少、怎么锁、锁完之后怎么验证每一步都有讲究。适合正在做移动端性能优化、被发热问题折磨过的 Unity 开发者也适合刚接触性能调优、想搞清楚帧率和功耗关系的朋友。下面我按实际项目里踩过的顺序把这件事拆开讲。2. 为什么帧率越高越烫不是线性关系2.1 帧生成时间与功耗的真实曲线很多人直觉上认为帧率和功耗是线性关系60 帧耗电是 30 帧的两倍。实际完全不是这样。GPU 和 CPU 的功耗曲线在接近满载时会急剧上翘因为电压频率调节机制会在高负载时拉高电压来维持稳定而功耗大致和电压的平方成正比。也就是说从 30 帧提到 60 帧功耗可能涨了 2.5 倍甚至更多但从 60 帧降到 45 帧功耗可能只降了 20%。这就解释了一个常见现象锁 30 帧往往比锁 45 帧更划算。因为 45 帧这个档位在很多设备上仍然处于功耗曲线的陡峭段省下来的电有限但画面流畅度的损失却很明显。我在一个中端机项目上实测过锁 45 帧相比不锁帧机身温度只降了 1.8 度但锁 30 帧能降 5 度以上。所以锁帧的档位选择本质上是在功耗曲线的拐点附近做取舍。2.2 帧率波动比高帧率更伤散热还有一个反直觉的点不稳定的帧率比稳定的高帧率更费电。原因是帧生成时间的抖动会让 CPU 和 GPU 频繁在高低负载之间切换每次切换都伴随频率调整和调度开销这些开销本身就要耗电。而且抖动会让温控策略更激进设备更容易触发降频保护。所以锁帧的真正价值不只是降低上限更是削平波动。一个稳定在 30 帧的游戏帧生成时间方差很小芯片可以长时间维持在一个舒适的频率区间温度自然就稳。这也是为什么有些项目锁帧之后虽然平均帧率降了但玩家反馈更跟手了——因为卡顿感消失了。2.3 发热余量到底留给谁发热余量这个词听起来抽象落到实际就是给突发负载留出频率上探的空间。游戏里总有突发情况——大招特效、场景切换、大量单位同屏。如果平时就跑在功耗曲线的顶端突发时芯片没有余量可调只能降频结果就是关键时刻掉帧。反过来如果平时锁在 30 帧、功耗只用了 60%突发时芯片能瞬间拉到高频扛过去玩家感知到的就是一直很稳。这就是拿帧率换发热余量的核心逻辑用平时省下来的功耗预算去支付突发时刻的性能开销。它不是单纯地降帧而是一种功耗预算的重新分配。3. Unity 里锁帧的四个层级选错等于白锁3.1 Application.targetFrameRate 的真实作用范围这是最常用的锁帧方式但它的行为在不同平台差异很大。在移动端Application.targetFrameRate 30会告诉引擎尽量以 30 帧渲染但它不保证一定锁在 30——如果某帧渲染耗时超过 33.3ms它照样会掉下去。而且它和垂直同步QualitySettings.vSyncCount是互斥的当 vSyncCount 不为 0 时targetFrameRate 会被忽略。// 移动端锁帧的标准写法 QualitySettings.vSyncCount 0; // 先关垂直同步 Application.targetFrameRate 30; // 再设目标帧率这里有个坑vSyncCount 必须在 targetFrameRate 之前设置顺序反了在某些 Android 机型上会失效。我遇到过一台设备先设 targetFrameRate 再关 vSync结果帧率还是跟着屏幕刷新率跑排查了半天才发现是顺序问题。3.2 垂直同步与屏幕刷新率的纠缠垂直同步的本质是让渲染节奏跟随屏幕刷新率。60Hz 屏幕开垂直同步就是锁 60120Hz 屏幕就是锁 120。问题在于现在很多手机支持可变刷新率90Hz、120Hz、144Hz如果不显式关掉垂直同步你的锁帧可能锁了个寂寞——在 120Hz 设备上跑 120 帧发热直接爆炸。所以移动端锁帧的第一步永远是关垂直同步。但关掉之后又带来新问题画面可能出现撕裂。对于大多数手游来说撕裂的感知远小于发热和掉帧所以这个取舍是值得的。如果确实在意撕裂可以考虑用Application.targetFrameRate配合三重缓冲但三重缓冲会额外占用显存和带宽低端机上要谨慎。3.3 渲染管线层面的帧率控制到了 URP 或 HDRP 管线锁帧还有更细的玩法。比如可以通过OnDemandRendering.renderFrameInterval来控制渲染间隔这个 API 比 targetFrameRate 更底层能精确控制每 N 帧渲染一次。// 每 2 帧渲染一次等效于锁半帧率 OnDemandRendering.renderFrameInterval 2;这个方式的好处是逻辑帧和渲染帧可以解耦。比如物理和游戏逻辑仍然跑 60Hz但渲染只跑 30Hz这样既保证了逻辑精度又降低了 GPU 负载。适合那种逻辑复杂但画面变化不剧烈的游戏比如策略类、模拟经营类。不过要注意renderFrameInterval 设成 2 之后UI 动画和粒子效果也会跟着降频视觉上会有明显的顿感需要针对性调整。3.4 系统级温控与引擎锁帧的配合最后一层是系统级的。Android 和 iOS 都有自己的温控策略设备温度到阈值后会主动降频这时候你在引擎里锁的帧率可能根本维持不住。所以锁帧不能只考虑引擎层还要给系统温控留出触发余量。我的经验是如果目标是长时间稳定 30 帧那引擎里最好锁在 28 或 29留一点缓冲给系统调度。这样即使系统轻微降频玩家感知到的仍然是稳定 30 帧而不是从 30 掉到 25。这个 1 到 2 帧的余量就是前面说的发热余量在系统层面的体现。4. 锁多少帧才合适一套可落地的决策方法4.1 按游戏类型定基线锁帧档位没有万能答案但可以按游戏类型划一个大致范围游戏类型建议锁帧理由休闲益智、卡牌30画面变化小30 帧足够省电优先策略、模拟经营30-45逻辑为主画面次要可接受较低帧率MOBA、射击60操作精度要求高帧率直接影响手感开放世界、动作45-60需要平衡流畅度和发热动态调整竞速、音游60帧率即体验宁可发热也不能降这张表是起点不是终点。实际项目里还要结合目标机型分布来调。如果主力用户是中低端机那基线要往下压如果是旗舰机为主可以适当放宽。4.2 用帧生成时间方差做验证锁帧之后怎么判断锁得对不对不能只看平均帧率要看帧生成时间的方差。理想状态下锁 30 帧的帧生成时间应该稳定在 33.3ms 附近方差越小越好。如果方差很大说明锁帧没锁住或者有其他瓶颈在拖后腿。用 Unity Profiler 或者 Frame Debugger 都能看这个数据。我习惯在真机上跑一段固定场景导出帧时间曲线然后算标准差。标准差超过 5ms 就说明有问题需要继续排查是 GC、DrawCall 还是别的什么在捣乱。4.3 动态锁帧让帧率跟着温度走固定锁帧有个天然缺陷它不知道设备当前烫不烫。旗舰机锁 30 帧可能太保守低端机锁 30 帧可能还是烫。更好的做法是动态锁帧——根据设备温度或功耗状态实时调整目标帧率。// 简化的动态锁帧逻辑 void UpdateTargetFrameRate(float temperature) { if (temperature 42f) Application.targetFrameRate 24; // 高温降档 else if (temperature 38f) Application.targetFrameRate 30; // 中温维持 else Application.targetFrameRate 45; // 低温放开 }温度数据可以通过系统 API 获取但要注意读取频率不能太高否则本身就成了耗电源。一般 5 到 10 秒读一次就够。另外降档要有滞后避免在阈值附近反复横跳那样反而更费电。5. 锁帧之后画面变卡这些副作用要提前防5.1 输入延迟的补偿锁帧最直接的副作用是输入延迟增加。30 帧意味着从玩家操作到画面反馈最多有 33ms 的延迟加上渲染管线本身的延迟实际可能到 60-80ms。对于射击、音游这类对延迟敏感的游戏这个数字是致命的。补偿手段有几个一是把输入采样和渲染解耦输入仍然按屏幕刷新率采样只是渲染降频二是预测性渲染根据输入趋势提前渲染下一帧三是降低渲染管线延迟比如关闭后处理里的高延迟效果。这些手段不能完全消除延迟但能把感知拉到可接受范围。5.2 UI 动画和粒子的降频适配锁帧之后所有依赖帧率的动画都会变慢或变顿。UI 的补间动画、粒子的发射频率、Shader 里的时间驱动效果都会受影响。如果不做适配玩家会觉得整个游戏都变卡了而不是帧率降了但很稳。适配的核心思路是把时间驱动的逻辑改成基于 deltaTime而不是基于帧数。这样无论帧率多少动画速度都一致。粒子系统要检查发射率和生命周期是否和帧率绑定Shader 里的_Time变量在低帧率下也要确认行为正常。5.3 物理和逻辑帧的独立控制前面提到过物理和逻辑不一定要跟着渲染降频。Unity 的 FixedUpdate 默认是 50Hz和渲染帧率无关这一点是好的。但如果你在 Update 里做了大量逻辑计算锁帧之后这些计算仍然每帧执行CPU 负载并不会因为渲染降频而降低。所以锁帧要配合逻辑层的优化把不必要的每帧计算改成事件驱动把复杂的 Update 逻辑挪到协程或定时器里。只有渲染和逻辑一起降下来功耗才能真正降下去。我见过只锁渲染不锁逻辑的项目温度几乎没变化就是这个原因。6. 一次真实的锁帧调优记录6.1 问题现象与初始数据去年接手的一个项目中端机跑 60 帧玩 8 分钟机身温度到 45 度然后开始掉帧到 40 左右玩家反馈越玩越卡。用 Profiler 抓了一段数据平均帧率 58但帧生成时间方差达到 8msGC 每 2 秒触发一次DrawCall 在 180 左右。初步判断是功耗预算被拉满没有余量应对突发。特效一多GPU 瞬间满载触发降频然后帧率掉下来温度稍微降一点又升回去来回震荡。6.2 分阶段锁帧的尝试过程第一轮直接锁 30 帧温度确实降了8 分钟到 40 度但玩家反馈太卡了像换了个游戏。说明 30 帧对这个品类的体验损失太大。第二轮改成锁 45 帧温度到 43 度比不锁好一点但掉帧现象还在只是没那么严重。分析发现 45 帧仍然在功耗曲线的陡峭段省下来的余量不够。第三轮尝试动态锁帧平时 45 帧检测到温度超过 40 度降到 30 帧温度回落到 38 度以下再升回 45。这个方案温度控制住了但帧率切换时玩家能明显感知到顿一下体验割裂。6.3 最终方案与实测收益最后落地的方案是锁 40 帧 逻辑层优化 特效分级。40 帧这个档位比较微妙它低于 45 的功耗拐点又高于 30 的体验底线。配合把 GC 优化到 5 秒一次、DrawCall 压到 120以及根据设备档位动态调整特效质量最终 8 分钟温度稳定在 41 度帧生成时间方差降到 3ms玩家反馈很稳不烫手。这个案例给我的最大启发是锁帧不是孤立的手段它必须和逻辑优化、资源分级一起用。单独锁帧能解决的问题有限组合拳才能既降功耗又保体验。7. 几个容易忽略的细节和实操建议7.1 编辑器里的帧率设置会骗人在 Unity 编辑器里Application.targetFrameRate的行为和真机完全不同。编辑器有自己的刷新机制而且 Profiler 连接时也会影响帧率。所以锁帧效果必须在真机上验证编辑器里的数据只能参考。我习惯用 Development Build 打包到真机关掉 Profiler 连接用系统自带的帧率显示或者第三方工具看实际表现。7.2 不同 Android 机型的锁帧差异Android 碎片化在锁帧这件事上体现得淋漓尽致。同样设 30 帧高通芯片和联发科芯片的实际表现可能差 2 到 3 帧有的机型会强制把 targetFrameRate 对齐到屏幕刷新率的约数导致 30 帧变成 24 帧或 20 帧。所以锁帧参数要做机型适配至少要在主流芯片平台上各测一遍。7.3 锁帧和电池模式的联动很多手机有省电模式开启后会限制后台活动和降低屏幕刷新率。如果游戏不感知这个状态可能会出现省电模式下帧率反而更不稳的情况。建议在游戏启动时检测系统电池状态省电模式下主动降一档帧率避免和系统策略打架。7.4 别忘了测试长时间运行的稳定性锁帧的收益要放在长时间维度看。短时间测试可能看不出差别但跑 30 分钟之后不锁帧的设备可能已经烫到降频锁帧的设备还能维持稳定。所以测试至少要跑 20 到 30 分钟记录温度曲线和帧率曲线的对应关系才能判断锁帧方案是否真的有效。8. 关于帧率取舍的一点个人体会做了这么多项目我越来越觉得锁帧是一门取舍的艺术而不是技术活。技术上把 targetFrameRate 改个数字谁都会难的是判断这个项目、这批用户、这个场景下多少帧是够的。有的团队为了追求 60 帧的营销卖点宁可让手机烫到握不住有的团队一刀切锁 30 帧结果把操作手感也锁没了。我的经验是先定体验底线再定功耗上限最后在两者之间找平衡点。体验底线由游戏类型决定功耗上限由目标机型决定平衡点靠真机实测找。这个过程没有捷径但每调一次你对帧率和功耗关系的理解就深一层。等哪天你能凭经验估出这个场景锁 40 帧大概能把温度压在 42 度以内这门手艺就算入门了。
返回列表