
作为常年泡在 Unity 性能优化一线的开发者我几乎每周都能在测试群里看到类似的话“帧率看着挺稳怎么玩 20 分钟手机就烫得能煎鸡蛋了” 或者更经典的“帧率 60温度 60这算不算某种意义上的平衡” 说实话“玩游戏发热”这个问题绝大多数情况下不是手机散热不行而是我们做出来的包在持续压榨硬件。这一篇作为系列第 1 篇我想先把最核心的“为什么烫”讲透从硬件功耗和 Unity 渲染、逻辑脚本之间的关系说起把这个病根拆明白了后面的系列文章才会落地。这个内容适合谁看两类人。一类是自己做独立游戏、在真机上测出过热但完全不知道从哪里下手排查的开发者另一类是团队里负责性能优化但每次开会只能给出“降低画质”这种建议的所谓优化负责人。我会尽量用大白话把 CPU、GPU、电压、频率这些听起来很高大上的词跟你在 Profiler 里看到的一条条尖峰对应上。1. 为什么烫从功耗物理课上说起1.1 发热的本质是“耗电做的功没变成画面”先问一个问题手机为什么会烫初中学过能量守恒电流通过芯片内部的晶体管一部分能量变成计算输出另一部分就变成热能散掉了。芯片的功耗近似公式是 P C × V² × fC 是电容负载V 是核心电压f 是运行频率。这里最要命的是 V²电压稍微一拉功耗直接翻倍往上冲。手机端 SoC 的调频策略一般是在温控阈值之内尽量跑高频一旦温度到了 45℃ 左右开始降频。降频带来的直接后果就是你的 60 帧变成了 50 帧、40 帧然后画面开始卡玩家手指就开始骂娘。换句话说游戏发烫的本质就是你的代码和渲染指令让硬件不得不在高电压、高频率的状态下持续工作。如果你能想办法“偷个懒”——在肉眼不容易察觉的地方削减计算量——温度自然就下来了。我们做优化的终极思路不是“让手机多干活还得不烫”而是“让手机少干点没意义的活把功率花在刀刃上”。1.2 为什么帧率稳并不代表功耗低这里有个天坑我见过大量开发者犯迷糊。用 Unity 默认设置打个包什么都不动场景里就摆了一个 Cube帧率能跑 120你以为挺优秀实际上 Unity 的空场景在跑Camera.Render的时候每一帧依旧会做Skybox渲染、光照计算、阴影判定、OnRenderImage之类的处理。哪怕场景再空这些固定开销也一分不少在消耗功率。我做过一次实测比较粗线条的统计同样一个模型展示场景不做任何优化时CPU 主线程开销大概是 8ms/帧渲染线程 6ms/帧GPU 12ms/帧当我们把垂直同步改成半速30 FPS把不需要的组件禁用把后处理特效全部干掉后CPU 能降到 3ms/帧GPU 降到 5ms/帧。这里有意思的是改动之前和改动之后的画面“感觉”没有差多少但功耗差了将近 40%。所以“帧率稳”只能说明你没有严重的卡顿完全不能代表你在省电也不能代表手机不热。以后你再看到谁说“我帧率很稳所以性能没问题”你心里就该知道他是外行。2. Unity 里发热的几大污染源2.1 GPU 侧FillRate、分辨率与 Shader 复杂度是头号杀手GPU 负责把三角形变成像素。手机上屏幕就那么大但如果你一个物体被画了很多次或者每一次像素填充所需的运算非常重GPU 功耗就会直线飙升。Unity 里面最常见的问题有三个第一是Overdraw过度绘制。同一个像素被反复填色每一次填充都在消耗 GPU 的 ALU 吞吐。粒子系统尤其严重一个半透明粒子叠十个同一个像素可能被片元着色器执行了十几次。查看方法也简单在 Game 视图的右上角把Overdraw模式打开如果整个屏幕是红的发紫的那你的粒子层级肯定出了问题。第二是Render Scale和屏幕分辨率后处理。有时候你不小心把Dynamic Resolution的 scale 设得过高或者用了一个全屏模糊、全屏辉光效果这相当于让 GPU 每帧把整块屏幕的像素都做一遍重型“数学按摩”。后处理这类操作对芯片的功耗冲击比你想象中严重得多。以移动端为例默认的 Vignette 一开可能直接多出 4~6ms 的 GPU 耗时发热体的主体之一就是这些全屏特效。第三是 Shader 里写了太多分支、用了太多纹理采样。移动端 GPU 的架构和 PC 不太一样PC 上随便写写问题不大移动端一个带 6 张纹理采样的片元着色器在低端机上直接能把 GPU 单元干满。这里我强烈建议所有自定义 Shader 都要用Shader Inspector里的Compile and show code看一眼生成后的 GLSL如果发现一堆循环嵌套还有分支趁早重写。2.2 CPU 侧脚本和物理是消耗 CPU 电量的大户CPU 发烫通常是被四类事情折磨疯了垃圾回收GC、Mono 调用开销、物理引擎的碰撞检测、动画系统骨骼计算。GC 是大家最熟悉的陌生人。你每帧new一个 List、每秒钟拼一个字符串、频繁触发GameObject.Find这些都会在托管堆上留下垃圾。Unity 的 Mono/IL2CPP 在堆内存达到一定阈值后就会触发 GC而 GC 一旦触发主线程直接卡一下。卡一下看起来只是帧率波动但它对 CPU 功耗的影响是这样的GC 需要遍历整个托管堆把所有对象引用关系重新整理一遍这期间 CPU 大部分处理单元会处于高负载状态频率可能会临时拔高功耗瞬间上去。发热就是这么一趟一趟积累的。物理引擎是另一个容易被忽视的点。默认 3D 物理是 PhysX2D 是 Box2D。如果你场景里有几百个带Collider和Rigidbody的物体彼此靠近碰撞检测的复杂度是 O(n²) 级别的当然有 broadphase 优化但空间哈希也不可能完全救回来。每一次物理迭代都在消耗 CPU 的核心运算单元。很多开发者觉得物理是“引擎自动算的”不需要花心思但忽略了大规模物理体本身就是一场耗电马拉松。动画系统同样不省心。Animator的StateMachine计算、OnAnimatorMove事件、IK Pass这些都是每帧执行的。如果你把几百个角色的动画全部交给 GPU Skin 去算除非用了GPU Instancing 骨骼纹理方案否则 CPU 的负担会非常夸张。我们团队之前优化过一个密集人群场景把动画采样从每帧一次改成了每 0.1 秒一次CPU 耗时直接砍半功耗温度双双降下来。2.3 内存与显存带宽的隐藏消耗还有一个非常隐蔽的发热元凶就是带宽。手机芯片上除了 CPU 和 GPU 之外还有一块 DRAM 控制器。当你频繁读取大纹理、反复在内存和显存之间搬数据整个系统的功耗也会大幅上涨。这就像你把书从书柜搬到书桌、再从书桌搬回书柜书本身没变但来回搬书这件事本身累死活人。在 Unity 里面AssetBundle加载和卸载、纹理格式选错比如用 RGBA32 高精度但我们根本不需要 Alpha、Mipmap没开导致远处物体纹理采样消耗激增这些全都会体现在带宽消耗上。移动端的显存带宽非常有限GPU 和 CPU 抢带宽的结果就是大家都在等数据功耗自然下不来。3. 真机实测我拿三个典型场景做了下 Thermal 对比3.1 测试设备与环境说明为了把这个“烫”字量化我简单跑了一组对比测试。设备是一台骁龙 8 的手机室温 25℃左右屏幕亮度固定 50%用同一部手机跑同一个包。测试场景三个A 场景是典型的小白默认场景空场景天空盒BloomB 场景是正常开发场景有一些动态物体碰撞粒子特效若干UI 用了 TextMeshProC 场景是做过一轮优化后的版本限制帧率、Overdraw 削减、无后处理、物理替代为触发器射线检测。我主要用PerfDog记录功耗、帧率和温度每场跑 10 分钟取仿真平均值。测试结果见下表场景平均帧率平均功耗W10 分钟温度℃表现评价A默认空场景后处理594.842.3功耗偏高纯浪费B正常开发场景446.546.8能玩但明显发热C一轮基础优化后563.237.5温度大幅下降流畅度不错这个结果有点反直觉的地方在于A 场景画面极其简单功耗却有 4.8W跟 C 场景比功耗高了一半。原因就是 A 场景每帧都在做全屏 BloomGPU 的大量执行单元被这种“重量级”后处理拖住了哪怕场景空后处理的计算量却是实实在在的。所以如果你只是把后处理关掉、把帧率限制在 60你的基础功耗就已经能降下来一大截了。3.2 帧率限制是投入产出比最高的“降温按钮”有人觉得限制帧率等于画质缩水其实在手机上绝大多数游戏跑 60 帧所产生的功耗换来的视觉提升很有限。我做了帧率对照实验同一个带后处理场景分别在 60 FPS、45 FPS、30 FPS 下跑功耗数据分别是 5.6W、4.3W、2.9W。温度差值就更夸张了60 帧 10 分钟后 46℃30 帧只有 36℃。原因很好理解运行帧率高意味着 CPU 和 GPU 每秒钟需要“运算并提交画面”的次数更多。每一帧即便没有玩家输入所有更新逻辑、物理、动画、渲染管线都依然要走一遍。多数场景里把帧率锁到 45 或 30 对肉眼的流畅度影响没有想象中那么大但能耗和发热却能立竿见影地降下来。当然如果是射击类电竞游戏这个结论需要打折扣需要综合权衡。在实际落地的时候我通常用Application.targetFrameRate 45;或者 30加一个设置界面让玩家自己选画质档位。手机上玩家能碰到“烫手”的机会远比“不够流畅”的机会少。3.3 屏幕分辨率与 Render Scale 的降温实测配套除了帧率还有一个会影响发热的因素就是Render Scale或官方建议的Dynamic Resolution。我同一场景下分别跑了 1.0 和 0.8 的 Render Scale功耗从 5.4W 降到 4.0W下降了四分之一还多。这个逻辑也很直白Render Scale 降到 0.8意味着内部渲染分辨率约为原来的 64%GPU 的片元着色器计算量直接打了六折多。屏幕实际输出时通过双线性拉升到目标分辨率画质损失远不如你想的那么严重尤其在移动端那种小屏幕上观感差距竟然比数据看起来温和得多。所以我的结论是以后谁跟你提“游戏越玩越烫”第一个反问就应该是——锁帧了吗Render Scale 调到多少后处理开着几个这三个问题比什么都管用。4. 定位发热元凶的实操方法论4.1 用 Profiler 找到 CPU 和 GPU 的尖峰很多人在发热问题上卡住是因为“热”是一个整体现象没法告诉你哪一行代码不行。所以第一步永远是查工具Window Analysis Profiler。这里我特别强调一个习惯真机联调时把Development Build和Autoconnect Profiler都打开同时勾上Deep Profile一个会显著拖慢帧率但是能定位到具体函数开销的选项。在 Profiler 里有一条至关重要的分隔线——16.67ms对应 60 FPS如果你的 CPU 主线程或者 GPU 时间频繁超过这条线掉帧发热迟早发生。但你首先要用到的其实是CPU Usage模块里的Player Loop展开后能看到ScriptRunBehaviourUpdate、Physics.FixedUpdate、Animation.Update、UI.Update等等。哪一栏树的叶子最长你的瓶颈就在哪。我在排查发热问题时绝大多数情况都会在ScriptRunBehaviourUpdate里找到若干个隐藏的Update死循环。4.2 Frame Debugger看 GPU 到底画了什么CPU 查完GPU 侧的事要用Window Analysis Frame Debugger。它能一帧一帧地回放渲染过程你可以直观看到每一个Draw Call每一个RenderPass。我排查过的一个典型项目用一个角色模型结果因为材质球数量太多被拆成了 30 多个 Draw Call而且每个材质球都有独立的MainTex和NormalMap采样GPU 会疯狂地在不同的纹理之间切来切去。Frame Debugger 里能看到明显的连续SetTexture调用消耗这在移动端是发热大户。解决办法很简单把材质球合并纹理依赖的贴图做进一张图集或者直接用GPU Instancing处理相同物体的绘制。还有一个很好用的技巧Frame Debugger 的右侧会显示每个 Draw Call 用到的 Shader 名称。如果你看到同一个模型的 Shader 是Standard (Specular setup)而不是Standard (Roughness setup)在移动端这就有可能是几毫秒的差异。移动端尽量用为移动 GPU 定制的 Shader比如Universal Render Pipeline/Lit或者自己手写 Mobile Shader。4.3 温度与功耗的第三方工具Profiler 不够直观的时候我会用 Android 平台上的PerfDog或者Snapdragon Profiler。PerfDog 能同时采样 FPS、CPU 占用率、功耗、SoC 温度把这些曲线叠加之后你能很清晰地看到游戏的操作高峰和功耗峰值是否同步。我曾经排查过一个案例打开某个商店界面时温度曲线瞬间陡增点回去看了代码发现只是打开了 UI 界面但界面背景迭代加载了一个 4K 大图同时该界面里有个动画在每帧新建 UI 顶点数据直接导致 GPU 和 CPU 同时爆负载。这种问题如果只看帧率曲线你根本看不出名堂只有把温度和功耗变量纳入记录表里才能锁定元凶。真机调温度优化有三件套我用下来最顺手工具用途说明Target FPS / 垂直同步设置限制输出帧率Application.targetFrameRate 最直接UPR / PerfDog采集功耗与温度需真机用于前后对比Unity Profiler定位 CPU 函数级开销Deep Profile 模式更精细Frame Debugger分析渲染提交明细配合 RenderDoc 可深入分析4.4 一个真实案例的排查复盘这里分享一个我印象很深的项目。那是一款卡牌轻度 3D 展示的手游玩家反馈“只要进入抽卡动画就发烫严重”。一开始我们以为是特效场景的原因因为抽卡动画做了非常复杂的粒子汇聚效果和全屏光效。我们先用 Profile 确认了 CPU 耗时正常但 GPU 时间报表里ForwardOpaque和ParticleSystem占了 80% 以上。再用 PerfDog 看功耗曲线进入抽卡动画那一刹那功耗直接冲上 7W。然后我们在 Frame Debugger 一帧一帧翻发现粒子数量远超预期。原来我们给每个粒子都加了Trail Renderer十几个粒子发射器再叠加出来就是几百个拖尾。拖尾的本质是不断生成新的 Mesh 几何体填充率和顶点数一下子全爆了。解决方案是重构了粒子方案把粒子发射数量从 500 降到 80关闭了大多数粒子的拖尾换成一个预烘焙的序列帧动画模拟全屏辉光后处理直接去掉改用一个比较廉价的 Bloom 变体。抽卡动画重构完同场景功耗从 7W 降到 4.3W温度从 48℃ 降到 39℃。玩家的体感直接翻转从“烫手”变成了正常的温热。这类问题我总结成一条经验抽卡、大招动画这类“华丽镜头”是发热的重灾区因为它们通常同时叠加了大量粒子、后处理、动态动画靠后期瞎调参数是没有用的必须正面拆解粒子数量和后处理等级。5. 常见发热问题与排查技巧实录5.1 明明没做复杂内容为什么手机上还是热这是新手最多的情况场景里只有一些放好的模型和 UI什么粒子、后处理都没加理论上不应该热。但我之前提过空场景 默认设置本身就不省电。这里有几个基础检查点第一看看有没有开垂直同步。如果垂直同步关闭游戏帧率“无上限”在高刷屏手机上能跑到 120 甚至 144 FPS。你以为空场景是在“独善其身”实际上手机 CPU/GPU 正以每 6.9ms 一帧的速度疯狂输出耗电速度可想而知。在开发初始阶段游戏里就应该预留一个DebugSettings界面把目标帧率固定下来。第二检查QualitySettings里的Shadow Quality、Pixel Light Count、Texture Quality。默认的 High 档位在移动端非常激进尤其是Shadow Distance如果设成了 150 米那意味着很远的物体也要渲阴影。阴影的大面积计算在移动端特别烧 GPU。适合移动端的通常是Shadow Distance 40~60、Pixel Light Count 1只保留最重要主光、纹理用Half Res就足够。第三TextMeshPro 或者 UGUI 动态字体。如果你频繁更新 UI 文本且使用了复杂的富文本UGUI 会重新构建整个Canvas的网格这个过程会消耗 CPU。解决方案是按需更新不要每帧都赋值text.text。5.2 Unity 对内存管理的潜在坑内存导致的发热比较隐蔽但值得单列一节。最常见的问题是AssetBundle加载后不释放或者Addressables资源LoadAssetAsync了但没调用Release。逻辑上只是内存占用变大感觉和发热没关系其实当内存接近极限时Unity 会频繁触发 GC每次 GC 都是处理器的“大扫除”功耗自然上去。更严重的是手机系统会定期把后台内存换到 zRAM 压缩交换区这个压缩解压操作在 CPU 上非常耗电。把Profiler Memory面板打开看看Total Allocated Memory是不是在缓慢爬坡如果爬坡恭喜你你找到了一个能让你手机热一整天的隐性罪犯。5.3 真机与编辑器表现差异过大有一种非常坑的情况在电脑上 Profiler 一切正常CPU 占用 8ms 以内GPU 也没有异常但一到真机上就热。这种情况的排查重心往往要转向 Shader 和纹理压缩。PC 上 Shader 是 DX11/DX12移动端是 OpenGL ES/Vulkan两者执行模型差异巨大。一些在 PC 上几乎不费力的半透明混合、全屏法线计算移动端可能会变成沉重负担。纹理格式上PC 的 BC7、BC5 在移动端往往不支持会自动转成 RGBA32内存翻好几倍带宽压力增大高温随之而来。针对移动端的正确做法是使用 ASTCAndroid和 ASTC/PVRTCiOS并且开启 Mipmap。5.4 排查时的风云问题UI 重叠、Canvas 重建与布局漂移UGUI 的 Canvas 是另一个容易“顺手坑你”的地方。一个常见问题是一个界面里有几百个 UI 元素而这些元素分别放在不同的 Canvas 下每个 Canvas 都开启了自己的Screen Space - Overlay于是每次 UI 更新都会触发多个 Canvas 的重建。更欠揍的是某些 UI 组件在隐藏时会频繁SetActive(true/false)这会强制 Canvas 重建极端情况下帧率直接从 60 掉到 35。排查方式也很直接Profiler 的UI Module里看Canvas.SendWillRenderCanvases如果占比较高说明 Canvas 更新很频繁。优化方向是把静态 UI 做成独立的Canvas设置UI Batch和Canvas的additionalShaderChannels最小化尽量避免动态变化影响整块 Canvas。5.5 关于 IL2CPP 与代码裁剪的小提示如果你用的Mono脚本后端打包在某些型号上会有额外的即时编译开销会加重发热。默认改成IL2CPP后运行效率会更好代价是包体更大且首次启动耗时增加。部分公司为了省事选择一直用 Mono结果在低端机上跑起来每次执行复杂 C# 逻辑时 CPU 都会发烫。IL2CPP 会把 C# 转成 C 再编译成机器码执行效率能提升不少是真机优化里性价比非常高的一步。另外 IL2CPP 提供了代码裁剪能去掉的冗余代码就去掉包体瘦身后加载也更快功耗会有间接收益。5.6 常见问题的速查对照表为了大家方便排查我整理了一个发热问题速查表基本覆盖了我这些年遇到的高频情况表现场景第一嫌疑第二嫌疑快速验证手段游戏一开始就明显发热后处理/高帧率大纹理无压缩关后处理、锁帧率、查看内存占用玩到后面才开始发热内存泄漏/资源不释放长时间运行的技能特效观察内存曲线、使用 PerfDog 记录温度趋势UI 界面操作时发热Canvas 频繁重建动态字体UI Profiler 查看 Canvas 重建次数3D 场景移动时发热阴影距离过大Shader 复杂度高降低阴影距离、替换 Mobile Shader粒子/特效场景发热Overdraw 严重Trail Renderer 过多Overdraw 模式检查、减少粒子数6. 写在系列前面的几条经验总结这篇是系列的第 1 篇做的是一件最基础但最重要的事——把发热的“元凶画像”给大家提前画出来。如果你连发热到底来自 CPU 还是 GPU、来自帧率还是后处理都分不清后面任何优化技巧都无处安放。我个人的体会是Unity 的发热优化跟“游戏好不好玩”没有关系它是一门纯粹依靠数据分析的工程题。每一次优化本质上都是回答三个问题——这一帧有没有在做无用功能不能偷个懒偷完懒之后肉眼看得出来吗实际动手时建议你从“锁帧 减少后处理 缩短阴影距离”这三板斧开始这是投入产出比最高的起步动作。测完第一轮温度数据再进 Profiler 去看大开销项逐步缩小范围。整个过程其实像在排雷一颗一颗扫过去心态别急就好。下一篇我打算针对移动端最普遍的“后处理发热”做一次拆解把 Bloom、Depth of Field、SSAO 这些看似惊艳的功能在手机功耗上是否值得开、怎么开更划算用实测数据一条条说清楚。按照我的经验光这一块就能帮你把一个项目的发热问题减少三成以上。