距离上一次写文章过去了很长一段时间了,前面几篇文章主要介绍了一下UE4渲染的主要框架以及数据流,这篇文章主要稍微详细介绍UE4阴影的渲染流程。
由于在之前的文章中我们主要介绍的是Color Pass是怎么处理场景中的物体的,并没有提到Shadow Pass是怎么处理物体的,这里主要介绍阴影相关的处理方法。阴影的处理实在Color Pass处理完之后执行的,入口为InitDynamicShadows函数。
在InitDynamicShadows函数中,对于场景中的每一个平行光根据一些设置初始化View.VisibleLightInfos.MobileCSMSubjectPrimitives.ShadowSubjectPrimitivesEncountered,将其初始值全部设置为false。
在初始化了上面的变量之后,进入FSceneRenderer::InitDynamicShadows函数。在这里我们跳过一些与设置有关的代码,直接进入我们使用到的代码里面,其他的代码可能是在处理其他的一些情况,这里由于我们没有遇到所以先不处理。在FSceneRenderer::InitDynamicShadows函数中我们调用AddViewDependentWholeSceneShadowsForView()函数,接下来我们看看AddViewDependentWholeSceneShadowsForView函数具体做了什么事情。
AddViewDependentWholeSceneShadowsForView首先初始化了FadeAlphas,并将其值全部初始化为1。
接着计算根据实际需求和用户设置计算我们最终需要几级级联阴影。接着对于每一级的阴影根据LocalIndex获取ProjectedShadowInitializer,该数据包含了很多阴影渲染需要的很多信息,比如矩阵、FaceDirection、Bound(根据距离分割,Cascade的级数越高该Cascade的距离间隔越大,具体的分割算法这里不详细介绍了,有兴趣的可以参考下图中的函数)以及SubjectBounds等数据。
在进行了上面的操作之后我们就可以得到ProjectedShadowInitializer了,然后就可以将其作为参数传递给SetupWholeSceneProjection函数,用来初始化ProjectedShadowInfo。在SetupWholeSceneProjection函数中会设置绝大部分的渲染阴影需要的数据。
在SetupWholeSceneProjection函数中首先会给自己的成员赋值,保存初始化的数据。
接着会计算按照光源的角度该Cascade的最大最小的深度值,并且根据设定的深度值进行Clamp得到clamp之后的值,然后对PreShadowTranslation参数做一下snap,这个主要是避免的在摄像机移动的造成的锯齿或者抖动,对于边缘的像素的处理很重要。
更新完PreShadowTranslation之后需要计算ShadowBound,然后计算CasterMatrix,根据CasterMatrix计算视锥的6个截面。
之后计算ShadowViewMatrix和InvReceiverMatrix以及ReceiverFrustum,然后更新DepthBias,具体的计算方法这里就不详细介绍了。
完成了上面的操作之后,就收集完了每个光源的每级阴影的Projection信息了。接下来就需要调用InitProjectedShadowVisibility函数检测阴影的可见性了。接下来我们详细的看一下InitProjectedShadowVisibility具体做了什么任务。
在InitProjectedShadowVisibility函数中,针对每一个光源,首先初始化View.VisibleLightInfos的一些数组,比如ProjectedShadowVisibilityMap和ProjectedShadowViewRelevanceMap。
在完成了上面的初始化操作之后,接着需要计算投射阴影的Pritimive的ViewRelevance,在我的测试场景中是没有ParentSceneInfo的,所以走的else分支。
接着需要判断这个ProjectedShadowInfo有没有被遮挡,这里使用的是Occlusion Query来判断的,Mobile平台默认应该不会开启。如果被遮挡了,这一级就不会绘制阴影了。这个函数后面的代码与主逻辑关系不大,这里就不介绍相关的代码了。
接下来需要做的是更新阴影的缓存,这里的阴影缓存指的是每一级的阴影。由于在我们的例子中没有使用到阴影缓存,所以这里暂时不介绍这些代码。
在处理完阴影缓存之后需要收集产生阴影的Primitvies了,然后对这些Primitvies进行筛选,这些处理逻辑和主相机的筛选逻辑是一样的,只是视锥体不一样而已。关于里面更加详细的步骤会在下篇中详细的介绍。
小结:通过这一部分我们发现其实阴影渲染的处理逻辑和之前介绍的主相机的剔除逻辑相差不大,只是阴影这一块的剔除逻辑相对于主相机更加复杂一点。