☰
Unity渲染性能优化全攻略:定位瓶颈、合批与Shader减负
2026/10/22 18:46:32 网站建设 项目流程

做Unity项目,只要开始优化性能,图形渲染这块永远是绕不开的大头。我见过的很多团队,项目一卡就急着关阴影、降分辨率、砍后处理,调了一通发现帧率没怎么涨,问题反而越改越乱。渲染性能优化不是靠感觉猜的,它有一套完整的定位、拆解、取舍的思路。这篇文章就把我在实际项目里做Unity渲染优化的完整思路写出来,从怎么定位瓶颈,到Draw Call、光照烘焙、Shader减负、管线选型,每一步都有直接的实操方法和参数参考。

不管你是刚开始接触Unity优化的新手,还是已经被移动端项目性能问题折磨了一阵的开发者,这篇文章都值得认真读一遍。我不会讲太多虚的渲染理论,更多的是可以直接抄到项目里的做法、参数和踩过坑之后的经验。看完你会明白,渲染优化为什么不是单纯“调设置”,而是一整套有逻辑、有优先级的技术决策。

1. 优化前先定位:用数据找出真正的瓶颈

1.1 别凭感觉,先把Profiler跑起来

我遇到过太多“优化大师”,连Profiler都不打开就开始改代码。渲染性能优化最忌讳的就是瞎猜。不管是卡顿掉帧还是发热发烫,都必须先找到瓶颈在CPU还是在GPU,然后才谈得上怎么优化。

Unity自带的Profiler就是第一手工具。打开Window -> Analysis -> Profiler,连上设备跑一会儿场景,重点看CPU Usage模块里Renderer这行。如果Render线程(Render Thread)的耗时占比很高,同时Main Thread不忙,那问题大概率在GPU侧或者Draw Call提交上。反过来,如果Main Thread里逻辑代码、动画、物理占了大部分时间,那就先别急着优化渲染,把逻辑层的问题解决再说。

还有一个容易忽略的点:真机测试和编辑器里跑出来的数据完全不是一回事。编辑器里CPU和GPU的调度方式跟移动端差异非常大,很多问题在编辑器里根本看不出来,到真机上才爆发。所以务必要在目标设备上跑Profiler,而且最好用Release版本的数据来评估。用Development Build跑出来的性能数据有很多Editor开销混在里面,会把你带偏。

1.2 几个必须看懂的关键指标

打开Game视图右上角的Stats面板,有几个指标值得重点关注:Batches(批次数)、Tris(三角形数量)、Verts(顶点数量)、SetPass Calls(着色器切换次数)。

先说Batches。移动端一个非常硬的经验值是:中低端机上一个Batch消耗大概在0.1ms到0.5ms的CPU时间,具体看驱动和机型。同屏如果跑出三百个Batch以上,CPU大概率就要开始吃力了。SetPass Calls是比Batches更细的指标,它代表的是着色器切换的次数,这个数字过高往往说明场景里材质种类太多或者排序太乱,后文会专门聊这块。

然后是Tris和Verts。很多人觉得三角形数量越少越好,其实在移动端,顶点的处理开销往往比三角形更敏感,尤其是那些带蒙皮动画、法线、多UV、顶点色等一大堆顶点属性的模型。你用的是一个面数很高的雕塑模型,又加了高精度法线贴图,光这部分的顶点处理开销就够你喝一壶的。

提示:先用Profiler录一段实际Gameplay数据,再开Frame Debugger逐帧看渲染状态,才能定位到具体是哪一步消耗了时间。不要凭“感觉这场景肯定会卡”去盲目优化。

2. Draw Call与批处理:把CPU从渲染提交中解放出来

2.1 为什么Draw Call会卡性能

很多新人不太理解Draw Call为什么贵。简单说,每一次Draw Call,CPU都要把各种渲染状态(材质、贴图绑定、Shader参数、顶点数据指针)一股脑地提交给GPU,中间还要经过图形API和驱动层的校验,这个开销在移动端尤其明显。一千个小型物体各提交一次,跟一万个物体合并成几十个批次提交,差距是数量级的——主要就是CPU的负担完全不一样。

