Unity移动设备阴影方案浅尝做移动端渲染的兄弟应该都有同感阴影这玩意儿往深了做能写几篇论文往浅了说就是性能和效果之间反复横跳。我一个做中轻度3D项目的最近刚好把移动端的阴影方案从头到尾捋了一遍从Unity自带方案到自定义优化踩了不少坑也总结了一套在画质和帧率之间相对平衡的做法。这篇东西不聊太玄乎的底层算法就是实打实地讲讲我在移动设备上落地阴影的选型思路、参数调优和排坑记录给同样被阴影折腾的同行一个参考。1. 方案选型之前的三个核心问题1.1 先搞清楚你的项目到底需要什么样的阴影接手阴影方案之前我建议你先别急着往工程里拖各种组件而是回答三个问题。第一你的游戏视角是什么类型。第三人称越肩视角、俯视角MOBA、还是第一人称射击对阴影的需求完全是两码事。第三人人称视角下角色身上的自阴影是质感的关键阴影分辨率不够会显得角色“浮”在地面上俯视角游戏对阴影精度的敏感度相对低但对阴影范围和稳定性要求更高因为整个屏幕都是地面。第二你的场景是室内为主还是室外为主。室内场景光源数量少、方向固定烘焙阴影几乎是唯一正确解室外场景有日夜变化或者动态光源那就必须上实时阴影但实时阴影在移动端的开销你得心里有数。第三你的目标机型性能档位是什么。这一步最关键直接决定你敢不敢开实时阴影、敢开多少分辨率。我见过太多项目美术在PC上把阴影调得美美的一到骁龙660这种中低端机上直接掉到20帧然后回头骂引擎垃圾——其实问题是方案选型一开始就没考虑性能预算。我自己的经验是在项目立项阶段就定好阴影的“分级策略”。高端机骁龙8系、麒麟9系、A14以上开全质量实时阴影中端机骁龙6系7系、麒麟8系开中等距离实时阴影配合烘焙低端机干脆只保留烘焙阴影和一些伪阴影技巧。这样一来不同档位的体验差距可以控制在“画质差异”而不是“帧率崩坏”的层面。1.2 移动端实时阴影的硬成本账不亲自算一遍心里没底很多刚上手移动端阴影的开发者会忽略一个基础事实实时阴影本质上是一趟额外的深度渲染pass只不过这次是从光源视角去渲染。这意味着你的场景会多画一遍如果你的主相机Draw Call是100那么阴影相机这一趟几乎同样要付出接近100的代价而且是完全额外的开销。算笔粗账。一个中等复杂的室外场景主相机渲染大概需要6到8ms的GPU时间。如果打开实时阴影光源视角的深度pass通常要额外吃掉2到4ms加上阴影贴图采样和软阴影滤波的消耗总共可能要到10到12ms。对60帧目标来说每帧预算只有16.6ms阴影就占了三分之一这还没算CPU端的遮挡剔除、阴影相机设置等开销。所以在移动端做实时阴影口号永远是“能用就行别贪”。贪的结果就是帧率雪崩这个坑我见过太多次了。1.3 别一上来就堆高分辨率理解阴影贴图的分辨率经济学很多美术同学上来就把阴影分辨率拉到2048甚至3072理由是PC上这么干没问题。但在移动端阴影贴图分辨率每翻一倍GPU带宽开销大约是平方级增长而且对显存带宽本来就紧张的移动GPU来说这是致命的。阴影贴图分辨率的合理选择逻辑不是“越大越好”而是“像素密度刚好满足表现需求”。通俗地讲你需要保证的是屏幕上阴影边缘最密集的区域每个屏幕像素大概对应一到两个阴影贴图纹素。超出这个比例提升分辨率肉眼几乎无感但性能代价是实打实的。我的经验值是这样的第三人称游戏角色近距离特写多阴影贴图用1024或1536就够俯视角游戏地面是视觉主体阴影覆盖范围大用2048才有保障如果是大世界或者超远视距那就别指望一张阴影贴图搞定切分级联阴影才是正路这个下面细说。2. 移动端Unity阴影方案的核心拼图光源类型与阴影映射2.1 平行光、点光源、聚光灯在移动端的表现差异Unity里常见的三种光源类型在移动端的阴影实现思路完全不同。平行光是室外场景的主角方向固定、覆盖范围大用的是正交投影的阴影贴图。它的优点是阴影质量在整张贴图范围内比较均匀缺点是一张贴图覆盖太大区域时精度不够所以需要级联阴影CSM来分级。移动端一般开两级级联就差不多了再多的话Draw Call和GPU开销会明显上升。点光源和聚光灯在移动端实时阴影的使用场景其实不多主要原因是它们的阴影贴图需要用透视投影渲染而且点光源要渲染6个面聚光灯至少1个面在移动端这个成本太高。我的建议是动态的点光源和聚光灯在移动端不要开阴影用烘焙光照或者Light Probe来补足空间感视觉效果差距很小性能差距巨大。如果你非要在移动端给点光源开阴影那就做好心理准备——我见过有人在移动端开了点光源阴影帧率直接掉一半后来改成Light Probe加烘焙AO画面反而更干净了。2.2 阴影贴图的核心输出环节从光源视角渲染深度再从主相机视角采样Unity的实时阴影流程可以分成两个环节理解这个流程对排查阴影问题特别有帮助。第一个环节是阴影映射Shadow Mapping生成阶段。Unity从光源的视角渲染一张深度图记录场景中离光源最近的表面深度。这张图就是阴影贴图。第二个环节是阴影采样阶段。主相机渲染每个像素时把该像素的世界坐标变换回光源空间比较深度值和阴影贴图中记录的深度值深度更大就说明被遮挡了该像素落在阴影中。这两个环节都会产生额外的Draw Call和GPU开销。生成阶段的Draw Call跟你的场景复杂度直接相关采样阶段的开销则跟屏幕分辨率相关。这也是为什么阴影开关会引起帧率大幅波动的根本原因。实操中我推荐单独监控这两个阶段的开销。Unity的Frame Debugger可以清晰看到ShadowMap的渲染Pass配合Profiler里的GPU数据可以判断究竟是生成阶段贵还是采样阶段贵从而有针对性地优化。这个排查思路在第三节里会继续展开。2.3 把支持实时阴影当成默认渲染管线的底线能力这里提一个比较实际的建议如果你不是特别需要URP或者HDRP的高级渲染特性移动端项目其实可以先用Unity内置渲染管线Built-in Render Pipeline把阴影方案跑通再说。内置渲染管线对阴影的支持非常成熟Shadow Distance、级联数、阴影贴图分辨率这些参数都有现成的接口和UI可以调出问题的概率低。URP的阴影方案也不错而且支持阴影距离的逐质量分级但它的阴影采样和滤波质量受管线版本影响比较大早期URP版本甚至没有软阴影后来更新了才加上。从稳定性角度考虑我建议中低端机优先保证内置管线的阴影方案稳定可用URP/HDRP的阴影方案作为高画质档位的选项来配置。这样即便你的项目后续要升级渲染管线阴影相关的底层逻辑也不需要推翻重来。3. 实操落地一套可复用的移动端阴影配置方案3.1 确定级联阴影的层数和距离分布级联阴影Cascaded Shadow Maps是我在移动端室外场景的首选方案。它的核心思路是把相机视锥体按距离切分成几段每一段单独生成一张阴影贴图。离相机近的区域用密度更高的贴图远处用密度低的这样可以在有限的总贴图大小内尽量保证近距离阴影的精锐度。在Unity里Quality Settings面板中可以调整Shadow Cascades的数量。移动端我建议设置2级级联部分高端机可以试3级。级联数不是越多越好因为每一级级联都要单独渲染一次阴影贴图意味着光源的shadow pass会多画一遍Draw Call会成倍增加。级联距离分布我常用的比例是近端30%、远端70%或者近端40%、远端60%具体看你游戏里角色和镜头的距离关系。原则是角色经常活动的区域尽量靠近相机这段距离内要尽量压实阴影贴图精度。如果角色走两步就出阴影范围说明Shadow Distance设太短了。3.2 精准控制阴影距离不是越远越好是够用就好Shadow Distance这个参数在移动端是最值得反复调校的。它直接决定阴影贴图要覆盖多远的世界范围范围越大同分辨率下阴影精度越低而且超出距离的场景物体直接不参与阴影计算等于白省性能。我的调法是这样先按游戏的核心玩法场景找一块相对开阔的地方把相机推到最常用的视角位置看角色周围多大的范围是玩家肉眼一定会关注的。这个范围就是Shadow Distance的基准值。第三人称动作游戏我常用20到40米俯视角MOBA因为屏幕全部是地面距离可以放到35到50米。超出这个范围的阴影玩家根本注意不到开了反而浪费。注意一个问题Shadow Distance设置得太小远处的物体虽然没阴影了但在特定角度下会出现“阴影突然消失”的边界线特别难看。我一般配合阴影渐隐来处理也就是让阴影在距离边界处逐渐变淡而不是突然消失。这个效果可以通过自定义Shader或者在材质里加一个基于世界距离的混合来实现。3.3 阴影贴图分辨率、偏移量和抗锯齿滤波的推荐配方直接给一套我验证过比较稳的组合适合第三人称中轻度项目在中等偏高端移动设备上运行阴影贴图分辨率1536或2048根据场景复杂度调整别超过2048级联数2级Shadow Distance30到40米Normal Bias0.5到1.0过大阴影会“瘦”过小会出现阴影痤疮Bias深度偏移0.05到0.1用于消除自阴影的波纹状痤疮软阴影PCF 3x3或者PCF 5x5移动端别开更高这里特别提一下Bias和Normal Bias的区别。Bias是沿深度方向偏移解决的是阴影贴图本身精度不足导致的深度比较误差Normal Bias是沿法线方向把采样点往表面外推一点解决的是倾斜表面上的自阴影痤疮。两者配合使用效果最好但设置过大都会导致阴影和物体分离的“悬浮感”需要微调到刚好消除瑕疵为止。软阴影方面我强烈建议在移动端用PCF滤波而不是PCSS百分比接触阴影。PCSS效果确实更真实但计算量在移动端扛不住哪怕高端机上我也只把它作为可选项。PCF 3x3在移动端表现已经很不错了阴影边缘的锯齿肉眼几乎看不见PCF 5x5更细腻但GPU开销更大。3.4 用ShadowMask或烘焙阴影来兜底远处和静态场景实时阴影跑起来之后你会发现一个现象远处的阴影要么没有要么质量很差。这时候就需要用ShadowMask或者烘焙阴影来兜底。Unity的ShadowMask方案能把场景中静态物体的阴影预计算成光照贴图动态物体还是走实时阴影。在移动端我的用法是场景中所有的静态物体建筑、地形、道具的阴影直接走ShadowMask只有动态物体角色、载具走实时阴影。这样既保证了核心交互物体的阴影质量又大幅减少了实时阴影的工作量。Be carefulShadowMask模式下动态物体和静态物体的阴影之间可能在交界处出现“软硬不一致”的观感。我的解决习惯是让动态物体的实时阴影采样参数和ShadowMask的软阴影风格尽量接近比如都用PCF 3x3或同等级的模糊处理这样过渡起来就协调多了。如果项目没有用ShadowMask的需求退一步纯粹用烘焙光照Baked Light也是可以的。对纯静态场景来说烘焙阴影质量极高且零运行时开销前提是你的光源和物体都不动。很多室内场景、休闲游戏场景烘焙阴影比实时阴影香得多。3.5 面向低端机的伪阴影方案Contact Shadow和面片阴影阴影方案本质上是一个“性能换观感”的取舍。对于实在跑不动实时阴影的低端设备我一般会用两个“伪阴影”技巧来兜底。第一个是Contact Shadow也就是接触阴影。它不是全局真实阴影而是通过在屏幕空间做深度检测在物体接触面附近生成一圈局部暗部。这个方案开销很小且效果非常“作弊”——角色脚底和地面接触的地方有了一圈自然过渡的暗色立刻摆脱了悬浮感。Unity Asset Store上有一些现成的Contact Shadow后处理或者Shader实现移动端可以直接用。第二个是传统艺能面片阴影Blob Shadow。在角色脚下放一个带半透明圆形贴图的Plane或者Quad跟随角色移动。贴图可以是软边圆颜色是深灰至透明的渐变。玩家基本不会注意到这是伪阴影但画面观感提升非常明显。低端机游戏或者一些MOBA类游戏的角色脚下用的几乎都是这个方案。这两个伪阴影方案的成本都是几Draw Call级别的和实时阴影动辄几十上百的额外Draw Call比起来简直是九牛一毛。遇到性能瓶颈时把它们作为降级方案非常实用。4. 阴影参数调优的排查顺序和常见问题速查4.1 从Profiler出发的排查顺序而不是瞎猜阴影相关的性能问题我排查的顺序是固定的这个顺序帮我解决过大量莫名其妙的掉帧问题。第一步先看Profiler的Rendering模块确认掉帧的瓶颈是不是在ShadowMap的渲染Pass。如果在Frame Debugger里能看到多个光源的shadow pass都在跑说明你场景里同时存在多个开启了实时阴影的光源。移动端这是大忌。解决方案是全场景只保留一个主平行光可开实时阴影其他光源一律去掉Shadow Mode或者改静态烘焙。第二步看Shadow Distance有没有设太长。一个常见的偷懒是把Shadow Distance拉到500米意思一下结果每帧都要渲染极大范围的阴影贴图GPU带宽和Draw Call一起爆炸。把这个参数往回收掉帧立刻缓解。第三步看阴影贴图分辨率是不是超标。2048以上在移动端是奢侈配置如果配合2级级联GPU开销会非常夸张。试着降到1024或1536画质差异可能没你想的那么大。第四步看看场景里有没有大面积的透明物体或者粒子系统在阴影贴图渲染阶段被重复渲染。透明物体和粒子系统在Unity里默认是可以投射阴影的但那其实是在额外消耗性能。把它们Shadow Mode调成Off也能省一笔开销。4.2 几个高频阴影Bug的快速修复阴影跑起来之后Bug才是真正消耗时间的东西。这里列几个我高频踩坑的问题和对应解法。阴影痤疮Shadow Acne。现象是阴影区域出现密密麻麻的黑色小噪点原因是阴影贴图精度不足或者Bias设置太小。解法增加Bias和Normal Bias的值或者稍微调高阴影贴图分辨率。注意调整幅度别太大否则阴影会整体偏移导致物体和阴影错位。阴影漏光Shadow Leaking。现象是墙角、门缝等位置本应被阴影遮挡的区域出现了漏光。原因是Bias设置过大或者阴影贴图分辨率过低导致深度不连续。解法把Bias调小一点同时适当提高阴影贴图分辨率。如果还是漏试试把级联数从1级升到2级效果往往立竿见影。阴影边缘锯齿严重。这是移动端最常见的画质痛点。解法按性价比排序升级软阴影滤波PCF 3x3升到5x5、适当提升阴影贴图分辨率、调整Shadow Distance让阴影贴图覆盖范围更精准。这三板斧下来锯齿基本肉眼不可见。阴影闪烁或跳动。原因一般是动态物体在阴影贴图边缘抖动或者阴影贴图分辨率在运行时被动态调整。解法锁定阴影贴图分辨率不要用动态分辨率缩放去影响阴影贴图给Cascades设固定的距离比例避免阴影贴图随相机移动实时更新位置时因数值精度问题产生抖动。4.3 为什么你的中低端机还是发烫掉帧一个被忽略的带宽陷阱高段位的开发者会发现有些时候Profiler上显示的GPU时间不高但手机上就是热得快、掉帧严重。这种情况十有八九是显存带宽超标了。阴影贴图在移动端的显存带宽消耗非常大尤其是在阴影距离开得比较大、场景又比较碎的情况下。阴影贴图在帧内的读写频率极高带宽占用会显著影响GPU的持续温度。一个我常用的小技巧是在Quality设置里为不同档位设置不同的Shadow Resolution和Shadow Distance中低端机的Shadow Distance直接减半能明显缓解发热和降频问题。另外要注意不要让ShadowMask贴图和实时阴影贴图同时占用大量显存。如果一个场景里既烘焙了很大的ShadowMask又开了很大的实时阴影距离显存压力会很高。此时可以把ShadowMask的分辨率调低或者限制实时阴影距离二选一保住一个就不会出大问题。4.4 阴影渐隐效果用一个Shader小技巧解决阴影弹出最后分享一个提升观感的小技巧。当Shadow Distance较短、相机移动较快时阴影在远处突然出现又突然消失看起来特别廉价。解决这个“阴影弹出”的办法是加一层基于视距的透明度混合——离阴影边界越近的阴影越淡越靠近相机的阴影越实。实现思路不复杂。在自定义Shader里加一个参数接收相机到像素的世界距离然后在阴影采样阶段把这个距离映射到0到1的渐变系数乘到阴影强度上。或者更简单一点在后期处理阶段做一个基于深度的阴影淡入淡出效果。这个步骤不是必需的但加上之后整个场景的阴影过渡会自然很多。尤其是在地形复杂、相机视野切换频繁的项目里效果非常明显。我上次加完之后连美术同事都专门来问做了什么处理。5. 最后的实操体会阴影在移动端是做渲染优化绕不开的核心话题也是“画质性价比”最敏感的环节之一。我最大的感受是别追求PC端的效果在手机上复刻而是根据目标机型的性能预算做逆推反推每个档位能接受多少Draw Call、多少GPU时间再去决定开什么级别的阴影。我目前的固定打法是高端机主平行光用2级级联实时阴影、配合ShadowMask兜底静态场景、PCF3x3软阴影中端机实时阴影距离收一半、分辨率降到1024、其他保持不变低端机直接关闭实时阴影全面用烘焙阴影加Contact Shadow加面片阴影的组合。这套方案在多个项目里验证下来画质和帧率的平衡点抓得比较稳。如果你也在移动端折腾Unity阴影我的建议是先把基础配置跑通再做减法。先去开一个默认的实时阴影然后一步步把距离、分辨率、级联数往回收每收一步看一眼画质损失和帧率收益找到那个让画面观感和性能都满意的临界点。这个点只有你自己动手调过才真正知道它在哪里。