Unity 性能优化 之 实战场景简化(LOD策略 | 遮挡剔除 | 光影剔除 | 渲染流程的精简与优化 | Terrain地形优化 | 主光源级联阴影优化)
Unity 之 性能优化实战
- 场景总览
- 远景、中景、近景定义
- 远景简化
- 模型简化插件
- LOD策略
- 遮挡剔除
- 光影剔除
- 渲染流程的精简与优化
- Terrain地形优化
- 主光源级联阴影优化
场景总览
通过编辑器下的线框模式,可以大致观察到场景三角形与顶点分布情况;
- SRP渲染管线下,开启Rendering Debugger界面中的OverDraw项,可以观察到场景的OverDraw情况;
- SRP渲染管线下,将Rendering Debugger界面中的Map Overlays项设置为Main Light Shadow Map,可以检查当前级联阴影可视视距,该视距设置不当将导致Shadow Caster数量过大;
- SRP渲染管线下,将Rendering Debugger界面中Lighting Debug Mode项设置为Shadow Cascades项,并将Scene视角拉高,同样可以观察到级联阴影的级别范围。
远景、中景、近景定义
-
远景(Background): 远景是游戏场景中最远的元素,通常用来增加场景的氛围和深度感。远景通常包括天空、远处的山脉、建筑物等,它们通常不可交互,并且在游戏中很少发生变化。
-
中景(Middle Ground): 中景是游戏场景中介于远景和近景之间的元素。中景通常包括一些比较大的物体或者景观,如建筑、树木、岩石等。这些元素通常可以与玩家进行交互,例如可以作为掩体、跳跃点或者是游戏目标。
-
近景(Foreground): 近景是游戏场景中最接近玩家的元素,通常位于玩家角色前方。近景通常包括一些较小的物体,如草丛、石头、障碍物等,它们可以通过与玩家交互来增加游戏的挑战性或者提供障碍。
其中中景和近景可能会随着玩家漫游的过程而发生相互转换。
远景简化
对于玩家无法到达的远景,相比于使用真实模型,使用天空盒能够带来较大的性能优化。
天空盒中需要使用到已配置Cube类型Texture的Cubemap材质球,这里提供两个将真实模型转为Cubemap材质球的两种方式:
-
在场景中Disable远景之外的所有模型;在场景中添加Reflection Probe,同时将其Type设置为Baked;将Reflection Probe的Static项设置为Reflection Probe Static;在Windows-Rendering-Lighting界面中关闭LightMappingSettings的Auto Generate项;在Reflection Probe的Cubemap Capture Settings中设置符合需求的Clipping Planes,并适当提高Resolution和配置HDR项;在Reflection Probe点击Bake即可烘焙出Cube类型的Texture;创建材质球,将Shader类型选为Skybox/CubeMap,并将烘焙的贴图挂载于此。
-
与上述方式的区别主要在于Cube类型Texture的生成。该方式通过Camera.RenderToCubemap将使用摄像机的位置、清除标记和裁剪面距离渲染到CubeTexture上,从而在编辑器中“烘焙”场景的静态CubeTexture或将实时反光渲染到CubeRenderTexture上。需要注意的是,某些图形硬件不支持该功能,一般情况下更推荐使用上述ReflectionProbes的方法。
模型简化插件
Unity Mesh Simplifier,通过该插件可以在编译器中自动生成指定分级的简化模型,并自动配置LOD Group,同时该工具配置Api以进行批量处理。
Amplify Imposters,对模型基于的不同使用类型进行Imporster的生成。
LOD策略
一般来说,LOD分级最好不要超过5级。其中0级使用原始模型,1级使用轻度简化模型,2级为重度简化模型,3级为Imposter,4级为剔除。
- 对于近景、中景、远景均会出现的对象:
将其LOD分为4级,0级对应近景使用,1级对应中景使用,2级对应远景使用,3级则为剔除; - 对于近景、中景会出现的对象:
将其LOD分为3级,0级对应近景使用,1级对应中景使用,2级则为剔除; - 对于只在近景中会出现的对象:
将其LOD分为2级,0级对应近景使用,1级则为剔除。
即使是用明确的分级策略,当放置环境不同、物体尺寸不同、物品功能性不同时,各级距离阈值也会有所不同。故需要根据项目实际情况进行LOD配置调整。
对于对象的低LOD级别模型,可以考虑关闭Cast Shadows等配置,以在远景等环境下关闭不必要的视觉效果。
在项目ProjectSettings-Quality中,可以对不同运行平台配置Lod Bias和Maximum LOD Level,从而实现不同平台对LOD分级策略的差异。
遮挡剔除
Unity存在Occulusion Culling和Portal Culling两种遮挡剔除方法,本质均为在场景中预烘焙Occulusion数据,在Runtime中根据Camere视锥进行Culling。
- Occulusion Culling:
- 适配于可以遮挡、被遮挡的静态对象。
- 在Window-Rendering-OcculusionCulling界面中,进行对象Static标签中的标记(Occluder\Occludee)、场景遮挡信息的烘焙,同时启动相机上的Occulusion Culling项。
- Portal Culling:
- 配置于对于门、窗等可开闭的门户类遮挡物。
- 无需标记为Static,而是在对象上挂载Occusion Portal组件,并重新进行场景遮挡信息的烘焙。在Runtime中,通过代码逻辑,维护occulusion.open属性,以实现被遮挡对象的实时Culling。
光影剔除
-
适当配置MeshRenderer的Cast Shadows和Receive Shadows项。例如对于Terrain,其产生的Shadow不可能被Camera观察到,则其不应该Cast Shadows。
-
可以通过配置灯光Light组件上的CullingMask项,将光源的影响范围限制在指定Layer的对象上。
-
在SRP管线下,可以使用Light Layer功能。CullingMask用于判定的Layer为Gameobject的Layer,而Light Layer使用的是一套单独的Layer,相对更灵活。需要注意是,自URP14起,该功能重新设计为Rendering Layer功能,其配置过程详见:Unity 官方说明文档。
-
如果场景内存在实时灯光,则可以考虑通过代码逻辑维护实时灯光的开启、阴影状态。例如当玩家距离非常远时,此时灯光不应产生影子;例如当玩家在室内时,室内封闭空间的灯光可以直接关闭。
-
在Render Pipeline Asset中,存在若干灯光渲染配置,可以尝试在尽量不破坏视觉效果的前提,调整相关参数。
- 其中,Per Object Limit项定义了在渲染场景时,每个对象允许接收的光源数量的上限;Shadow Atlas Resolution项定义了渲染阴影时所使用的阴影图集的分辨率;Shadow Resolution Tiers项定义了在渲染阴影时根据距离所使用的不同级别的阴影分辨率;Cascade Count项定义了在渲染阴影时所使用的级联数量。
- 例如,对于室外非主光源,可以将Per Object Limit项由默认的8调为4;
- 例如,对于室内非主光源,可以将Shadow Atlas Resolution由默认的4096调为2048,可以将Shadow Resolution Tiers项由1024/2048/4096调为256/512/1048;
- 例如,对于非主光源,可以将Cascade Count项由默认的4调为2,其中Split1和Last Border分别为37.5m/67.5m;
- 这些参数数值调整,在性能优化/效果受损方面的平衡非常主观,务必根据实际项目进行评估。
渲染流程的精简与优化
-
Naitive Render Pass
NaitiveRenderPass相对于传统RenderPass,由于其能够调度到现代图形Api设计出的更灵活的渲染管辖上下文切换渲染目标的方法,故可以在一遍渲染流程的开始与结束时指定贯穿始终的RenderTarget,这样可以在RenderSurface上做显示的Load和Store操作。同时单个NaitiveRenderPass允许运行多个子过程SubPass,这些SubPass可以在像素着色时可以读取到当前帧Pass中的像素信息。所以一些原先在传统RenderPass中需要多Pass才能完成操作,在NativeRenderPass中只需要一遍Pass就能完成,同时在基于Tile-Based Rendering渲染过程的平台上能够有更好的性能表现。 -
该功能需要依赖图形Api接口,目前Vulken、Metal都有支持,但OpenGL、OpenGL ES无法支持。
-
自定义Shader需要遵守Unity定义的上下文规则与限制,若不符合对应规范,则会造成相关渲染错误。Amplify Shader Editor生成的Shader无法支持NativeRenderPass。
-
在延迟渲染管线中,如果目标平台的图形Api支持,则建议开启该选项。如此,可以将DeferrredPass在传统RenderPass模式下所需的两遍Pass,优化为NativeRenderPass模式下的一遍Pass。
配置方式为,在Universal Renderer Data中启用“Native RendererPas”选项。
详见官方文档 -
渲染流程优化
检查各渲染步骤中,是否存在并非功能需求的Pass,进行对应优化。
Terrain地形优化
Terrin To Mesh
- Unity引擎中Terrain工具在移动端性能负担较大,尤其是TerrainShader,因此推荐对于地形需求使用基于网格的烘焙/预制体方案,而对于现有Terrain组件建议编写脚本或者使用插件进行Terrain转Mesh的操作。
- 插件推荐使用Terrain To Mesh 2021。该工具支持将现有地形组件转为Mesh,同时进行地形分块,并支持生成简化的地形碰撞体,且TerrainShader支持纹理数组的传递。不过该工具使用的Splatmap Shader不支持SRP Batcher,这是由于其Shader内部材质属性并没有定义到ConstBuffer中。该插件相关配置参考如下:
- Mesh中:使用Resolution模式;使地形大小设置Count,不应过大、也不宜过小,以便进行遮挡等剔除操作;勾选Generate Collider,并将Resolution设置为0.5;
- Material中:使用Splatmap模式,并开启Texture 2D Array;Texture Resolution选用Default/512/512,Texture Format根据目标平台进行配置;
- Objects中:可以选择性开启,如果可以的话,尽量不要选对植被等Objects进行处理,而是通过对各植被单独做Mesh配置实现,并通过SRP Batcher或GPU Instancing方案进行优化;
- Save中:勾选Prefab Flags的Static,并选取Occluder Static/Occludee Static/Batching Static/Refelction Probe Static;可以选择性得勾选Tag,并作对应配置,以方便代码逻辑与地形交互。
由于插件中的Splatmap Shader不支持SRP Batcher,可以修改其绘制顺序,如滞后到Opaque的最末绘制。这样可以不打断其他支持SRP Batcher对象的绘制。
主光源级联阴影优化
主光源阴影LOD
虽然可以将主光源级联阴影层级数Cascade Count设置为只有2级,但是如果场景里受主光源影响的物体较多,且均需要为动态阴影时,性能负担依然较大。
因此采用LOD的思路,基于级联阴影层级的配置,对不同距离的阴影采用不同的阴影刷新频率。
在URP源码中,主光源级投影通过MainLightShadowCasterPass.cs脚本实现,可以复制该源码、重命名、并做对应实现,示例如下:
- 重命名为MainLightShadowCasterCachePass.cs,同时修改UniversalRenderer.cs脚本中对原有MainLightShadowCasterPass对象的类型与命名,例如将对象重命名为m_MainLightShadowCasterCachePass;
- 移除OnCameraCleanup方法中对m_MainLightShadowmapTexture的释放逻辑;
- 增加Cleanup方法,实现对m_MainLightShadowmapTexture的释放逻辑,并在UniversalRenderer的Dipose方法中调用m_MainLightShadowCasterCachePass.Cleanup方法;
- 修改Setup方法中对m_MainLightShadowmapTexture的创建逻辑,只有该Texture为空、或Texture大小发生改变时,才重新创建;
- 在Setup方法中移除对Clear方法的调用;
- 增加并维护一个m_FirsetFrame布尔变量,在以记录当前是否是第一帧;
- 增加m_InterleavePattern整型数组,维护了帧序列绘制时对于不同层级阴影的绘制情况;
- 增加一个m_InterballeaveIndex整型变量,记录当前绘制到第几帧,以便取模后结合上述数组中获得该帧需要绘制的阴影层级;
- 移除Configure方法中的ConfigureClear逻辑;
- 在RenderMainLightCascadeShadowmap方法中的基于m_ShadowCasterCascadesCount的循环里,增加一个根据【当前阴影层级cascadeIndex】、【当前帧序列m_InterballeaveIndex】、【不同层级阴影绘制情况m_InterleavePattern】的判定逻辑,只有满足条件时,才会进行对应实现;
- 在Setup方法中同样存在一个m_ShadowCasterCascadesCount的循环,需要做上述判定处理;
- 将上述循环中ShadowUtils.RenderShadowSlice的调用改为自行复制实现RenderShadowSlice方法;
- 如果目标运行平台为metal图形Api架构,则需要将useNativeRenderPass变量设为false。
Demo工程:https://github.com/wenhaoGit/MainLightShadowOptimization.git
本文整理自:Metaverse大衍神君《Unity性能优化》