在Unity里,减少Draw Call的核心手段是批处理。但批处理不是无损的,它有适用范围,也有副作用,关键是选对场景。

2.2 静态批处理与动态批处理的适用边界

静态批处理对标记为Batching Static的物体有效,原理是在构建时把共享材质的网格合并成一个更大的网格。它最大的问题是会增加内存占用和启动加载时间,因为合并后的网格数据会被复制一份额外保存。场景里的建筑、地形装饰、路灯这类完全不动的物体,适合用静态批处理;但凡是运行时要移动、旋转、缩放的物体,就别勾Static标志了,否则不仅没用,还会白白增加内存。

动态批处理限制更多。它要求共享同一个材质,顶点数少(Unity官方建议小于300个顶点),而且缩放值要一致,否则Unity会判定为不可合并。实际操作中你会发现,动态批处理很容易因为一两个条件不满足而失效,效果不稳定。我的经验是:不要指望动态批处理解决大场景问题,它更适合处理小范围内零散的小物件。

2.3 GPU Instancing才是移动端的合批主力

一两年前我接手一个开放性场景项目,场景里布置了大量相同类型的树木、石头和路灯。最初实现的时候是每种Prefab直接摆了一堆,Profiler一看Batches直接冲上八百多,中端机上一跑,帧率肉眼可见地往下掉。后来改成GPU Instancing,同材质同网格的物体被合并成一个批次渲染,Batches瞬间降到几十。

GPU Instancing的使用门槛不高:给Shader开启Instancing变体,把场景里重复的物体共用同一个材质(可以用MaterialPropertyBlock来修改颜色、缩放等差异),然后Unity会自动合批。关键是,不能用标准的材质属性块逐个创建新的材质实例,那样会破坏合批。

批处理方式适用场景核心限制备注
静态批处理完全静止的物体内存和加载时间增加大世界场景慎用
动态批处理可移动的小物件顶点数少、缩放一致、材质相同条件苛刻,容易失效
GPU Instancing大量相同物体网格一致、材质一致移动端强烈推荐
SRP BatcherURP下几乎所有使用SRP的材质需使用兼容Shader降低SetPass调用,收益明显

2.4 SRP Batcher:现代Unity管线的合批红利

如果你把项目迁移到了URP,那SRP Batcher是必须利用起来的功能。它跟传统的批处理不一样,不是合网格,而是复用着色器状态,让多个物体在切换材质属性时不用重新绑定整套Shader状态,大幅降低CPU的SetPass调用开销。

SRP Batcher生效有条件:Shader必须是URP兼容的(包括你自己的自定义Shader),并且材质不要随意用实例化创建新材质。面板上的SRP Batcher开关打开后,Profiler里能看到SRP Batcher的合批数量。我用过的项目里,从内置管线迁到URP并启用SRP Batcher后,渲染线程的CPU耗时普遍能降30%以上,这个收益非常可观。

注意:SRP Batcher不是万能的,它主要降低的是SetPass开销,如果瓶颈在GPU的Fragment负载或者Overdraw上,单纯开SRP Batcher并不能挽救帧率。

3. 光照体系:别让实时灯光成为帧率杀手

3.1 实时光照的计算代价比想象中高

移动端的GPU算力本来就有限,一盏实时Directional Light还能接受,但如果场景里有几盏实时的点光源或者聚光灯,每次渲染都要为每个受影响的物体额外计算光照,片元着色器的负担就会成倍增加。加上实时阴影,渲染每个阴影投射物都要多跑一遍深度Pass,开销又翻一层。

我记得有个项目最初为了表现效果,在一个室内场景里摆了三盏实时点光源作为灯光点缀,手机一跑直接掉到20帧,而且发热严重。后面把两盏点光源改成烘焙光照贴图,只保留一盏实时主光源,帧率一下就回到55以上。这就是典型的“渲染开销全花在了不该花的地方”。

