UE4:移动端俯视角远距离摄像机的级联阴影优化
前言
项目中使用了方向光级联阴影,但是leader跟我说这个级联阴影的ShadowMap的利用率很低,让我去看看能不能搞一搞,然鹅本人光照和阴影啥都没看过(甚至第一次听说CSM)当时真的是:
于是抓紧时间看了一下相关的知识和UE4的源码部分,算是一定程度解决了问题,本文作为这次任务的小总结。
级联阴影简介
级联阴影是对ShadowMap的一种优化,理想情况下,空间中的每一个阴影的像素都对应到阴影贴图的每一个像素就能获得最高质量的阴影。但这是基本办不到的,考虑到性能的问题,尤其是在移动端,一般情况下贴图用4096已经挺奢侈了。
如果在视野中的是一个超大场景,场景里有很多产生动态阴影的物体的话,即时贴图很大,也会是多个物体的阴影可能会对应到同一个像素,但在这种情况下,距离摄像机很远的地方的物体的阴影是没有必要做很高精度,于是就有了奇妙的想法,级联阴影
希望阴影和物体模型精度一样,距离摄像机远的地方的阴影精度降低,假设使用相同大小的ShadowMap,那么我们将摄像机的视锥从近到远分块,距离摄像机越近的地方,分块后视锥的体积越小,那么单位物体阴影对应的阴影贴图的面积就越大,阴影质量越高
在合适的分级和阴影贴图的情况下,我们可以获得近处质量高,整体效率也不错的阴影贴图
总之就是非常好用的东西!
这里贴上两个链接:
俯视角远距离摄像机使用情况
我们有如下场景:
地板的坐标是000,摄像机的坐标如下图:
这里给到了一个很小的FOV和比较大的长宽比
摄像机第一视角如下:
首先我们可以直观的了解一下情况,先来一张RenderDoc截图的状况:
上图是CSM阴影使用的ShadowMap,大小是2048,那两个小黑点分别是场景中球和方块的阴影贴图(这里只用了1级级联)。
看,摄像机视角下那么大的俩影子!咋就这么点儿呢!
前文DX的那个链接里面有说过,级联阴影计算的时候有Fit to View和Fit to Scene,看过UE的实现之后知道,UE是Fit to view,具体的可以看我水的写的这篇:
问题就是在这种摄像机很远,FOV小,俯视角的情况下,这个视锥的包围球极其的巨大,也就意味着单位ShadowMap的像素需要承载更多场景中的阴影,那么相当于用了一个2048大小的贴图,画了一个128左右大小的阴影,阴影质量差而且内存浪费,所以需要想办法将这个ShadowMap的空白面积减少,尽量用较小的贴图画出更高质量的阴影
解决思路
在看了UE实现的源码之后,定位到了级联视锥生成的部分,接下来想办法减小视锥包围体,那么接下来本文仅针对标题情况来想办法,其他情况还不清楚都有啥问题,毕竟
在我们面对的具体的问题中,摄像机视锥构建的Volume已经差不多和整个场景一样大了,但是并不是整个场景都存在动态物体需要计算阴影,更重要的是我们场景很大但是摄像机只能看到较小的一部分,当然是想看到什么画什么,总之就要是试图减小Volume
下面的思路为了简化问题,假设为1级级联,就是整个视锥只有一个级联
下面是灵魂画手时间:
减小级联阴影视锥
大概是这么个意思,红色的是相机的视锥(但不完全是,红色最远端其实是CSM设置的最远距离),因为只有一级级联,所以这个Sphere直接就是摄像机视锥的包围球。
那么首先,从这张图上可以看出,CSM最远距离设置的太远了,浪费,那么就设置的小一点
我们再把摄像机近平面向前推,那么我们的级联视锥就更小了,相应的包围球也会变小
这是一种比较简单的做法,略有效果,但是需要多测试合适的参数,但是比较方便,直接在计算级联Volume的地方给一个沿着摄像机方向的偏移,并保持级联范围的最远距离不变就可以了
这各方法在实际测试后最终没有采用,因为在我们的实际使用中,仅有一级CSM的时候,虽然相机很远,但是由于FOV很小,我们的相机向下偏移的幅度有限,聊胜于无,提升不大,而启用多级CSM后发现了更加尴尬的事情,我们的场景在最远端,前几级的CSM完全没用
直接构建投影矩阵
UE这里其实就是通过视锥构建了投影矩阵,那我们直接给他一个就可以了,UE算的太大了,我们给个小一点的
比如,直接将当前视锥内的所有产生阴影的物体找到一个包围了他们的盒子,用这个盒子计算Volume,在只有一级级联的时候直接就可以忽略掉CSM最大距离,可以达到最高效的利用
在我们具体的实现中,因为我们的游戏视角几乎固定,摄像机不会转,这样让解决问题的办法容易了很多,我们直接在沿摄像机方向找到与场景地面的焦点,构建出一个刚好包裹目前能看到场景Volume,直接使用这个Volume去进行后续相关的计算。
但这样依然会有一个新问题,如果开启了多级CSM,由于强行使用了自定义的Volume,那么CSM的多级ShadowMap会完全一致,很浪费内存。而我们的具体情况又是物体完全落在了最后一级,前几级完全没用,最后决定在移动端只使用一级级联,直接使用自定义Volume,在PC端开启多级级联的时候,将前几级使用UE的Fit to view,最后一级改为自定义Volume,这样能解决在我们的摄像机最远的时候能有一个很高质量的阴影,而摄像机拉到很近(实际游戏过程几乎不存在这种情况)的时候,Fit to view本身在前几级级联也能提供比较不错的阴影了
效果比较
这里是我新建了一个空白项目来模拟我们遇到的情况和解决办法
在我们远距离小FOV俯视角的情况下:
对应的ShadowMap:
自定义构建Volume后:
对应的ShadowMap:
效果提升还是很明显的
总结
遇到的问题
CSM是对大范围场景大量动态物体投射动态阴影的非常有效的应用LOD思想的一个方法,非常好用,UE实现的Fit to view方式也是现在很多主流游戏需要的
但是对我们的项目摄像机不合适,阴影质量差,内存和带宽占用大,4096甚至画不出1024应有的效果
解决办法
最后根据项目是积极情况选择自定义CSM Volume,大幅提升ShadowMap利用率,提高阴影质量
当然以上情况是针对性的,对我们的项目来讲这种方式是最简单最有效的办法,直接复用性不高,毕竟这种情况的摄像机比较少见而且CSM原本的目的是减少大场景多动态物体的开销,但可以提供一个有效的思路
直接相关的源码
4.27版本
- DirectionalLightComponent.cpp
- GetShadowSplitBounds
- GetShadowSplitBoundsDepthRange