UE4:移动端级联阴影简要分析
前言
假装过一下移动端渲染管线的源码,其实是看一下移动端的固定方向光源对移动物体生成阴影的部分。
本文重点在UE4 关于CSM阴影的这部分源码,其他的略过
之前从来没有看过这部分源码,本人基础也很薄弱,rendering方面能力不足,本文仅仅是给我的脑子梳理一下,避免稀里糊涂的看完不出三天就忘记了……
学而不思则罔~
目录:
流程梳理
函数
类或结构体
变量
简单描述
简单的文字描述一下UE4 CSM实现大概的流程,防止本文作者出现猪脑过载迹象……
- 移动端的主渲染流程开始
- 渲染进入初始化View阶段
- 初始化View中有一个阶段是生成动态阴影
- 生成动态阴影的其中一步是创建全场景投影阴影,就是生成CSM阴影
生成CSM的流程:
- 确认级联的数量
- 对每一个级联生成投影阴影信息
- 据阴影数据构造灯光视锥体和灯光变换矩阵
- 确认物体是否在灯光视锥中产生阴影,并确认阴影落在了第几级的级联
- 分配CSM深度Target,但是在移动端会延迟分配的时机以便确认是否可以将聚光灯阴影与之结合
详细流程
- 1、 FMobileSceneRenderer的Render函数,是移动端渲染的主流程函数
- 2、 Render函数中调用了InitViews函数
- 3、 InitViews函数调用了FMobileSceneRenderer::InitDynamicShadows函数,为每一个View寻找可见的动态阴影,该函数又调用了FSceneRenderer::InitDynamicShadows
- 4、 InitDynamicShadows 函数中AddViewDependentWholeSceneShadowsForView 处理了CSM相关数据,该函数生成了指定灯光的所有wholesceneshadows的FProjectedShadowInfo
- 4.1、 确认是否需要为View创建whole scene view dependent shadow
- 4.2、 GetNumViewDependentWholeSceneShadows 函数确认级联的数量
- 4.3、 根据级联数量开始对每一个级联进行处理
- 4.3.1、 声明FWholeSceneProjectedShadowInitializer类型的一个变量
- 4.3.2、 通过GetViewDependentWholeSceneProjectedShadowInitializer函数对上面那个变量填充其成员
- 4.3.2.1、 GetShadowSplitBounds计算该级联部分的视锥体的包围球
- 4.3.2.1.1、 通过神奇的方式算出来了这个球的中心和半径,得到了包围球
- 4.3.2.1.2、 计算阴影裁剪体ComputeShadowCullingVolume
- 4.3.2.1.2.1、 计算出了一个凸多面体
该投影裁剪体在后续的GatherDynamicMeshElements 的过程中有使用到
- 4.3.2.1.2.1、 计算出了一个凸多面体
- 4.3.2.1、 GetShadowSplitBounds计算该级联部分的视锥体的包围球
- 4.3.3、 创建投影阴影信息
- 4.3.3.1、 声明FProjectedShadowInfo变量
- 4.3.3.2、 通过SetupWholeSceneProjection函数对上面那个变量进行成员填充,需要用到前面创建的FWholeSceneProjectedShadowInitializer
- 4.3.3.2.1、 填充了各种变量
- 4.3.3.2.1、 通过神奇的方式更新了PreShadowTranslation,说是可以避免
- 5、 InitProjectedShadowVisibility,计算投影阴影的可见性
- 6、 UpdatePreshadowCache,清除旧的preshadows并尝试向缓存中添加新的
- 7、 GatherShadowPrimitives,收集用于绘制各种阴影类型的图元列表
- 8、 AllocateShadowDepthTargets,分配阴影深度渲染目标
- 8.1、 非移动端会在这里面调用AllocateCSMDepthTargets,但是移动端会MobileWholeSceneDirectionalShadows.Append(WholeSceneDirectionalShadows)延迟分配时机
- 8.2、 AllocateMobileCSMAndSpotLightShadowDepthTargets(RHICmdList, MobileWholeSceneDirectionalShadows)进行分配
- 9、 GatherShadowDynamicMeshElements,从阴影图元数组生成网格元素数组
源码学习
移动端渲染主函数
那么我们首先来到移动端渲染代码的地方:FMobileSceneRenderer
位于SceneRendering.h文件中
1 |
|
那么我们直奔Render函数,看看里面都要干啥
1 |
|
移动端InitViews
别的步骤暂时略过,发现InitViews里处理了dynamic shadows:
1 |
|
看上面的意思就是,开启了动态阴影的选项,并且不是简单前向渲染的话,是要InitDynamicShadows的,那么本文的主要目标就出现了,着重看一下FMobileSceneRenderer::InitDynamicShadows
移动端初始化动态阴影
移动端初始化动态阴影的函数
FMobileSceneRenderer::InitDynamicShadows
1 |
|
不同Scene可以共用的初始化动态阴影的部分
FSceneRenderer::InitDynamicShadows
FSceneRenderer::InitDynamicShadows
1 |
|
为当前View添加全场景阴影信息
FSceneRenderer::AddViewDependentWholeSceneShadowsForView
收集CSM相关的数据
- 创建了ProjectedShadowInitializer
- 用ProjectedShadowInitializer创建了ProjectedShadowInfo
- 将ProjectedShadowInfo保存到了InitDynamicShadows函数里的局部变量VisibleLightInfo和ViewDependentWholeSceneShadows中
1 |
|
设置投影阴影初始化器
- 就是那个每个级联的SplitNear和SplitFar,就是分割级联,这个是在计算包围球的时候才计算的,具体计算方法是GetSplitDistance
- 计算了每个级联的外界包围球,具体方法是GetShadowSplitBounds
- 计算了这个球的内接盒
- 在计算包围球的同时计算了阴影裁剪体
GetViewDependentWholeSceneProjectedShadowInitializer
1 |
|
生成投影阴影的信息
SetupWholeSceneProjection
1 |
|
相关结构体
FWholeSceneProjectedShadowInitializer
1 |
|
FProjectedShadowInitializer
1 |
|
FBoxSphereBounds
1 |
|
UE4:移动端级联阴影简要分析
http://muchenhen.com/posts/46173/