3.2 烘焙光照:把静态物体的光照计算预先做好

对于所有不动的静态物体,都应该让它走全局光照烘焙。把场景里的关键物体设为静态,勾上Contribute GI,然后在Window -> Rendering -> Lighting面板里切到Baked模式,配置好Lightmap参数,点击Generate Lighting生成光照贴图。

烘焙完成之后,静态物体就直接从光照贴图里读取明暗信息,运行时不参与实时光照计算,GPU的负担轻很多。这一步对移动端项目来说几乎是不做不行的,理论上只要你的场景不是完全动态变化的竞技场类型,就应该走烘焙。

动态物体可以搭配Light Probe(光照探针)获得烘焙光照的间接光。Light Probe的布置密度要合理,不需要太密,在物体运动路线上隔几米放一个即可。探针太多了,运行时插值计算也会增加开销。

3.3 阴影设置与反射探针的合理取舍

实时阴影在移动端是奢侈品,建议只在主光源上开实时阴影,并且把Shadow Distance调到一个合理的范围。比如室内场景,阴影距离控制在15米到20米就够用了,远处物体没有实时阴影玩家其实感知不到。Shadow Distance越大,越多的投射物要进入阴影Pass,开销直线上升。

反射探针同样要慎用。Realtime类型的探针如果你没有特殊需求,建议改成Baked模式。运行时每帧刷新反射探针的开销大得离谱,很多时候只是为了墙上挂的那块“反光地板”效果。除此之外,Reflection Probe的贴图分辨率也值得控制,128~256的立方体贴图在大多数移动端场景下完全够用。

4. Shader与Overdraw:从GPU角度做减法

4.1 简单Shader省下的开销远超想象

Shader是渲染优化的重灾区。我见过很多项目直接拿PC平台的复杂Shader往手机上跑:多层贴图采样、一堆Custom Lighting函数、多个Pass,结果就是GPU负载居高不下。移动端Shader的第一原则是:够用就行。

能一步计算的光照就别写复杂的物理光照模型,能用half精度就别用float。Unity Shader中half精度对移动端友好,能降低GPU寄存器和带宽的压力。自定义Shader里如果遇到多层循环或大量数学运算,一定要想想能不能预计算,或者拆到顶点着色器里去算。

用Surface Shader要特别注意变体数量。Shader变体一多,不仅增加打包体积,还会让运行时着色器的编译与切换变得很慢。我一般会在打包前通过Shader Variant Collector仔细筛选用到的变体,把用不到的keyword全部去掉,包体体积和加载时间都有明显改善。

4.2 Overdraw:看不见的填充率黑洞

Overdraw的意思是同一个屏幕像素被反复绘制多次。最常见的情况就是大量的透明粒子特效叠加,或者是半透明UI的前后层叠。Unity的Scene视图里可以切到Overdraw模式,颜色越亮越红的地方,说明填充率越吃紧。

那些把整个屏幕铺满的粒子光圈、全屏光晕、大范围烟雾特效,都是Overdraw大户。移动GPU的填充率是有限的,你的分辨率越高,填充率压力越大(所以降低分辨率能明显提升帧率,这点后文会讲)。

处理Overdraw的常规手段:粒子发射数量在不影响观感的前提下砍一半;特效贴图缩小到实际显示尺寸能接受的范围,让更多粒子走Additive混合;减少重叠的大范围特效。我优化过的一个战斗场景,光是把几个全屏粒子技能的特效尺寸缩小35%,中端机的帧率就提升了8帧上下。

4.3 纹理压缩与Mipmap的细节控制

纹理这块的坑更加隐蔽。移动端纹理格式一定要用ASTC或者ETC2,不能用RGBA32这种原始格式直接打包,不然不仅包体大,GPU的带宽占用也很可怕。一张1024x1024的RGBA32纹理在移动端的带宽消耗,比一张同样尺寸的ASTC 6x6纹理高出好几倍。

