性能优化(31)——级联阴影优化


 

  • 记得关闭地形投影

  • 既然我们做场景LOD时,远处的模型被替换成低模,动画看上去也不会动了,那么阴影贴图渲染其实每帧变化并不大,也会随着级联阴影层级的提高变化越来越小
  • 那么我们有没有办法在远距离的级联阴影级别下,可以不用对每个物体阴影做重新绘制呢
  • 我们应该可以想象的,如果将阴影贴图进行cache缓存,并对级联阴影的每个级别的阴影贴图块,按不同的刷新率去更新,这样就可以降低每帧投影体绘制的次数了。也就是将LOD思想应用到了级联阴影上

  • 首先想到的还是从URP源码入手
  • 在URP下,主光源投影是通过MainLightShadowCasterPass.cs完成的,我们只需将其拷贝一份出来,并重新命名为MainLightShadowCasterCachedPass.cs,并将对应的Pass类名修改为MainLightShadowCasterCachedPass,同时修改UniversalRenderer.cs文件,将创建MainLightShadowCasterPass的地方替换成为MainLightShadowCasterPass对象

  • 接下来看对MainLightShadowCasterPass做哪些修改
  • 首先,我们既然要缓存生成的Shadowmap,那么我们的级联阴影的Texture创建就不能每帧创建了。我们需要移除OnCameraCleanup函数下的MainLightShadowmapTexture的释放逻辑,同时添加一个Cleanup接口,将其释放逻辑放到UniversalRenderer下的MainLightShadowCasterPass的释放逻辑

  • 这样就变相延长了MainLightShadowmapTexture的资源生命周期
  • 同时修改Setup函数下的MainLightShadowmapTexture创建逻辑,只有这个对象为空,或者Shadowmap大小发生改变时,才重新创建
  • 同时添加一个判断是否为第一帧的bool变量,方便后面只处理创建后第一帧逻辑使用
  • 然后需要添加一个针对于Shadowmap的更新数组
  • 这里的1,2,4,8分别对应级联阴影的第1,2,3,4四个级别
  • 这个数组有几个元素就代表了做几帧的更新循环
  • 如:这里创建了第一帧更新是第一个级别与第三个级别的级联阴影块;第二帧更新是第一个级别与第四个级别的阴影块;第三帧更新的是第二个级别与第四个级别的阴影块。相当于每3帧更新2次第一个级别的级联阴影块,更新1次第二个级别的级联阴影块。这样就变相的相当于第一个级别的渲染频率降低到用来的2/3,而第二个级别的渲染降低到原来的1/3
  • 同时需要定义一个int变量,用来记录当前总共进行了多少帧,方便取模后,按3帧一个循环来更新阴影贴图
  • 我们还需要移除Configure中的ConfigureClear逻辑,这样就不需要每帧clear整体的Shadowmap了。若远处的级联阴影在远距离级别上会出现闪烁,就是没有移除该逻辑的后果
  • 这些都准备好后,就可以修改阴影的渲染逻辑了
  • 将RendererMainLightCascadeShadowmap中的每帧渲染逻辑替换为按更新数组3帧一循环的渲染逻辑,同时将ShadowUtils、RenderShadowSlice替换成我们自己写的RenderShadowSlice逻辑,主要是控制Shadowmap中不同级别阴影块的单独绘制,而不是更新整个Shadowmap了

  • 同时你也可以修改SetupMainLightShadowReceiverConstants中的级联阴影各个级别矩阵的更新频率,降低一点CPU开销。不过不修改也没有关系,差异并不大

  • 到此,按级联阴影的级别,通过更新频率控制每个阴影块渲染的优化的代码修改基本上完成了
  • 但到我们捎入到手机上运行会出现崩溃,主要会发生在Matal的SetScissorRect函数里,其原因是MainLightShadowCasterCache的Path默认设置了NativevRenderPass导致生成的Shadowmap的临时RenderTarget的LoadAction与StoreAction都是DontCare,不做存储与加载操作。但由于我们的优化是跨帧修改了Shadowmap,因此需要Load与Store操作。这里最简单的修改方式就是将MainLightShadowCasterCache的Path不使用NativeRenderPass,将useNativeRenderPass变量设置为false