拓冰建站拓冰建站
首页 / 资讯中心 / 正文

Unity移动端Overdraw深度优化:从原理到实战解决渲染性能问题

写这篇的时候我正被一个项目折腾得不行主城界面一打开背包手机温度肉眼可见地往上飙帧率像心电图一样上下乱跳。查了一圈CPU没压力、Draw Call也不算夸张最后用Frame Debugger一帧一帧翻下去发现罪魁祸首就是Overdraw——同一块像素被UI、特效、透明地面来回画了七八遍。像一堵白墙非要刷上十层漆看着还是那堵墙但干活的GPU已经累冒烟了。这篇是“发烫优化系列”的第三篇前面聊过Draw Call和网格这次专门把Overdraw摊开讲清楚。我会从原理、诊断手段、常见重灾区一路讲到完整的实战优化案例最后附上我踩过的坑。内容主要面向移动端Unity开发者技术美术和TA应该也能直接拿走用。1. Overdraw是什么一堵被反复粉刷的墙1.1 先还原一个真实事故现场那是一个ARPG项目测试机是某款中端安卓。现象很典型主城界面稳定60帧但只要一打开背包或者商城GPU渲染时间直接翻倍手机边框发烫随后开始掉帧——从60掉到30再掉到20。我第一反应是Draw Call爆炸结果Profiler一看Draw Call也就120左右CPU端完全没压力。又怀疑是Shader太复杂但场景里的角色、NPC用的都是标准PBR。最后打开Frame Debugger逐事件翻才发现在背包界面里至少有7层全屏UI元素叠在一起全屏半透明背景虚化层实时高斯模糊还是两层的半透明暗色遮罩界面底图两个带Alpha的装饰条按钮的Color Tint状态层弹出提示用的全屏半透明Panel再往下看角色脚下的地表是半透明草地Shader怪物身上挂着三四个大范围光效UI转场时还叠了一个全屏冲击波粒子。这些全挤在同一个像素区域里一层一层反复混合。这就是Overdraw同一个屏幕像素在单帧内被多次写入了颜色。每次写入都涉及采样、混合、再写回缓存GPU的像素填充率就这么被白白烧掉了。1.2 图形学层面的过绘制在渲染管线里一个物体被光栅化后会产生一系列片元Fragment片元经过着色、深度测试、混合后写入帧缓冲区。Overdraw高的时候同一个像素位置会执行多次片元着色和混合操作。尤其要注意半透明物体。不透明物体因为有深度缓冲大部分被遮挡的片元会被Early-Z直接干掉但半透明物体通常不写入深度还会从远到近排序并逐个混合。这意味着只要多个半透明物体在屏幕上重叠每一层都会完整执行一遍混合计算。这里用GPU架构来理解会更直观。桌面GPU大多采用立即模式渲染Immediate Mode每一次绘制直接操作帧缓存虽然overdraw也费性能但显存带宽相对大影响没那么致命。而移动端GPU主流是Tile-Based Deferred RenderingTBDR先把一帧的几何数据按屏幕分块Tile收集到芯片上再统一光栅化到片上缓存。TBDR架构的优势是省带宽但它的片上缓存带宽依然有限overdraw一旦上去了每一个额外写入都会显著拉高功耗和发热。用个通俗的类比画一面墙刷一遍漆就完成目标了但如果你手贱刷了十遍每刷一遍还得等漆干、再补色差粉刷工累得半死墙看起来还是一样的。GPU就是那个粉刷工Overdraw就是让他白干活的“无效劳动”。1.3 Overdraw、发烫、掉帧三者的关系Overdraw高不代表一定会卡但它一定会让GPU的工作量变大。移动端为了控制功耗GPU核心频率是动态调节的。当某几帧的渲染压力陡增GPU会尝试拉高频率去顶住频率拉高功耗上升芯片发热量越来越大。手机是被动散热热量堆积到一定程度系统就开始降频。频率一降帧率跟着跳水玩家感受到的就是“发烫 卡顿”。而且这是恶性循环玩家被卡顿激怒然后继续点开UI看overdraw继续压上去温度继续升高。所以移动端项目里overdraw从来不只是“画面问题”它直接决定游戏能不能长时间稳定跑高帧率。这里也顺带说明一个容易误解的点不是只有特效才有overdrawUI反而是重灾区。后面第三部分会专门展开。2. 怎么诊断Overdraw从编辑器到真机工具链2.1 Unity自带的Scene Overdraw视图模式Unity的Scene视图里有一个显示模式切换菜单就是左上角的那个下拉框。切到“Overdraw”模式后视口会用颜色来标识overdraw程度颜色越白、越青表示绘制次数少正常颜色越黄、越红表示overdraw越严重叠加很多层后甚至会出现深红发黑的情况。实际操作中建议配合相机的Culling Mask使用。比如只显示UI层就能专门检查UI的叠加情况只显示特效层就能单独看粒子系统的overdraw分布。不然所有物体混在一起满屏都红你根本分不清到底是哪块出了问题。这个视图模式在Scene里是实时预览的切过去就能看到不需要运行游戏Play模式下会跟随场景相机实时更新。对静态场景排查尤其方便比如检查楼顶装饰、地表植被、半透明栅栏这些东西的叠加情况。2.2 Frame Debugger逐帧翻渲染事件Window - Analysis - Frame Debugger开启后点击Enable然后拖到某帧就能逐事件查看渲染队列。Frame Debugger的价值在于它能告诉你每一帧到底按什么顺序画了哪些物体、用了哪些Shader Pass、开没开深度写入、用的什么混合模式。看overdraw时重点观察几个字段Render Queue在Transparent范围内的物体混合模式Blend SrcAlpha OneMinusSrcAlpha之类的是否写了深度ZWrite Off全屏Quad后处理常用的全屏网格如果你发现某个区域连续渲染了好几个半透明物体且它们的屏幕包围盒高度重叠基本可以判定这里就是overdraw热点。官方文档的说法是Frame Debugger不直接统计overdraw次数它更像一个事件排队记录仪。但配合人眼判断和Scene视图的Overdraw模式已经能锁定绝大多数热点。2.3 真机Profile数据说话更靠谱编辑器里看着红的区域到真机上不一定是最要命的反过来编辑器里看着正常的区域真机上可能因为分辨率、GPU架构差异而爆炸。所以我一直强调性能优化以真机为准编辑器只能用来观察相对趋势。Android平台的工具推荐Android GPU InspectorAGIGoogle官方工具支持抓帧、查看渲染Target的overdraw情况、分析Shader耗时功能很全面。Snapdragon Profiler高通芯片手机专用老牌工具信息量很大但界面略旧。iOS平台推荐Xcode的GPU Frame CaptureMetal调试神器能抓取完整帧的渲染操作还能查看每个Draw的像素覆盖率。Metal System Trace主要看GPU时间线和功耗数据。真机Profile有几个实用习惯固定分辨率和画质档位再测试否则对比无意义固定相机位置和朝向让测试场景一致用低电量模式测一遍发热表现更接近真实玩家场景至少连续跑10分钟看温度曲线和帧率曲线的相关性2.4 一个极其实用的分层诊断技巧当你已经用Overdraw视图看得眼花缭乱时有一个办法瞬间缩小范围分层开关。步骤大致如下在游戏里做一个叫“OverdrawDiagnose”的调试开关分别控制UI层、特效层、场景层、角色层的显示和隐藏每隐藏一组用真机或Editor的Overdraw模式看一眼热点是否消失。比如你发现隐藏UI层后红得发紫的区域没了那说明问题主要出在UI层。如果隐藏特效层后颜色降下来那接下来去检查粒子就好了。这个方法尤其适合大场景项目。一个城市场景动辄几百个物体靠肉眼逐层翻Frame Debugger效率太低分层诊断能快速把问题归到某个子系统。3. 哪些地方最容易产生Overdraw3.1 UI层隐藏的重灾区移动端游戏里UI的overdraw问题通常比3D场景还严重。原因很简单UI基本全是平面叠加而且很多界面设计为了美观会在一小块区域内摞上好几层半透明元素。最容易出问题的UI场景全屏半透明背景比如背包界面、登录界面一张半透明底图覆盖整个屏幕下面如果有实时模糊或者装饰纹理overdraw直接翻倍磨砂玻璃效果很多项目用Blur后处理或UI材质模拟磨砂质感Blur本身就是全屏多Pass再加上UI半透明层严重时单帧overdraw能到十几层多层嵌套Panel一个弹窗三层背景每层都是半透明的描边或渐变叠加起来视觉上可能看不出差异但绘制开销实打实控件的阴影和描边默认字体阴影、按钮投影、描边效果每一个都是一层额外的透明绘制ScrollRect里的内容列表滚动时很多元素重叠在同一个区域尤其是带透明背景的item对UI层排查的时候有个口诀看“有没有多个透明层叠在同一屏幕区域”。如果是想办法合并或者裁剪。3.2 特效粒子烟雾火焰魔法阵粒子系统是overdraw的另一大源头。粒子默认是半透明队列而且一帧可能有几百上千个粒子同时存在。它们彼此之间互相遮挡、彼此混合再加上粒子往往覆盖大屏区域overdraw数字飙升得非常夸张。我见过一个极端案例一个全屏火焰特效用了4个独立的ParticleSystem叠加每个系统2000个粒子同时还有全屏震动后处理。打开Overdraw视图整个屏幕几乎都是深红色。帧率直接从60掉到15手机烫得像暖手宝。粒子相关的高危因素大面积粒子贴合屏幕比如火墙、烟雾弥漫全屏粒子之间叠加密度过高同一区域发射多个发射器粒子生命周期过长旧的没消失新的又生成了大半径粒子配合半透明贴图一张512x512的半透明烟雾贴图在屏幕上缩放成四分之一屏幕大小每个粒子都等于对全屏做了多次半透明混合撞击特效、受击特效频繁触发高帧率下并行叠加3.3 场景里的透明地表和植被场景中常见的overdraw来源是地表和植被。地表方面一些项目做草地效果用的是半透明草地贴图加Alpha Blend镜头拉远后整片草地就是一块巨大的半透明区域覆盖大半个屏幕。更麻烦的是如果地形有多层贴图混合泥地草地碎石且每层都带半透明遮罩那么地表这个几何体本身就贡献了多层混合。植被方面树叶用Cutout模式还好但有很多美术为了边缘柔和把树叶改成Alpha Blend模式或者用“透明树叶”Shader。这种树叶在摄像机的近景中极容易造成大范围overdraw尤其是树林区域层层叠叠、密密麻麻GPU负担直线上升。水体和玻璃制品也是类似的逻辑。半透明水面材质本身不写深度后面如果有湖底石头、鱼、倒影什么的叠加层数直接翻倍。3.4 角色与特效的穿插角色身上的overdraw往往被忽视。角色模型本身的材质通常是不透明的但配上特效之后就不一样了角色周围的技能光效环形光圈、飘带、透明披风受击闪白时的全屏或半屏滤镜复活、传送等转场特效经常是一个全屏半透明网格罩在角色上武器拖尾、技能路径残留角色重叠时比如一堆怪物挤在屏幕中间每个怪都挂特效叠加区域会非常可怕。而人物特写镜头比如胜利结算和角色立绘同框更是overdraw重灾区。4. 实战优化从UI、Shader到渲染管线的调整4.1 UI层能合就合能砍就砍优化UI的overdraw我的优先级顺序一般是这样的第一削减透明层数量。能合并的Image就合并能用一张图代替的多层UI直接换图。比如背景虚化层如果美术接受直接用预烘焙的模糊贴图而不是运行时做高斯模糊。第二优先使用不透明元素。纯色背景直接用Solid Color不要用带Alpha的贴图充数。UI的底色、分割线、边框这些尽量做不透明。只有必须做半透明层次时才考虑透明的方案。第三给UI加裁剪。Unity的RectMask2D是个好东西。它能把子物体的渲染区域限制在指定矩形内超出部分直接剔除可以减少大量不必要的绘制。尤其是ScrollRect里有很多元素的时候给Content挂一个RectMask2D能明显降低UI的overdraw。第四尽量避免实时阴影和模糊。UI上的DropShadow、Outline、GaussianBlur这些效果在移动端都是开销大户。能预烘焙就预烘焙比如把阴影直接画进图集。第五做UI界面时控制“同时显示”的层次。有些游戏商城界面底层地图还在跑顶上又叠了一个全屏UI最外面还有一个弹窗提示。如果底层地图本身有很多特效和半透明粒子那overdraw会非常夸张。这种情况建议切换UI场景或直接隐藏底层活动物体。4.2 特效管住粒子数量优化混合模式特效层的overdraw优化核心思路是“减少粒子面积 减少粒子数量 优化混合模式”。减少粒子数量方面在ParticleSystem的Main模块里控制Max Particles别让它无限发射检查Emission Rate很多特效的Rate高得离谱但视觉上根本看不出差距减少Sub Emitters和Collision检测这些不只费CPU也会拖慢渲染长生命周期粒子比如烟雾可以考虑用Flipbook粒子图集来模拟而不是一个大半透明面片飘很久减少粒子面积方面控制粒子的初始大小别让一个粒子占四分之一屏幕用Noise模块做扰动时注意不要产生大量重叠在Renderer模块里调整Sorting Fudge和Order in Layer减少粒子之间的互相覆盖深度优化混合模式方面特效贴图尽量用Additive模式而不是Alpha Blend。Additive对overdraw的感知相对温和因为只会加亮颜色不会造成复杂的半透明层次不过要注意表现效果是否匹配需求能改成Cutout的特效尽量改成Cutout比如碎裂飞溅的碎片用不透明遮罩代替半透明纹理大面积烟雾、雾气如果视觉允许可以用拉长粒子或者多层稀疏粒子的方式降低密集度一个比较实用的经验粒子系统的Renderer模块可以开启GPU Instancing让相同材质、相同Mesh的粒子在一条Draw Call里合批。这和overdraw无关但能减少CPU批次压力让GPU有更多余量去处理高层特效。4.3 场景与Shader半透明改不透明双面渲染三思而后行场景优化的核心思路尽量把Alpha Blend改成Alpha TestCutout或者干脆做成不透明。草地推荐用Cutout材质配一张边缘粗糙但视觉密度高的草地贴图。虽然Cutout在极近视角下会有锯齿但在手机屏幕尺寸上观感是可以接受的地表边缘淡出效果可以用Dithered Transparency抖动透明实现通过程序化噪声做Alpha Test图案视觉上近似半透明但本质上不进行混合计算自然就不产生overdraw水面如果不需要实时反射可以用静态法线贴图反射探针模拟避免半透明水面材质反复叠加玻璃、树脂这类材质能Cutout就Cutout不能Cutout就减少层数避免一个窗户五层玻璃五层混合这里要专门提醒一下“双面渲染”的问题。很多人为了省事把材质的Cull Mode设为Off让物体前后两面都渲染。但双面渲染意味着背面像素也要经过片元着色在某些视角下等于同一区域被画了两次。除非真的需要双面可见比如单面草片、薄片树叶否则不要轻易关闭Cull。Shader层面还能做的一招是给半透明材质加一个“近裁淡出”或“距离淡出”功能相机离远了自动切换到不透明版本或者直接裁剪掉细碎的透明层。4.4 渲染管线与后处理减少全屏Pass移动端项目现在用URP的越来越多了URP提供了几个能直接减轻overdraw负担的设置Depth Priming Mode设置为Auto。它可以复用深度信息部分像素不需要重新执行光照计算减少不必要的片元着色开销关闭不必要的Opaque Texture和Depth Texture。如果项目没有特殊需求这俩Pass会额外渲染一遍场景影响带宽和发热后处理特效严格控制数量和强度。Bloom在URP里默认就有它内部会做多次降采样和模糊虽然不是传统overdraw但同样消耗GPU带宽。手机上Bloom的强度开到0.8以上发热明显增加另外也可以注意一下相机的Clear Flags。如果场景里天空盒非常复杂而且主角在室内场景考虑把Clear Flags改成Solid Color。这样室内绝大部分区域就直接清背景色不需要渲染复杂天空盒overdraw热点也会少一点。Occlusion Culling遮挡剔除对overdraw的影响常常被忽略。虽然overdraw指的是像素层面的重复绘制但Occlusion Culling能减少进入渲染管线的物体数量很多被遮挡的东西直接不画了自然就降低了添加到同一像素区域的概率。尤其是城市、室内这类大量遮挡关系的场景开了Occlusion Culling之后overdraw热点往往能肉眼可见地减少。5. 一个主城界面从“烤手机”到“凉快”的完整复盘5.1 原状和性能数据项目背景是一个移动端MMOUnity 2021.3 LTSURP管线目标机型覆盖中低端安卓和iPhone 12前后。热点区域在主城界面玩家点击“背包”按钮后界面弹出同时角色展示区有一个360度旋转特效背包界面是一个全屏UI面板包含实时模糊背景、暗色遮罩、半透明底图、若干带阴影的按钮角色脚下使用了半透明草皮配了一个循环播放的火焰粒子特效屏幕右下角有一个定期飘过的公告横幅半透明背景优化前在真机上的数据GPU Render耗时在背包界面最高到16.8ms对应约60fps的理论预算16.7ms已经超了手机表面温度在10分钟测试后达到43度帧率波动明显从60直接掉到20左右Overdraw视图下屏幕中央和UI弹出区域是大片深红色5.2 优化动作和结果第一步处理UI层。把实时模糊背景和暗色遮罩合并成一张预烘焙贴图同时去掉按钮的DropShadow改用图集里预绘好的阴影。曾经7层全屏半透明叠加变成了3层。这一步过后overdraw视图里的深红区域明显减少GPU Render时间降了约4ms。第二步处理地表。草皮材质从Alpha Blend改成Dithered Transparency用噪声做边缘淡出。颜色表现上和原来差不多但overdraw没了不透明物体还能部分利用深度缓冲做Early-Z剔除。GPU时间又降了约2ms。第三步处理粒子。把角色脚底的火焰粒子从3个独立系统合并成1个粒子数量从12000个降到4000个。同时关闭了粒子的Collision检测关掉了Sub Emitter。公告横幅的过渡动画从全屏半透明改为局部淡入淡出不再覆盖整个屏幕。第四步调后处理。Bloom强度从1.2降到0.8关闭Vignette。这个对画面风格有一定影响但在手机上换取的热量降低非常明显。优化后的数据GPU Render耗时最高7.2ms稳定在60fps手机表面温度10分钟测试后39度比原来低了4度帧率稳定没有再出现大幅掉帧overdraw视图下只剩UI弹窗区域有少量浅黄色整体健康5.3 过程中的反思回过头看这个案例最大的感触是UI层的overdraw往往比3D层更隐蔽但收益也最大。因为UI是平面叠层几个全屏透明层一叠overdraw直接翻倍而且玩家对UI的感知度又没那么高——你不需要一个“磨砂玻璃背包”也能正常玩。另外就是不要迷信某个单一优化手段。这个案例里没有哪一项改动是“一步到位”的每一步都降2~4ms叠加起来才把管线从亚健康状态拉回稳定60帧。性能优化就该这样一点点抠积累到临界值就会质变。6. 容易踩的坑和我的排查习惯6.1 误区Overdraw越低越好不是。Overdraw降到0意味着场景里几乎没有半透明效果、没有UI装饰、没有任何特效游戏画面会很平、很干。正确的目标不是“零overdraw”而是“overdraw集中在观众注意力中心且总量可控”。比如UI弹窗区域的少量叠加是可以接受的全屏都红的片就没法接受了。6.2 误区Transparent改成Cutout就万事大吉CutoutAlpha Test确实不做颜色混合overdraw会低但它会引入锯齿。而且移动端早期GPU对Alpha Test支持不好可能打断Early-Z反而增加耗时。解决方案是尽量用Dithered Transparency这种柔和剔除方式或者配合Mipmap的Alpha处理让边缘过渡更平滑。这中间需要美术多调一下但性能收益值得。6.3 坑双面渲染的隐形杀手有些材质开了Cull Off后普通视角看不出问题但当你从斜上方看一面半透明玻璃时正面和背面都在混合等于overdraw翻倍。尤其是在半透明材质上开启双面渲染真的会出事。我的建议是能不开就不开确实需要双面的优先考虑用“正反面两个面片”替代而不是让从正面看时把背面的片元也算上去。6.4 坑只信编辑器不信真机桌面GPU的像素填充率高得离谱你要是在Editor里放一个全屏半透明粒子根本感觉不到卡。同一个效果拿到中低端手机上一跑直接掉到20帧。所以我一直建议性能问题不能在Editor里断案必须以真机数据为准。要么在项目里写一个热切换的Profile脚本要么养成定期真机性能审查的习惯。6.5 我自己的排查习惯随便聊聊我现在的流程。改完任何一个渲染相关的东西先开Overdraw视图扫一遍场景和UI。不是盯着数字看而是看“红色区域有没有发生在我没想到的地方”。如果没有说明overdraw控制得不错如果有先分层定位再决定是合并层、裁剪区域还是改混合模式。真机测试的记录习惯是固定三个场景登录、主城、战斗每个场景跑3分钟记录GPU时间和电池温度。不用每次都测但每次优化后测一轮累积下来就能看到每次改动对全局的影响。数据积累得多了你还会发现很多“反直觉”的东西——比如有时候明明降低了overdraw帧率却没什么变化说明瓶颈根本不在像素填充率而在CPU端的Draw Call或者其他环节。这种判断力是需要靠数据喂出来的。希望这篇能帮你少走几条弯路早日把手机从暖手宝变回正常状态。
分享:

看完干货,该让你的企业上线了

免费需求沟通 · 48 小时内出具建站方案 · 河南本地可上门