另一个容易被忽略的是Mipmap。放在地面上的大纹理如果没有开Mipmap,当摄像机远离时,GPU仍然会加载完整分辨率纹理做采样,采样缓存效率很低,容易造成卡顿。打开Mipmap后,远处物体自动采样较低精度的mip等级,带宽占用显著下降。代价是纹理内存会增加约1/3,但换来的性能收益在多数情况下都值得。不过,如果纹理是UI贴图且在屏幕上的大小基本不变,就别开Mipmap,开了纯属浪费内存。

5. 遮挡剔除与大世界调度:让不该渲染的物体彻底消失

5.1 遮挡剔除的正确使用方式

很多场景卡是因为摄像机根本没看到的物体也在被渲染。视锥剔除(Frustum Culling)只剔除视野外的物体,但相机后面、被墙壁挡住的物体它不管。遮挡剔除(Occlusion Culling)就是为了解决这类问题存在的。

在Unity里,把静态场景物体标记为Occluder Static(遮挡物)和Occludee Static(被遮挡物),然后在Occlusion Culling窗口配置参数并烘焙。运行后,被墙挡住的小物件就不会进渲染队列了,帧数提升非常直观。

但遮挡剔除有它的局限。第一,它只对静态物体有最佳效果,动态角色和物件可能会逃过剔除。第二,如果Culling区域没设置对,或者场景太大,烘焙数据可能会很不精确。我踩过的一个坑是:把整个大地图当成一个巨大的Occlusion Area,结果烘焙了半天,Open Still和Closed Still参数没配好,反而有些物体在应该显示的地方被剔除,画面穿帮。后来把区域拆成多个子区域,逐个烘焙才正常。

5.2 LOD:用距离换性能的经典思路

LOD(Level of Detail)是在距离摄像机很远时,切换成低精度模型,让远处的物体不必渲染全部细节。移动端项目里,静态建筑、石头、树木、路灯这类重复摆放的物件,都应该做LOD Group。

LOD切换距离要结合场景尺度调整。比如一个角色模型,LOD 0(高模)在10米内用,LOD 1(中模)在10到25米用,LOD 2(低模)在25米以外用。切换方式我建议用硬切换而不是Cross Fade,Cross Fade虽然视觉过渡更平滑,但会在过渡期增加额外渲染开销,移动端能不用就不用。

在制作LOD模型的时候,经常有美术同学直接把高模减面后当低模用,减得太狠会导致远处模型轮廓严重变形。我一般会要求LOD 2至少保留外形轮廓的关键顶点数量,否则画质观感牺牲太大。

5.3 大世界的加载调度

大世界项目里,就算LOD和遮挡剔除都做了,如果加载策略不好,一样会卡。建议用Visible Only或者Streaming加载的方式,只加载玩家附近的区块。Unity有CullingGroup可以用来做动态的不可见物体管理,比反复Active/Deactive Gameobject要高效得多。

CullingGroup的核心用法是给一系列物体注册BoundingSphere,然后监听可见性变化。这样摄像机看不到的区域,物体可以直接跳过各种逻辑更新,不仅仅是渲染,连动画、寻路这些都能省下来。前期设计场景时就把区块划分好,比后期再去拆切要合算得多。

6. 渲染管线的选型:URP对性能的影响

6.1 为什么移动端项目建议直接上URP

新的Unity项目我一般建议直接选URP(Universal Render Pipeline),除非有特殊的美术诉求必须用HDRP。URP为移动端做了大量优化:单Pass前向渲染、SRP Batcher、更可控的后处理管线、更明确的渲染Pass顺序。同样是渲染一个场景,URP在CPU侧的提交开销通常比内置渲染管线低不少。

而且URP的Asset设置是全局的,你可以在不同设备档位上配置不同的渲染参数:中端机关掉MSAA,用低分辨率的后处理;高端机保留抗锯齿和多层阴影。按平台动态切换质量档位,比在代码里临时改各种渲染参数要稳妥得多。

