你有没有过这种经历:本来舒舒服服地躺着打游戏,打着打着,手机就开始发烫,机身越来越热,握在手里跟块烙铁似的,游戏也跟着一顿一顿地掉帧,关键时刻卡成PPT,气得你想把手机摔了。
今天就来聊一聊,手机为什么会发烫,还有发烫之后,游戏为什么会变卡。
很多人第一反应是手机不行了、该换了,其实背后的元凶,是所有手机都逃不掉的一个机制——热降频。这个坑特别隐蔽,开发者在编辑器里看不出来,刚启动的几分钟里看不出来,甚至用中高端机短暂试玩也看不出来。
01 先说清楚:手机为什么会"越玩越卡"
很多人对性能优化的理解还停留在"帧率越高越好",这在手机上是个巨大的误区。
手机和PC不一样,PC有风扇、有巨大的散热空间、可以持续满血跑;手机没有风扇(绝大多数),内部空间逼仄,CPU和GPU全靠一块小屏幕和薄薄的机身被动散热。这意味着,只要你让芯片持续高负载运转,热量就会不断累积,而热量散不出去,温度就会一路升高。
手机厂商为了保护硬件,会设一道温度阈值。当SoC(芯片)的温度升到一定程度,系统会强制降低CPU和GPU的运行频率,这个过程叫热降频(Thermal Throttling)。频率一降,你的芯片算力就缩水,原本能跑满帧的画面,现在跑不动了,帧率自然就往下掉。
这就是为什么很多人会有那种感觉:游戏刚进去还挺流畅,玩个十分钟就开始卡——不是你的手机不行,是游戏在这十分钟里持续把芯片烤到了触发降频的温度。
这里有一个特别重要的认知,我希望你记住:在手机上,优化的目标不是"能跑满帧",而是"能持续稳定地跑,且保留余量"。你刚打开游戏时能跑到满帧,一点意义都没有,因为那个时间点芯片还没热起来;真正考验功力的是玩了半小时、设备已经发热之后,它还能不能稳住。
更坑的是,降频一旦触发,不是你把负载降下来就能立刻恢复的——芯片温度降下来需要时间,这段时间玩家就一直被卡顿折磨。所以正确的思路是别让它热到触发降频,而不是等降频了再去救。
02 热量到底从哪来?两个大头和一个小头
要治发热,先得搞清楚热量是谁产生的。手机玩游戏的热量,主要来自两个地方:GPU和CPU,还有一个容易被忽略的小头是网络和其他模块。
第一大头:GPU,也就是渲染。
GPU是手机里最耗电、最发热的部件之一,尤其是3D游戏。你画面里每个像素、每个特效、每束光照,都在让GPU拼命干活。GPU发热的几个常见元凶,我给你点出来:
一是Overdraw(过度绘制)。这个词很多人听过,但没真重视。Overdraw就是同一个像素被反复画了好几遍。你想,屏幕上一个像素明明已经被前面的地形盖住了,结果你还在它后面画一堆透明特效、粒子、全屏UI,GPU就得把这个像素反复计算、读出来、混合、再写回去,全是无用功。
最典型的Overdraw重灾区是:大面积半透明UI、层层叠叠的粒子特效、全屏后处理(比如bloom、模糊)、还有半透明的精灵。Unity里有个Overdraw绘制模式,你切过去一看,颜色越亮、越偏红的地方就是Overdraw越严重的地方,一目了然。
二是Fragment Shader(片元着色器)太重。每个像素都要执行一遍你的片元着色器,屏幕上几百万像素,如果你的shader里塞了一堆复杂的光照计算、多层纹理、各种花哨效果,乘以像素数量,那就是个天文数字。
三是实时阴影、高分辨率、后处理这些"画面税"。实时阴影很吃性能,尤其是多盏实时光、高分辨率阴影;后处理也是全屏操作,每加一层GPU就多跑一遍全屏;渲染分辨率开得越高,要计算的像素越多,发热越明显。
第二大头:CPU。
CPU这边的发热主要来自三块:渲染准备(DrawCall数量、渲染状态切换)、游戏逻辑和物理(尤其是每帧跑的物理检测、大量对象的更新)、还有老生常谈的GC。关于GC我之前专门写过一篇,这里不展开,你只需要记住:GC频繁触发、主线程经常卡顿,不仅影响帧率,同样会让CPU负载升高、发热加剧。
那个容易被忽略的小头:网络和持续唤醒。
网络模块在持续收发数据、射频模块一直工作也会发热;另外如果你的游戏一直让手机保持最高性能状态、不让它休息,热量也会累积。
03 怎么治?
第一招:限制帧率,这是最简单也最有效的一招。
很多人做游戏,上来就追求满帧、高刷,觉得帧率越高越牛。但你想过没有,你的游戏真的需要全程最高帧率吗?一个回合制游戏、一个卡牌游戏、一个剧情向的小游戏,玩家在菜单里、在对话框里、在思考的时候,你让GPU拼命跑60帧甚至更高,纯属浪费电、白发热。
正确做法是分场景设置帧率:战斗、操作密集的场景可以追求高帧率,菜单、背包、剧情对话这种静态场景,把帧率压下来。引擎里专门有个目标帧率的设置,你在场景切换的时候把它调高或者调低就行,进战斗拉高、进菜单调低,具体怎么设查一下文档就有,这里就不贴代码了,知道有这么个东西即可。
别小看这一招,它几乎是零成本的,很多发热问题就是靠"该慢的时候让它慢下来"解决的。手机不需要你时刻满血,它需要的是在该快的时候快、该休息的时候休息。
第二招:专治GPU的各种过度绘制。
用Overdraw绘制模式排查,把那些看不见却还在画的东西砍掉。被遮挡的粒子、藏在界面后面的3D模型,该关的关,该剔除的剔除。UI能合批的合批,别让一个界面碎成成百上千个图层,尤其是透明图层。粒子特效控制数量和层数,一个技能特效叠七八个粒子系统,看着爽,手机哭。静态场景合理做遮挡剔除(Occlusion Culling),被墙挡住的房间内部就别渲染了。第三招:给画面做"减负"。
用LOD(细节层次),远处的模型用低面数版本,别让远处一棵树还顶着几万个三角面。实时阴影能减就减,能用烘焙的静态阴影就别用实时光;阴影分辨率、阴影距离都可以调。后处理谨慎使用,移动端的全屏后处理很金贵,尤其那些需要多次采样的效果。提供渲染分辨率缩放(Render Scale),中低端机适当降低渲染分辨率,靠画面缩放换性能,这是立竿见影的一招。合批静态和动态物体,减少DrawCall,给CPU减负。第四招:做画质分档,让不同手机跑不同的画面。
这是商业化手游的标配,也是必须做的。你不能指望一台千元机和一台旗舰机跑同一套画质。通常会做高、中、低几档画质:高档机开特效、开阴影、开高分辨率;低端机关掉实时阴影、关掉后处理、降分辨率、减特效。
更进阶的做法是根据设备的发热状态动态调整,而不是一上来就把档位定死。Unity官方有个Adaptive Performance的包,就是干这个的:它能读取设备当前的热状态和功耗状态,你可以拿到热警告等级和温度等级,在设备快撑不住的时候主动降画质、降帧率,让它缓一缓,而不是等系统把你一刀降频。
思路也不复杂:当它反馈设备温度开始告警,你就主动往下调一档——轻度告警时先关后处理、砍特效,重度告警时再降渲染分辨率、降帧率、关阴影,一步步给设备降温,不用自己写复杂的判断,知道有这么个东西、知道能拿到热状态就行。
iOS和安卓也各自有获取设备热状态的系统接口,原理类似。核心思想就一句话:让游戏自己主动、平滑地降负载,好过被系统粗暴地强制降频。
04 我自己排查发热问题的完整流程
最后说下我接到发热类bug时,一般是怎么排查的,给你一套能直接套用的流程。
第一步,真机、长时间复现。一定要在真机上、连续跑十几分钟以上,最好用中低端机,因为它们最容易触发降频。编辑器里、短时间试玩,根本复现不了。
第二步,用Profiler看持续负载曲线。接上真机Profiler,盯着CPU、GPU、帧率、温度相关的指标看长曲线,重点关注:是不是一开始稳定、后面逐渐恶化?如果是,基本就是热降频;如果是某个时刻突然尖峰,那可能是别的逻辑问题。
第三步,分清是GPU瓶颈还是CPU瓶颈。帧时间里看GPU耗时和CPU耗时谁占大头,是GPU就先治渲染(Overdraw、shader、分辨率),是CPU就先治DrawCall、逻辑、物理和GC。
第四步,针对性地降,而不是盲目砍。找到瓶颈之后,用前面那些招数针对性地优化,并且每改一处就测一次,观察温度和帧率的变化。
第五步,建立分档和动态降载机制。优化到一定程度后,靠分档和热状态自适应兜底,保证不同设备、不同时长下都有稳定体验。
05 最后
其实做玩法的时候,脑子里得有根弦:你不是在做一个Demo,是在做一个玩家要拿在手里玩很久的游戏。Demo只需要证明"能跑",而产品要保证"持续好玩、不烫手、不掉帧"。
移动端优化这件事,很多时候拼的不是你能不能把画面做得多炫,而是你能不能克制——能不能在炫和稳之间找到那个平衡,能不能舍得砍掉那些华而不实、只会烤手机的效果。