6.2 迁移到URP的注意事项

把旧项目迁到URP不是简单切个管线就完事。内置管线的标准Shader和URP的Lit/SimpleLit不是完全兼容的,材质、Shader都要重新检查。旧项目里的后处理特效(比如OnRenderImage那套)在URP下不能直接用,需要改成URP的Volume系统。

还有一个坑:迁移后如果出现画面变暗、颜色不一致,多半是光照强度单位和伽马/线性色彩空间设置不对。Unity从内置管线转到URP时,光照强度、后期曝光参数都要按URP的规则重新调。项目里有两三个美术在迁移后因为画面变暗来回返工,最后统一了项目里的Color Space和Light Unit才解决。

提示:迁移到URP后,建议立刻复查Profiler里的SRP Batcher是否生效,再逐个检查场景中的材质是否都走了URP兼容Shader,这一步漏掉,等于换了个管线但没吃到性能红利。

7. 常见问题与排查技巧实录

7.1 帧率低但Batches不高,问题出在哪

有时候看Stats面板,Batches只有几十,可帧率还是上不去。这种情况大概率是GPU Bound,也就是GPU的处理速度成了瓶颈。你该去查:是不是Shader太复杂?是不是Overdraw太高?是不是纹理采样带宽太大?是不是用了高分辨率还没有开Mipmap?

这种场景,降低分辨率往往立竿见影。调低渲染分辨率到屏幕原始分辨率的75%甚至更低,配合画质的轻微模糊,移动端玩家的感受影响很小,但帧率能恢复不少。原神这类大作移动端也都是靠动态分辨率来保帧率的——虽然我们不能这么粗暴,但思路是一样的。

7.2 为什么阴影在手机上会有闪烁或变暗

实时阴影在部分机型上异常,最常见的原因是Shadow Bias设置不合适。Bias值太小,阴影会因为精度不够产生波浪纹;Bias太大,阴影会偏暗、变模糊甚至消失。各品牌机型深度精度不一致,这种问题只要兼容多机型就难免。处理方式是把Shadow Distance适当缩短,同时调整全局的Shadow Bias,让阴影在大部分设备上看起来正常即可。

7.3 批处理为什么莫名失效

很多同学跑来问,为什么明明都是同一个Prefab,GPU Instancing却没起作用?我检查过的案例里,一大半是因为循环里创建了新的材质实例。就是代码里用了renderer.material = xxx或renderer.material.color = xxx,Unity会自动实例化一份新材质,合批必然失败。这种应该用MaterialPropertyBlock来处理,它能修改属性而不会破坏合批。

另一个常见问题是,模型开启了Read/Write Enabled顶点数据再改了顶点,Unity为了支持脚本访问,反而可能导致合批失效。凡是模型src导入设置里可读可写没有明确需求的,都关掉。

7.4 发热严重但没明显掉帧

发热说明设备的负载持续偏高,只是没到降频临界点而已。这种情况要提前预判:先看整体渲染负载是不是一直贴着极限跑,如果是,就必须留出余量。我习惯的做法是控制GPU负载不超过85%——给系统留一点空间,否则玩十分钟后设备开始降频,帧率一样会崩。

从这个角度说,优化不是调到“刚好流畅”就行了,而是要调到“长时间稳定流畅”。

最后的实操体会

优化Unity渲染性能这件事,做得越久越发现它不是一次性的工作,而是持续迭代的过程。我自己的习惯是,每改一个渲染相关配置,就在Profiler上对比前后两帧的耗时分布,并把改动记录下来。否则几轮调整下来,谁也说不清到底是哪一步让性能变好的。

最后一个建议,做完优化之后一定要做多设备回归测试,不要只看编辑器数据,也不要只看自己的高端测试机。渲染性能问题在低端机上的暴露程度远超想象,你把中端机稳住了,高端机自然更稳。图形渲染这块没有银弹,但只要你一直坚持先定位数据再动手改,而不是凭感觉乱调,项目的帧率就不会让你太难看。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询