剖析虚幻渲染体系(05)- 光源和阴影(阴影篇)

5.6 UE的阴影

UE的阴影种类繁多,实现复杂,涉及的优化技术也多。本章将花大篇幅介绍和阐述UE的阴影渲染机制。

5.6.1 阴影概述

UE的光源类型有多种,而UE的阴影则具有更丰富的类型,如下所述。

  • 静态阴影(Static Shadow)

静态阴影由静态光源射到静态物体在静态接收体中产生的投影,故而只会在静态物体中产生,动态物体通常不能产生静态阴影。

左边的动态角色在静态光源下不会受到光照也不会产生投影,而右边的静态角色则会。

  • 级联阴影(Cascading Shadow Map)

级联阴影又被称为Directional Light Cascading Shadow Maps或Whole Scene Shadows,是针对平行光影响全场景范围内的多级阴影图,以解决光源视图空间远处的深度图精度不足出现各类视觉瑕疵的问题。

级联阴影示意图,在光源视图空间中,红色最近,深度图分辨率最小,黄色最远,深度图分辨率最大。

平行光的Cascaded Shadow Maps属性组可以设置级联阴影的详细参数。

此外,平行固定光源(Directional Stationary Lights)比较特殊,因为它们支持静态阴影的同时又可以通过级联阴影贴图支持整个场景的阴影。对于拥有海量带动画的植被的关卡非常有用。因为可以支持动态物体的阴影,而在远处某个距离会逐渐过渡到静态阴影。

可以通过调整平行光级联阴影属性组的Dynamic Shadow Distance StationaryLight的数值来调整过渡区域。

  • 固定光源阴影(Stationary Light Shadows)

动态物体必须从距离场阴影图集成到世界的静态阴影中。为此,需要借助逐物体阴影。每个可移动的物体都会从一个固定光源中产生两个动态阴影:一个阴影用来处理静态世界投影到动态物体上,另一个阴影用来处理动态物体投影到世界上。

通过这种设置,固定光源的唯一阴影成本来自它所影响的动态物体。这意味着成本可大可小,取决于有多少动态物体。如果场景中足够多的动态物体,使用可移动光源会更加高效。

  • 逐物体阴影(Per Object Shadow)

可移动组件使用的逐物体阴影应用阴影图到物体的包围盒,因此包围盒必须是精确的。对于骨骼网格,这意味着它们应该有一个物理资产。对于粒子系统,任何固定的边界框必须足够大,以容纳所有的粒子。

在网格的Lighting属性组中,Dynamic Inset Shadow可以开启逐物体阴影,对需要高质量高精度的物体非常有用。

  • 动态阴影(Dynamic Shadow)

可移动光源在所有物体上投射出完全动态的阴影(和光)。这种光源的任何数据不会被烘焙到光照图中,它可以自由地在所有东西上投射动态阴影。静态网格、骨架网格、粒子效果等等将完全从可移动光源投射和接收动态阴影。

一般来说,可移动的动态阴影投射光源是最耗性能的。

  • 预览阴影(Preview Shadow)

当正在编辑固定或静态光源时,而它们没有被及时构建,UE会使用预览阴影来代替未构建的阴影。预览阴影将带有Preview字样:

  • 胶囊体阴影(Capsule Shadow)

UE支持使用物体的物理资产(Physics Asset)生成的胶囊体代替物体本身投射阴影,可以产生软阴影,主要用于拥有物理资产的蒙皮网格(Skeletal Mesh)。

上:胶囊体阴影;下:对应的物理资产代表。

  • 接触阴影(Contact Shadow)

接触阴影是逐光源的基于屏幕空间的补偿阴影,原理在于阴影计算阶段,若开启了光源的接触阴影,会额外投射可见性判定射线,执行场景深度缓冲射的Ray Marching,以确定该像素是否被遮挡(在阴影中),从而获得更加精准的阴影信息。

左:关闭接触阴影;右:开启接触阴影。

  • 距离场阴影(Distance Field Shadow)

距离场阴影故名意思就是利用预先生成的距离场信息投射的阴影。

在使用需要场景距离场的特性之前,都需要在Project Setting中开启Generate Mesh Distance Fields,包含距离场阴影、距离场碰撞、距离场AO等。

距离场阴影支持软阴影、非直接阴影、远距离阴影等等,还可以结合级联阴影实现更加细腻接近真实的阴影效果(下图)。

上:级联阴影效果;下:级联阴影+距离场阴影。

下表是传统阴影和距离场阴影的性能对比(GPU是 Radeon 7870,分辨率是1080p,单位是ms):

场景级联、立方体阴影图消耗距离场阴影消耗速度提升
平行光,10k距离单位,3级CSM3.12.325%
平行光,30k距离单位,6级CSM4.92.843%
拥有超大半径的点光源1.81.330%
5个拥有小半径的点光源3.21.845%

由此可见,距离场阴影的渲染性能会高一筹,但会增加场景构建时间,增加磁盘、内存、显存消耗。

5.6.2 阴影基础类型

在进入阴影渲染剖析前,先详细理解阴影相关的部分关键概念:

// Engine\Source\Runtime\Renderer\Private\ShadowRendering.h

// 投射阴影信息.
class FProjectedShadowInfo : public FRefCountedObject
{
public:
    typedef TArray<const FPrimitiveSceneInfo*,SceneRenderingAllocator> PrimitiveArrayType;

    // 渲染阴影时使用的view.
    FViewInfo* ShadowDepthView;
    // 阴影必须渲染的主view, Null表示独立于view的阴影.
    FViewInfo* DependentView;

    // 阴影Uniform Buffer.
    TUniformBufferRef<FShadowDepthPassUniformParameters> ShadowDepthPassUniformBuffer;
    TUniformBufferRef<FMobileShadowDepthPassUniformParameters> MobileShadowDepthPassUniformBuffer;

    // 阴影图渲染纹理(深度或颜色).
    FShadowMapRenderTargets RenderTargets;
    // FVisibleLightInfo::AllProjectedShadows的索引.
    int32 ShadowId;
    
    // 缓存模式.
    EShadowDepthCacheMode CacheMode;
    // 变换阴影矩阵之前须偏移的位置.
    FVector PreShadowTranslation;
    // 阴影的视图矩阵, 用来代替DependentView的视图矩阵.
    FMatrix ShadowViewMatrix;
    // 主体和接收者矩阵, 用来渲染阴影深度缓冲的矩阵.
    FMatrix SubjectAndReceiverMatrix;
    FMatrix ReceiverMatrix;
    FMatrix InvReceiverMatrix;
    
    float InvMaxSubjectDepth;

    // 主体深度扩展, 世界空间的单位. 可用于转换阴影深度值到世界空间.
    float MaxSubjectZ;
    float MinSubjectZ;
    float MinPreSubjectZ;

    // 包含所有潜在的阴影投射者的锥体.
    FConvexVolume CasterFrustum;
    FConvexVolume ReceiverFrustum;
    // 阴影的球体包围盒.
    FSphere ShadowBounds;
    // 级联阴影设置.
    FShadowCascadeSettings CascadeSettings;
    
    // 边界尺寸, 防止在图集(atlas)中过滤阴影时引入错误的效果.
    uint32 BorderSize;
    // 阴影在深度缓冲(或atlas)中的位置(偏移), 实际的阴影图内容在: X + BorderSize, Y + BorderSize.
    uint32 X;
    uint32 Y;
    // 阴影的分辨率, 包含了边界. 实际分配的阴影分辨率是: ResolutionX + 2 * BorderSize, ResolutionY + 2 * BorderSize.
    uint32 ResolutionX;
    uint32 ResolutionY;

    // 最大屏幕百分比, 取任意一个view的宽或高的最大值.
    float MaxScreenPercent;
    // 每个view的过渡值.
    TArray<float, TInlineAllocator<2> > FadeAlphas;

    // 阴影是否在深度缓冲区分配过, 若是, 则X和Y属性将被初始化过.
    uint32 bAllocated : 1;
    // 阴影投射是否已被渲染过.
    uint32 bRendered : 1;
    
    // 阴影是否已在preshadow缓存中分配过, 若是, 则X和Y就是preshadow缓存深度buffer的偏移.
    uint32 bAllocatedInPreshadowCache : 1;
    // 阴影是否在preshadow缓存中且它的深度已被更新.
    uint32 bDepthsCached : 1;

    uint32 bDirectionalLight : 1;
    
    // 是否在同一个通道中渲染完cubemap的所有面的点光源阴影.
    uint32 bOnePassPointLightShadow : 1;
    // 是影响整个场景还是一组物体的阴影.
    uint32 bWholeSceneShadow : 1;
    // 是否RSM(ReflectiveShadowmap).
    uint32 bReflectiveShadowmap : 1; 
    // 是否透明物体阴影.
    uint32 bTranslucentShadow : 1;
    // 是否胶囊体阴影.
    uint32 bCapsuleShadow : 1;
    // 是否预阴影, 预阴影是处理静态环境投射到动态接收者的逐物体阴影.
    uint32 bPreShadow : 1;
    // 是否只有自阴影, 若是, 则不会投影到自身之外的物体, 拥有高质量阴影(适用于第一人称游戏).
    uint32 bSelfShadowOnly : 1;
    // 是否逐物体不透明阴影.
    uint32 bPerObjectOpaqueShadow : 1;
    // 是否开启背光传输.
    uint32 bTransmission : 1;

    // 用于点光源渲染cubemap6个面的阴影图使用的视图投影矩阵.
    TArray<FMatrix> OnePassShadowViewProjectionMatrices;
    // 用于点光源渲染cubemap6个面的阴影图使用的视图矩阵.
    TArray<FMatrix> OnePassShadowViewMatrices;
    /** Frustums for each cubemap face, used for object culling one pass point light shadows. */
    TArray<FConvexVolume> OnePassShadowFrustums;

    (......)

    // 控制逐物体阴影之外的过渡参数, 防止远处出现超级锐利的阴影.
    float PerObjectShadowFadeStart;
    float InvPerObjectShadowFadeLength;

public:
    // 设置逐物体阴影.
    bool SetupPerObjectProjection(FLightSceneInfo* InLightSceneInfo, ...);
    // 设置全场景(全景)阴影.
    void SetupWholeSceneProjection(FLightSceneInfo* InLightSceneInfo, ...);

    // 渲染不透明物体的阴影深度.
    void RenderDepth(FRHICommandListImmediate& RHICmdList, ...);
    // 渲染透明物体的阴影深度.
    void RenderTranslucencyDepths(FRHICommandList& RHICmdList, ...);
    // 为特殊的view渲染投射到场景的阴影.
    void RenderProjection(FRHICommandListImmediate& RHICmdList, ...) const;
    // 渲染单通道点光源阴影.
    void RenderOnePassPointLightProjection(FRHICommandListImmediate& RHICmdList, ...) const;
    void RenderFrustumWireframe(FPrimitiveDrawInterface* PDI) const;
    
    // 渲染状态接口.
    void SetStateForView(FRHICommandList& RHICmdList) const;
    void SetStateForDepth(FMeshPassProcessorRenderState& DrawRenderState) const;
    void ClearDepth(FRHICommandList& RHICmdList, class FSceneRenderer* SceneRenderer, ...);
    static FRHIBlendState* GetBlendStateForProjection(int32 ShadowMapChannel, ...);
    FRHIBlendState* GetBlendStateForProjection(bool bProjectingForForwardShading, ...) const;

    // 增加需要投射阴影的主体图元.
    void AddSubjectPrimitive(FPrimitiveSceneInfo* PrimitiveSceneInfo, TArray<FViewInfo>* ViewArray, ...);
    // 增加阴影接收者图元.
    void AddReceiverPrimitive(FPrimitiveSceneInfo* PrimitiveSceneInfo);

    // 为所有须投射阴影的图元收集动态网格元素.
    void GatherDynamicMeshElements(FSceneRenderer& Renderer, ...);
    // 将DynamicMeshElement转换成FMeshDrawCommand.
    void SetupMeshDrawCommandsForShadowDepth(FSceneRenderer& Renderer, ...);
    void SetupMeshDrawCommandsForProjectionStenciling(FSceneRenderer& Renderer);
    
    // 从内存池中创建一个新的view且在ShadowDepthView中缓存起来, 用以渲染阴影深度.
    void SetupShadowDepthView(FRHICommandListImmediate& RHICmdList, FSceneRenderer* SceneRenderer);
    // 设置和更新Uniform Buffer.
    void SetupShadowUniformBuffers(FRHICommandListImmediate& RHICmdList, FScene* Scene, ...);
    // 保证缓存的阴影图处于EReadable状态.
    void TransitionCachedShadowmap(FRHICommandListImmediate& RHICmdList, FScene* Scene);
    
     // 计算和更新ShaderDepthBias和ShaderSlopeDepthBias.
    void UpdateShaderDepthBias();
    // 计算PCF比较参数.
    float ComputeTransitionSize() const;

     // 数据获取和操作接口.
    float GetShaderDepthBias() const;
    float GetShaderSlopeDepthBias() const;
    float GetShaderMaxSlopeDepthBias() const;
    float GetShaderReceiverDepthBias() const;
    
    bool HasSubjectPrims() const;
    bool SubjectsVisible(const FViewInfo& View) const;
    void ClearTransientArrays();
    friend uint32 GetTypeHash(const FProjectedShadowInfo* ProjectedShadowInfo);
    
    FMatrix GetScreenToShadowMatrix(const FSceneView& View) const;
    FMatrix GetScreenToShadowMatrix(const FSceneView& View, ...) const;
    FMatrix GetWorldToShadowMatrix(FVector4& ShadowmapMinMax, ...) const;
    FIntPoint GetShadowBufferResolution() const
    
    bool IsWholeSceneDirectionalShadow() const;
    bool IsWholeScenePointLightShadow() const;
    const FLightSceneInfo& GetLightSceneInfo() const;
    const FLightSceneInfoCompact& GetLightSceneInfoCompact() const;
    const FPrimitiveSceneInfo* GetParentSceneInfo() const;
    FShadowDepthType GetShadowDepthType() const;

    (......)
    
private:
    const FLightSceneInfo* LightSceneInfo;
    FLightSceneInfoCompact LightSceneInfoCompact;
    const FPrimitiveSceneInfo* ParentSceneInfo;

    // 阴影投射图元列表.
    PrimitiveArrayType DynamicSubjectPrimitives;
    // 接收者图元, 只在preshadow有效.
    PrimitiveArrayType ReceiverPrimitives;
    // 透明阴影投射图元列表.
    PrimitiveArrayType SubjectTranslucentPrimitives;

    // 投射阴影的图元对应的网格元素.
    TArray<FMeshBatchAndRelevance,SceneRenderingAllocator> DynamicSubjectMeshElements;
    TArray<FMeshBatchAndRelevance,SceneRenderingAllocator> DynamicSubjectTranslucentMeshElements;

    TArray<const FStaticMeshBatch*, SceneRenderingAllocator> SubjectMeshCommandBuildRequests;

    // DynamicSubjectMeshElements数量.
    int32 NumDynamicSubjectMeshElements;
    // SubjectMeshCommandBuildRequests数量.
    int32 NumSubjectMeshCommandBuildRequestElements;

    // 绘制阴影所需的绘制命令/渲染状态等.
    FMeshCommandOneFrameArray ShadowDepthPassVisibleCommands;
    FParallelMeshDrawCommandPass ShadowDepthPass;
    TArray<FShadowMeshDrawCommandPass, TInlineAllocator<2>> ProjectionStencilingPasses;
    FDynamicMeshDrawCommandStorage DynamicMeshDrawCommandStorage;
    FGraphicsMinimalPipelineStateSet GraphicsMinimalPipelineStateSet;
    bool NeedsShaderInitialisation;

    // 阴影渲染时的偏移值. 会被UpdateShaderDepthBias()设置, 被GetShaderDepthBias()获取, -1表示未初始化.
    float ShaderDepthBias;
    float ShaderSlopeDepthBias;
    float ShaderMaxSlopeDepthBias;

    // 内部接口
    void CopyCachedShadowMap(FRHICommandList& RHICmdList, ...);
    void RenderDepthInner(FRHICommandListImmediate& RHICmdList, ...);
    void ModifyViewForShadow(FRHICommandList& RHICmdList, FViewInfo* FoundView) const;
    FViewInfo* FindViewForShadow(FSceneRenderer* SceneRenderer) const;
    void AddCachedMeshDrawCommandsForPass(int32 PrimitiveIndex, ...);
    bool ShouldDrawStaticMeshes(FViewInfo& InCurrentView, ...);
    void GetShadowTypeNameForDrawEvent(FString& TypeName) const;
    int32 UpdateShadowCastingObjectBuffers() const;
    void GatherDynamicMeshElementsArray(FViewInfo* FoundView, ...);
    void SetupFrustumForProjection(const FViewInfo* View, ...) const;
    void SetupProjectionStencilMask(FRHICommandListImmediate& RHICmdList, ...) const;
};

由上面的代码可知,FProjectedShadowInfo几乎囊括了阴影处理和渲染所需的重要数据和操作接口。当然,UE的阴影系统太过复杂,单单它一个,还不足以解决阴影的所有渲染功能。下面继续分析其它基础或关键性类型:

// Engine\Source\Runtime\Renderer\Private\SceneRendering.h

// 视图[不]相关的可见光源信息, 主要是阴影相关的信息.
class FVisibleLightInfo
{
public:
    // 在场景内存堆栈(mem stack)分配和管理的投射阴影信息.
    TArray<FProjectedShadowInfo*,SceneRenderingAllocator> MemStackProjectedShadows;
    // 所有可见的投射阴影信息, 由阴影设置阶段输出, 不是所有的都会被渲染.
    TArray<FProjectedShadowInfo*,SceneRenderingAllocator> AllProjectedShadows;

    // 特殊的阴影投射信息, 用于专用的特性, 如投射物/胶囊体阴影/RSM等.
    TArray<FProjectedShadowInfo*,SceneRenderingAllocator> ShadowsToProject;
    TArray<FProjectedShadowInfo*,SceneRenderingAllocator> CapsuleShadowsToProject;
    TArray<FProjectedShadowInfo*,SceneRenderingAllocator> RSMsToProject;

    // 所有可见的投射预阴影. 这些不在场景的内存堆栈中分配和管理, 所以需要用TRefCountPtr引用计数.
    TArray<TRefCountPtr<FProjectedShadowInfo>,SceneRenderingAllocator> ProjectedPreShadows;
    // 被遮挡的逐物体阴影, 为了提交遮挡剔除申请所以需要追踪它们.
    TArray<FProjectedShadowInfo*,SceneRenderingAllocator> OccludedPerObjectShadows;
};

// 视图相关的可见光源信息.
class FVisibleLightViewInfo
{
public:
    // 光源能够影响到的可见图元.
    TArray<FPrimitiveSceneInfo*,SceneRenderingAllocator> VisibleDynamicLitPrimitives;
    // 对应FVisibleLightInfo::AllProjectedShadows的阴影可见性映射表.
    FSceneBitArray ProjectedShadowVisibilityMap;
    // 对应FVisibleLightInfo::AllProjectedShadows的阴影ViewRelevance.
    TArray<FPrimitiveViewRelevance,SceneRenderingAllocator> ProjectedShadowViewRelevanceMap;
    // 是否在视锥体内. (平行光/天空光总是true)
    uint32 bInViewFrustum : 1;

    (......)
};

// Engine\Source\Runtime\Renderer\Private\ScenePrivate.h

class FSceneViewState
{
public:
    // 投射阴影的键值. 主要用于比较两个投射阴影实例是否一样.
    class FProjectedShadowKey
    {
    public:
        // 键值比较接口.
        inline bool operator == (const FProjectedShadowKey &Other) const
        {
            return (PrimitiveId == Other.PrimitiveId && Light == Other.Light && ShadowSplitIndex == Other.ShadowSplitIndex && bTranslucentShadow == Other.bTranslucentShadow);
        }
        // 键值哈希接口.
        friend inline uint32 GetTypeHash(const FSceneViewState::FProjectedShadowKey& Key)
        {
            return PointerHash(Key.Light,GetTypeHash(Key.PrimitiveId));
        }

    private:
        // 阴影的图元id.
        FPrimitiveComponentId PrimitiveId;
        // 阴影的光源.
        const ULightComponent* Light;
        // 阴影在阴影图集中的索引.
        int32 ShadowSplitIndex;
        // 是否透明阴影.
        bool bTranslucentShadow;
    };
};

5.6.3 阴影初始化

本节将花大篇幅分析阴影的初始化。如果不想看冗余的代码分析的童鞋,可以直接跳到5.6.3.12 阴影初始化总结

阴影的初始化位于InitViews阶段,调用堆栈示意图如下:

void FDeferredShadingSceneRenderer::Render(FRHICommandListImmediate& RHICmdList)
{
    bool FDeferredShadingSceneRenderer::InitViews(RHICmdList, ...)
    {
        void FDeferredShadingSceneRenderer::InitViewsPossiblyAfterPrepass(FRHICommandListImmediate& RHICmdList, ...)
        {
            // 初始化动态阴影.
            void FSceneRenderer::InitDynamicShadows(FRHICommandListImmediate& RHICmdList, ...)
            {
                (......)
            }
        }
    }
}

5.6.3.1 InitDynamicShadows

下面是FSceneRenderer::InitDynamicShadows的代码分析(节选):

// Engine\Source\Runtime\Renderer\Private\ShadowSetup.cpp

void FSceneRenderer::InitDynamicShadows(FRHICommandListImmediate& RHICmdList, FGlobalDynamicIndexBuffer& DynamicIndexBuffer, FGlobalDynamicVertexBuffer& DynamicVertexBuffer, FGlobalDynamicReadBuffer& DynamicReadBuffer)
{
    // 初始化各类标记和数量.
    const bool bMobile = FeatureLevel < ERHIFeatureLevel::SM5;
    bool bStaticSceneOnly = false;
    for (int32 ViewIndex = 0; ViewIndex < Views.Num(); ViewIndex++)
    {
        FViewInfo& View = Views[ViewIndex];
        bStaticSceneOnly = bStaticSceneOnly || View.bStaticSceneOnly;
    }

    const bool bProjectEnablePointLightShadows = Scene->ReadOnlyCVARCache.bEnablePointLightShadows;
    uint32 NumPointShadowCachesUpdatedThisFrame = 0;
    uint32 NumSpotShadowCachesUpdatedThisFrame = 0;

    // 预计算阴影.
    TArray<FProjectedShadowInfo*,SceneRenderingAllocator> PreShadows;
    // 视图关联的全景阴影.
    TArray<FProjectedShadowInfo*,SceneRenderingAllocator> ViewDependentWholeSceneShadows;
    // 视图关联的需要裁剪的全景阴影.
    TArray<FProjectedShadowInfo*,SceneRenderingAllocator> ViewDependentWholeSceneShadowsThatNeedCulling;
    {
        // 遍历所有光源, 将不同类型的光源加入不同类型的待渲染的阴影列表中.
        for (TSparseArray<FLightSceneInfoCompact>::TConstIterator LightIt(Scene->Lights); LightIt; ++LightIt)
        {
            const FLightSceneInfoCompact& LightSceneInfoCompact = *LightIt;
            FLightSceneInfo* LightSceneInfo = LightSceneInfoCompact.LightSceneInfo;

            FScopeCycleCounter Context(LightSceneInfo->Proxy->GetStatId());

            FVisibleLightInfo& VisibleLightInfo = VisibleLightInfos[LightSceneInfo->Id];

            // LightOcclusionType有阴影图和光追两种, 如果非阴影图类型, 则忽略.
            const FLightOcclusionType OcclusionType = GetLightOcclusionType(LightSceneInfoCompact);
            if (OcclusionType != FLightOcclusionType::Shadowmap)
                continue;

            // 如果光源没有开启阴影或阴影质量太小, 则忽略阴影图.
            if ((LightSceneInfoCompact.bCastStaticShadow || LightSceneInfoCompact.bCastDynamicShadow) && GetShadowQuality() > 0)
            {
                // 检测该光源是否在某个view里可见, 如果不可见, 则忽略.
                bool bIsVisibleInAnyView = false;
                for (int32 ViewIndex = 0; ViewIndex < Views.Num(); ViewIndex++)
                {
                    bIsVisibleInAnyView = LightSceneInfo->ShouldRenderLight(Views[ViewIndex]);

                    if (bIsVisibleInAnyView) 
                    {
                        break;
                    }
                }

                // 所有裁剪条件都通过了, 处理光源的阴影.
                if (bIsVisibleInAnyView)
                {
                    // 初始化阴影的各种标记和变量.
                    static const auto AllowStaticLightingVar = IConsoleManager::Get().FindTConsoleVariableDataInt(TEXT("r.AllowStaticLighting"));
                    const bool bAllowStaticLighting = (!AllowStaticLightingVar || AllowStaticLightingVar->GetValueOnRenderThread() != 0);
                    // 是否点光源阴影. 注意矩形光也当做点光源处理.
                    const bool bPointLightShadow = LightSceneInfoCompact.LightType == LightType_Point || LightSceneInfoCompact.LightType == LightType_Rect;

                    // 对不预计算阴影的移动光源只创建全景阴影(whole scene shadow).
                    const bool bShouldCreateShadowForMovableLight = 
                        LightSceneInfoCompact.bCastDynamicShadow
                        && (!LightSceneInfo->Proxy->HasStaticShadowing() || !bAllowStaticLighting);

                    const bool bCreateShadowForMovableLight = 
                        bShouldCreateShadowForMovableLight
                        && (!bPointLightShadow || bProjectEnablePointLightShadows);

                    // 对带有预计算阴影的尚未构建的光源创建全景阴影.
                    const bool bShouldCreateShadowToPreviewStaticLight =
                        LightSceneInfo->Proxy->HasStaticShadowing()
                        && LightSceneInfoCompact.bCastStaticShadow
                        && !LightSceneInfo->IsPrecomputedLightingValid();                        

                    const bool bCreateShadowToPreviewStaticLight = 
                        bShouldCreateShadowToPreviewStaticLight                        
                        && (!bPointLightShadow || bProjectEnablePointLightShadows);

                    // 对需要静态阴影但由于重叠导致没有有效阴影图的光源创建全景阴影.
                    const bool bShouldCreateShadowForOverflowStaticShadowing =
                        LightSceneInfo->Proxy->HasStaticShadowing()
                        && !LightSceneInfo->Proxy->HasStaticLighting()
                        && LightSceneInfoCompact.bCastStaticShadow
                        && LightSceneInfo->IsPrecomputedLightingValid()
                        && LightSceneInfo->Proxy->GetShadowMapChannel() == INDEX_NONE;

                    const bool bCreateShadowForOverflowStaticShadowing =
                        bShouldCreateShadowForOverflowStaticShadowing
                        && (!bPointLightShadow || bProjectEnablePointLightShadows);

                    // 添加点光源的全景阴影.
                    const bool bPointLightWholeSceneShadow = (bShouldCreateShadowForMovableLight || bShouldCreateShadowForOverflowStaticShadowing || bShouldCreateShadowToPreviewStaticLight) && bPointLightShadow;
                    if (bPointLightWholeSceneShadow)
                    {                        
                        UsedWholeScenePointLightNames.Add(LightSceneInfoCompact.LightSceneInfo->Proxy->GetComponentName());
                    }

                    // 创建光源的全景阴影.
                    if (bCreateShadowForMovableLight || bCreateShadowToPreviewStaticLight || bCreateShadowForOverflowStaticShadowing)
                    {
                        CreateWholeSceneProjectedShadow(LightSceneInfo, NumPointShadowCachesUpdatedThisFrame, NumSpotShadowCachesUpdatedThisFrame);
                    }

                    // 允许移动和固定的光源创建CSM(级联阴影), 或者是尚未构建的静态光源.
                    if ((!LightSceneInfo->Proxy->HasStaticLighting() && LightSceneInfoCompact.bCastDynamicShadow) || bCreateShadowToPreviewStaticLight)
                    {
                        // 增加视图关联的全景阴影.
                        if( !bMobile ||
                            ((LightSceneInfo->Proxy->UseCSMForDynamicObjects() || LightSceneInfo->Proxy->IsMovable()) 
                                && (LightSceneInfo == Scene->MobileDirectionalLights[0] || LightSceneInfo == Scene->MobileDirectionalLights[1] || LightSceneInfo == Scene->MobileDirectionalLights[2])))
                        {
                            AddViewDependentWholeSceneShadowsForView(ViewDependentWholeSceneShadows, ViewDependentWholeSceneShadowsThatNeedCulling, VisibleLightInfo, *LightSceneInfo);
                        }

                        // 处理交互阴影, 此处的交互是指光源和图元之间的影响. 包含PerObject阴影、透明阴影、自阴影等. 
                        if( !bMobile || (LightSceneInfo->Proxy->CastsModulatedShadows() && !LightSceneInfo->Proxy->UseCSMForDynamicObjects()))
                        {
                            Scene->FlushAsyncLightPrimitiveInteractionCreation();

                            // 处理动态图元的交互阴影.
                            for (FLightPrimitiveInteraction* Interaction = LightSceneInfo->GetDynamicInteractionOftenMovingPrimitiveList(false); Interaction; Interaction = Interaction->GetNextPrimitive())
                            {
                                SetupInteractionShadows(RHICmdList, Interaction, VisibleLightInfo, bStaticSceneOnly, ViewDependentWholeSceneShadows, PreShadows);
                            }

                            // 处理静态图元的交互阴影.
                            for (FLightPrimitiveInteraction* Interaction = LightSceneInfo->GetDynamicInteractionStaticPrimitiveList(false); Interaction; Interaction = Interaction->GetNextPrimitive())
                            {
                                SetupInteractionShadows(RHICmdList, Interaction, VisibleLightInfo, bStaticSceneOnly, ViewDependentWholeSceneShadows, PreShadows);
                            }
                        }
                    }
                }
            }
        }

        // 计算投射阴影的可见性.
        InitProjectedShadowVisibility(RHICmdList);
    }

    // 清理旧的预计算阴影, 尝试增加新的到缓存中.
    UpdatePreshadowCache(FSceneRenderTargets::Get(RHICmdList));

    // 收集图元列表, 以绘制不同类型的阴影.
    GatherShadowPrimitives(PreShadows, ViewDependentWholeSceneShadowsThatNeedCulling, bStaticSceneOnly);

    // 分配阴影深度渲染纹理.
    AllocateShadowDepthTargets(RHICmdList);

    // 收集阴影的动态网格元素, 跟之前剖析的GatherDynamicMeshElements类似.
    GatherShadowDynamicMeshElements(DynamicIndexBuffer, DynamicVertexBuffer, DynamicReadBuffer);
}

5.6.3.2 CreateWholeSceneProjectedShadow

继续分析上面代码中涉及的几个重要接口,首先是CreateWholeSceneProjectedShadow

void FSceneRenderer::CreateWholeSceneProjectedShadow(FLightSceneInfo* LightSceneInfo, uint32& InOutNumPointShadowCachesUpdatedThisFrame, uint32& InOutNumSpotShadowCachesUpdatedThisFrame)
{
    SCOPE_CYCLE_COUNTER(STAT_CreateWholeSceneProjectedShadow);
    FVisibleLightInfo& VisibleLightInfo = VisibleLightInfos[LightSceneInfo->Id];

    // 尝试为光源创建全景投射阴影的初始化器.
    TArray<FWholeSceneProjectedShadowInitializer, TInlineAllocator<6> > ProjectedShadowInitializers;
    if (LightSceneInfo->Proxy->GetWholeSceneProjectedShadowInitializer(ViewFamily, ProjectedShadowInitializers))
    {
        FSceneRenderTargets& SceneContext_ConstantsOnly = FSceneRenderTargets::Get_FrameConstantsOnly();

        // 阴影分辨率常量.
        const uint32 ShadowBorder = ProjectedShadowInitializers[0].bOnePassPointLightShadow ? 0 : SHADOW_BORDER;
        const uint32 EffectiveDoubleShadowBorder = ShadowBorder * 2;
        const uint32 MinShadowResolution = FMath::Max<int32>(0, CVarMinShadowResolution.GetValueOnRenderThread());
        const int32 MaxShadowResolutionSetting = GetCachedScalabilityCVars().MaxShadowResolution;
        const FIntPoint ShadowBufferResolution = SceneContext_ConstantsOnly.GetShadowDepthTextureResolution();
        const uint32 MaxShadowResolution = FMath::Min(MaxShadowResolutionSetting, ShadowBufferResolution.X) - EffectiveDoubleShadowBorder;
        const uint32 MaxShadowResolutionY = FMath::Min(MaxShadowResolutionSetting, ShadowBufferResolution.Y) - EffectiveDoubleShadowBorder;
        const uint32 ShadowFadeResolution = FMath::Max<int32>(0, CVarShadowFadeResolution.GetValueOnRenderThread());

        // 计算视图内的阴影需要的最大分辨率, 包含用于过渡的未限制分辨率.
        float MaxDesiredResolution = 0;
        TArray<float, TInlineAllocator<2> > FadeAlphas;
        float MaxFadeAlpha = 0;
        bool bStaticSceneOnly = false;
        bool bAnyViewIsSceneCapture = false;
        for(int32 ViewIndex = 0, ViewCount = Views.Num(); ViewIndex < ViewCount; ++ViewIndex)
        {
            const FViewInfo& View = Views[ViewIndex];

            const float ScreenRadius = LightSceneInfo->Proxy->GetEffectiveScreenRadius(View.ShadowViewMatrices);

            // 计算分辨率缩放因子UnclampedResolution.
            float UnclampedResolution = 1.0f;

            switch (LightSceneInfo->Proxy->GetLightType())
            {
            case LightType_Point:
                UnclampedResolution = ScreenRadius * CVarShadowTexelsPerPixelPointlight.GetValueOnRenderThread();
                break;
            case LightType_Spot:
                UnclampedResolution = ScreenRadius * CVarShadowTexelsPerPixelSpotlight.GetValueOnRenderThread();
                break;
            case LightType_Rect:
                UnclampedResolution = ScreenRadius * CVarShadowTexelsPerPixelRectlight.GetValueOnRenderThread();
                break;
            default:
                // 平行光并不在此处理.
                checkf(false, TEXT("Unexpected LightType %d appears in CreateWholeSceneProjectedShadow %s"),
                    (int32)LightSceneInfo->Proxy->GetLightType(),
                    *LightSceneInfo->Proxy->GetComponentName().ToString());
            }

            // 在应用ShadowResolutionScale贡献之前计算过渡因子FadeAlpha.
            const float FadeAlpha = CalculateShadowFadeAlpha( UnclampedResolution, ShadowFadeResolution, MinShadowResolution ) * LightSceneInfo->Proxy->GetShadowAmount();
            MaxFadeAlpha = FMath::Max(MaxFadeAlpha, FadeAlpha);
            FadeAlphas.Add(FadeAlpha);

            const float ShadowResolutionScale = LightSceneInfo->Proxy->GetShadowResolutionScale();

            float ClampedResolution = UnclampedResolution;

            if (ShadowResolutionScale > 1.0f)
            {
                ClampedResolution *= ShadowResolutionScale;
            }

            ClampedResolution = FMath::Min<float>(ClampedResolution, MaxShadowResolution);

            if (ShadowResolutionScale <= 1.0f)
            {
                ClampedResolution *= ShadowResolutionScale;
            }

            MaxDesiredResolution = FMath::Max(
                MaxDesiredResolution,
                FMath::Max<float>(
                    ClampedResolution,
                    FMath::Min<float>(MinShadowResolution, ShadowBufferResolution.X - EffectiveDoubleShadowBorder)
                    )
                );

            bStaticSceneOnly = bStaticSceneOnly || View.bStaticSceneOnly;
            bAnyViewIsSceneCapture = bAnyViewIsSceneCapture || View.bIsSceneCapture;
        }

        // 过渡因子大于阈值才创建阴影.
        if (MaxFadeAlpha > 1.0f / 256.0f)
        {
            Scene->FlushAsyncLightPrimitiveInteractionCreation();

            // 遍历该光源的所有阴影初始化器, 根据级联数量创建和设置ProjectedShadowInfo.
            for (int32 ShadowIndex = 0, ShadowCount = ProjectedShadowInitializers.Num(); ShadowIndex < ShadowCount; ShadowIndex++)
            {
                FWholeSceneProjectedShadowInitializer& ProjectedShadowInitializer = ProjectedShadowInitializers[ShadowIndex];

                int32 RoundedDesiredResolution = FMath::Max<int32>((1 << (FMath::CeilLogTwo(MaxDesiredResolution + 1.0f) - 1)) - ShadowBorder * 2, 1);
                int32 SizeX = MaxDesiredResolution >= MaxShadowResolution ? MaxShadowResolution : RoundedDesiredResolution;
                int32 SizeY = MaxDesiredResolution >= MaxShadowResolutionY ? MaxShadowResolutionY : RoundedDesiredResolution;

                if (ProjectedShadowInitializer.bOnePassPointLightShadow)
                {
                    // Round to a resolution that is supported for one pass point light shadows
                    SizeX = SizeY = SceneContext_ConstantsOnly.GetCubeShadowDepthZResolution(SceneContext_ConstantsOnly.GetCubeShadowDepthZIndex(MaxDesiredResolution));
                }

                int32 NumShadowMaps = 1;
                EShadowDepthCacheMode CacheMode[2] = { SDCM_Uncached, SDCM_Uncached };

                if (!bAnyViewIsSceneCapture && !ProjectedShadowInitializer.bRayTracedDistanceField)
                {
                    FIntPoint ShadowMapSize(SizeX + ShadowBorder * 2, SizeY + ShadowBorder * 2);

                    // 计算全景阴影的缓存模式, 包含阴影图的尺寸和数量等数据.
                    ComputeWholeSceneShadowCacheModes(
                        LightSceneInfo,
                        ProjectedShadowInitializer.bOnePassPointLightShadow,
                        ViewFamily.CurrentRealTime,
                        MaxDesiredResolution,
                        FIntPoint(MaxShadowResolution, MaxShadowResolutionY),
                        Scene,
                        // 下面是传入或传出参数, 可被接口内部改变.
                        ProjectedShadowInitializer,
                        ShadowMapSize,
                        InOutNumPointShadowCachesUpdatedThisFrame,
                        InOutNumSpotShadowCachesUpdatedThisFrame,
                        NumShadowMaps,
                        CacheMode);

                    SizeX = ShadowMapSize.X - ShadowBorder * 2;
                    SizeY = ShadowMapSize.Y - ShadowBorder * 2;
                }

                // 创建NumShadowMaps个阴影图, 每个阴影图的数据存于FProjectedShadowInfo实例中.
                for (int32 CacheModeIndex = 0; CacheModeIndex < NumShadowMaps; CacheModeIndex++)
                {
                    // 创建FProjectedShadowInfo实例.
                    FProjectedShadowInfo* ProjectedShadowInfo = new(FMemStack::Get(), 1, 16) FProjectedShadowInfo;

                    // 设置ProjectedShadowInfo的全景投射参数(视锥体边界, 变换矩阵, 深度, 深度偏移等).
                    ProjectedShadowInfo->SetupWholeSceneProjection(
                        LightSceneInfo,
                        NULL,
                        ProjectedShadowInitializer,
                        SizeX,
                        SizeY,
                        ShadowBorder,
                        false    // no RSM
                        );

                    ProjectedShadowInfo->CacheMode = CacheMode[CacheModeIndex];
                    ProjectedShadowInfo->FadeAlphas = FadeAlphas;

                    // 加入可见光源的投射阴影列表.
                    VisibleLightInfo.MemStackProjectedShadows.Add(ProjectedShadowInfo);

                    // 单通道点光源阴影.
                    if (ProjectedShadowInitializer.bOnePassPointLightShadow)
                    {
                        const static FVector CubeDirections[6] =
                        {
                            FVector(-1, 0, 0),
                            FVector(1, 0, 0),
                            FVector(0, -1, 0),
                            FVector(0, 1, 0),
                            FVector(0, 0, -1),
                            FVector(0, 0, 1)
                        };

                        const static FVector UpVectors[6] =
                        {
                            FVector(0, 1, 0),
                            FVector(0, 1, 0),
                            FVector(0, 0, -1),
                            FVector(0, 0, 1),
                            FVector(0, 1, 0),
                            FVector(0, 1, 0)
                        };

                        const FLightSceneProxy& LightProxy = *(ProjectedShadowInfo->GetLightSceneInfo().Proxy);

                        const FMatrix FaceProjection = FPerspectiveMatrix(PI / 4.0f, 1, 1, 1, LightProxy.GetRadius());
                        const FVector LightPosition = LightProxy.GetPosition();

                        ProjectedShadowInfo->OnePassShadowViewMatrices.Empty(6);
                        ProjectedShadowInfo->OnePassShadowViewProjectionMatrices.Empty(6);
                        ProjectedShadowInfo->OnePassShadowFrustums.Empty(6);
                        ProjectedShadowInfo->OnePassShadowFrustums.AddZeroed(6);
                        const FMatrix ScaleMatrix = FScaleMatrix(FVector(1, -1, 1));
                        
                        // 利用投射锥体和远平面填充所有(6个)面的数据.
                        ProjectedShadowInfo->CasterFrustum.Planes.Empty();
                        for (int32 FaceIndex = 0; FaceIndex < 6; FaceIndex++)
                        {
                            // 给每个面创建视图投影矩阵.
                            const FMatrix WorldToLightMatrix = FLookAtMatrix(LightPosition, LightPosition + CubeDirections[FaceIndex], UpVectors[FaceIndex]) * ScaleMatrix;
                            ProjectedShadowInfo->OnePassShadowViewMatrices.Add(WorldToLightMatrix);
                            const FMatrix ShadowViewProjectionMatrix = WorldToLightMatrix * FaceProjection;
                            ProjectedShadowInfo->OnePassShadowViewProjectionMatrices.Add(ShadowViewProjectionMatrix);
                            // 创建凸面体积包围锥体, 用来物体的快速裁剪.
                            GetViewFrustumBounds(ProjectedShadowInfo->OnePassShadowFrustums[FaceIndex], ShadowViewProjectionMatrix, false);

                            // 确保有有效的锥体.
                            if (ProjectedShadowInfo->OnePassShadowFrustums[FaceIndex].Planes.Num() > 0 )
                            {
                                // 假设了最后那个面是远平面, 须包含PreShadowTranslation
                                FPlane Src = ProjectedShadowInfo->OnePassShadowFrustums[FaceIndex].Planes.Last();
                                // add world space preview translation
                                Src.W += (FVector(Src) | ProjectedShadowInfo->PreShadowTranslation);
                                ProjectedShadowInfo->CasterFrustum.Planes.Add(Src);
                            }
                        }
                        // 初始化投射锥体.
                        ProjectedShadowInfo->CasterFrustum.Init();
                    }

                    // 对非光追距离场阴影, 执行CPU侧裁剪.
                    if (!ProjectedShadowInfo->bRayTracedDistanceField)
                    {
                        // 构建光源视图的凸面体, 以裁剪阴影投射.
                        FLightViewFrustumConvexHulls LightViewFrustumConvexHulls;
                        if (CacheMode[CacheModeIndex] != SDCM_StaticPrimitivesOnly)
                        {
                            FVector const& LightOrigin = LightSceneInfo->Proxy->GetOrigin();
                            BuildLightViewFrustumConvexHulls(LightOrigin, Views, LightViewFrustumConvexHulls);
                        }

                        bool bCastCachedShadowFromMovablePrimitives = GCachedShadowsCastFromMovablePrimitives || LightSceneInfo->Proxy->GetForceCachedShadowsForMovablePrimitives();
                        if (CacheMode[CacheModeIndex] != SDCM_StaticPrimitivesOnly 
                            && (CacheMode[CacheModeIndex] != SDCM_MovablePrimitivesOnly || bCastCachedShadowFromMovablePrimitives))
                        {
                            // 将所有受光源影响的图元加入到受影响的图元列表(subject primitive list).
                            for (FLightPrimitiveInteraction* Interaction = LightSceneInfo->GetDynamicInteractionOftenMovingPrimitiveList(false);
                                Interaction;
                                Interaction = Interaction->GetNextPrimitive())
                            {
                                // 投射阴影且非自阴影(InsetShader)且和光源相交的图元才加入subject primitive list.
                                if (Interaction->HasShadow()
                                    && !Interaction->CastsSelfShadowOnly()
                                    && (!bStaticSceneOnly || Interaction->GetPrimitiveSceneInfo()->Proxy->HasStaticLighting()))
                                {
                                    FBoxSphereBounds const& Bounds = Interaction->GetPrimitiveSceneInfo()->Proxy->GetBounds();
                                    if (IntersectsConvexHulls(LightViewFrustumConvexHulls, Bounds))
                                    {
                                        ProjectedShadowInfo->AddSubjectPrimitive(Interaction->GetPrimitiveSceneInfo(), &Views, FeatureLevel, false);
                                    }
                                }
                            }
                        }
                        
                        if (CacheMode[CacheModeIndex] != SDCM_MovablePrimitivesOnly)
                        {
                            // 将所有受阴影投射锥体影响的图元添加到SubjectPrimitiveList.
                            for (FLightPrimitiveInteraction* Interaction = LightSceneInfo->GetDynamicInteractionStaticPrimitiveList(false);
                                Interaction;
                                Interaction = Interaction->GetNextPrimitive())
                            {
                                if (Interaction->HasShadow()
                                    && !Interaction->CastsSelfShadowOnly()
                                    && (!bStaticSceneOnly || Interaction->GetPrimitiveSceneInfo()->Proxy->HasStaticLighting()))
                                {
                                    FBoxSphereBounds const& Bounds = Interaction->GetPrimitiveSceneInfo()->Proxy->GetBounds();
                                    if (IntersectsConvexHulls(LightViewFrustumConvexHulls, Bounds))
                                    {
                                        ProjectedShadowInfo->AddSubjectPrimitive(Interaction->GetPrimitiveSceneInfo(), &Views, FeatureLevel, false);
                                    }
                                }
                            }
                        }
                    }

                    bool bRenderShadow = true;
                    
                    // 缓存模式是否只有静态图元.
                    if (CacheMode[CacheModeIndex] == SDCM_StaticPrimitivesOnly)
                    {
                        const bool bHasStaticPrimitives = ProjectedShadowInfo->HasSubjectPrims();
                        // 静态阴影不需要渲染.
                        bRenderShadow = bHasStaticPrimitives;
                        FCachedShadowMapData& CachedShadowMapData = Scene->CachedShadowMaps.FindChecked(ProjectedShadowInfo->GetLightSceneInfo().Id);
                        CachedShadowMapData.bCachedShadowMapHasPrimitives = bHasStaticPrimitives;
                    }

                    // 需要渲染阴影则添加到VisibleLightInfo.AllProjectedShadows列表中.
                    if (bRenderShadow)
                    {
                        VisibleLightInfo.AllProjectedShadows.Add(ProjectedShadowInfo);
                    }
                }
            }
        }
    }
}

5.6.3.3 AddViewDependentWholeSceneShadowsForView

下面将分析AddViewDependentWholeSceneShadowsForView

void FSceneRenderer::AddViewDependentWholeSceneShadowsForView(
    TArray<FProjectedShadowInfo*, SceneRenderingAllocator>& ShadowInfos, 
    TArray<FProjectedShadowInfo*, SceneRenderingAllocator>& ShadowInfosThatNeedCulling,
    FVisibleLightInfo& VisibleLightInfo, 
    FLightSceneInfo& LightSceneInfo)
{
    // 遍历所有view, 给每个view创建view关联的全景阴影.
    for (int32 ViewIndex = 0; ViewIndex < Views.Num(); ViewIndex++)
    {
        FViewInfo& View = Views[ViewIndex];

        const float LightShadowAmount = LightSceneInfo.Proxy->GetShadowAmount();
        TArray<float, TInlineAllocator<2> > FadeAlphas;
        FadeAlphas.Init(0.0f, Views.Num());
        FadeAlphas[ViewIndex] = LightShadowAmount;    
        
        (......)

        // 如果是主视图, 处理投射阴影.
        if (IStereoRendering::IsAPrimaryView(View))
        {
            const bool bExtraDistanceFieldCascade = LightSceneInfo.Proxy->ShouldCreateRayTracedCascade(View.GetFeatureLevel(), LightSceneInfo.IsPrecomputedLightingValid(), View.MaxShadowCascades);

            // 获取视图相关的投影数量.
            const int32 ProjectionCount = LightSceneInfo.Proxy->GetNumViewDependentWholeSceneShadows(View, LightSceneInfo.IsPrecomputedLightingValid()) + (bExtraDistanceFieldCascade?1:0);

            FSceneRenderTargets& SceneContext_ConstantsOnly = FSceneRenderTargets::Get_FrameConstantsOnly();

            // 根据投影数量, 创建投射阴影初始化器和对应的FProjectedShadowInfo.
            for (int32 Index = 0; Index < ProjectionCount; Index++)
            {
                FWholeSceneProjectedShadowInitializer ProjectedShadowInitializer;

                int32 LocalIndex = Index;

                // Indexing like this puts the ray traced shadow cascade last (might not be needed)
                if(bExtraDistanceFieldCascade && LocalIndex + 1 == ProjectionCount)
                {
                    LocalIndex = INDEX_NONE;
                }

                if (LightSceneInfo.Proxy->GetViewDependentWholeSceneProjectedShadowInitializer(View, LocalIndex, LightSceneInfo.IsPrecomputedLightingValid(), ProjectedShadowInitializer))
                {
                    const FIntPoint ShadowBufferResolution(
                    FMath::Clamp(GetCachedScalabilityCVars().MaxCSMShadowResolution, 1, (int32)GMaxShadowDepthBufferSizeX),
                    FMath::Clamp(GetCachedScalabilityCVars().MaxCSMShadowResolution, 1, (int32)GMaxShadowDepthBufferSizeY));

                    // 创建FProjectedShadowInfo实例.
                    FProjectedShadowInfo* ProjectedShadowInfo = new(FMemStack::Get(), 1, 16) FProjectedShadowInfo;
                    // 阴影边界.
                    uint32 ShadowBorder = NeedsUnatlasedCSMDepthsWorkaround(FeatureLevel) ? 0 : SHADOW_BORDER;
                    // 设置全景投影.
                    ProjectedShadowInfo->SetupWholeSceneProjection(
                        &LightSceneInfo,
                        &View,
                        ProjectedShadowInitializer,
                        ShadowBufferResolution.X - ShadowBorder * 2,
                        ShadowBufferResolution.Y - ShadowBorder * 2,
                        ShadowBorder,
                        false    // no RSM
                        );

                    ProjectedShadowInfo->FadeAlphas = FadeAlphas;

                    // 将ProjectedShadowInfo添加到可见光源信息的相关列表中.
                    FVisibleLightInfo& LightViewInfo = VisibleLightInfos[LightSceneInfo.Id];
                    VisibleLightInfo.MemStackProjectedShadows.Add(ProjectedShadowInfo);
                    VisibleLightInfo.AllProjectedShadows.Add(ProjectedShadowInfo);
                    ShadowInfos.Add(ProjectedShadowInfo);

                    // 添加到待裁剪阴影列表.
                    if (!ProjectedShadowInfo->bRayTracedDistanceField)
                    {
                        ShadowInfosThatNeedCulling.Add(ProjectedShadowInfo);
                    }
                }
            }

            // 处理RSM(Refletive Shadow Map), 用于LPV光照.
            FSceneViewState* ViewState = (FSceneViewState*)View.State;
            if (ViewState)
            {
                (......)
            }
        }
    }
}

5.6.3.4 SetupInteractionShadows

接着继续分析SetupInteractionShadows

void FSceneRenderer::SetupInteractionShadows(
    FRHICommandListImmediate& RHICmdList,
    FLightPrimitiveInteraction* Interaction, 
    FVisibleLightInfo& VisibleLightInfo, 
    bool bStaticSceneOnly,
    const TArray<FProjectedShadowInfo*,SceneRenderingAllocator>& ViewDependentWholeSceneShadows,
    TArray<FProjectedShadowInfo*,SceneRenderingAllocator>& PreShadows)
{
    FPrimitiveSceneInfo* PrimitiveSceneInfo = Interaction->GetPrimitiveSceneInfo();
    FLightSceneProxy* LightProxy = Interaction->GetLight()->Proxy;
    extern bool GUseTranslucencyShadowDepths;

    bool bShadowHandledByParent = false;

    // 处理光源附加根组件, 如果存在附加根节点, 置bShadowHandledByParent为true.
    if (PrimitiveSceneInfo->LightingAttachmentRoot.IsValid())
    {
        FAttachmentGroupSceneInfo& AttachmentGroup = Scene->AttachmentGroups.FindChecked(PrimitiveSceneInfo->LightingAttachmentRoot);
        bShadowHandledByParent = AttachmentGroup.ParentSceneInfo && AttachmentGroup.ParentSceneInfo->Proxy->LightAttachmentsAsGroup();
    }

    // bShadowHandledByParent的阴影会被父节点组件处理.
    if (!bShadowHandledByParent)
    {
        const bool bCreateTranslucentObjectShadow = GUseTranslucencyShadowDepths && Interaction->HasTranslucentObjectShadow();
        const bool bCreateInsetObjectShadow = Interaction->HasInsetObjectShadow();
        const bool bCreateObjectShadowForStationaryLight = ShouldCreateObjectShadowForStationaryLight(Interaction->GetLight(), PrimitiveSceneInfo->Proxy, Interaction->IsShadowMapped());

        if (Interaction->HasShadow() 
            && (!bStaticSceneOnly || PrimitiveSceneInfo->Proxy->HasStaticLighting())
            && (bCreateTranslucentObjectShadow || bCreateInsetObjectShadow || bCreateObjectShadowForStationaryLight))
        {
            // 创建逐物体阴影.
            CreatePerObjectProjectedShadow(RHICmdList, Interaction, bCreateTranslucentObjectShadow, bCreateInsetObjectShadow || bCreateObjectShadowForStationaryLight, ViewDependentWholeSceneShadows, PreShadows);
        }
    }
}

5.6.3.5 CreatePerObjectProjectedShadow

上面代码涉及了CreatePerObjectProjectedShadow,进入其实现代码分析:

void FSceneRenderer::CreatePerObjectProjectedShadow(
    FRHICommandListImmediate& RHICmdList,
    FLightPrimitiveInteraction* Interaction, 
    bool bCreateTranslucentObjectShadow, 
    bool bCreateOpaqueObjectShadow,
    const TArray<FProjectedShadowInfo*,SceneRenderingAllocator>& ViewDependentWholeSceneShadows,
    TArray<FProjectedShadowInfo*, SceneRenderingAllocator>& OutPreShadows)
{
    FPrimitiveSceneInfo* PrimitiveSceneInfo = Interaction->GetPrimitiveSceneInfo();
    const int32 PrimitiveId = PrimitiveSceneInfo->GetIndex();

    FLightSceneInfo* LightSceneInfo = Interaction->GetLight();
    FVisibleLightInfo& VisibleLightInfo = VisibleLightInfos[LightSceneInfo->Id];

    // 检测阴影是否在任意一个view内可见.
    bool bShadowIsPotentiallyVisibleNextFrame = false;
    bool bOpaqueShadowIsVisibleThisFrame = false;
    bool bSubjectIsVisible = false;
    bool bOpaque = false;
    bool bTranslucentRelevance = false;
    bool bTranslucentShadowIsVisibleThisFrame = false;
    int32 NumBufferedFrames = FOcclusionQueryHelpers::GetNumBufferedFrames(FeatureLevel);

    for (int32 ViewIndex = 0; ViewIndex < Views.Num(); ViewIndex++)
    {
        const FViewInfo& View = Views[ViewIndex];

        // 获取图元的缓存ViewRelevance.
        FPrimitiveViewRelevance ViewRelevance = View.PrimitiveViewRelevanceMap[PrimitiveId];

        if (!ViewRelevance.bInitializedThisFrame)
        {
            // Compute the subject primitive's view relevance since it wasn't cached
            ViewRelevance = PrimitiveSceneInfo->Proxy->GetViewRelevance(&View);
        }

        // Check if the subject primitive is shadow relevant.
        const bool bPrimitiveIsShadowRelevant = ViewRelevance.bShadowRelevance;

        // 不透明物体阴影键值, 依次是: 图元id, 光源组件地址, 阴影拆分后的索引, 是否透明阴影.
        const FSceneViewState::FProjectedShadowKey OpaqueKey(PrimitiveSceneInfo->PrimitiveComponentId, LightSceneInfo->Proxy->GetLightComponent(), INDEX_NONE, false);

        // 检测不透明物体阴影或预阴影是否被遮挡.
        const bool bOpaqueShadowIsOccluded = 
            !bCreateOpaqueObjectShadow ||
            (
                !View.bIgnoreExistingQueries &&    View.State &&
                ((FSceneViewState*)View.State)->IsShadowOccluded(RHICmdList, OpaqueKey, NumBufferedFrames)
            );

        // 透明物体阴影排序键值.
        const FSceneViewState::FProjectedShadowKey TranslucentKey(PrimitiveSceneInfo->PrimitiveComponentId, LightSceneInfo->Proxy->GetLightComponent(), INDEX_NONE, true);

        // 检测透明物体阴影或预阴影是否被遮挡.
        const bool bTranslucentShadowIsOccluded = 
            !bCreateTranslucentObjectShadow ||
            (
                !View.bIgnoreExistingQueries && View.State &&
                ((FSceneViewState*)View.State)->IsShadowOccluded(RHICmdList, TranslucentKey, NumBufferedFrames)
            );

        // 忽略不在主Pass渲染的图元.
        if (PrimitiveSceneInfo->Proxy->ShouldRenderInMainPass())
        {
            const bool bSubjectIsVisibleInThisView = View.PrimitiveVisibilityMap[PrimitiveSceneInfo->GetIndex()];
            bSubjectIsVisible |= bSubjectIsVisibleInThisView;
        }

        // 阴影如果与视图关联且未被遮挡, 则视为可见.
        bOpaqueShadowIsVisibleThisFrame |= (bPrimitiveIsShadowRelevant && !bOpaqueShadowIsOccluded);
        bTranslucentShadowIsVisibleThisFrame |= (bPrimitiveIsShadowRelevant && !bTranslucentShadowIsOccluded);
        bShadowIsPotentiallyVisibleNextFrame |= bPrimitiveIsShadowRelevant;
        bOpaque |= ViewRelevance.bOpaque;
        bTranslucentRelevance |= ViewRelevance.HasTranslucency();
    } // for

    // 如果本帧不可见且下一帧也没有潜在可见, 则直接返回, 以跳过后续的阴影创建和设置.
    if (!bOpaqueShadowIsVisibleThisFrame && !bTranslucentShadowIsVisibleThisFrame && !bShadowIsPotentiallyVisibleNextFrame)
    {
        return;
    }

    // 收集阴影组图元.
    TArray<FPrimitiveSceneInfo*, SceneRenderingAllocator> ShadowGroupPrimitives;
    PrimitiveSceneInfo->GatherLightingAttachmentGroupPrimitives(ShadowGroupPrimitives);

#if ENABLE_NAN_DIAGNOSTIC
    // 没有有效的阴影组图元, 直接返回.
    if (ShadowGroupPrimitives.Num() == 0)
    {
        return;
    }
#endif

    // 计算该组的阴影图元的组合包围盒.
    FBoxSphereBounds OriginalBounds = ShadowGroupPrimitives[0]->Proxy->GetBounds();
    // 修正非法的包围盒.
    if (!ensureMsgf(OriginalBounds.ContainsNaN() == false, TEXT("OriginalBound contains NaN : %s"), *OriginalBounds.ToString()))
    {
        OriginalBounds = FBoxSphereBounds(FVector::ZeroVector, FVector(1.f), 1.f);
    }
    for (int32 ChildIndex = 1; ChildIndex < ShadowGroupPrimitives.Num(); ChildIndex++)
    {
        const FPrimitiveSceneInfo* ShadowChild = ShadowGroupPrimitives[ChildIndex];
        if (ShadowChild->Proxy->CastsDynamicShadow())
        {
            FBoxSphereBounds ChildBound = ShadowChild->Proxy->GetBounds();
            OriginalBounds = OriginalBounds + ChildBound;

            if (!ensureMsgf(OriginalBounds.ContainsNaN() == false, TEXT("Child %s contains NaN : %s"), *ShadowChild->Proxy->GetOwnerName().ToString(), *ChildBound.ToString()))
            {
                // fix up OriginalBounds. This is going to cause flickers
                OriginalBounds = FBoxSphereBounds(FVector::ZeroVector, FVector(1.f), 1.f);
            }
        }
    }

    // 下面的代码和CreateWholeSceneProjectedShadow比较相似, 将省略分析...
    FSceneRenderTargets& SceneContext = FSceneRenderTargets::Get(RHICmdList);
    
    // 阴影常量.
    const uint32 MaxShadowResolutionSetting = GetCachedScalabilityCVars().MaxShadowResolution;
    const FIntPoint ShadowBufferResolution = SceneContext.GetShadowDepthTextureResolution();
    const uint32 MaxShadowResolution = FMath::Min<int32>(MaxShadowResolutionSetting, ShadowBufferResolution.X) - SHADOW_BORDER * 2;
    const uint32 MaxShadowResolutionY = FMath::Min<int32>(MaxShadowResolutionSetting, ShadowBufferResolution.Y) - SHADOW_BORDER * 2;
    const uint32 MinShadowResolution     = FMath::Max<int32>(0, CVarMinShadowResolution.GetValueOnRenderThread());
    const uint32 ShadowFadeResolution    = FMath::Max<int32>(0, CVarShadowFadeResolution.GetValueOnRenderThread());
    const uint32 MinPreShadowResolution  = FMath::Max<int32>(0, CVarMinPreShadowResolution.GetValueOnRenderThread());
    const uint32 PreShadowFadeResolution = FMath::Max<int32>(0, CVarPreShadowFadeResolution.GetValueOnRenderThread());
    
    // 阴影最大分辨率.
    uint32 MaxDesiredResolution = 0;
    float MaxScreenPercent = 0;
    TArray<float, TInlineAllocator<2> > ResolutionFadeAlphas;
    TArray<float, TInlineAllocator<2> > ResolutionPreShadowFadeAlphas;
    float MaxResolutionFadeAlpha = 0;
    float MaxResolutionPreShadowFadeAlpha = 0;

    (......)

    FBoxSphereBounds Bounds = OriginalBounds;

    // 是否渲染预阴影(阴影缓存).
    const bool bRenderPreShadow = 
        CVarAllowPreshadows.GetValueOnRenderThread() 
        && LightSceneInfo->Proxy->HasStaticShadowing()
        && bSubjectIsVisible 
        && (!PrimitiveSceneInfo->Proxy->HasStaticLighting() || !Interaction->IsShadowMapped())
        && !(PrimitiveSceneInfo->Proxy->UseSingleSampleShadowFromStationaryLights() && LightSceneInfo->Proxy->GetLightType() == LightType_Directional);

    // 如果需要渲染预阴影, 则扩大包围盒, 以提升缓存利用率.
    if (bRenderPreShadow && ShouldUseCachePreshadows())
    {
        float PreshadowExpandFraction = FMath::Max(CVarPreshadowExpandFraction.GetValueOnRenderThread(), 0.0f);
        Bounds.SphereRadius += (Bounds.BoxExtent * PreshadowExpandFraction).Size();
        Bounds.BoxExtent *= PreshadowExpandFraction + 1.0f;
    }

    // 阴影初始化器.
    FPerObjectProjectedShadowInitializer ShadowInitializer;

    if ((MaxResolutionFadeAlpha > 1.0f / 256.0f || (bRenderPreShadow && MaxResolutionPreShadowFadeAlpha > 1.0f / 256.0f))
        && LightSceneInfo->Proxy->GetPerObjectProjectedShadowInitializer(Bounds, ShadowInitializer))
    {
        const float MaxFadeAlpha = MaxResolutionFadeAlpha;

        // 没有完全过渡掉的阴影才需要创建阴影投射实例.
        if (CVarAllowPerObjectShadows.GetValueOnRenderThread() && MaxFadeAlpha > 1.0f / 256.0f)
        {
            const int32 SizeX = MaxDesiredResolution >= MaxShadowResolution ? MaxShadowResolution : (1 << (FMath::CeilLogTwo(MaxDesiredResolution) - 1));

            if (bOpaque && bCreateOpaqueObjectShadow && (bOpaqueShadowIsVisibleThisFrame || bShadowIsPotentiallyVisibleNextFrame))
            {
                // 创建FProjectedShadowInfo实例.
                FProjectedShadowInfo* ProjectedShadowInfo = new(FMemStack::Get(),1,16) FProjectedShadowInfo;

                if(ProjectedShadowInfo->SetupPerObjectProjection(
                    LightSceneInfo,
                    PrimitiveSceneInfo,
                    ShadowInitializer,
                    false,                    // no preshadow
                    SizeX,
                    MaxShadowResolutionY,
                    SHADOW_BORDER,
                    MaxScreenPercent,
                    false))                    // no translucent shadow
                {
                    ProjectedShadowInfo->bPerObjectOpaqueShadow = true;
                    ProjectedShadowInfo->FadeAlphas = ResolutionFadeAlphas;
                    VisibleLightInfo.MemStackProjectedShadows.Add(ProjectedShadowInfo);

                    // 将ProjectedShadowInfo添加到对应的列表.
                    if (bOpaqueShadowIsVisibleThisFrame)
                    {
                        VisibleLightInfo.AllProjectedShadows.Add(ProjectedShadowInfo);

                        for (int32 ChildIndex = 0, ChildCount = ShadowGroupPrimitives.Num(); ChildIndex < ChildCount; ChildIndex++)
                        {
                            FPrimitiveSceneInfo* ShadowChild = ShadowGroupPrimitives[ChildIndex];
                            ProjectedShadowInfo->AddSubjectPrimitive(ShadowChild, &Views, FeatureLevel, false);
                        }
                    }
                    else if (bShadowIsPotentiallyVisibleNextFrame)
                    {
                        VisibleLightInfo.OccludedPerObjectShadows.Add(ProjectedShadowInfo);
                    }
                }
            }

            // 半透明物体阴影.
            if (bTranslucentRelevance
                && Scene->GetFeatureLevel() >= ERHIFeatureLevel::SM5
                && bCreateTranslucentObjectShadow 
                && (bTranslucentShadowIsVisibleThisFrame || bShadowIsPotentiallyVisibleNextFrame))
            {
                (......)
            }
        }

        const float MaxPreFadeAlpha = MaxResolutionPreShadowFadeAlpha;

        // 处理有效的预阴影.
        if (MaxPreFadeAlpha > 1.0f / 256.0f 
            && bRenderPreShadow
            && bOpaque
            && Scene->GetFeatureLevel() >= ERHIFeatureLevel::SM5)
        {
            int32 PreshadowSizeX = 1 << (FMath::CeilLogTwo(FMath::TruncToInt(MaxDesiredResolution * CVarPreShadowResolutionFactor.GetValueOnRenderThread())) - 1);

            const FIntPoint PreshadowCacheResolution = SceneContext.GetPreShadowCacheTextureResolution();
            
            // 检测是否在全景阴影之内.
            bool bIsOutsideWholeSceneShadow = true;
            for (int32 i = 0; i < ViewDependentWholeSceneShadows.Num(); i++)
            {
                const FProjectedShadowInfo* WholeSceneShadow = ViewDependentWholeSceneShadows[i];
                const FVector2D DistanceFadeValues = WholeSceneShadow->GetLightSceneInfo().Proxy->GetDirectionalLightDistanceFadeParameters(Scene->GetFeatureLevel(), WholeSceneShadow->GetLightSceneInfo().IsPrecomputedLightingValid(), WholeSceneShadow->DependentView->MaxShadowCascades);
                const float DistanceFromShadowCenterSquared = (WholeSceneShadow->ShadowBounds.Center - Bounds.Origin).SizeSquared();
                const float DistanceFromViewSquared = ((FVector)WholeSceneShadow->DependentView->ShadowViewMatrices.GetViewOrigin() - Bounds.Origin).SizeSquared();
                // 如果preshadow的球体包围盒在近过渡距离之内, 则表示它在全景阴影内.
                if (DistanceFromShadowCenterSquared < FMath::Square(FMath::Max(WholeSceneShadow->ShadowBounds.W - Bounds.SphereRadius, 0.0f))
                    && DistanceFromViewSquared < FMath::Square(FMath::Max(DistanceFadeValues.X - 200.0f - Bounds.SphereRadius, 0.0f)))
                {
                    bIsOutsideWholeSceneShadow = false;
                    break;
                }
            }

            // 只有部分阴影投射者在全景阴影之外才创建不透明preshadow.
            if (bIsOutsideWholeSceneShadow)
            {
                // 尝试从缓存中重用preshadow.
                TRefCountPtr<FProjectedShadowInfo> ProjectedPreShadowInfo = GetCachedPreshadow(Interaction, ShadowInitializer, OriginalBounds, PreshadowSizeX);

                bool bOk = true;

                // 创建和设置ProjectedPreShadowInfo.
                if(!ProjectedPreShadowInfo)
                {
                    ProjectedPreShadowInfo = new FProjectedShadowInfo;

                    bOk = ProjectedPreShadowInfo->SetupPerObjectProjection(
                        LightSceneInfo,
                        PrimitiveSceneInfo,
                        ShadowInitializer,
                        true,                // preshadow
                        PreshadowSizeX,
                        FMath::TruncToInt(MaxShadowResolutionY * CVarPreShadowResolutionFactor.GetValueOnRenderThread()),
                        SHADOW_BORDER,
                        MaxScreenPercent,
                        false                // not translucent shadow
                        );
                }

                // 继续设置有效的ProjectedPreShadowInfo的其它数据, 并添加到VisibleLightInfo相关列表中.
                if (bOk)
                {
                    ProjectedPreShadowInfo->FadeAlphas = ResolutionPreShadowFadeAlphas;

                    VisibleLightInfo.AllProjectedShadows.Add(ProjectedPreShadowInfo);
                    VisibleLightInfo.ProjectedPreShadows.Add(ProjectedPreShadowInfo);

                    // 如果preshadow没有深度缓存, 则将它加进OutPreShadows列表中. OutPreShadows用于仅生成在渲染阴影图所需的信息.
                    if (!ProjectedPreShadowInfo->bDepthsCached && ProjectedPreShadowInfo->CasterFrustum.PermutedPlanes.Num())
                    {
                        OutPreShadows.Add(ProjectedPreShadowInfo);
                    }

                    // 将所有可见的图元加进阴影的接收图元列表.
                    for (int32 ChildIndex = 0; ChildIndex < ShadowGroupPrimitives.Num(); ChildIndex++)
                    {
                        FPrimitiveSceneInfo* ShadowChild = ShadowGroupPrimitives[ChildIndex];
                        bool bChildIsVisibleInAnyView = false;
                        for (int32 ViewIndex = 0; ViewIndex < Views.Num(); ViewIndex++)
                        {
                            const FViewInfo& View = Views[ViewIndex];
                            if (View.PrimitiveVisibilityMap[ShadowChild->GetIndex()])
                            {
                                bChildIsVisibleInAnyView = true;
                                break;
                            }
                        }
                        if (bChildIsVisibleInAnyView)
                        {
                            ProjectedPreShadowInfo->AddReceiverPrimitive(ShadowChild);
                        }
                    }
                }
            }
        }
    }
}

5.6.3.6 InitProjectedShadowVisibility

阴影的可见性初始化由InitProjectedShadowVisibility担当,它的部分代码和解析如下:

void FSceneRenderer::InitProjectedShadowVisibility(FRHICommandListImmediate& RHICmdList)
{
    int32 NumBufferedFrames = FOcclusionQueryHelpers::GetNumBufferedFrames(FeatureLevel);

    // 遍历场景的所有光源, 初始化视图的ProjectedShadowVisibilityMaps, 删除没有主体图元的阴影.
    for(TSparseArray<FLightSceneInfoCompact>::TConstIterator LightIt(Scene->Lights);LightIt;++LightIt)
    {
        FVisibleLightInfo& VisibleLightInfo = VisibleLightInfos[LightIt.GetIndex()];

        // 分配视图内的投射阴影可见性和关联容器.
        for(int32 ViewIndex = 0;ViewIndex < Views.Num();ViewIndex++)
        {
            FViewInfo& View = Views[ViewIndex];
            FVisibleLightViewInfo& VisibleLightViewInfo = View.VisibleLightInfos[LightIt.GetIndex()];
            VisibleLightViewInfo.ProjectedShadowVisibilityMap.Init(false,VisibleLightInfo.AllProjectedShadows.Num());
            VisibleLightViewInfo.ProjectedShadowViewRelevanceMap.Empty(VisibleLightInfo.AllProjectedShadows.Num());
            VisibleLightViewInfo.ProjectedShadowViewRelevanceMap.AddZeroed(VisibleLightInfo.AllProjectedShadows.Num());
        }

        // 遍历可见光源的所有投射阴影实例.
        for( int32 ShadowIndex=0; ShadowIndex<VisibleLightInfo.AllProjectedShadows.Num(); ShadowIndex++ )
        {
            FProjectedShadowInfo& ProjectedShadowInfo = *VisibleLightInfo.AllProjectedShadows[ShadowIndex];

            // 保存阴影索引.
            ProjectedShadowInfo.ShadowId = ShadowIndex;

            for(int32 ViewIndex = 0;ViewIndex < Views.Num();ViewIndex++)
            {
                FViewInfo& View = Views[ViewIndex];

                // 处理视图关联的阴影.
                if (ProjectedShadowInfo.DependentView && ProjectedShadowInfo.DependentView != &View)
                {
                    (......)
                }

                FVisibleLightViewInfo& VisibleLightViewInfo = View.VisibleLightInfos[LightIt.GetIndex()];

                // 确保光源处于视锥体内.
                if(VisibleLightViewInfo.bInViewFrustum)
                {
                    // 计算主体图元的视图关联数据.
                    FPrimitiveViewRelevance ViewRelevance;
                    if(ProjectedShadowInfo.GetParentSceneInfo())
                    {
                        ViewRelevance = ProjectedShadowInfo.GetParentSceneInfo()->Proxy->GetViewRelevance(&View);
                    }
                    else
                    {
                        ViewRelevance.bDrawRelevance = ViewRelevance.bStaticRelevance = ViewRelevance.bDynamicRelevance = ViewRelevance.bShadowRelevance = true;
                    }                            
                    VisibleLightViewInfo.ProjectedShadowViewRelevanceMap[ShadowIndex] = ViewRelevance;

                    // Check if the subject primitive's shadow is view relevant.
                    const bool bPrimitiveIsShadowRelevant = ViewRelevance.bShadowRelevance;

                    // 判断阴影是否被遮挡.
                    bool bShadowIsOccluded = false;
                    if (!View.bIgnoreExistingQueries && View.State)
                    {
                        // Check if the shadow is occluded.
                        bShadowIsOccluded =
                            ((FSceneViewState*)View.State)->IsShadowOccluded(
                            RHICmdList,
                            FSceneViewState::FProjectedShadowKey(ProjectedShadowInfo),
                            NumBufferedFrames
                            );
                    }

                    // 如果符合可见性条件, 则设置标记
                    if(bPrimitiveIsShadowRelevant && !bShadowIsOccluded)
                    {
                        VisibleLightViewInfo.ProjectedShadowVisibilityMap[ShadowIndex] = true;
                    }

                    // 如果阴影可见且不是RSM阴影, 则绘制阴影锥体.
                    if(bPrimitiveIsShadowRelevant && !bShadowIsOccluded && !ProjectedShadowInfo.bReflectiveShadowmap)  
                    {
                        bool bDrawPreshadowFrustum = CVarDrawPreshadowFrustum.GetValueOnRenderThread() != 0;

                        // 绘制preshadow锥体.
                        if ((ViewFamily.EngineShowFlags.ShadowFrustums)
                            && ((bDrawPreshadowFrustum && ProjectedShadowInfo.bPreShadow) || (!bDrawPreshadowFrustum && !ProjectedShadowInfo.bPreShadow)))
                        {
                            // 绘制阴影锥体的PDI.
                            FViewElementPDI ShadowFrustumPDI(&Views[ViewIndex], nullptr, &Views[ViewIndex].DynamicPrimitiveShaderData);
                            
                            // 全景平行阴影才需要绘制锥体.
                            if(ProjectedShadowInfo.IsWholeSceneDirectionalShadow())
                            {
                                // Get split color
                                FColor Color = FColor::White;
                                switch(ProjectedShadowInfo.CascadeSettings.ShadowSplitIndex)
                                {
                                    case 0: Color = FColor::Red; break;
                                    case 1: Color = FColor::Yellow; break;
                                    case 2: Color = FColor::Green; break;
                                    case 3: Color = FColor::Blue; break;
                                }

                                const FMatrix ViewMatrix = View.ViewMatrices.GetViewMatrix();
                                const FMatrix ProjectionMatrix = View.ViewMatrices.GetProjectionMatrix();
                                const FVector4 ViewOrigin = View.ViewMatrices.GetViewOrigin();

                                float AspectRatio = ProjectionMatrix.M[1][1] / ProjectionMatrix.M[0][0];
                                float ActualFOV = (ViewOrigin.W > 0.0f) ? FMath::Atan(1.0f / ProjectionMatrix.M[0][0]) : PI/4.0f;

                                float Near = ProjectedShadowInfo.CascadeSettings.SplitNear;
                                float Mid = ProjectedShadowInfo.CascadeSettings.FadePlaneOffset;
                                float Far = ProjectedShadowInfo.CascadeSettings.SplitFar;

                                // 摄像机子锥体.
                                DrawFrustumWireframe(&ShadowFrustumPDI, (ViewMatrix * FPerspectiveMatrix(ActualFOV, AspectRatio, 1.0f, Near, Mid)).Inverse(), Color, 0);
                                DrawFrustumWireframe(&ShadowFrustumPDI, (ViewMatrix * FPerspectiveMatrix(ActualFOV, AspectRatio, 1.0f, Mid, Far)).Inverse(), FColor::White, 0);

                                // 阴影图投影包围盒.
                                DrawFrustumWireframe(&ShadowFrustumPDI, ProjectedShadowInfo.SubjectAndReceiverMatrix.Inverse() * FTranslationMatrix(-ProjectedShadowInfo.PreShadowTranslation), Color, 0);
                            }
                            else // 非全景阴影, 直接调用ProjectedShadowInfo的绘制接口.
                            {
                                ProjectedShadowInfo.RenderFrustumWireframe(&ShadowFrustumPDI);
                            }
                        }
                    }
                }
            }
        }
    }
    
    (......)
}

以上调用DrawFrustumWireframeRenderFrustumWireframe后并不是立即绘制,而是通过FViewElementPDI生成了FBatchedElements,然后将它们保存到view内,以便后续在阴影渲染阶段真正地执行绘制。

5.6.3.7 UpdatePreshadowCache

下面继续分析初始化阶段的UpdatePreshadowCache

void FSceneRenderer::UpdatePreshadowCache(FSceneRenderTargets& SceneContext)
{
    if (ShouldUseCachePreshadows() && !Views[0].bIsSceneCapture)
    {
        // 初始化纹理布局.
        if (Scene->PreshadowCacheLayout.GetSizeX() == 0)
        {
            const FIntPoint PreshadowCacheBufferSize = SceneContext.GetPreShadowCacheTextureResolution();
            Scene->PreshadowCacheLayout = FTextureLayout(1, 1, PreshadowCacheBufferSize.X, PreshadowCacheBufferSize.Y, false, ETextureLayoutAspectRatio::None, false);
        }

        // 遍历所有缓存的预阴影, 删除不在此帧渲染的实例.
        for (int32 CachedShadowIndex = Scene->CachedPreshadows.Num() - 1; CachedShadowIndex >= 0; CachedShadowIndex--)
        {
            TRefCountPtr<FProjectedShadowInfo> CachedShadow = Scene->CachedPreshadows[CachedShadowIndex];
            bool bShadowBeingRenderedThisFrame = false;

            for (int32 LightIndex = 0; LightIndex < VisibleLightInfos.Num() && !bShadowBeingRenderedThisFrame; LightIndex++)
            {
                bShadowBeingRenderedThisFrame = VisibleLightInfos[LightIndex].ProjectedPreShadows.Find(CachedShadow) != INDEX_NONE;
            }

            if (!bShadowBeingRenderedThisFrame)
            {
                Scene->CachedPreshadows.RemoveAt(CachedShadowIndex);
            }
        }

        TArray<TRefCountPtr<FProjectedShadowInfo>, SceneRenderingAllocator> UncachedPreShadows;
        
        // 收集可以被缓存的preshadow列表.
        for (int32 LightIndex = 0; LightIndex < VisibleLightInfos.Num(); LightIndex++)
        {
            for (int32 ShadowIndex = 0; ShadowIndex < VisibleLightInfos[LightIndex].ProjectedPreShadows.Num(); ShadowIndex++)
            {
                TRefCountPtr<FProjectedShadowInfo> CurrentShadow = VisibleLightInfos[LightIndex].ProjectedPreShadows[ShadowIndex];
                checkSlow(CurrentShadow->bPreShadow);

                if (!CurrentShadow->bAllocatedInPreshadowCache)
                {
                    UncachedPreShadows.Add(CurrentShadow);
                }
            }
        }

        // 对preshadow从大到小排序, 假设更大的preshadow在渲染深度时会有更多的物体.
        UncachedPreShadows.Sort(FComparePreshadows());

        for (int32 ShadowIndex = 0; ShadowIndex < UncachedPreShadows.Num(); ShadowIndex++)
        {
            TRefCountPtr<FProjectedShadowInfo> CurrentShadow = UncachedPreShadows[ShadowIndex];
            // 尝试从纹理布局中给preshadow找到空间, 若找到, 则设置相关数据.
            if (Scene->PreshadowCacheLayout.AddElement(CurrentShadow->X, CurrentShadow->Y, CurrentShadow->ResolutionX + CurrentShadow->BorderSize * 2, CurrentShadow->ResolutionY + CurrentShadow->BorderSize * 2))
            {
                CurrentShadow->bAllocatedInPreshadowCache = true;
                CurrentShadow->bAllocated = true;
                Scene->CachedPreshadows.Add(CurrentShadow);
            }
        }
    }
}

5.6.3.8 GatherShadowPrimitives

接下来分析初始化阶段的GatherShadowPrimitives

void FSceneRenderer::GatherShadowPrimitives(const TArray<FProjectedShadowInfo*, SceneRenderingAllocator>& PreShadows, const TArray<FProjectedShadowInfo*, SceneRenderingAllocator>& ViewDependentWholeSceneShadows, bool bStaticSceneOnly)
{
    // 存在预阴影或视图关联全景阴影.
    if (PreShadows.Num() || ViewDependentWholeSceneShadows.Num())
    {
        TArray<FGatherShadowPrimitivesPacket*,SceneRenderingAllocator> Packets;

        // 八叉树遍历裁剪阴影.
        if (GUseOctreeForShadowCulling)
        {
            Packets.Reserve(100);

            // 查找和位于八叉树的阴影锥体相交的图元.
            for(FScenePrimitiveOctree::TConstIterator<SceneRenderingAllocator> PrimitiveOctreeIt(Scene->PrimitiveOctree); PrimitiveOctreeIt.HasPendingNodes(); PrimitiveOctreeIt.Advance())
            {
                const FScenePrimitiveOctree::FNode& PrimitiveOctreeNode = PrimitiveOctreeIt.GetCurrentNode();
                const FOctreeNodeContext& PrimitiveOctreeNodeContext = PrimitiveOctreeIt.GetCurrentContext();
                
                {
                    // 查找八叉树节点可能包含关联图元的孩子节点.
                    FOREACH_OCTREE_CHILD_NODE(ChildRef)
                    {
                        if(PrimitiveOctreeNode.HasChild(ChildRef))
                        {
                            // 检查孩子节点是否至少在一个阴影内.
                            const FOctreeNodeContext ChildContext = PrimitiveOctreeNodeContext.GetChildContext(ChildRef);
                            bool bIsInFrustum = false;
                            
                            if(!bIsInFrustum)
                            {
                                // 遍历所有的preshadow, 判断孩子节点是否和preshadow相交.
                                for(int32 ShadowIndex = 0, Num = PreShadows.Num(); ShadowIndex < Num; ShadowIndex++)
                                {
                                    FProjectedShadowInfo* ProjectedShadowInfo = PreShadows[ShadowIndex];
                                    // 检测图元是否在阴影锥体内.
                                    if(ProjectedShadowInfo->CasterFrustum.IntersectBox(ChildContext.Bounds.Center + ProjectedShadowInfo->PreShadowTranslation, ChildContext.Bounds.Extent))
                                    {
                                        bIsInFrustum = true;
                                        break;
                                    }
                                }
                            }

                            // 如果还不在锥体内, 则让孩子节点的包围盒和视图相关的全景阴影执行相交检测.
                            if (!bIsInFrustum)
                            {
                                for(int32 ShadowIndex = 0, Num = ViewDependentWholeSceneShadows.Num(); ShadowIndex < Num; ShadowIndex++)
                                {
                                    FProjectedShadowInfo* ProjectedShadowInfo = ViewDependentWholeSceneShadows[ShadowIndex];

                                    //check(ProjectedShadowInfo->CasterFrustum.PermutedPlanes.Num());
                                    // Check if this primitive is in the shadow's frustum.
                                    if(ProjectedShadowInfo->CasterFrustum.IntersectBox(
                                        ChildContext.Bounds.Center + ProjectedShadowInfo->PreShadowTranslation,
                                        ChildContext.Bounds.Extent
                                        ))
                                    {
                                        bIsInFrustum = true;
                                        break;
                                    }
                                }
                            }

                            // 如何图元和至少一个阴影相交, 则加入图元八叉树迭代器的待定节点堆栈(pending node stack)中.
                            if(bIsInFrustum)
                            {
                                PrimitiveOctreeIt.PushChild(ChildRef);
                            }
                        }
                    } // FOREACH_OCTREE_CHILD_NODE
                }

                if (PrimitiveOctreeNode.GetElementCount() > 0)
                {
                    FGatherShadowPrimitivesPacket* Packet = new(FMemStack::Get()) FGatherShadowPrimitivesPacket(Scene, Views, &PrimitiveOctreeNode, 0, 0, PreShadows, ViewDependentWholeSceneShadows, FeatureLevel, bStaticSceneOnly);
                    Packets.Add(Packet);
                }
            } // for
        }
        else // 非八叉树遍历.
        {
            const int32 PacketSize = CVarParallelGatherNumPrimitivesPerPacket.GetValueOnRenderThread();
            const int32 NumPackets = FMath::DivideAndRoundUp(Scene->Primitives.Num(), PacketSize);
            
            Packets.Reserve(NumPackets);

            // 非八叉树模式, 直接线性遍历所有图元, 添加同等数量的FGatherShadowPrimitivesPacket实例.
            for (int32 PacketIndex = 0; PacketIndex < NumPackets; PacketIndex++)
            {
                const int32 StartPrimitiveIndex = PacketIndex * PacketSize;
                const int32 NumPrimitives = FMath::Min(PacketSize, Scene->Primitives.Num() - StartPrimitiveIndex);
                FGatherShadowPrimitivesPacket* Packet = new(FMemStack::Get()) FGatherShadowPrimitivesPacket(Scene, Views, NULL, StartPrimitiveIndex, NumPrimitives, PreShadows, ViewDependentWholeSceneShadows, FeatureLevel, bStaticSceneOnly);
                Packets.Add(Packet);
            }
        }
        
        // 调用并行For过滤掉和阴影不相交的图元
        ParallelFor(Packets.Num(), 
            [&Packets](int32 Index)
            {
                Packets[Index]->AnyThreadTask();
            },
            !(FApp::ShouldUseThreadingForPerformance() && CVarParallelGatherShadowPrimitives.GetValueOnRenderThread() > 0) );

        // 释放资源.
        for (int32 PacketIndex = 0; PacketIndex < Packets.Num(); PacketIndex++)
        {
            FGatherShadowPrimitivesPacket* Packet = Packets[PacketIndex];
            Packet->RenderThreadFinalize();
            Packet->~FGatherShadowPrimitivesPacket();
        }
    }
}

5.6.3.9 FGatherShadowPrimitivesPacket

上面代码中调用Packets[Index]->AnyThreadTask()以收集阴影图元数据包,调用FilterPrimitiveForShadows过滤不受阴影影响的图元,调用Packet->RenderThreadFinalize()将收集的图元添加到对应的阴影列表中。下面分析它们的具体执行逻辑:

void FGatherShadowPrimitivesPacket::AnyThreadTask()
{
    if (Node) // 如果存在节点
    {
        // 利用八叉树过滤和收集受阴影影响的图元.
        for (FScenePrimitiveOctree::ElementConstIt NodePrimitiveIt(Node->GetElementIt()); NodePrimitiveIt; ++NodePrimitiveIt)
        {
            if (NodePrimitiveIt->PrimitiveFlagsCompact.bCastDynamicShadow)
            {
                FilterPrimitiveForShadows(NodePrimitiveIt->Bounds, NodePrimitiveIt->PrimitiveFlagsCompact, NodePrimitiveIt->PrimitiveSceneInfo, NodePrimitiveIt->Proxy);
            }
        }
    }
    else
    {
        // 利用信息包的索引范围遍历, 逐个去过滤和收集受阴影影响的图元.
        for (int32 PrimitiveIndex = StartPrimitiveIndex; PrimitiveIndex < StartPrimitiveIndex + NumPrimitives; PrimitiveIndex++)
        {
            FPrimitiveFlagsCompact PrimitiveFlagsCompact = Scene->PrimitiveFlagsCompact[PrimitiveIndex];

            if (PrimitiveFlagsCompact.bCastDynamicShadow)
            {
                FilterPrimitiveForShadows(Scene->PrimitiveBounds[PrimitiveIndex].BoxSphereBounds, PrimitiveFlagsCompact, Scene->Primitives[PrimitiveIndex], Scene->PrimitiveSceneProxies[PrimitiveIndex]);
            }
        }
    }
}

void FGatherShadowPrimitivesPacket::FilterPrimitiveForShadows(const FBoxSphereBounds& PrimitiveBounds, FPrimitiveFlagsCompact PrimitiveFlagsCompact, FPrimitiveSceneInfo* PrimitiveSceneInfo, FPrimitiveSceneProxy* PrimitiveProxy)
{
    // 检测图元是否受任意一个preshadow影响。
    if (PreShadows.Num() && PrimitiveFlagsCompact.bCastStaticShadow && PrimitiveFlagsCompact.bStaticLighting)
    {
        for (int32 ShadowIndex = 0, Num = PreShadows.Num(); ShadowIndex < Num; ShadowIndex++)
        {
            FProjectedShadowInfo* RESTRICT ProjectedShadowInfo = PreShadows[ShadowIndex];
            // 图元包围盒和阴影投射锥体相交测试.
            bool bInFrustum = ProjectedShadowInfo->CasterFrustum.IntersectBox(PrimitiveBounds.Origin, ProjectedShadowInfo->PreShadowTranslation, PrimitiveBounds.BoxExtent);
            // 在投影锥体内且相互影响的加入PreShadowSubjectPrimitives中.
            if (bInFrustum && ProjectedShadowInfo->GetLightSceneInfoCompact().AffectsPrimitive(PrimitiveBounds, PrimitiveProxy))
            {
                PreShadowSubjectPrimitives[ShadowIndex].Add(PrimitiveSceneInfo);
            }
        }
    }

    // 图元和全景阴影相交测试.
    for (int32 ShadowIndex = 0, Num = ViewDependentWholeSceneShadows.Num();ShadowIndex < Num;ShadowIndex++)
    {
        const FProjectedShadowInfo* RESTRICT ProjectedShadowInfo = ViewDependentWholeSceneShadows[ShadowIndex];
        const FLightSceneInfo& RESTRICT LightSceneInfo = ProjectedShadowInfo->GetLightSceneInfo();
        const FLightSceneProxy& RESTRICT LightProxy = *LightSceneInfo.Proxy;

        const FVector LightDirection = LightProxy.GetDirection();
        const FVector PrimitiveToShadowCenter = ProjectedShadowInfo->ShadowBounds.Center - PrimitiveBounds.Origin;
        // 投影图元的包围盒到光源向量上.
        const float ProjectedDistanceFromShadowOriginAlongLightDir = PrimitiveToShadowCenter | LightDirection;
        const float PrimitiveDistanceFromCylinderAxisSq = (-LightDirection * ProjectedDistanceFromShadowOriginAlongLightDir + PrimitiveToShadowCenter).SizeSquared();
        const float CombinedRadiusSq = FMath::Square(ProjectedShadowInfo->ShadowBounds.W + PrimitiveBounds.SphereRadius);

        // 检测图元是否在阴影的[圆柱体]内.
        if (PrimitiveDistanceFromCylinderAxisSq < CombinedRadiusSq
            && !(ProjectedDistanceFromShadowOriginAlongLightDir < 0 && PrimitiveToShadowCenter.SizeSquared() > CombinedRadiusSq)
            && ProjectedShadowInfo->CascadeSettings.ShadowBoundsAccurate.IntersectBox(PrimitiveBounds.Origin, PrimitiveBounds.BoxExtent))
        {
            // 为RSM执行距离裁剪.
            const float MinScreenRadiusForShadowCaster = ProjectedShadowInfo->bReflectiveShadowmap ? GMinScreenRadiusForShadowCasterRSM : GMinScreenRadiusForShadowCaster;

            // 屏幕空间尺寸裁剪
            bool bScreenSpaceSizeCulled = false;
            {
                const float DistanceSquared = (PrimitiveBounds.Origin - ProjectedShadowInfo->DependentView->ShadowViewMatrices.GetViewOrigin()).SizeSquared();
                bScreenSpaceSizeCulled = FMath::Square(PrimitiveBounds.SphereRadius) < FMath::Square(MinScreenRadiusForShadowCaster) * DistanceSquared * ProjectedShadowInfo->DependentView->LODDistanceFactorSquared;
            }

            // 是否计算嵌入阴影(InsetShadow).
            bool bCastsInsetShadows = PrimitiveProxy->CastsInsetShadow();
            // 处理图元的光源附加根组件.
            if (PrimitiveSceneInfo->LightingAttachmentRoot.IsValid())
            {
                FAttachmentGroupSceneInfo& AttachmentGroup = PrimitiveSceneInfo->Scene->AttachmentGroups.FindChecked(PrimitiveSceneInfo->LightingAttachmentRoot);
                bCastsInsetShadows = AttachmentGroup.ParentSceneInfo && AttachmentGroup.ParentSceneInfo->Proxy->CastsInsetShadow();
            }

            // 检测各种各样的条件判断, 所有条件通过后才加入ViewDependentWholeSceneShadowSubjectPrimitives列表.
            if (!bScreenSpaceSizeCulled
                && ProjectedShadowInfo->GetLightSceneInfoCompact().AffectsPrimitive(PrimitiveBounds, PrimitiveProxy)
                && (!LightProxy.HasStaticLighting() || (!LightSceneInfo.IsPrecomputedLightingValid() || LightProxy.UseCSMForDynamicObjects()))
                && !(ProjectedShadowInfo->bReflectiveShadowmap && !PrimitiveProxy->AffectsDynamicIndirectLighting())
                && (!bCastsInsetShadows || ProjectedShadowInfo->bReflectiveShadowmap)
                && !ShouldCreateObjectShadowForStationaryLight(&LightSceneInfo, PrimitiveProxy, true)
                && (!bStaticSceneOnly || PrimitiveProxy->HasStaticLighting())
                && (!LightProxy.UseCSMForDynamicObjects() || !PrimitiveProxy->HasStaticLighting()))
            {
                ViewDependentWholeSceneShadowSubjectPrimitives[ShadowIndex].Add(PrimitiveSceneInfo);
            }
        }
    }
}

void FGatherShadowPrimitivesPacket::RenderThreadFinalize()
{
    // 将每个PreShadow实例内的所有图元都加入该阴影实例中.
    for (int32 ShadowIndex = 0; ShadowIndex < PreShadowSubjectPrimitives.Num(); ShadowIndex++)
    {
        FProjectedShadowInfo* ProjectedShadowInfo = PreShadows[ShadowIndex];

        for (int32 PrimitiveIndex = 0; PrimitiveIndex < PreShadowSubjectPrimitives[ShadowIndex].Num(); PrimitiveIndex++)
        {
            ProjectedShadowInfo->AddSubjectPrimitive(PreShadowSubjectPrimitives[ShadowIndex][PrimitiveIndex], &Views, FeatureLevel, false);
        }
    }

    // 将每个全景阴影实例内的所有图元都加入该阴影实例中.
    for (int32 ShadowIndex = 0; ShadowIndex < ViewDependentWholeSceneShadowSubjectPrimitives.Num(); ShadowIndex++)
    {
        FProjectedShadowInfo* ProjectedShadowInfo = ViewDependentWholeSceneShadows[ShadowIndex];

        (......)

        for (int32 PrimitiveIndex = 0; PrimitiveIndex < ViewDependentWholeSceneShadowSubjectPrimitives[ShadowIndex].Num(); PrimitiveIndex++)
        {
            ProjectedShadowInfo->AddSubjectPrimitive(ViewDependentWholeSceneShadowSubjectPrimitives[ShadowIndex][PrimitiveIndex], NULL, FeatureLevel, bRecordShadowSubjectsForMobile);
        }
    }
}

5.6.3.10 AllocateShadowDepthTargets

继续分析初始化阶段的AllocateShadowDepthTargets

void FSceneRenderer::AllocateShadowDepthTargets(FRHICommandListImmediate& RHICmdList)
{
    FSceneRenderTargets& SceneContext = FSceneRenderTargets::Get(RHICmdList);

    // 对可见阴影基于分配需求排序.
    // 此帧的2d阴影图可以跨光源合并成图集.
    TArray<FProjectedShadowInfo*, SceneRenderingAllocator> Shadows;
    // 横跨持续存在于多帧的2d阴影图不能合并成图集.
    TArray<FProjectedShadowInfo*, SceneRenderingAllocator> CachedSpotlightShadows;
    TArray<FProjectedShadowInfo*, SceneRenderingAllocator> TranslucentShadows;
    // 横跨持续存在于多帧的2d阴影图
    TArray<FProjectedShadowInfo*, SceneRenderingAllocator> CachedPreShadows;
    TArray<FProjectedShadowInfo*, SceneRenderingAllocator> RSMShadows;
    // 点光源的cubemap, 不能合并成图集.
    TArray<FProjectedShadowInfo*, SceneRenderingAllocator> WholeScenePointShadows;

    // 遍历所有光源
    for (TSparseArray<FLightSceneInfoCompact>::TConstIterator LightIt(Scene->Lights); LightIt; ++LightIt)
    {
        const FLightSceneInfoCompact& LightSceneInfoCompact = *LightIt;
        FLightSceneInfo* LightSceneInfo = LightSceneInfoCompact.LightSceneInfo;
        FVisibleLightInfo& VisibleLightInfo = VisibleLightInfos[LightSceneInfo->Id];

        // 同一个光源的所有级联阴影须在同一个纹理中.
        TArray<FProjectedShadowInfo*, SceneRenderingAllocator> WholeSceneDirectionalShadows;

        // 遍历光源的所有投射阴影实例.
        for (int32 ShadowIndex = 0; ShadowIndex < VisibleLightInfo.AllProjectedShadows.Num(); ShadowIndex++)
        {
            FProjectedShadowInfo* ProjectedShadowInfo = VisibleLightInfo.AllProjectedShadows[ShadowIndex];

            // 检测阴影是否至少在一个view中可见.
            bool bShadowIsVisible = false;
            for (int32 ViewIndex = 0; ViewIndex < Views.Num(); ViewIndex++)
            {
                FViewInfo& View = Views[ViewIndex];

                if (ProjectedShadowInfo->DependentView && ProjectedShadowInfo->DependentView != &View)
                {
                    continue;
                }

                const FVisibleLightViewInfo& VisibleLightViewInfo = View.VisibleLightInfos[LightSceneInfo->Id];
                const FPrimitiveViewRelevance ViewRelevance = VisibleLightViewInfo.ProjectedShadowViewRelevanceMap[ShadowIndex];
                const bool bHasViewRelevance = (ProjectedShadowInfo->bTranslucentShadow && ViewRelevance.HasTranslucency()) 
                    || (!ProjectedShadowInfo->bTranslucentShadow && ViewRelevance.bOpaque);

                bShadowIsVisible |= bHasViewRelevance && VisibleLightViewInfo.ProjectedShadowVisibilityMap[ShadowIndex];
            }

            // 检测阴影缓存模式为可移动图元时的条件可见性.
            if (ProjectedShadowInfo->CacheMode == SDCM_MovablePrimitivesOnly && !ProjectedShadowInfo->HasSubjectPrims())
            {
                FCachedShadowMapData& CachedShadowMapData = Scene->CachedShadowMaps.FindChecked(ProjectedShadowInfo->GetLightSceneInfo().Id);
                if (!CachedShadowMapData.bCachedShadowMapHasPrimitives)
                {
                    bShadowIsVisible = false;
                }
            }

            // 移动端渲染器只支持半透明物体逐阴影或CSM.
            if (FeatureLevel < ERHIFeatureLevel::SM5
                && (!ProjectedShadowInfo->bPerObjectOpaqueShadow && !(ProjectedShadowInfo->bDirectionalLight && ProjectedShadowInfo->bWholeSceneShadow)))
            {
                bShadowIsVisible = false;
            }

            // 前向渲染路径中, 动态阴影会被投射到光源衰减纹理(light attenuation texture)的通道中.
            if (IsForwardShadingEnabled(ShaderPlatform)
                && ProjectedShadowInfo->GetLightSceneInfo().GetDynamicShadowMapChannel() == -1)
            {
                bShadowIsVisible = false;
            }

            // 阴影可见, 则根据不同类型加入到不同的阴影实例列表中.
            if (bShadowIsVisible)
            {
                (......)

                bool bNeedsProjection = ProjectedShadowInfo->CacheMode != SDCM_StaticPrimitivesOnly
                    // Mobile rendering only projects opaque per object shadows.
                    && (FeatureLevel >= ERHIFeatureLevel::SM5 || ProjectedShadowInfo->bPerObjectOpaqueShadow);

                if (bNeedsProjection)
                {
                    if (ProjectedShadowInfo->bReflectiveShadowmap)
                    {
                        VisibleLightInfo.RSMsToProject.Add(ProjectedShadowInfo);
                    }
                    else if (ProjectedShadowInfo->bCapsuleShadow)
                    {
                        VisibleLightInfo.CapsuleShadowsToProject.Add(ProjectedShadowInfo);
                    }
                    else
                    {
                        VisibleLightInfo.ShadowsToProject.Add(ProjectedShadowInfo);
                    }
                }

                const bool bNeedsShadowmapSetup = !ProjectedShadowInfo->bCapsuleShadow && !ProjectedShadowInfo->bRayTracedDistanceField;

                if (bNeedsShadowmapSetup)
                {
                    if (ProjectedShadowInfo->bReflectiveShadowmap)
                    {
                        check(ProjectedShadowInfo->bWholeSceneShadow);
                        RSMShadows.Add(ProjectedShadowInfo);
                    }
                    else if (ProjectedShadowInfo->bPreShadow && ProjectedShadowInfo->bAllocatedInPreshadowCache)
                    {
                        CachedPreShadows.Add(ProjectedShadowInfo);
                    }
                    else if (ProjectedShadowInfo->bDirectionalLight && ProjectedShadowInfo->bWholeSceneShadow)
                    {
                        WholeSceneDirectionalShadows.Add(ProjectedShadowInfo);
                    }
                    else if (ProjectedShadowInfo->bOnePassPointLightShadow)
                    {
                        WholeScenePointShadows.Add(ProjectedShadowInfo);
                    }
                    else if (ProjectedShadowInfo->bTranslucentShadow)
                    {
                        TranslucentShadows.Add(ProjectedShadowInfo);
                    }
                    else if (ProjectedShadowInfo->CacheMode == SDCM_StaticPrimitivesOnly)
                    {
                        check(ProjectedShadowInfo->bWholeSceneShadow);
                        CachedSpotlightShadows.Add(ProjectedShadowInfo);
                    }
                    else
                    {
                        Shadows.Add(ProjectedShadowInfo);
                    }
                }
            }
        }

        // 排序级联阴影, 在级联之间的混合是必须的.
        VisibleLightInfo.ShadowsToProject.Sort(FCompareFProjectedShadowInfoBySplitIndex());
        VisibleLightInfo.RSMsToProject.Sort(FCompareFProjectedShadowInfoBySplitIndex());

        // 分配CSM深度渲染纹理.
        AllocateCSMDepthTargets(RHICmdList, WholeSceneDirectionalShadows);
    }

    // 处理缓存的PreShadow.
    if (CachedPreShadows.Num() > 0)
    {
        // 创建场景的PreShadow缓存深度纹理.
        if (!Scene->PreShadowCacheDepthZ)
        {
            FPooledRenderTargetDesc Desc(FPooledRenderTargetDesc::Create2DDesc(SceneContext.GetPreShadowCacheTextureResolution(), PF_ShadowDepth, FClearValueBinding::None, TexCreate_None, TexCreate_DepthStencilTargetable | TexCreate_ShaderResource, false));
            Desc.AutoWritable = false;
            GRenderTargetPool.FindFreeElement(RHICmdList, Desc, Scene->PreShadowCacheDepthZ, TEXT("PreShadowCacheDepthZ"), true, ERenderTargetTransience::NonTransient);
        }

        SortedShadowsForShadowDepthPass.PreshadowCache.RenderTargets.DepthTarget = Scene->PreShadowCacheDepthZ;

        for (int32 ShadowIndex = 0; ShadowIndex < CachedPreShadows.Num(); ShadowIndex++)
        {
            FProjectedShadowInfo* ProjectedShadowInfo = CachedPreShadows[ShadowIndex];
            ProjectedShadowInfo->RenderTargets.DepthTarget = Scene->PreShadowCacheDepthZ.GetReference();
            // 设置阴影深度视图, 会在场景渲染器中为阴影找到一个专用的视图.
            ProjectedShadowInfo->SetupShadowDepthView(RHICmdList, this);
            SortedShadowsForShadowDepthPass.PreshadowCache.Shadows.Add(ProjectedShadowInfo);
        }
    }

    // 分配各类阴影的渲染纹理, 包含点光源cubemap,RSM,缓存的聚光灯,逐物体,透明阴影等.(CSM已经在前面分配过了)
    AllocateOnePassPointLightDepthTargets(RHICmdList, WholeScenePointShadows);
    AllocateRSMDepthTargets(RHICmdList, RSMShadows);
    AllocateCachedSpotlightShadowDepthTargets(RHICmdList, CachedSpotlightShadows);
    AllocatePerObjectShadowDepthTargets(RHICmdList, Shadows);
    AllocateTranslucentShadowDepthTargets(RHICmdList, TranslucentShadows);

    // 更新透明阴影图的uniform buffer.
    for (int32 TranslucentShadowIndex = 0; TranslucentShadowIndex < TranslucentShadows.Num(); ++TranslucentShadowIndex)
    {
        FProjectedShadowInfo* ShadowInfo = TranslucentShadows[TranslucentShadowIndex];
        const int32 PrimitiveIndex = ShadowInfo->GetParentSceneInfo()->GetIndex();

        for (int32 ViewIndex = 0; ViewIndex < Views.Num(); ++ViewIndex)
        {
            FViewInfo& View = Views[ViewIndex];
            FUniformBufferRHIRef* UniformBufferPtr = View.TranslucentSelfShadowUniformBufferMap.Find(PrimitiveIndex);
            if (UniformBufferPtr)
            {
                FTranslucentSelfShadowUniformParameters Parameters;
                SetupTranslucentSelfShadowUniformParameters(ShadowInfo, Parameters);
                RHIUpdateUniformBuffer(*UniformBufferPtr, &Parameters);
            }
        }
    }

    // 删除完全没有被使用的阴影缓存.
    for (TMap<int32, FCachedShadowMapData>::TIterator CachedShadowMapIt(Scene->CachedShadowMaps); CachedShadowMapIt; ++CachedShadowMapIt)
    {
        FCachedShadowMapData& ShadowMapData = CachedShadowMapIt.Value();
        if (ShadowMapData.ShadowMap.IsValid() && ViewFamily.CurrentRealTime - ShadowMapData.LastUsedTime > 2.0f)
        {
            ShadowMapData.ShadowMap.Release();
        }
    }
}

5.6.3.11 GatherShadowDynamicMeshElements

阴影初始化阶段的最后一步是GatherShadowDynamicMeshElements

void FSceneRenderer::GatherShadowDynamicMeshElements(FGlobalDynamicIndexBuffer& DynamicIndexBuffer, FGlobalDynamicVertexBuffer& DynamicVertexBuffer, FGlobalDynamicReadBuffer& DynamicReadBuffer)
{
    TArray<const FSceneView*> ReusedViewsArray;
    ReusedViewsArray.AddZeroed(1);

    // 遍历所有阴影图图集.
    for (int32 AtlasIndex = 0; AtlasIndex < SortedShadowsForShadowDepthPass.ShadowMapAtlases.Num(); AtlasIndex++)
    {
        FSortedShadowMapAtlas& Atlas = SortedShadowsForShadowDepthPass.ShadowMapAtlases[AtlasIndex];

        // 遍历阴影图图集上的所有阴影实例, 收集它们需要投射阴影的网格元素.
        for (int32 ShadowIndex = 0; ShadowIndex < Atlas.Shadows.Num(); ShadowIndex++)
        {
            FProjectedShadowInfo* ProjectedShadowInfo = Atlas.Shadows[ShadowIndex];
            FVisibleLightInfo& VisibleLightInfo = VisibleLightInfos[ProjectedShadowInfo->GetLightSceneInfo().Id];
            ProjectedShadowInfo->GatherDynamicMeshElements(*this, VisibleLightInfo, ReusedViewsArray, DynamicIndexBuffer, DynamicVertexBuffer, DynamicReadBuffer);
        }
    }

    // 遍历所有RSM阴影图图集, 收集每个图集内的所有阴影实例的网格元素.
    for (int32 AtlasIndex = 0; AtlasIndex < SortedShadowsForShadowDepthPass.RSMAtlases.Num(); AtlasIndex++)
    {
        FSortedShadowMapAtlas& Atlas = SortedShadowsForShadowDepthPass.RSMAtlases[AtlasIndex];

        for (int32 ShadowIndex = 0; ShadowIndex < Atlas.Shadows.Num(); ShadowIndex++)
        {
            FProjectedShadowInfo* ProjectedShadowInfo = Atlas.Shadows[ShadowIndex];
            FVisibleLightInfo& VisibleLightInfo = VisibleLightInfos[ProjectedShadowInfo->GetLightSceneInfo().Id];
            ProjectedShadowInfo->GatherDynamicMeshElements(*this, VisibleLightInfo, ReusedViewsArray, DynamicIndexBuffer, DynamicVertexBuffer, DynamicReadBuffer);
        }
    }

    // 遍历所有点光源立方体阴影图, 收集每个立方体阴影图内的所有阴影实例的网格元素.
    for (int32 AtlasIndex = 0; AtlasIndex < SortedShadowsForShadowDepthPass.ShadowMapCubemaps.Num(); AtlasIndex++)
    {
        FSortedShadowMapAtlas& Atlas = SortedShadowsForShadowDepthPass.ShadowMapCubemaps[AtlasIndex];

        for (int32 ShadowIndex = 0; ShadowIndex < Atlas.Shadows.Num(); ShadowIndex++)
        {
            FProjectedShadowInfo* ProjectedShadowInfo = Atlas.Shadows[ShadowIndex];
            FVisibleLightInfo& VisibleLightInfo = VisibleLightInfos[ProjectedShadowInfo->GetLightSceneInfo().Id];
            ProjectedShadowInfo->GatherDynamicMeshElements(*this, VisibleLightInfo, ReusedViewsArray, DynamicIndexBuffer, DynamicVertexBuffer, DynamicReadBuffer);
        }
    }

    // 遍历所有PreShadow缓存的阴影图, 收集阴影实例的网格元素.
    for (int32 ShadowIndex = 0; ShadowIndex < SortedShadowsForShadowDepthPass.PreshadowCache.Shadows.Num(); ShadowIndex++)
    {
        FProjectedShadowInfo* ProjectedShadowInfo = SortedShadowsForShadowDepthPass.PreshadowCache.Shadows[ShadowIndex];
        FVisibleLightInfo& VisibleLightInfo = VisibleLightInfos[ProjectedShadowInfo->GetLightSceneInfo().Id];
        ProjectedShadowInfo->GatherDynamicMeshElements(*this, VisibleLightInfo, ReusedViewsArray, DynamicIndexBuffer, DynamicVertexBuffer, DynamicReadBuffer);
    }

    // 遍历所有透明物体阴影图图集, 收集每个图集内的所有阴影实例的网格元素.
    for (int32 AtlasIndex = 0; AtlasIndex < SortedShadowsForShadowDepthPass.TranslucencyShadowMapAtlases.Num(); AtlasIndex++)
    {
        FSortedShadowMapAtlas& Atlas = SortedShadowsForShadowDepthPass.TranslucencyShadowMapAtlases[AtlasIndex];

        for (int32 ShadowIndex = 0; ShadowIndex < Atlas.Shadows.Num(); ShadowIndex++)
        {
            FProjectedShadowInfo* ProjectedShadowInfo = Atlas.Shadows[ShadowIndex];
            FVisibleLightInfo& VisibleLightInfo = VisibleLightInfos[ProjectedShadowInfo->GetLightSceneInfo().Id];
            ProjectedShadowInfo->GatherDynamicMeshElements(*this, VisibleLightInfo, ReusedViewsArray, DynamicIndexBuffer, DynamicVertexBuffer, DynamicReadBuffer);
        }
    }
}

5.6.3.12 阴影初始化总结

用于前面花费很多笔墨和小节来详细剖析阴影初始化的具体步骤和细节,难免会让很多童鞋望而生畏,那么本节就简洁扼要地总结阴影初始化InitDynamicShadows的主要过程,如下:

  • 根据view、场景光源、控制台变量初始化阴影相关标记。

  • 遍历场景所有光源(Scene->Lights),执行以下操作:

    • 如果光源没有开启阴影或阴影质量太小,或者光源在所有view都不可见,忽略之,不执行阴影投射。

    • 如果是点光源全景阴影,则将该光源组件名字加入Scene的UsedWholeScenePointLightNames列表中。

    • 如果符合全景阴影的创建条件,调用CreateWholeSceneProjectedShadow:

      • 初始化阴影数据,计算阴影所需的分辨率、过渡因子(FadeAlpha)等。
      • 若过渡因子太小(小于1/256),则直接返回。
      • 遍历光源的投射阴影数量,每次都执行分辨率计算、位置(SizeX、SizeY)计算、需创建的阴影图数量等。
        • 根据阴影图数量创建同等个数的FProjectedShadowInfo阴影实例,对每个阴影实例:
          • 设置全景投射参数(视锥体边界、变换矩阵、深度、深度偏移等)、过渡因子、缓存模式等。
          • 加入VisibleLightInfo.MemStackProjectedShadows列表;如果是单通道点光源阴影,填充阴影实例的cubemap6个面的数据(视图投影矩阵、包围盒、远近裁剪面等),并初始化锥体。
          • 对非光追距离场阴影,执行CPU侧裁剪。构建光源视图的凸面体,再遍历光源的移动图元列表,调用IntersectsConvexHulls让光源包围盒和图元包围盒求交测试,相交的那些图元才会添加到阴影实例的主体图元列表中。对光源的静态图元做相似的操作。
          • 最后将需要渲染的阴影实例加入VisibleLightInfo.AllProjectedShadows列表中。
    • 针对两类光源(移动和固定的光源、尚未构建的静态光源)创建CSM(级联阴影)。调用AddViewDependentWholeSceneShadowsForView创建视图关联的CSM:

      • 遍历所有view,针对每个view:
        • 如果不是主view,直接跳过后续步骤。
        • 获取视图相关的全景投影数量,创建同等数量的FProjectedShadowInfo阴影实例,给每个阴影实例设置全景投影参数,最后添加到阴影实例列表和待裁剪列表中。
    • 处理交互阴影(指光源和图元之间的影响,包含PerObject阴影、透明阴影、自阴影等),遍历光源的动态和静态图元,给每个图元调用SetupInteractionShadows设置交互阴影:

      • 处理光源附加根组件,设置相关标记。如果存在附加根组件,跳过后续步骤。
      • 如果需要创建阴影实例,则调用CreatePerObjectProjectedShadow创建逐物体阴影:
        • 遍历所有view,收集阴影相关的标记。
        • 如果本帧不可见且下一帧也没有潜在可见,则直接返回,跳过后续的阴影创建和设置。
        • 没有有效的阴影组图元,直接返回。
        • 计算阴影的各类数据(阴影视锥、分辨率、可见性标记、图集位置等)。
        • 若过渡因子(FadeAlpha)小于某个阈值(1.0/256.0),直接返回。
        • 符合阴影创建条件且是不透明阴影,则创和设置建阴影实例,加入VisibleLightInfo.MemStackProjectedShadows列表中。如果本帧可见则加入到阴影实例的主体图元列表,如果下一帧潜在可见则加入到VisibleLightInfo.OccludedPerObjectShadows的实例中。
        • 符合阴影创建条件且是半透明阴影,执行上步骤类似操作。
  • 调用InitProjectedShadowVisibility执行阴影可见性判定:

    • 遍历场景的所有光源,针对每个光源:
      • 分配视图内的投射阴影可见性和关联的容器。
      • 遍历可见光源所有的阴影实例,针对每个阴影实例:
        • 保存阴影索引。
        • 遍历所有view,针对每个view:
          • 处理视图关联的阴影,跳过那些不在视图视锥内的光源。
          • 计算主体图元的视图关联数据,断阴影是否被遮挡,设置阴影可见性标记。
          • 如果阴影可见且不是RSM,利用FViewElementPDI绘制阴影锥体。
  • 调用UpdatePreshadowCache清理旧的预计算阴影,尝试增加新的到缓存中:

    • 初始化纹理布局。
    • 遍历所有缓存的预阴影, 删除不在此帧渲染的实例。
    • 收集可以被缓存的PreShadow列表。
    • 对PreShadow从大到小排序(更大的PreShadow在渲染深度时会有更多的物体)。
    • 遍历所有未缓存的PreShadow,尝试从纹理布局中给PreShadow找空间,若找到,则设置相关数据并添加到Scene->CachedPreshadows列表中。
  • 调用GatherShadowPrimitives收集图元列表,以处理不同类型的阴影:

    • 如果没有PreShadow且没有视图关联的全景阴影(ViewDependentWholeSceneShadows),则直接返回。
    • 如果允许八叉树遍历(GUseOctreeForShadowCulling决定),利用八叉树遍历Scene->PrimitiveOctree,针对每个孩子节点:
      • 检查孩子节点是否至少在一个阴影(包含PreShadow和视图相关的全景阴影)内,如果是,则push到节点容器中。
      • 如果图元节点的元素大于0,从FMemStack创建一个FGatherShadowPrimitivesPacket实例,将该节点的相关数据存入其中,添加到FGatherShadowPrimitivesPacket实例列表中。
    • 如果是非八叉树遍历模式,则线性遍历图元,创建FGatherShadowPrimitivesPacket并加入到列表中。
    • 利用ParallelFor并行地过滤掉和阴影不相交的图元,收集和阴影相交的图元。
    • 收集最后阶段,将受阴影影响的图元加入阴影实例的SubjectPrimitive列表中,清理之前申请的资源。
  • 调用AllocateShadowDepthTargets分配阴影图所需的渲染纹理:

    • 初始化不同类型的指定了分配器的阴影列表。
    • 遍历所有光源,针对每个光源:
      • 遍历光源的所有阴影实例,针对每个阴影实例:
        • 检测阴影是否至少在一个view中可见。
        • 检测阴影缓存模式为可移动图元时的条件可见性。
        • 其它特殊的可见性判断。
        • 如果阴影可见,根据不同类型加入到不同的阴影实例列表中。
      • 排序级联阴影,因为在级联之间的混合要求是有序的。
      • 调用AllocateCSMDepthTargets分配CSM深度渲染纹理。
      • 处理PreShadow。
      • 依次分配点光源cubemap、RSM、缓存的聚光灯、逐物体、透明阴影的渲染纹理。
      • 更新透明阴影图的uniform buffer。
      • 删除完全没有被使用的阴影缓存。
  • 调用GatherShadowDynamicMeshElements收集阴影的动态网格元素:

    • 遍历所有阴影图图集(ShadowMapAtlases),收集每个图集内的所有阴影实例的网格元素。
    • 遍历所有RSM阴影图图集(RSMAtlases),收集每个图集内的所有阴影实例的网格元素。
    • 遍历所有点光源立方体阴影图(ShadowMapCubemaps),收集每个立方体阴影图内的所有阴影实例的网格元素。
    • 遍历所有PreShadow缓存的阴影图(PreshadowCache),收集阴影实例的网格元素。
    • 遍历所有透明物体阴影图图集(TranslucencyShadowMapAtlases),收集每个图集内的所有阴影实例的网格元素。

阴影初始化总结完了,由此可知阴影的处理非常非常复杂,涉及的逻辑和优化技术甚多。这里粗略总结一下阴影初始化阶段涉及的优化技巧

  • 利用物体(视图、光源、阴影、图元)的简单形状做相交测试,剔除不相交的阴影元素。
  • 利用各类标记(物体、视图、控制台、全局变量等等)及衍生标记,剔除不符合的阴影元素。
  • 利用中间数据(过渡因子、屏幕尺寸大小、深度值等等)剔除不符合的阴影元素。
  • 特殊数据结构(纹理布局、阴影图集、八叉树、连续线性数组)和遍历方式(并行、八叉树、线性)提升执行效果。
  • 充分利用缓存(PreShadowCache、潜在可见性等)减少渲染效果。
  • 对阴影类型进行排序,减少渲染状态切换,减少CPU和GPU交互数据,提升缓存命中率。
  • 不同粒度的遮挡剔除。

5.6.4 阴影渲染

上面分析完阴影的初始化阶段,接下来分析阴影的渲染阶段。阴影的渲染Pass在PrePass之后BasePass之前:

void FDeferredShadingSceneRenderer::Render(FRHICommandListImmediate& RHICmdList)
{
    (......)
    
    RenderPrePass(RHICmdList, ...);
    
    (......)
    
    // 阴影渲染Pass.
    RenderShadowDepthMaps(RHICmdList);
    
    (......)
    
    RenderBasePass(RHICmdList, ...);
    
    (......)
}

5.6.4.1 RenderShadowDepthMaps

下面直接进入RenderShadowDepthMaps分析源码:

// Engine\Source\Runtime\Renderer\Private\ShadowDepthRendering.cpp

void FSceneRenderer::RenderShadowDepthMaps(FRHICommandListImmediate& RHICmdList)
{
    (......)
    
    // 渲染阴影图集. 
    FSceneRenderer::RenderShadowDepthMapAtlases(RHICmdList);

    // 渲染点光源阴影立方体图.
    for (int32 CubemapIndex = 0; CubemapIndex < SortedShadowsForShadowDepthPass.ShadowMapCubemaps.Num(); CubemapIndex++)
    {
        const FSortedShadowMapAtlas& ShadowMap = SortedShadowsForShadowDepthPass.ShadowMapCubemaps[CubemapIndex];
        FSceneRenderTargetItem& RenderTarget = ShadowMap.RenderTargets.DepthTarget->GetRenderTargetItem();
        FIntPoint TargetSize = ShadowMap.RenderTargets.DepthTarget->GetDesc().Extent;

        FProjectedShadowInfo* ProjectedShadowInfo = ShadowMap.Shadows[0];

        // 是否可以并行绘制
        const bool bDoParallelDispatch = RHICmdList.IsImmediate() &&  // translucent shadows are draw on the render thread, using a recursive cmdlist (which is not immediate)
            GRHICommandList.UseParallelAlgorithms() && CVarParallelShadows.GetValueOnRenderThread() &&
            (ProjectedShadowInfo->IsWholeSceneDirectionalShadow() || CVarParallelShadowsNonWholeScene.GetValueOnRenderThread());
        
        FString LightNameWithLevel;
        GetLightNameForDrawEvent(ProjectedShadowInfo->GetLightSceneInfo().Proxy, LightNameWithLevel);

        // 设置Uniform Buffer.
        ProjectedShadowInfo->SetupShadowUniformBuffers(RHICmdList, Scene);

        // 阴影渲染Pass开始.
        auto BeginShadowRenderPass = [this, &RenderTarget, &SceneContext](FRHICommandList& InRHICmdList, bool bPerformClear)
        {
            FRHITexture* DepthTarget = RenderTarget.TargetableTexture;
            ERenderTargetLoadAction DepthLoadAction = bPerformClear ? ERenderTargetLoadAction::EClear : ERenderTargetLoadAction::ELoad;

            // 渲染Pass信息.
            FRHIRenderPassInfo RPInfo(DepthTarget, MakeDepthStencilTargetActions(MakeRenderTargetActions(DepthLoadAction, ERenderTargetStoreAction::EStore), ERenderTargetActions::Load_Store), nullptr, FExclusiveDepthStencil::DepthWrite_StencilWrite);

            if (!GSupportsDepthRenderTargetWithoutColorRenderTarget)
            {
                RPInfo.ColorRenderTargets[0].Action = ERenderTargetActions::DontLoad_DontStore;
                RPInfo.ColorRenderTargets[0].ArraySlice = -1;
                RPInfo.ColorRenderTargets[0].MipIndex = 0;
                RPInfo.ColorRenderTargets[0].RenderTarget = SceneContext.GetOptionalShadowDepthColorSurface(InRHICmdList, DepthTarget->GetTexture2D()->GetSizeX(), DepthTarget->GetTexture2D()->GetSizeY());

                InRHICmdList.TransitionResource(EResourceTransitionAccess::EWritable, RPInfo.ColorRenderTargets[0].RenderTarget);
            }
            // 转换渲染纹理状态为可写.
            InRHICmdList.TransitionResource(EResourceTransitionAccess::EWritable, DepthTarget);
            InRHICmdList.BeginRenderPass(RPInfo, TEXT("ShadowDepthCubeMaps"));
        };

        // 是否需要清理阴影图.
        {
            bool bDoClear = true;

            if (ProjectedShadowInfo->CacheMode == SDCM_MovablePrimitivesOnly
                && Scene->CachedShadowMaps.FindChecked(ProjectedShadowInfo->GetLightSceneInfo().Id).bCachedShadowMapHasPrimitives)
            {
                // Skip the clear when we'll copy from a cached shadowmap
                bDoClear = false;
            }

            BeginShadowRenderPass(RHICmdList, bDoClear);
        }

        if (bDoParallelDispatch)
        {
            // In parallel mode this first pass will just be the clear.
            RHICmdList.EndRenderPass();
        }

        // 真正开始渲染阴影图.
        ProjectedShadowInfo->RenderDepth(RHICmdList, this, BeginShadowRenderPass, bDoParallelDispatch);

        if (!bDoParallelDispatch)
        {
            RHICmdList.EndRenderPass();
        }

        // 转换渲染纹理状态为可读.
        RHICmdList.TransitionResource(EResourceTransitionAccess::EReadable, RenderTarget.TargetableTexture);
    }
    
    // Preshadow缓存.
    if (SortedShadowsForShadowDepthPass.PreshadowCache.Shadows.Num() > 0)
    {
        FSceneRenderTargetItem& RenderTarget = SortedShadowsForShadowDepthPass.PreshadowCache.RenderTargets.DepthTarget->GetRenderTargetItem();

        // 遍历所有PreshadowCache的所有阴影实例.
        for (int32 ShadowIndex = 0; ShadowIndex < SortedShadowsForShadowDepthPass.PreshadowCache.Shadows.Num(); ShadowIndex++)
        {
            FProjectedShadowInfo* ProjectedShadowInfo = SortedShadowsForShadowDepthPass.PreshadowCache.Shadows[ShadowIndex];

            // 没有被缓存的才需要绘制.
            if (!ProjectedShadowInfo->bDepthsCached)
            {
                const bool bDoParallelDispatch = RHICmdList.IsImmediate() && GRHICommandList.UseParallelAlgorithms() && CVarParallelShadows.GetValueOnRenderThread() && (ProjectedShadowInfo->IsWholeSceneDirectionalShadow() || CVarParallelShadowsNonWholeScene.GetValueOnRenderThread());

                ProjectedShadowInfo->SetupShadowUniformBuffers(RHICmdList, Scene);

                auto BeginShadowRenderPass = [this, ProjectedShadowInfo](FRHICommandList& InRHICmdList, bool bPerformClear)
                {
                    FRHITexture* PreShadowCacheDepthZ = Scene->PreShadowCacheDepthZ->GetRenderTargetItem().TargetableTexture.GetReference();
                    InRHICmdList.TransitionResources(EResourceTransitionAccess::EWritable, &PreShadowCacheDepthZ, 1);

                    FRHIRenderPassInfo RPInfo(PreShadowCacheDepthZ, EDepthStencilTargetActions::LoadDepthStencil_StoreDepthStencil, nullptr, FExclusiveDepthStencil::DepthWrite_StencilWrite);

                    // Must preserve existing contents as the clear will be scissored
                    InRHICmdList.BeginRenderPass(RPInfo, TEXT("ShadowDepthMaps"));
                    ProjectedShadowInfo->ClearDepth(InRHICmdList, this, 0, nullptr, PreShadowCacheDepthZ, bPerformClear);
                };

                BeginShadowRenderPass(RHICmdList, true);

                if (bDoParallelDispatch)
                {
                    // In parallel mode the first pass is just the clear.
                    RHICmdList.EndRenderPass();
                }

                // 开始绘制.
                ProjectedShadowInfo->RenderDepth(RHICmdList, this, BeginShadowRenderPass, bDoParallelDispatch);

                if (!bDoParallelDispatch)
                {
                    RHICmdList.EndRenderPass();
                }

                // 已经绘制过, 标记已缓存.
                ProjectedShadowInfo->bDepthsCached = true;
            }
        }

        RHICmdList.TransitionResource(EResourceTransitionAccess::EReadable, RenderTarget.TargetableTexture);
    }

    // 半透明物体阴影图集.
    for (int32 AtlasIndex = 0; AtlasIndex < SortedShadowsForShadowDepthPass.TranslucencyShadowMapAtlases.Num(); AtlasIndex++)
    {
        const FSortedShadowMapAtlas& ShadowMapAtlas = SortedShadowsForShadowDepthPass.TranslucencyShadowMapAtlases[AtlasIndex];
        FIntPoint TargetSize = ShadowMapAtlas.RenderTargets.ColorTargets[0]->GetDesc().Extent;

        // 半透明阴影需要用到两张渲染纹理.
        FSceneRenderTargetItem ColorTarget0 = ShadowMapAtlas.RenderTargets.ColorTargets[0]->GetRenderTargetItem();
        FSceneRenderTargetItem ColorTarget1 = ShadowMapAtlas.RenderTargets.ColorTargets[1]->GetRenderTargetItem();

        FRHITexture* RenderTargetArray[2] =
        {
            ColorTarget0.TargetableTexture,
            ColorTarget1.TargetableTexture
        };

        FRHIRenderPassInfo RPInfo(UE_ARRAY_COUNT(RenderTargetArray), RenderTargetArray, ERenderTargetActions::Load_Store);
        TransitionRenderPassTargets(RHICmdList, RPInfo);
        RHICmdList.BeginRenderPass(RPInfo, TEXT("RenderTranslucencyDepths"));
        {
            // 遍历半透明阴影图集的所有阴影实例, 执行半透明阴影深度绘制.
            for (int32 ShadowIndex = 0; ShadowIndex < ShadowMapAtlas.Shadows.Num(); ShadowIndex++)
            {
                FProjectedShadowInfo* ProjectedShadowInfo = ShadowMapAtlas.Shadows[ShadowIndex];
                // 渲染半透明阴影深度.
                ProjectedShadowInfo->RenderTranslucencyDepths(RHICmdList, this);
            }
        }
        RHICmdList.EndRenderPass();

        RHICmdList.TransitionResource(EResourceTransitionAccess::EReadable, ColorTarget0.TargetableTexture);
        RHICmdList.TransitionResource(EResourceTransitionAccess::EReadable, ColorTarget1.TargetableTexture);
    }

    // 设置LPV的RSM uniform Buffer, 以便后面可以并行提交绘制.
    {
        for (int32 ViewIdx = 0; ViewIdx < Views.Num(); ++ViewIdx)
        {
            FViewInfo& View = Views[ViewIdx];
            FSceneViewState* ViewState = View.ViewState;

            if (ViewState)
            {
                FLightPropagationVolume* Lpv = ViewState->GetLightPropagationVolume(FeatureLevel);

                if (Lpv)
                {
                    Lpv->SetRsmUniformBuffer();
                }
            }
        }
    }

    // 渲染RSM(Reflective Shadow Map, 反射阴影图)图集.
    for (int32 AtlasIndex = 0; AtlasIndex < SortedShadowsForShadowDepthPass.RSMAtlases.Num(); AtlasIndex++)
    {
        checkSlow(RHICmdList.IsOutsideRenderPass());

        const FSortedShadowMapAtlas& ShadowMapAtlas = SortedShadowsForShadowDepthPass.RSMAtlases[AtlasIndex];
        FSceneRenderTargetItem ColorTarget0 = ShadowMapAtlas.RenderTargets.ColorTargets[0]->GetRenderTargetItem();
        FSceneRenderTargetItem ColorTarget1 = ShadowMapAtlas.RenderTargets.ColorTargets[1]->GetRenderTargetItem();
        FSceneRenderTargetItem DepthTarget = ShadowMapAtlas.RenderTargets.DepthTarget->GetRenderTargetItem();
        FIntPoint TargetSize = ShadowMapAtlas.RenderTargets.DepthTarget->GetDesc().Extent;

        SCOPED_DRAW_EVENTF(RHICmdList, EventShadowDepths, TEXT("RSM%u %ux%u"), AtlasIndex, TargetSize.X, TargetSize.Y);

        for (int32 ShadowIndex = 0; ShadowIndex < ShadowMapAtlas.Shadows.Num(); ShadowIndex++)
        {
            FProjectedShadowInfo* ProjectedShadowInfo = ShadowMapAtlas.Shadows[ShadowIndex];
            SCOPED_GPU_MASK(RHICmdList, GetGPUMaskForShadow(ProjectedShadowInfo));

            const bool bDoParallelDispatch = RHICmdList.IsImmediate() &&  // translucent shadows are draw on the render thread, using a recursive cmdlist (which is not immediate)
                GRHICommandList.UseParallelAlgorithms() && CVarParallelShadows.GetValueOnRenderThread() &&
                (ProjectedShadowInfo->IsWholeSceneDirectionalShadow() || CVarParallelShadowsNonWholeScene.GetValueOnRenderThread());

            FSceneViewState* ViewState = (FSceneViewState*)ProjectedShadowInfo->DependentView->State;
            FLightPropagationVolume* LightPropagationVolume = ViewState->GetLightPropagationVolume(FeatureLevel);

            ProjectedShadowInfo->SetupShadowUniformBuffers(RHICmdList, Scene, LightPropagationVolume);

            auto BeginShadowRenderPass = [this, LightPropagationVolume, ProjectedShadowInfo, &ColorTarget0, &ColorTarget1, &DepthTarget](FRHICommandList& InRHICmdList, bool bPerformClear)
            {
                FRHITexture* RenderTargets[2];
                RenderTargets[0] = ColorTarget0.TargetableTexture;
                RenderTargets[1] = ColorTarget1.TargetableTexture;

                // Hook up the geometry volume UAVs
                FRHIUnorderedAccessView* Uavs[4];
                Uavs[0] = LightPropagationVolume->GetGvListBufferUav();
                Uavs[1] = LightPropagationVolume->GetGvListHeadBufferUav();
                Uavs[2] = LightPropagationVolume->GetVplListBufferUav();
                Uavs[3] = LightPropagationVolume->GetVplListHeadBufferUav();

                FRHIRenderPassInfo RPInfo(UE_ARRAY_COUNT(RenderTargets), RenderTargets, ERenderTargetActions::Load_Store);
                RPInfo.DepthStencilRenderTarget.Action = EDepthStencilTargetActions::LoadDepthStencil_StoreDepthStencil;
                RPInfo.DepthStencilRenderTarget.DepthStencilTarget = DepthTarget.TargetableTexture;
                RPInfo.DepthStencilRenderTarget.ExclusiveDepthStencil = FExclusiveDepthStencil::DepthWrite_StencilWrite;


                InRHICmdList.TransitionResources(EResourceTransitionAccess::ERWBarrier, EResourceTransitionPipeline::EGfxToGfx, Uavs, UE_ARRAY_COUNT(Uavs));
                InRHICmdList.BeginRenderPass(RPInfo, TEXT("ShadowAtlas"));

                ProjectedShadowInfo->ClearDepth(InRHICmdList, this, UE_ARRAY_COUNT(RenderTargets), RenderTargets, DepthTarget.TargetableTexture, bPerformClear);
            };

            {
                SCOPED_DRAW_EVENT(RHICmdList, Clear);
                BeginShadowRenderPass(RHICmdList, true);
            }

            // In parallel mode the first renderpass is just the clear.
            if (bDoParallelDispatch)
            {
                RHICmdList.EndRenderPass();
            }

            ProjectedShadowInfo->RenderDepth(RHICmdList, this, BeginShadowRenderPass, bDoParallelDispatch);

            if (!bDoParallelDispatch)
            {
                RHICmdList.EndRenderPass();
            }
            {
                // Resolve the shadow depth z surface.
                RHICmdList.CopyToResolveTarget(DepthTarget.TargetableTexture, DepthTarget.ShaderResourceTexture, FResolveParams());
                RHICmdList.CopyToResolveTarget(ColorTarget0.TargetableTexture, ColorTarget0.ShaderResourceTexture, FResolveParams());
                RHICmdList.CopyToResolveTarget(ColorTarget1.TargetableTexture, ColorTarget1.ShaderResourceTexture, FResolveParams());

                FRHIUnorderedAccessView* UavsToReadable[2];
                UavsToReadable[0] = LightPropagationVolume->GetGvListBufferUav();
                UavsToReadable[1] = LightPropagationVolume->GetGvListHeadBufferUav();
                RHICmdList.TransitionResources(EResourceTransitionAccess::EReadable, EResourceTransitionPipeline::EGfxToGfx, UavsToReadable, UE_ARRAY_COUNT(UavsToReadable));
            }
        }
    }
}

总结阴影渲染流程,就是先后绘制全景阴影图集、点光源阴影立方体图、PreShadow、半透明物体阴影图集、RSM。期间会调用FProjectedShadowInfo的RenderDepth和RenderTranslucencyDepths渲染不透明阴影和半透明阴影。

5.6.4.2 FProjectedShadowInfo::RenderDepth

// Engine\Source\Runtime\Renderer\Private\ShadowDepthRendering.cpp

void FProjectedShadowInfo::RenderDepth(FRHICommandListImmediate& RHICmdList, FSceneRenderer* SceneRenderer, FBeginShadowRenderPassFunction BeginShadowRenderPass, bool bDoParallelDispatch)
{
    RenderDepthInner(RHICmdList, SceneRenderer, BeginShadowRenderPass, bDoParallelDispatch);
}

void FProjectedShadowInfo::RenderDepthInner(FRHICommandListImmediate& RHICmdList, FSceneRenderer* SceneRenderer, FBeginShadowRenderPassFunction BeginShadowRenderPass, bool bDoParallelDispatch)
{
    const ERHIFeatureLevel::Type FeatureLevel = ShadowDepthView->FeatureLevel;
    FRHIUniformBuffer* PassUniformBuffer = ShadowDepthPassUniformBuffer;

    const bool bIsWholeSceneDirectionalShadow = IsWholeSceneDirectionalShadow();

    // 平行光全景阴影的Uniform Buffer更新.
    if (bIsWholeSceneDirectionalShadow)
    {
        // 利用缓存的Uniform Buffer更新阴影视图的Uniform Buffer.
        ShadowDepthView->ViewUniformBuffer.UpdateUniformBufferImmediate(*ShadowDepthView->CachedViewUniformShaderParameters);
        
        // 如果存在依赖视图, 遍历所有持续存在的视图UB扩展, 执行开始渲染命令.
        if (DependentView)
        {
            extern TSet<IPersistentViewUniformBufferExtension*> PersistentViewUniformBufferExtensions;

            for (IPersistentViewUniformBufferExtension* Extension : PersistentViewUniformBufferExtensions)
            {
                Extension->BeginRenderView(DependentView);
            }
        }
    }

    // 移动端平台的绘制的Uniform Buffer更新.
    if (FSceneInterface::GetShadingPath(FeatureLevel) == EShadingPath::Mobile)
    {
        FMobileShadowDepthPassUniformParameters ShadowDepthPassParameters;
        SetupShadowDepthPassUniformBuffer(this, RHICmdList, *ShadowDepthView, ShadowDepthPassParameters);
        SceneRenderer->Scene->UniformBuffers.MobileCSMShadowDepthPassUniformBuffer.UpdateUniformBufferImmediate(ShadowDepthPassParameters);
        MobileShadowDepthPassUniformBuffer.UpdateUniformBufferImmediate(ShadowDepthPassParameters);
        PassUniformBuffer = SceneRenderer->Scene->UniformBuffers.MobileCSMShadowDepthPassUniformBuffer;
    }

    // 设置网格Pass的渲染状态.
    FMeshPassProcessorRenderState DrawRenderState(*ShadowDepthView, PassUniformBuffer);
    SetStateForShadowDepth(bReflectiveShadowmap, bOnePassPointLightShadow, DrawRenderState);
    SetStateForView(RHICmdList);

    if (CacheMode == SDCM_MovablePrimitivesOnly)
    {
        if (bDoParallelDispatch)
        {
            BeginShadowRenderPass(RHICmdList, false);
        }

        // 在渲染可移动图元的深度之前拷贝静态图元的深度.
        CopyCachedShadowMap(RHICmdList, DrawRenderState, SceneRenderer, *ShadowDepthView);

        if (bDoParallelDispatch)
        {
            RHICmdList.EndRenderPass();
        }
    }

    // 并行绘制
    if (bDoParallelDispatch)
    {
        bool bFlush = CVarRHICmdFlushRenderThreadTasksShadowPass.GetValueOnRenderThread() > 0
            || CVarRHICmdFlushRenderThreadTasks.GetValueOnRenderThread() > 0;
        FScopedCommandListWaitForTasks Flusher(bFlush);

        // 发送渲染命令.
        {
            FShadowParallelCommandListSet ParallelCommandListSet(*ShadowDepthView, SceneRenderer, RHICmdList, CVarRHICmdShadowDeferredContexts.GetValueOnRenderThread() > 0, !bFlush, DrawRenderState, *this, BeginShadowRenderPass);

            ShadowDepthPass.DispatchDraw(&ParallelCommandListSet, RHICmdList);
        }
    }
    // 非并行绘制
    else
    {
        ShadowDepthPass.DispatchDraw(nullptr, RHICmdList);
    }
}

由此看到,深度渲染的Pass和场景的深度Pass大同小异,只是部分状态处理略有差异。

5.6.4.3 FProjectedShadowInfo::RenderTranslucencyDepths

// Engine\Source\Runtime\Renderer\Private\TranslucentLighting.cpp

void FProjectedShadowInfo::RenderTranslucencyDepths(FRHICommandList& RHICmdList, FSceneRenderer* SceneRenderer)
{
    // 设置透明深度Pass的Uniform Buffer.
    FTranslucencyDepthPassUniformParameters TranslucencyDepthPassParameters;
    SetupTranslucencyDepthPassUniformBuffer(this, RHICmdList, *ShadowDepthView, TranslucencyDepthPassParameters);
    TUniformBufferRef<FTranslucencyDepthPassUniformParameters> PassUniformBuffer = TUniformBufferRef<FTranslucencyDepthPassUniformParameters>::CreateUniformBufferImmediate(TranslucencyDepthPassParameters, UniformBuffer_SingleFrame, EUniformBufferValidation::None);

    // 设置MeshPassProcessor的渲染状态.
    FMeshPassProcessorRenderState DrawRenderState(*ShadowDepthView, PassUniformBuffer);
    {
        // 清理阴影和边框.
        RHICmdList.SetViewport(
            X, Y, 0.0f,
            // 注意视口长宽包含了2*BorderSize.
            (X + BorderSize * 2 + ResolutionX),
            (Y + BorderSize * 2 + ResolutionY), 1.0f);

        FLinearColor ClearColors[2] = {FLinearColor(0,0,0,0), FLinearColor(0,0,0,0)};
        DrawClearQuadMRT(RHICmdList, true, UE_ARRAY_COUNT(ClearColors), ClearColors, false, 1.0f, false, 0);

        // Set the viewport for the shadow.
        RHICmdList.SetViewport(
            (X + BorderSize), (Y + BorderSize), 0.0f,
            // 注意视口长宽包含了BorderSize(不是上面的2*BorderSize).
            (X + BorderSize + ResolutionX),
            (Y + BorderSize + ResolutionY), 1.0f);

        DrawRenderState.SetDepthStencilState(TStaticDepthStencilState<false, CF_Always>::GetRHI());
        // 混合状态为叠加.
        DrawRenderState.SetBlendState(TStaticBlendState<
            CW_RGBA, BO_Add, BF_One, BF_One, BO_Add, BF_One, BF_One,
            CW_RGBA, BO_Add, BF_One, BF_One, BO_Add, BF_One, BF_One>::GetRHI());

        // 可见的MeshDrawCommand列表.
        FMeshCommandOneFrameArray VisibleMeshDrawCommands;
        FDynamicPassMeshDrawListContext TranslucencyDepthContext(DynamicMeshDrawCommandStorage, VisibleMeshDrawCommands, GraphicsMinimalPipelineStateSet, NeedsShaderInitialisation);

        // 声明透明深度Pass处理器.
        FTranslucencyDepthPassMeshProcessor TranslucencyDepthPassMeshProcessor(
            SceneRenderer->Scene,
            ShadowDepthView,
            DrawRenderState,
            this,
            &TranslucencyDepthContext);

        // 遍历所有动态透明网格元素
        for (int32 MeshBatchIndex = 0; MeshBatchIndex < DynamicSubjectTranslucentMeshElements.Num(); MeshBatchIndex++)
        {
            const FMeshBatchAndRelevance& MeshAndRelevance = DynamicSubjectTranslucentMeshElements[MeshBatchIndex];
            const uint64 BatchElementMask = ~0ull;
            // 将透明MeshBatch加入到Processor中, 以便转换成MeshDrawCommand.
            TranslucencyDepthPassMeshProcessor.AddMeshBatch(*MeshAndRelevance.Mesh, BatchElementMask, MeshAndRelevance.PrimitiveSceneProxy);
        }

        // 遍历所有静态透明网格图元.
        for (int32 PrimitiveIndex = 0; PrimitiveIndex < SubjectTranslucentPrimitives.Num(); PrimitiveIndex++)
        {
            const FPrimitiveSceneInfo* PrimitiveSceneInfo = SubjectTranslucentPrimitives[PrimitiveIndex];
            int32 PrimitiveId = PrimitiveSceneInfo->GetIndex();
            FPrimitiveViewRelevance ViewRelevance = ShadowDepthView->PrimitiveViewRelevanceMap[PrimitiveId];

            if (!ViewRelevance.bInitializedThisFrame)
            {
                // Compute the subject primitive's view relevance since it wasn't cached
                ViewRelevance = PrimitiveSceneInfo->Proxy->GetViewRelevance(ShadowDepthView);
            }

            if (ViewRelevance.bDrawRelevance && ViewRelevance.bStaticRelevance)
            {
                for (int32 MeshIndex = 0; MeshIndex < PrimitiveSceneInfo->StaticMeshes.Num(); MeshIndex++)
                {
                    const FStaticMeshBatch& StaticMeshBatch = PrimitiveSceneInfo->StaticMeshes[MeshIndex];
                    const uint64 BatchElementMask = StaticMeshBatch.bRequiresPerElementVisibility ? ShadowDepthView->StaticMeshBatchVisibility[StaticMeshBatch.BatchVisibilityId] : ~0ull;
                    TranslucencyDepthPassMeshProcessor.AddMeshBatch(StaticMeshBatch, BatchElementMask, StaticMeshBatch.PrimitiveSceneInfo->Proxy, StaticMeshBatch.Id);
                }
            }
        }

        // 存在有效的网格绘制指令才真正提交绘制指令.
        if (VisibleMeshDrawCommands.Num() > 0)
        {
            const bool bDynamicInstancing = IsDynamicInstancingEnabled(ShadowDepthView->FeatureLevel);

            FRHIVertexBuffer* PrimitiveIdVertexBuffer = nullptr;
            // 视图的数据到网格绘制指令.
            ApplyViewOverridesToMeshDrawCommands(*ShadowDepthView, VisibleMeshDrawCommands, DynamicMeshDrawCommandStorage, GraphicsMinimalPipelineStateSet, NeedsShaderInitialisation);
            // 排序网格绘制指令.
            SortAndMergeDynamicPassMeshDrawCommands(SceneRenderer->FeatureLevel, VisibleMeshDrawCommands, DynamicMeshDrawCommandStorage, PrimitiveIdVertexBuffer, 1);
            // 提交网格绘制指令.
            SubmitMeshDrawCommands(VisibleMeshDrawCommands, GraphicsMinimalPipelineStateSet, PrimitiveIdVertexBuffer, 0, bDynamicInstancing, 1, RHICmdList);
        }
    }
}

由此可知,渲染半透明物体阴影时,跟不透明不一样的是,它在渲染阶段才会通过FTranslucencyDepthPassMeshProcessor将FMesh转成FMeshDrawCommand,存在有效指令才会对指令排序并真正提交绘制。

5.6.4.4 RenderShadowDepthMapAtlases

RenderShadowDepthMaps的最开头就是调用RenderShadowDepthMapAtlases绘制CSM阴影图集,下面是它的代码剖析:

void FSceneRenderer::RenderShadowDepthMapAtlases(FRHICommandListImmediate& RHICmdList)
{
    FSceneRenderTargets& SceneContext = FSceneRenderTargets::Get(RHICmdList);

    // 是否可用并行绘制.
    bool bCanUseParallelDispatch = RHICmdList.IsImmediate() &&
        GRHICommandList.UseParallelAlgorithms() && CVarParallelShadows.GetValueOnRenderThread();

    // 遍历所有阴影图图集.
    for (int32 AtlasIndex = 0; AtlasIndex < SortedShadowsForShadowDepthPass.ShadowMapAtlases.Num(); AtlasIndex++)
    {
        const FSortedShadowMapAtlas& ShadowMapAtlas = SortedShadowsForShadowDepthPass.ShadowMapAtlases[AtlasIndex];
        // 渲染纹理.
        FSceneRenderTargetItem& RenderTarget = ShadowMapAtlas.RenderTargets.DepthTarget->GetRenderTargetItem();
        FIntPoint AtlasSize = ShadowMapAtlas.RenderTargets.DepthTarget->GetDesc().Extent;

        // 开始阴影渲染Pass.
        auto BeginShadowRenderPass = [this, &RenderTarget, &SceneContext](FRHICommandList& InRHICmdList, bool bPerformClear)
        {
            ERenderTargetLoadAction DepthLoadAction = bPerformClear ? ERenderTargetLoadAction::EClear : ERenderTargetLoadAction::ELoad;

            // 渲染Pass信息.
            FRHIRenderPassInfo RPInfo(RenderTarget.TargetableTexture, MakeDepthStencilTargetActions(MakeRenderTargetActions(DepthLoadAction, ERenderTargetStoreAction::EStore), ERenderTargetActions::Load_Store), nullptr, FExclusiveDepthStencil::DepthWrite_StencilWrite);

            // 如果不支持不带颜色的深度渲染纹理, 则重新分配渲染纹理, 改变其Action状态.
            if (!GSupportsDepthRenderTargetWithoutColorRenderTarget)
            {
                RPInfo.ColorRenderTargets[0].Action = ERenderTargetActions::DontLoad_DontStore;
                RPInfo.ColorRenderTargets[0].RenderTarget = SceneContext.GetOptionalShadowDepthColorSurface(InRHICmdList, RPInfo.DepthStencilRenderTarget.DepthStencilTarget->GetTexture2D()->GetSizeX(), RPInfo.DepthStencilRenderTarget.DepthStencilTarget->GetTexture2D()->GetSizeY());
                InRHICmdList.TransitionResource(EResourceTransitionAccess::EWritable, RPInfo.ColorRenderTargets[0].RenderTarget);
            }
            InRHICmdList.TransitionResource(EResourceTransitionAccess::EWritable, RPInfo.DepthStencilRenderTarget.DepthStencilTarget);
            InRHICmdList.BeginRenderPass(RPInfo, TEXT("ShadowMapAtlases"));

            if (!bPerformClear)
            {
                InRHICmdList.BindClearMRTValues(false, true, false);
            }
        };

        TArray<FProjectedShadowInfo*, SceneRenderingAllocator> ParallelShadowPasses;
        TArray<FProjectedShadowInfo*, SceneRenderingAllocator> SerialShadowPasses;

        // 在此处收集渲染Pass, 以最小化切换渲染Pass.
        for (int32 ShadowIndex = 0; ShadowIndex < ShadowMapAtlas.Shadows.Num(); ShadowIndex++)
        {
            FProjectedShadowInfo* ProjectedShadowInfo = ShadowMapAtlas.Shadows[ShadowIndex];

            const bool bDoParallelDispatch = bCanUseParallelDispatch &&
                (ProjectedShadowInfo->IsWholeSceneDirectionalShadow() || CVarParallelShadowsNonWholeScene.GetValueOnRenderThread());

            // 根据是否并行加入到不同的渲染Pass列表.
            if (bDoParallelDispatch)
            {
                // 并行列表.
                ParallelShadowPasses.Add(ProjectedShadowInfo);
            }
            else
            {
                // 连续列表.
                SerialShadowPasses.Add(ProjectedShadowInfo);
            }
        }

        FLightSceneProxy* CurrentLightForDrawEvent = NULL;

        // 并行渲染队列.
        if (ParallelShadowPasses.Num() > 0)
        {
            {
                // Clear before going wide.
                BeginShadowRenderPass(RHICmdList, true);
                RHICmdList.EndRenderPass();
            }

            for (int32 ShadowIndex = 0; ShadowIndex < ParallelShadowPasses.Num(); ShadowIndex++)
            {
                FProjectedShadowInfo* ProjectedShadowInfo = ParallelShadowPasses[ShadowIndex];
                
                (......)
                
                ProjectedShadowInfo->SetupShadowUniformBuffers(RHICmdList, Scene);
                ProjectedShadowInfo->TransitionCachedShadowmap(RHICmdList, Scene);
                // 调用阴影实例的渲染深度接口.
                ProjectedShadowInfo->RenderDepth(RHICmdList, this, BeginShadowRenderPass, true);
            }
        }

        CurrentLightForDrawEvent = nullptr;

        // 非并行绘制方式.
        if (SerialShadowPasses.Num() > 0)
        {
            bool bForceSingleRenderPass = CVarShadowForceSerialSingleRenderPass.GetValueOnAnyThread() != 0;
            if(bForceSingleRenderPass)
            {
                BeginShadowRenderPass(RHICmdList, true);
            }

            for (int32 ShadowIndex = 0; ShadowIndex < SerialShadowPasses.Num(); ShadowIndex++)
            {
                FProjectedShadowInfo* ProjectedShadowInfo = SerialShadowPasses[ShadowIndex];

                (......)

                ProjectedShadowInfo->SetupShadowUniformBuffers(RHICmdList, Scene);
                ProjectedShadowInfo->TransitionCachedShadowmap(RHICmdList, Scene);
                if (!bForceSingleRenderPass)
                {
                    BeginShadowRenderPass(RHICmdList, ShadowIndex == 0);
                }
                
                // 调用阴影实例的渲染深度接口.
                ProjectedShadowInfo->RenderDepth(RHICmdList, this, BeginShadowRenderPass, false);
                
                if(!bForceSingleRenderPass)
                {
                    RHICmdList.EndRenderPass();
                }
            }
            if(bForceSingleRenderPass)
            {
                RHICmdList.EndRenderPass();
            }
        }
        
        // 转换渲染纹理为可读取.
        RHICmdList.TransitionResource(EResourceTransitionAccess::EReadable, RenderTarget.TargetableTexture);
    }
}

5.6.5 阴影应用

前面两节详尽地剖析了阴影的初始化和渲染逻辑,那么渲染完成的深度图是怎么被应用到光照当中的呢?本节将揭晓期间的技术细节。

5.6.5.1 RenderLights

阴影图的应用还是要从RenderLights接口开始着色分析,虽然上一篇文章已经介绍过RenderLights的逻辑,不过这里主要聚焦在阴影的应用逻辑:

void FDeferredShadingSceneRenderer::RenderLights(FRHICommandListImmediate& RHICmdList, FSortedLightSetSceneInfo &SortedLightSet, ...)
{
    (......)
    
    const TArray<FSortedLightSceneInfo, SceneRenderingAllocator> &SortedLights = SortedLightSet.SortedLights;
    const int32 AttenuationLightStart = SortedLightSet.AttenuationLightStart;

    (......)
    
    {
        SCOPED_DRAW_EVENT(RHICmdList, DirectLighting);

        FSceneRenderTargets& SceneContext = FSceneRenderTargets::Get(RHICmdList);
        EShaderPlatform ShaderPlatformForFeatureLevel = GShaderPlatformForFeatureLevel[FeatureLevel];
        
        (......)

        // 处理带阴影的光照.
        {
            SCOPED_DRAW_EVENT(RHICmdList, ShadowedLights);

            (......)

            bool bDirectLighting = ViewFamily.EngineShowFlags.DirectLighting;
            bool bShadowMaskReadable = false;
            TRefCountPtr<IPooledRenderTarget> ScreenShadowMaskTexture;
            TRefCountPtr<IPooledRenderTarget> ScreenShadowMaskSubPixelTexture;
            
            // 遍历所有光源,绘制带阴影和光照函数的光源.
            for (int32 LightIndex = AttenuationLightStart; LightIndex < SortedLights.Num(); LightIndex++)
            {
                const FSortedLightSceneInfo& SortedLightInfo = SortedLights[LightIndex];
                const FLightSceneInfo& LightSceneInfo = *SortedLightInfo.LightSceneInfo;

                const bool bDrawShadows = SortedLightInfo.SortKey.Fields.bShadowed && !ShouldRenderRayTracingStochasticRectLight(LightSceneInfo);
                bool bUsedShadowMaskTexture = false;
                
                (......)

                // 分配屏幕阴影遮蔽纹理.
                if ((bDrawShadows || bDrawLightFunction || bDrawPreviewIndicator) && !ScreenShadowMaskTexture.IsValid())
                {
                    SceneContext.AllocateScreenShadowMask(RHICmdList, ScreenShadowMaskTexture);
                    bShadowMaskReadable = false;
                    
                    (......)
                }

                // 绘制阴影.
                if (bDrawShadows)
                {
                    INC_DWORD_STAT(STAT_NumShadowedLights);

                    const FLightOcclusionType OcclusionType = GetLightOcclusionType(*LightSceneInfo.Proxy);

                    (......)
                    // 阴影图遮蔽类型.
                    else // (OcclusionType == FOcclusionType::Shadowmap)
                    {
                        (......)
                    
                        // 清理阴影遮蔽.
                        auto ClearShadowMask = [&](TRefCountPtr<IPooledRenderTarget>& InScreenShadowMaskTexture)
                        {
                            const bool bClearLightScreenExtentsOnly = CVarAllowClearLightSceneExtentsOnly.GetValueOnRenderThread() && SortedLightInfo.SortKey.Fields.LightType != LightType_Directional;
                            bool bClearToWhite = !bClearLightScreenExtentsOnly;

                            // 阴影渲染通道信息.
                            FRHIRenderPassInfo RPInfo(InScreenShadowMaskTexture->GetRenderTargetItem().TargetableTexture, ERenderTargetActions::Load_Store);
                            RPInfo.DepthStencilRenderTarget.Action = MakeDepthStencilTargetActions(ERenderTargetActions::Load_DontStore, ERenderTargetActions::Load_Store);
                            RPInfo.DepthStencilRenderTarget.DepthStencilTarget = SceneContext.GetSceneDepthSurface();
                            RPInfo.DepthStencilRenderTarget.ExclusiveDepthStencil = FExclusiveDepthStencil::DepthRead_StencilWrite;
                            if (bClearToWhite)
                            {
                                RPInfo.ColorRenderTargets[0].Action = ERenderTargetActions::Clear_Store;
                            }

                            // 转换渲染Pass的渲染目标.
                            TransitionRenderPassTargets(RHICmdList, RPInfo);
                            RHICmdList.BeginRenderPass(RPInfo, TEXT("ClearScreenShadowMask"));
                            if (bClearLightScreenExtentsOnly)
                            {
                                SCOPED_DRAW_EVENT(RHICmdList, ClearQuad);
    
                                for (int32 ViewIndex = 0; ViewIndex < Views.Num(); ViewIndex++)
                                {
                                    const FViewInfo& View = Views[ViewIndex];
                                    FIntRect ScissorRect;

                                    if (!LightSceneInfo.Proxy->GetScissorRect(ScissorRect, View, View.ViewRect))
                                    {
                                        ScissorRect = View.ViewRect;
                                    }

                                    if (ScissorRect.Min.X < ScissorRect.Max.X && ScissorRect.Min.Y < ScissorRect.Max.Y)
                                    {
                                        RHICmdList.SetViewport(ScissorRect.Min.X, ScissorRect.Min.Y, 0.0f, ScissorRect.Max.X, ScissorRect.Max.Y, 1.0f);
                                        // 阴影遮蔽纹理原始状态是全白.
                                        DrawClearQuad(RHICmdList, true, FLinearColor(1, 1, 1, 1), false, 0, false, 0);
                                    }
                                    else
                                    {
                                        LightSceneInfo.Proxy->GetScissorRect(ScissorRect, View, View.ViewRect);
                                    }
                                }
                            }
                            RHICmdList.EndRenderPass();
                        };

                        // 清理屏幕的阴影遮蔽.
                        ClearShadowMask(ScreenShadowMaskTexture);
                        // 清理屏幕的阴影遮蔽子像素纹理.
                        if (ScreenShadowMaskSubPixelTexture)
                        {
                            ClearShadowMask(ScreenShadowMaskSubPixelTexture);
                        }

                        // 渲染阴影投射.
                        RenderShadowProjections(RHICmdList, &LightSceneInfo, ScreenShadowMaskTexture, ScreenShadowMaskSubPixelTexture, HairDatas, bInjectedTranslucentVolume);
                    }

                    // 标记已渲染阴影遮蔽纹理.
                    bUsedShadowMaskTexture = true;
                }

                (......)

                // 使用阴影遮蔽纹理.
                if (bUsedShadowMaskTexture)
                {
                    // 拷贝并解析阴影遮蔽纹理.
                    RHICmdList.CopyToResolveTarget(ScreenShadowMaskTexture->GetRenderTargetItem().TargetableTexture, ScreenShadowMaskTexture->GetRenderTargetItem().ShaderResourceTexture, FResolveParams(FResolveRect()));
                    if (ScreenShadowMaskSubPixelTexture)
                    {
                        RHICmdList.CopyToResolveTarget(ScreenShadowMaskSubPixelTexture->GetRenderTargetItem().TargetableTexture, ScreenShadowMaskSubPixelTexture->GetRenderTargetItem().ShaderResourceTexture, FResolveParams(FResolveRect()));
                    }

                    // 转换ScreenShadowMaskTexture为可读.
                    if (!bShadowMaskReadable)
                    {
                        RHICmdList.TransitionResource(EResourceTransitionAccess::EReadable, ScreenShadowMaskTexture->GetRenderTargetItem().ShaderResourceTexture);
                        if (ScreenShadowMaskSubPixelTexture)
                        {
                            RHICmdList.TransitionResource(EResourceTransitionAccess::EReadable, ScreenShadowMaskSubPixelTexture->GetRenderTargetItem().ShaderResourceTexture);
                        }
                        bShadowMaskReadable = true;
                    }
                }

                (......)
                
                else
                {
                    // 计算标准延迟光照, 包含阴影.
                    SCOPED_DRAW_EVENT(RHICmdList, StandardDeferredLighting);
                    SceneContext.BeginRenderingSceneColor(RHICmdList, ESimpleRenderTargetMode::EExistingColorAndDepth, FExclusiveDepthStencil::DepthRead_StencilWrite, true);

                    // 光照图可能已被上一个灯光创建过, 但只有本光源有写入有效数据, 才可用. 故而用bUsedShadowMaskTexture来判断而不是用ScreenShadowMaskTexture的地址判断.
                    IPooledRenderTarget* LightShadowMaskTexture = nullptr;
                    IPooledRenderTarget* LightShadowMaskSubPixelTexture = nullptr;
                    if (bUsedShadowMaskTexture)
                    {
                        LightShadowMaskTexture = ScreenShadowMaskTexture;
                        LightShadowMaskSubPixelTexture = ScreenShadowMaskSubPixelTexture;
                    }

                    // 渲染延迟光照, 其中LightShadowMaskTexture就是光源LightSceneInfo的阴影信息.
                    if (bDirectLighting)
                    {
                        RenderLight(RHICmdList, &LightSceneInfo, LightShadowMaskTexture, InHairVisibilityViews, false, true);
                    }

                    SceneContext.FinishRenderingSceneColor(RHICmdList);

                    (......)
                }
            }
        } // shadowed lights
    }
}

5.6.5.2 RenderShadowProjections

上一小节有调用RenderShadowProjections为光源再次渲染屏幕空间的阴影遮蔽纹理,代码如下:

// Engine\Source\Runtime\Renderer\Private\ShadowRendering.cpp

bool FDeferredShadingSceneRenderer::RenderShadowProjections(FRHICommandListImmediate& RHICmdList, const FLightSceneInfo* LightSceneInfo, IPooledRenderTarget* ScreenShadowMaskTexture, IPooledRenderTarget* ScreenShadowMaskSubPixelTexture, ...)
{
    (......)

    FVisibleLightInfo& VisibleLightInfo = VisibleLightInfos[LightSceneInfo->Id];

    // 调用父类渲染阴影投射.
    FSceneRenderer::RenderShadowProjections(RHICmdList, LightSceneInfo, ScreenShadowMaskTexture, ScreenShadowMaskSubPixelTexture, false, false, ...);

    // 遍历所有可见光源待投射的阴影.
    for (int32 ShadowIndex = 0; ShadowIndex < VisibleLightInfo.ShadowsToProject.Num(); ShadowIndex++)
    {
        FProjectedShadowInfo* ProjectedShadowInfo = VisibleLightInfo.ShadowsToProject[ShadowIndex];

        // 处理透明体积阴影.
        if (ProjectedShadowInfo->bAllocated
            && ProjectedShadowInfo->bWholeSceneShadow
            && !ProjectedShadowInfo->bRayTracedDistanceField
            && (!LightSceneInfo->Proxy->HasStaticShadowing() || ProjectedShadowInfo->IsWholeSceneDirectionalShadow()))
        {
            (......)
        }
    }

    // 渲染胶囊体直接阴影.
    RenderCapsuleDirectShadows(RHICmdList, *LightSceneInfo, ScreenShadowMaskTexture, VisibleLightInfo.CapsuleShadowsToProject, false);

    // 高度场阴影.
    for (int32 ViewIndex = 0; ViewIndex < Views.Num(); ViewIndex++)
    {
        (......)
    }

    // 头发阴影.
    if (HairDatas)
    {
        RenderHairStrandsShadowMask(RHICmdList, Views, LightSceneInfo, HairDatas, ScreenShadowMaskTexture);
    }

    return true;
}

bool FSceneRenderer::RenderShadowProjections(FRHICommandListImmediate& RHICmdList, const FLightSceneInfo* LightSceneInfo, IPooledRenderTarget* ScreenShadowMaskTexture, IPooledRenderTarget* ScreenShadowMaskSubPixelTexture, ...)
{
    FVisibleLightInfo& VisibleLightInfo = VisibleLightInfos[LightSceneInfo->Id];
    FSceneRenderTargets& SceneContext = FSceneRenderTargets::Get(RHICmdList);

    // 收集光源的所有阴影信息, 以便后面只需一个Pass可以渲染完成.
    TArray<FProjectedShadowInfo*> DistanceFieldShadows; // 距离场阴影.
    TArray<FProjectedShadowInfo*> NormalShadows; // 普遍阴影.

    // 收集光源的阴影实例, 按类型分别放到距离场或普遍阴影列表中.
    for (int32 ShadowIndex = 0; ShadowIndex < VisibleLightInfo.ShadowsToProject.Num(); ShadowIndex++)
    {
        FProjectedShadowInfo* ProjectedShadowInfo = VisibleLightInfo.ShadowsToProject[ShadowIndex];
        // 收集距离场或普遍阴影.
        if (ProjectedShadowInfo->bRayTracedDistanceField)
        {
            DistanceFieldShadows.Add(ProjectedShadowInfo);
        }
        else
        {
            NormalShadows.Add(ProjectedShadowInfo);
            if (ProjectedShadowInfo->bAllocated && ProjectedShadowInfo->RenderTargets.DepthTarget 
                && !bMobileModulatedProjections) 
            {
                // 转换资源状态为可读.
                RHICmdList.TransitionResource(EResourceTransitionAccess::EReadable, ProjectedShadowInfo->RenderTargets.DepthTarget->GetRenderTargetItem().ShaderResourceTexture.GetReference());
            }
        }
    }

    // 普通阴影渲染.
    if (NormalShadows.Num() > 0)
    {
        // 渲染阴影遮蔽接口.
        auto RenderShadowMask = [&](const FHairStrandsVisibilityViews* HairVisibilityViews)
        {
            // 为所有视图渲染阴影.
            for (int32 ViewIndex = 0; ViewIndex < Views.Num(); ViewIndex++)
            {
                const FViewInfo& View = Views[ViewIndex];

                (......)

                RHICmdList.SetViewport(View.ViewRect.Min.X, View.ViewRect.Min.Y, 0.0f, View.ViewRect.Max.X, View.ViewRect.Max.Y, 1.0f);
                LightSceneInfo->Proxy->SetScissorRect(RHICmdList, View, View.ViewRect);

                // 更新场景的Uniform Buffer.
                Scene->UniformBuffers.UpdateViewUniformBuffer(View);

                // 投影所有普通阴影深度缓冲到场景.
                for (int32 ShadowIndex = 0; ShadowIndex < NormalShadows.Num(); ShadowIndex++)
                {
                    FProjectedShadowInfo* ProjectedShadowInfo = NormalShadows[ShadowIndex];

                    if (ProjectedShadowInfo->bAllocated && ProjectedShadowInfo->FadeAlphas[ViewIndex] > 1.0f / 256.0f)
                    {
                        // 渲染点光源阴影.
                        if (ProjectedShadowInfo->bOnePassPointLightShadow)
                        {
                            ProjectedShadowInfo->RenderOnePassPointLightProjection(RHICmdList, ViewIndex, View, bProjectingForForwardShading, HairVisibilityData);
                        }
                        else // 普遍光源阴影.
                        {
                            ProjectedShadowInfo->RenderProjection(RHICmdList, ViewIndex, &View, this, bProjectingForForwardShading, bMobileModulatedProjections, HairVisibilityData);
                        }
                    }
                }
            }
        };

        (......)
        else // 渲染普通阴影.
        {
            // 构造渲染Pass信息, 渲染纹理是ScreenShadowMaskTexture.
            FRHIRenderPassInfo RPInfo(ScreenShadowMaskTexture->GetRenderTargetItem().TargetableTexture, ERenderTargetActions::Load_Store);
            RPInfo.DepthStencilRenderTarget.Action = MakeDepthStencilTargetActions(ERenderTargetActions::Load_DontStore, ERenderTargetActions::Load_Store);
            RPInfo.DepthStencilRenderTarget.DepthStencilTarget = SceneContext.GetSceneDepthSurface();
            RPInfo.DepthStencilRenderTarget.ExclusiveDepthStencil = FExclusiveDepthStencil::DepthRead_StencilWrite;

            TransitionRenderPassTargets(RHICmdList, RPInfo);
            RHICmdList.BeginRenderPass(RPInfo, TEXT("RenderShadowProjection"));
            
            // 渲染阴影遮蔽.
            RenderShadowMask(nullptr);
            
            RHICmdList.SetScissorRect(false, 0, 0, 0, 0);
            RHICmdList.EndRenderPass();
        }

        // 子像素阴影.
        if (!bMobileModulatedProjections && ScreenShadowMaskSubPixelTexture && InHairVisibilityViews)
        {            
            (......)
        }
    }

    // 距离场阴影.
    if (DistanceFieldShadows.Num() > 0)
    {
        (......)
    }

    return true;
}

由此可见,利用RenderShadowProjections可以将光源关联的所有阴影投射到ScreenShadowMaskTexture中,此外,还可能包含距离场阴影、子像素阴影、透明体积阴影、胶囊体阴影、高度场阴影、头发阴影等类型。

5.6.5.3 FProjectedShadowInfo::RenderProjection

为了查探ScreenShadowMaskTexture的渲染细节,进入FProjectedShadowInfo::RenderProjection(注意,阴影渲染阶段剖析的是FProjectedShadowInfo::RenderDepthFProjectedShadowInfo::RenderTranslucencyDepth):

void FProjectedShadowInfo::RenderProjection(FRHICommandListImmediate& RHICmdList, int32 ViewIndex, const FViewInfo* View, const FSceneRenderer* SceneRender, bool bProjectingForForwardShading, bool bMobileModulatedProjections, ...) const
{
    (......)

    FGraphicsPipelineStateInitializer GraphicsPSOInit;
    RHICmdList.ApplyCachedRenderTargets(GraphicsPSOInit);

    // 检测阴影的视图标记是否开启, 没开启直接返回.
    const FVisibleLightViewInfo& VisibleLightViewInfo = View->VisibleLightInfos[LightSceneInfo->Id];
    {
        FPrimitiveViewRelevance ViewRelevance = VisibleLightViewInfo.ProjectedShadowViewRelevanceMap[ShadowId];
        if (ViewRelevance.bShadowRelevance == false)
        {
            return;
        }
    }

    bool bCameraInsideShadowFrustum;
    TArray<FVector4, TInlineAllocator<8>> FrustumVertices;
    SetupFrustumForProjection(View, FrustumVertices, bCameraInsideShadowFrustum);

    const bool bSubPixelSupport = HairVisibilityData != nullptr;
    const bool bStencilTestEnabled = !bSubPixelSupport;
    const bool bDepthBoundsTestEnabled = IsWholeSceneDirectionalShadow() && GSupportsDepthBoundsTest && CVarCSMDepthBoundsTest.GetValueOnRenderThread() != 0 && !bSubPixelSupport;
    
    if (!bDepthBoundsTestEnabled && bStencilTestEnabled)
    {
        SetupProjectionStencilMask(RHICmdList, View, ViewIndex, SceneRender, FrustumVertices, bMobileModulatedProjections, bCameraInsideShadowFrustum);
    }

    // 光栅化状态.
    GraphicsPSOInit.RasterizerState = (View->bReverseCulling || IsWholeSceneDirectionalShadow()) ? TStaticRasterizerState<FM_Solid,CM_CCW>::GetRHI() : TStaticRasterizerState<FM_Solid,CM_CW>::GetRHI();

    // 深度模板状态.
    GraphicsPSOInit.bDepthBounds = bDepthBoundsTestEnabled;
    if (bDepthBoundsTestEnabled)
    {
        GraphicsPSOInit.DepthStencilState = TStaticDepthStencilState<false, CF_Always>::GetRHI();
    }
    else if (bStencilTestEnabled)
    {
        if (GStencilOptimization)
        {
            // No depth test or writes, zero the stencil
            // Note: this will disable hi-stencil on many GPUs, but still seems 
            // to be faster. However, early stencil still works 
            GraphicsPSOInit.DepthStencilState =
                TStaticDepthStencilState<
                false, CF_Always,
                true, CF_NotEqual, SO_Zero, SO_Zero, SO_Zero,
                false, CF_Always, SO_Zero, SO_Zero, SO_Zero,
                0xff, 0xff
                >::GetRHI();
        }
        else
        {
            // no depth test or writes, Test stencil for non-zero.
            GraphicsPSOInit.DepthStencilState = 
                TStaticDepthStencilState<
                false, CF_Always,
                true, CF_NotEqual, SO_Keep, SO_Keep, SO_Keep,
                false, CF_Always, SO_Keep, SO_Keep, SO_Keep,
                0xff, 0xff
                >::GetRHI();
        }
    }
    else
    {
        GraphicsPSOInit.DepthStencilState = TStaticDepthStencilState<false, CF_Always>::GetRHI();
    }

    // 获取混合状态, 具体逻辑见后面的剖析.
    GraphicsPSOInit.BlendState = GetBlendStateForProjection(bProjectingForForwardShading, bMobileModulatedProjections);
    GraphicsPSOInit.PrimitiveType = IsWholeSceneDirectionalShadow() ? PT_TriangleStrip : PT_TriangleList;

    {
        uint32 LocalQuality = GetShadowQuality();

        // 处理阴影质量和阴影分辨率.
        if (LocalQuality > 1)
        {
            if (IsWholeSceneDirectionalShadow() && CascadeSettings.ShadowSplitIndex > 0)
            {
                // adjust kernel size so that the penumbra size of distant splits will better match up with the closer ones
                const float SizeScale = CascadeSettings.ShadowSplitIndex / FMath::Max(0.001f, CVarCSMSplitPenumbraScale.GetValueOnRenderThread());
            }
            else if (LocalQuality > 2 && !bWholeSceneShadow)
            {
                static auto CVarPreShadowResolutionFactor = IConsoleManager::Get().FindTConsoleVariableDataFloat(TEXT("r.Shadow.PreShadowResolutionFactor"));
                const int32 TargetResolution = bPreShadow ? FMath::TruncToInt(512 * CVarPreShadowResolutionFactor->GetValueOnRenderThread()) : 512;

                int32 Reduce = 0;

                {
                    int32 Res = ResolutionX;

                    while (Res < TargetResolution)
                    {
                        Res *= 2;
                        ++Reduce;
                    }
                }

                // Never drop to quality 1 due to low resolution, aliasing is too bad
                LocalQuality = FMath::Clamp((int32)LocalQuality - Reduce, 3, 5);
            }
        }

        // 绑定顶点布局
        GraphicsPSOInit.BoundShaderState.VertexDeclarationRHI = GetVertexDeclarationFVector4();
        // 绑定阴影投影shader.
        BindShadowProjectionShaders(LocalQuality, RHICmdList, GraphicsPSOInit, ViewIndex, *View, HairVisibilityData, this, bMobileModulatedProjections);

        if (bDepthBoundsTestEnabled)
        {
            SetDepthBoundsTest(RHICmdList, CascadeSettings.SplitNear, CascadeSettings.SplitFar, View->ViewMatrices.GetProjectionMatrix());
        }

        RHICmdList.SetStencilRef(0);
    }

    // 执行屏幕空间的绘制.
    if (IsWholeSceneDirectionalShadow()) // 全景平行光阴影.
    {
        // 设置顶点buffer.
        RHICmdList.SetStreamSource(0, GClearVertexBuffer.VertexBufferRHI, 0);
        // 调用绘制.
        RHICmdList.DrawPrimitive(0, 2, 1);
    }
    else
    {
        // 动态创建顶点缓冲.
        FRHIResourceCreateInfo CreateInfo;
        FVertexBufferRHIRef VertexBufferRHI = RHICreateVertexBuffer(sizeof(FVector4) * FrustumVertices.Num(), BUF_Volatile, CreateInfo);
        // 上传数据到顶点缓冲.
        void* VoidPtr = RHILockVertexBuffer(VertexBufferRHI, 0, sizeof(FVector4) * FrustumVertices.Num(), RLM_WriteOnly);
        FPlatformMemory::Memcpy(VoidPtr, FrustumVertices.GetData(), sizeof(FVector4) * FrustumVertices.Num());
        RHIUnlockVertexBuffer(VertexBufferRHI);

        // 设置顶点缓冲并绘制.
        RHICmdList.SetStreamSource(0, VertexBufferRHI, 0);
        // Draw the frustum using the projection shader..
        RHICmdList.DrawIndexedPrimitive(GCubeIndexBuffer.IndexBufferRHI, 0, 0, 8, 0, 12, 1);
        // 释放顶点缓冲.
        VertexBufferRHI.SafeRelease();
    }

    // 清理模板缓冲成0.
    if (!bDepthBoundsTestEnabled && bStencilTestEnabled)
    {
        if (!GStencilOptimization)
        {
            DrawClearQuad(RHICmdList, false, FLinearColor::Transparent, false, 0, true, 0);
        }
    }
}

以上代码调用了几个关键的接口:GetBlendStateForProjectionBindShadowProjectionShaders,下面对它们进行分析:

// Engine\Source\Runtime\Renderer\Private\ShadowRendering.cpp

FRHIBlendState* FProjectedShadowInfo::GetBlendStateForProjection(bool bProjectingForForwardShading, bool bMobileModulatedProjections) const
{
    return GetBlendStateForProjection(
        GetLightSceneInfo().GetDynamicShadowMapChannel(),
        IsWholeSceneDirectionalShadow(),
        CascadeSettings.FadePlaneLength > 0 && !bRayTracedDistanceField,
        bProjectingForForwardShading,
        bMobileModulatedProjections);
}

FRHIBlendState* FProjectedShadowInfo::GetBlendStateForProjection(
    int32 ShadowMapChannel, 
    bool bIsWholeSceneDirectionalShadow,
    bool bUseFadePlane,
    bool bProjectingForForwardShading, 
    bool bMobileModulatedProjections)
{
    // 延迟渲染模式每个光源有4个通道(RGBA).
    // CSM和逐物体阴影被存在独立的通道, 以允许CSM过渡到预计算阴影而逐物体阴影可以超过渐隐距离.
    // 次表面阴影需要一个额外的通道.

    FRHIBlendState* BlendState = nullptr;

    // 前向渲染模式
    if (bProjectingForForwardShading)
    {
        (......)
    }
    else // 延迟渲染模式
    {
        // 光照衰减(ScreenShadowMaskTexture)的通道分配:
        //  R:     WholeSceneShadows, non SSS
        //  G:     WholeSceneShadows,     SSS
        //  B: non WholeSceneShadows, non SSS
        //  A: non WholeSceneShadows,     SSS
        //
        // 以上名词解析:
        // SSS: 次表面散射材质.
        // non SSS: 不透明物体阴影.
        // WholeSceneShadows: 平行光CSM.
        // non WholeSceneShadows: 聚光灯、逐物体阴影、透明照明、全方位平行光(omni-directional lights).

        if (bIsWholeSceneDirectionalShadow) // 全景平行光阴影.
        {
            // 混合逻辑需要匹配FCompareFProjectedShadowInfoBySplitIndex的顺序. 比如渐隐平面混合模式需要阴影先被渲染.
            // 全景阴影只启用了R和G通道.
            if (bUseFadePlane) // 使用渐隐平面.
            {
                // alpha通道用来在层级之间过渡. 不需要BO_Min, 因为B和A通道被用作透明阴影.
                BlendState = TStaticBlendState<CW_RG, BO_Add, BF_SourceAlpha, BF_InverseSourceAlpha>::GetRHI();
            }
            else // 不使用渐隐平面.
            {
                // CSM首个层级不需要过渡.  BO_Min是为了组合多个阴影通道.
                BlendState = TStaticBlendState<CW_RG, BO_Min, BF_One, BF_One>::GetRHI();
            }
        }
        else // 非全景平行光阴影.
        {
            if (bMobileModulatedProjections) // 颜色调制阴影.
            {
                // 颜色调制阴影, 忽略Alpha.
                BlendState = TStaticBlendState<CW_RGB, BO_Add, BF_Zero, BF_SourceColor, BO_Add, BF_Zero, BF_One>::GetRHI();
            }
            else // 非颜色调制阴影.
            {
                // BO_Min是为了组合多个阴影通道.
                BlendState = TStaticBlendState<CW_BA, BO_Min, BF_One, BF_One, BO_Min, BF_One, BF_One>::GetRHI();
            }
        }
    }

    return BlendState;
}

// 绑定阴影投影shader.
static void BindShadowProjectionShaders(int32 Quality, FRHICommandList& RHICmdList, FGraphicsPipelineStateInitializer GraphicsPSOInit, int32 ViewIndex, const FViewInfo& View, const FHairStrandsVisibilityData* HairVisibilityData, const FProjectedShadowInfo* ShadowInfo, bool bMobileModulatedProjections)
{
    if (HairVisibilityData)
    {
        (......)
        
        return;
    }

    // 透明阴影.
    if (ShadowInfo->bTranslucentShadow)
    {
        (......)
    }
    // 全景平行光阴影.
    else if (ShadowInfo->IsWholeSceneDirectionalShadow())
    {
        // PCF软阴影.
        if (CVarFilterMethod.GetValueOnRenderThread() == 1)
        {
            if (ShadowInfo->CascadeSettings.FadePlaneLength > 0)
                BindShaderShaders<FShadowProjectionNoTransformVS, TDirectionalPercentageCloserShadowProjectionPS<5, true> >(RHICmdList, GraphicsPSOInit, ViewIndex, View, HairVisibilityData, ShadowInfo);
            else
                BindShaderShaders<FShadowProjectionNoTransformVS, TDirectionalPercentageCloserShadowProjectionPS<5, false> >(RHICmdList, GraphicsPSOInit, ViewIndex, View, HairVisibilityData, ShadowInfo);
        }
        else if (ShadowInfo->CascadeSettings.FadePlaneLength > 0)
        {
            if (ShadowInfo->bTransmission)
            {
                (......)
            }
            else
            {
                switch (Quality)
                {
                case 1: BindShaderShaders<FShadowProjectionNoTransformVS, TShadowProjectionPS<1, true> >(RHICmdList, GraphicsPSOInit, ViewIndex, View, HairVisibilityData, ShadowInfo); break;
                case 2: BindShaderShaders<FShadowProjectionNoTransformVS, TShadowProjectionPS<2, true> >(RHICmdList, GraphicsPSOInit, ViewIndex, View, HairVisibilityData, ShadowInfo); break;
                case 3: BindShaderShaders<FShadowProjectionNoTransformVS, TShadowProjectionPS<3, true> >(RHICmdList, GraphicsPSOInit, ViewIndex, View, HairVisibilityData, ShadowInfo); break;
                case 4: BindShaderShaders<FShadowProjectionNoTransformVS, TShadowProjectionPS<4, true> >(RHICmdList, GraphicsPSOInit, ViewIndex, View, HairVisibilityData, ShadowInfo); break;
                case 5: BindShaderShaders<FShadowProjectionNoTransformVS, TShadowProjectionPS<5, true> >(RHICmdList, GraphicsPSOInit, ViewIndex, View, HairVisibilityData, ShadowInfo); break;
                default:
                    check(0);
                }
            }
        }
        else
        {
            if (ShadowInfo->bTransmission)
            {
                (......)
            }
            else
            { 
                switch (Quality)
                {
                case 1: BindShaderShaders<FShadowProjectionNoTransformVS, TShadowProjectionPS<1, false> >(RHICmdList, GraphicsPSOInit, ViewIndex, View, HairVisibilityData, ShadowInfo); break;
                case 2: BindShaderShaders<FShadowProjectionNoTransformVS, TShadowProjectionPS<2, false> >(RHICmdList, GraphicsPSOInit, ViewIndex, View, HairVisibilityData, ShadowInfo); break;
                case 3: BindShaderShaders<FShadowProjectionNoTransformVS, TShadowProjectionPS<3, false> >(RHICmdList, GraphicsPSOInit, ViewIndex, View, HairVisibilityData, ShadowInfo); break;
                case 4: BindShaderShaders<FShadowProjectionNoTransformVS, TShadowProjectionPS<4, false> >(RHICmdList, GraphicsPSOInit, ViewIndex, View, HairVisibilityData, ShadowInfo); break;
                case 5: BindShaderShaders<FShadowProjectionNoTransformVS, TShadowProjectionPS<5, false> >(RHICmdList, GraphicsPSOInit, ViewIndex, View, HairVisibilityData, ShadowInfo); break;
                default:
                    check(0);
                }
            }
        }
    }
    // 局部光源/逐物体等阴影.
    else
    {
        if(bMobileModulatedProjections)
        {
            (......)
        }
        else if (ShadowInfo->bTransmission)
        {
            (......)
        }
        else
        {
            if (CVarFilterMethod.GetValueOnRenderThread() == 1 && ShadowInfo->GetLightSceneInfo().Proxy->GetLightType() == LightType_Spot)
            {
                BindShaderShaders<FShadowVolumeBoundProjectionVS, TSpotPercentageCloserShadowProjectionPS<5, false> >(RHICmdList, GraphicsPSOInit, ViewIndex, View, HairVisibilityData, ShadowInfo);
            }
            else
            {
                switch (Quality)
                {
                case 1: BindShaderShaders<FShadowVolumeBoundProjectionVS, TShadowProjectionPS<1, false> >(RHICmdList, GraphicsPSOInit, ViewIndex, View, HairVisibilityData, ShadowInfo); break;
                case 2: BindShaderShaders<FShadowVolumeBoundProjectionVS, TShadowProjectionPS<2, false> >(RHICmdList, GraphicsPSOInit, ViewIndex, View, HairVisibilityData, ShadowInfo); break;
                case 3: BindShaderShaders<FShadowVolumeBoundProjectionVS, TShadowProjectionPS<3, false> >(RHICmdList, GraphicsPSOInit, ViewIndex, View, HairVisibilityData, ShadowInfo); break;
                case 4: BindShaderShaders<FShadowVolumeBoundProjectionVS, TShadowProjectionPS<4, false> >(RHICmdList, GraphicsPSOInit, ViewIndex, View, HairVisibilityData, ShadowInfo); break;
                case 5: BindShaderShaders<FShadowVolumeBoundProjectionVS, TShadowProjectionPS<5, false> >(RHICmdList, GraphicsPSOInit, ViewIndex, View, HairVisibilityData, ShadowInfo); break;
                default:
                    check(0);
                }
            }
        }
    }
}

// 绑定阴影投射VS和PS.
template<typename VertexShaderType, typename PixelShaderType>
static void BindShaderShaders(FRHICommandList& RHICmdList, FGraphicsPipelineStateInitializer& GraphicsPSOInit, int32 ViewIndex, const FViewInfo& View, const FHairStrandsVisibilityData* HairVisibilityData, const FProjectedShadowInfo* ShadowInfo)
{
    TShaderRef<VertexShaderType> VertexShader = View.ShaderMap->GetShader<VertexShaderType>();
    TShaderRef<PixelShaderType> PixelShader = View.ShaderMap->GetShader<PixelShaderType>();

    // 绑定shader和渲染状态.
    GraphicsPSOInit.BoundShaderState.VertexShaderRHI = VertexShader.GetVertexShader();
    GraphicsPSOInit.BoundShaderState.PixelShaderRHI = PixelShader.GetPixelShader();
    SetGraphicsPipelineState(RHICmdList, GraphicsPSOInit);

    // 设置shader参数.
    VertexShader->SetParameters(RHICmdList, View, ShadowInfo);
    PixelShader->SetParameters(RHICmdList, ViewIndex, View, HairVisibilityData, ShadowInfo);
}

5.6.5.4 FShadowProjectionNoTransformVS和TShadowProjectionPS

上一小节可以看出,不同类型的阴影会调用不同的VS和PS。其中以具有代表性的全景阴影为例,分析其使用的FShadowProjectionNoTransformVS和TShadowProjectionPS在C++和Shader逻辑:

// Engine\Source\Runtime\Renderer\Private\ShadowRendering.h

// 阴影投射VS.
class FShadowProjectionNoTransformVS : public FShadowProjectionVertexShaderInterface
{
    DECLARE_SHADER_TYPE(FShadowProjectionNoTransformVS,Global);
public:
    (......)

    static void ModifyCompilationEnvironment(const FGlobalShaderPermutationParameters& Parameters, FShaderCompilerEnvironment& OutEnvironment)
    {
        FShadowProjectionVertexShaderInterface::ModifyCompilationEnvironment(Parameters, OutEnvironment);
        // 不使用USE_TRANSFORM.
        OutEnvironment.SetDefine(TEXT("USE_TRANSFORM"), (uint32)0);
    }

    static bool ShouldCompilePermutation(const FGlobalShaderPermutationParameters& Parameters)
    {
        return true;
    }

    // 设置view的Uniform Buffer.
    void SetParameters(FRHICommandList& RHICmdList, FRHIUniformBuffer* ViewUniformBuffer)
    {
        FGlobalShader::SetParameters<FViewUniformShaderParameters>(RHICmdList, RHICmdList.GetBoundVertexShader(), ViewUniformBuffer);
    }

    void SetParameters(FRHICommandList& RHICmdList, const FSceneView& View, const FProjectedShadowInfo*)
    {
        FGlobalShader::SetParameters<FViewUniformShaderParameters>(RHICmdList, RHICmdList.GetBoundVertexShader(), View.ViewUniformBuffer);
    }
};

// 阴影投射PS.
template<uint32 Quality, bool bUseFadePlane = false, bool bModulatedShadows = false, bool bUseTransmission = false, bool SubPixelShadow = false>
class TShadowProjectionPS : public FShadowProjectionPixelShaderInterface
{
    DECLARE_SHADER_TYPE(TShadowProjectionPS,Global);
public:

    (.....)
    
    TShadowProjectionPS(const ShaderMetaType::CompiledShaderInitializerType& Initializer):
        FShadowProjectionPixelShaderInterface(Initializer)
    {
        ProjectionParameters.Bind(Initializer);
        ShadowFadeFraction.Bind(Initializer.ParameterMap,TEXT("ShadowFadeFraction"));
        ShadowSharpen.Bind(Initializer.ParameterMap,TEXT("ShadowSharpen"));
        TransmissionProfilesTexture.Bind(Initializer.ParameterMap, TEXT("SSProfilesTexture"));
        LightPosition.Bind(Initializer.ParameterMap, TEXT("LightPositionAndInvRadius"));

    }

    static bool ShouldCompilePermutation(const FGlobalShaderPermutationParameters& Parameters)
    {
        return IsFeatureLevelSupported(Parameters.Platform, ERHIFeatureLevel::SM5);
    }

    // 修改宏定义.
    static void ModifyCompilationEnvironment(const FGlobalShaderPermutationParameters& Parameters, FShaderCompilerEnvironment& OutEnvironment)
    {
        FShadowProjectionPixelShaderInterface::ModifyCompilationEnvironment(Parameters,OutEnvironment);
        OutEnvironment.SetDefine(TEXT("SHADOW_QUALITY"), Quality);
        OutEnvironment.SetDefine(TEXT("SUBPIXEL_SHADOW"), (uint32)(SubPixelShadow ? 1 : 0));
        OutEnvironment.SetDefine(TEXT("USE_FADE_PLANE"), (uint32)(bUseFadePlane ? 1 : 0));
        OutEnvironment.SetDefine(TEXT("USE_TRANSMISSION"), (uint32)(bUseTransmission ? 1 : 0));
    }

    void SetParameters(
        FRHICommandList& RHICmdList, 
        int32 ViewIndex,
        const FSceneView& View,
        const FHairStrandsVisibilityData* HairVisibilityData,
        const FProjectedShadowInfo* ShadowInfo)
    {
        FRHIPixelShader* ShaderRHI = RHICmdList.GetBoundPixelShader();

        // 这里会设置跟阴影渲染相关的很多shader绑定.(见后面代码)
        FShadowProjectionPixelShaderInterface::SetParameters(RHICmdList, ViewIndex, View, HairVisibilityData, ShadowInfo);

        const bool bUseFadePlaneEnable = ShadowInfo->CascadeSettings.FadePlaneLength > 0;

        // 设置shader值.
        ProjectionParameters.Set(RHICmdList, this, View, ShadowInfo, HairVisibilityData, bModulatedShadows, bUseFadePlaneEnable);
        const FLightSceneProxy& LightProxy = *(ShadowInfo->GetLightSceneInfo().Proxy);
        SetShaderValue(RHICmdList, ShaderRHI, ShadowFadeFraction, ShadowInfo->FadeAlphas[ViewIndex] );
        SetShaderValue(RHICmdList, ShaderRHI, ShadowSharpen, LightProxy.GetShadowSharpen() * 7.0f + 1.0f );
        SetShaderValue(RHICmdList, ShaderRHI, LightPosition, FVector4(LightProxy.GetPosition(), 1.0f / LightProxy.GetRadius()));

        // 绑定延迟光源的Uniform Buffer.
        auto DeferredLightParameter = GetUniformBufferParameter<FDeferredLightUniformStruct>();
        if (DeferredLightParameter.IsBound())
        {
            SetDeferredLightParameters(RHICmdList, ShaderRHI, DeferredLightParameter, &ShadowInfo->GetLightSceneInfo(), View);
        }
        
        FScene* Scene = nullptr;

        if (View.Family->Scene)
        {
            Scene = View.Family->Scene->GetRenderScene();
        }

        // 绑定纹理资源.
        FSceneRenderTargets& SceneContext = FSceneRenderTargets::Get(RHICmdList);
        {
            const IPooledRenderTarget* PooledRT = GetSubsufaceProfileTexture_RT((FRHICommandListImmediate&)RHICmdList);

            if (!PooledRT)
            {
                PooledRT = GSystemTextures.BlackDummy;
            }

            const FSceneRenderTargetItem& Item = PooledRT->GetRenderTargetItem();
            SetTextureParameter(RHICmdList, ShaderRHI, TransmissionProfilesTexture, Item.ShaderResourceTexture);
        }
    }

protected:
    LAYOUT_FIELD(FShadowProjectionShaderParameters, ProjectionParameters);
    LAYOUT_FIELD(FShaderParameter, ShadowFadeFraction);
    LAYOUT_FIELD(FShaderParameter, ShadowSharpen);
    LAYOUT_FIELD(FShaderParameter, LightPosition);
    LAYOUT_FIELD(FShaderResourceParameter, TransmissionProfilesTexture);
};

// 设置阴影投射相关的很多shader绑定参数.
void FShadowProjectionShaderParameters::Set(FRHICommandList& RHICmdList, FShader* Shader, const FSceneView& View, const FProjectedShadowInfo* ShadowInfo, const FHairStrandsVisibilityData* HairVisibilityData, bool bModulatedShadows, bool bUseFadePlane)
{
    FRHIPixelShader* ShaderRHI = RHICmdList.GetBoundPixelShader();

    // 设置场景纹理.
    SceneTextureParameters.Set(RHICmdList, ShaderRHI, View.FeatureLevel, ESceneTextureSetupMode::All);

    const FIntPoint ShadowBufferResolution = ShadowInfo->GetShadowBufferResolution();

    // 阴影偏移和尺寸.
    if (ShadowTileOffsetAndSizeParam.IsBound())
    {
        FVector2D InverseShadowBufferResolution(1.0f / ShadowBufferResolution.X, 1.0f / ShadowBufferResolution.Y);
        FVector4 ShadowTileOffsetAndSize(
            (ShadowInfo->BorderSize + ShadowInfo->X) * InverseShadowBufferResolution.X,
            (ShadowInfo->BorderSize + ShadowInfo->Y) * InverseShadowBufferResolution.Y,
            ShadowInfo->ResolutionX * InverseShadowBufferResolution.X,
            ShadowInfo->ResolutionY * InverseShadowBufferResolution.Y);
        SetShaderValue(RHICmdList, ShaderRHI, ShadowTileOffsetAndSizeParam, ShadowTileOffsetAndSize);
    }

    // 设置从屏幕坐标转换到阴影深度纹理坐标的变换矩阵.
    if (bModulatedShadows)
    {
        const FMatrix ScreenToShadow = ShadowInfo->GetScreenToShadowMatrix(View, 0, 0, ShadowBufferResolution.X, ShadowBufferResolution.Y);
        SetShaderValue(RHICmdList, ShaderRHI, ScreenToShadowMatrix, ScreenToShadow);
    }
    else
    {
        const FMatrix ScreenToShadow = ShadowInfo->GetScreenToShadowMatrix(View);
        SetShaderValue(RHICmdList, ShaderRHI, ScreenToShadowMatrix, ScreenToShadow);
    }

    // 阴影过渡缩放.
    if (SoftTransitionScale.IsBound())
    {
        const float TransitionSize = ShadowInfo->ComputeTransitionSize();

        SetShaderValue(RHICmdList, ShaderRHI, SoftTransitionScale, FVector(0, 0, 1.0f / TransitionSize));
    }

    // 阴影缓冲尺寸.
    if (ShadowBufferSize.IsBound())
    {
        FVector2D ShadowBufferSizeValue(ShadowBufferResolution.X, ShadowBufferResolution.Y);

        SetShaderValue(RHICmdList, ShaderRHI, ShadowBufferSize,
                       FVector4(ShadowBufferSizeValue.X, ShadowBufferSizeValue.Y, 1.0f / ShadowBufferSizeValue.X, 1.0f / ShadowBufferSizeValue.Y));
    }

    // ------ 处理阴影深度纹理 ------
    
    FRHITexture* ShadowDepthTextureValue;
    if (ShadowInfo->RenderTargets.DepthTarget)
    {
        // 如果阴影存在深度纹理, 则取之作为PS的ShadowDepthTextureValue.
        ShadowDepthTextureValue = ShadowInfo->RenderTargets.DepthTarget->GetRenderTargetItem().ShaderResourceTexture.GetReference();
    }
    // 透明阴影没有深度纹理.
    else
    {
        ShadowDepthTextureValue = GSystemTextures.BlackDummy->GetRenderTargetItem().ShaderResourceTexture.GetReference();
    }

    // 阴影深度纹理的采样器, 注意是点采样, 且是Clamp模式.
    FRHISamplerState* DepthSamplerState = TStaticSamplerState<SF_Point,AM_Clamp,AM_Clamp,AM_Clamp>::GetRHI();
    
    // 设置阴影深度纹理和采样器.
    SetTextureParameter(RHICmdList, ShaderRHI, ShadowDepthTexture, ShadowDepthTextureSampler, DepthSamplerState, ShadowDepthTextureValue);
    if (ShadowDepthTextureSampler.IsBound())
    {
        RHICmdList.SetShaderSampler(
            ShaderRHI, 
            ShadowDepthTextureSampler.GetBaseIndex(),
            DepthSamplerState
        );
    }

    // 深度偏移和渐隐平面偏移.
    SetShaderValue(RHICmdList, ShaderRHI, ProjectionDepthBias, FVector4(ShadowInfo->GetShaderDepthBias(), ShadowInfo->GetShaderSlopeDepthBias(), ShadowInfo->GetShaderReceiverDepthBias(), ShadowInfo->MaxSubjectZ - ShadowInfo->MinSubjectZ));
    SetShaderValue(RHICmdList, ShaderRHI, FadePlaneOffset, ShadowInfo->CascadeSettings.FadePlaneOffset);

    if(InvFadePlaneLength.IsBound() && bUseFadePlane)
    {
        SetShaderValue(RHICmdList, ShaderRHI, InvFadePlaneLength, 1.0f / ShadowInfo->CascadeSettings.FadePlaneLength);
    }

    // 光源方向或位置.
    if (LightPositionOrDirection.IsBound())
    {
        const FVector LightDirection = ShadowInfo->GetLightSceneInfo().Proxy->GetDirection();
        const FVector LightPosition = ShadowInfo->GetLightSceneInfo().Proxy->GetPosition();
        const bool bIsDirectional = ShadowInfo->GetLightSceneInfo().Proxy->GetLightType() == LightType_Directional;
        SetShaderValue(RHICmdList, ShaderRHI, LightPositionOrDirection, bIsDirectional ? FVector4(LightDirection,0) : FVector4(LightPosition,1));
    }

    (......)

    // 逐物体阴影参数.
    SetShaderValue(RHICmdList, ShaderRHI, PerObjectShadowFadeStart, ShadowInfo->PerObjectShadowFadeStart);
    SetShaderValue(RHICmdList, ShaderRHI, InvPerObjectShadowFadeLength, ShadowInfo->InvPerObjectShadowFadeLength);
}

上面详尽地剖析了FShadowProjectionNoTransformVS和TShadowProjectionPS在C++层的逻辑,下面转到它们对应的Shader代码:

// Engine\Shaders\Private\ShadowProjectionVertexShader.usf

#include "Common.ush"

#ifndef USE_TRANSFORM
    #define USE_TRANSFORM 1
#endif

#if USE_TRANSFORM
    float4 StencilingGeometryPosAndScale;
#endif

// VS主入口.
void Main(in float4 InPosition : ATTRIBUTE0, out float4 OutPosition : SV_POSITION)
{
    #if USE_TRANSFORM // 使用变换.
        // 转换物体位置到裁剪空间.
        float3 WorldPosition = InPosition.xyz * StencilingGeometryPosAndScale.w + StencilingGeometryPosAndScale.xyz;
        OutPosition = mul(float4(WorldPosition,1), View.TranslatedWorldToClip);
    #else // 不使用变换.
        // 物体位置已经在裁剪空间, 不需要再变换.
        OutPosition = float4(InPosition.xyz, 1);
    #endif
}


// Engine\Shaders\Private\ShadowProjectionPixelShader.usf

(......)

float ShadowFadeFraction;
float ShadowSharpen;
float4 LightPositionAndInvRadius;

float PerObjectShadowFadeStart;
float InvPerObjectShadowFadeLength;

float4 LightPositionOrDirection;
float ShadowReceiverBias;

#if USE_FADE_PLANE || SUBPIXEL_SHADOW
    float FadePlaneOffset;
    float InvFadePlaneLength;
    uint bCascadeUseFadePlane;
#endif

#if USE_PCSS
    // PCSS specific parameters.
    //    - x: tan(0.5 * Directional Light Angle) in shadow projection space;
    //  - y: Max filter size in shadow tile UV space.
    float4 PCSSParameters;
#endif

float4 ModulatedShadowColor;
float4 ShadowTileOffsetAndSize;

(.....)

// PS主入口.
void Main(in float4 SVPos : SV_POSITION, 
          out float4 OutColor : SV_Target0)
{
    #if USE_FADE_PLANE
    const bool bUseFadePlane = true;
    #endif

    const FPQMPContext PQMPContext = PQMPInit(SVPos.xy);
    float2 ScreenUV = float2(SVPos.xy * View.BufferSizeAndInvSize.zw);
    float SceneW = CalcSceneDepth(ScreenUV);

    (......)

    // 计算屏幕空间/阴影空间/世界空间的坐标.
    float4 ScreenPosition = float4(((ScreenUV.xy - View.ScreenPositionScaleBias.wz ) / View.ScreenPositionScaleBias.xy) * SceneW, SceneW, 1);
    float4 ShadowPosition = mul(ScreenPosition, ScreenToShadowMatrix);
    float3 WorldPosition = mul(ScreenPosition, View.ScreenToWorld).xyz;

    // 计算阴影空间的坐标(其中阴影裁剪空间坐标需要除以w, 以转换到阴影的屏幕空间)
    float ShadowZ = ShadowPosition.z;
    ShadowPosition.xyz /= ShadowPosition.w;

#if MODULATED_SHADOWS
    ShadowPosition.xy *= ShadowTileOffsetAndSize.zw;
    ShadowPosition.xy += ShadowTileOffsetAndSize.xy;
#endif

    // PCSS软阴影.
#if USE_PCSS
    float3 ScreenPositionDDX = DDX(ScreenPosition.xyz);
    float3 ScreenPositionDDY = DDY(ScreenPosition.xyz);
    float4 ShadowPositionDDX = mul(float4(ScreenPositionDDX, 0), ScreenToShadowMatrix);
    float4 ShadowPositionDDY = mul(float4(ScreenPositionDDY, 0), ScreenToShadowMatrix);
    #if SPOT_LIGHT_PCSS
        ShadowPositionDDX.xyz -= ShadowPosition.xyz * ShadowPositionDDX.w;
        ShadowPositionDDY.xyz -= ShadowPosition.xyz * ShadowPositionDDY.w;
    #endif
#endif

    // 调整阴影空间(光源空间)的深度到0.99999f, 因为阴影深度缓冲无法被清除到1的值. 也可以强制光源空间远平面的像素不被遮挡.
    float LightSpacePixelDepthForOpaque = min(ShadowZ, 0.99999f);
    // SSS的深度不能调整, 因为次表面的渐变必须超过远平面.
    float LightSpacePixelDepthForSSS = ShadowZ;

    // 逐物体阴影在被剪掉之前执行过渡, 开始平面是1而结束平面是0.
    float PerObjectDistanceFadeFraction = 1.0f - saturate((LightSpacePixelDepthForSSS - PerObjectShadowFadeStart) * InvPerObjectShadowFadeLength);

    // 初始化阴影和投射等参数.
    float Shadow = 1;
    float SSSTransmission = 1;
    
    float BlendFactor = 1;

    // 未过滤的阴影投射.
    #if UNFILTERED_SHADOW_PROJECTION
    {
        // 直接对比调整过的光源空间(阴影空间)的像素深度和对应像素坐标的阴影深度的r通道值.
        // 如果前者<后者, 说明未被遮挡, Shadow=1; 反之, Shadow=0.
        Shadow = LightSpacePixelDepthForOpaque < Texture2DSampleLevel(ShadowDepthTexture, ShadowDepthTextureSampler, ShadowPosition.xy, 0).r;
    }
    // 透明阴影.
    #elif APPLY_TRANSLUCENCY_SHADOWS
    {
        Shadow = CalculateTranslucencyShadowing(ShadowPosition.xy, ShadowZ);
    }
    // PCSS软阴影.
    #elif USE_PCSS
    {
        FPCSSSamplerSettings Settings;

        #if SPOT_LIGHT_PCSS
        {
            float CotanOuterCone = DeferredLightUniforms.SpotAngles.x * rsqrt(1. - DeferredLightUniforms.SpotAngles.x * DeferredLightUniforms.SpotAngles.x);
            float WorldLightDistance = dot(DeferredLightUniforms.Direction, DeferredLightUniforms.Position - WorldPosition);
            Settings.ProjectedSourceRadius = 0.5 * DeferredLightUniforms.SourceRadius * CotanOuterCone / WorldLightDistance;
            Settings.TanLightSourceAngle = 0;
        }
        #else
        {
            Settings.ProjectedSourceRadius = 0;
            Settings.TanLightSourceAngle = PCSSParameters.x;
        }
        #endif
        Settings.ShadowDepthTexture = ShadowDepthTexture;
        Settings.ShadowDepthTextureSampler = ShadowDepthTextureSampler;
        Settings.ShadowBufferSize = ShadowBufferSize;
        Settings.ShadowTileOffsetAndSize = ShadowTileOffsetAndSize;
        Settings.SceneDepth = LightSpacePixelDepthForOpaque;
        Settings.TransitionScale = SoftTransitionScale.z;
        Settings.MaxKernelSize = PCSSParameters.y;
        Settings.SvPosition = SVPos.xy;
        Settings.PQMPContext = PQMPContext;
        Settings.DebugViewportUV = ScreenUV;
        
        Shadow = DirectionalPCSS(Settings, ShadowPosition.xy, ShadowPositionDDX.xyz, ShadowPositionDDY.xyz);
    }
    // 自定义的PCF阴影.
    #else 
    {
        #if SHADING_PATH_DEFERRED && !FORWARD_SHADING
            FGBufferData GBufferData = GetGBufferData(ScreenUV);
            const bool bIsDirectional = LightPositionOrDirection.w == 0;
            const float3 LightDirection = bIsDirectional ? -LightPositionOrDirection.xyz : normalize(LightPositionOrDirection.xyz - WorldPosition);
            const float NoL = saturate(dot(GBufferData.WorldNormal, LightDirection));
        #endif
        
        FPCFSamplerSettings Settings;
        
        Settings.ShadowDepthTexture = ShadowDepthTexture;
        Settings.ShadowDepthTextureSampler = ShadowDepthTextureSampler;
        Settings.ShadowBufferSize = ShadowBufferSize;
        #if SHADING_PATH_DEFERRED && !FORWARD_SHADING
        Settings.TransitionScale = SoftTransitionScale.z * lerp(ProjectionDepthBiasParameters.z, 1.0, NoL);
        #else
        Settings.TransitionScale = SoftTransitionScale.z;
        #endif
        Settings.SceneDepth = LightSpacePixelDepthForOpaque;
        Settings.bSubsurface = false;
        Settings.bTreatMaxDepthUnshadowed = false;
        Settings.DensityMulConstant = 0;
        Settings.ProjectionDepthBiasParameters = 0;

        // 自定义的PCF阴影.
        Shadow = ManualPCF(ShadowPosition.xy, Settings);
    }
    #endif // !USE_PCSS
        
    #if USE_FADE_PLANE || SUBPIXEL_SHADOW
    if (bUseFadePlane)
    {
        // Create a blend factor which is one before and at the fade plane, and lerps to zero at the far plane.
        BlendFactor = 1.0f - saturate((SceneW - FadePlaneOffset) * InvFadePlaneLength);
    }
    #endif

    // 次表面阴影.
    #if FEATURE_LEVEL >= FEATURE_LEVEL_SM4 && !FORWARD_SHADING && !APPLY_TRANSLUCENCY_SHADOWS

        FGBufferData GBufferData = GetGBufferData(ScreenUV);

        BRANCH
        if (bIsSubsurfaceCompatible && IsSubsurfaceModel(GBufferData.ShadingModelID))
        {
            float Opacity = GBufferData.CustomData.a;
            float Density = -.05f * log(1 - min(Opacity, .999f));
            if( GBufferData.ShadingModelID == SHADINGMODELID_HAIR || GBufferData.ShadingModelID == SHADINGMODELID_EYE )
            {
                Opacity = 1;
                Density = 1;
            }
            
            float SquareRootFilterScale = lerp(1.999f, 0, Opacity);
            int SquareRootFilterScaleInt = int(SquareRootFilterScale) + 1;

            #if UNFILTERED_SHADOW_PROJECTION
                float ShadowMapDepth = Texture2DSampleLevel(ShadowDepthTexture, ShadowDepthTextureSampler, ShadowPosition.xy, 0).x;
                SSSTransmission = CalculateSubsurfaceOcclusion(Density, LightSpacePixelDepthForSSS, ShadowMapDepth.xxx).x;
            #else
        
                // default code path
                FPCFSamplerSettings Settings;
                
                Settings.ShadowDepthTexture = ShadowDepthTexture;
                Settings.ShadowDepthTextureSampler = ShadowDepthTextureSampler;
                Settings.ShadowBufferSize = ShadowBufferSize;
                Settings.TransitionScale = SoftTransitionScale.z;
                Settings.SceneDepth = LightSpacePixelDepthForSSS + ProjectionDepthBiasParameters.x;
                Settings.bSubsurface = true;
                Settings.bTreatMaxDepthUnshadowed = false;
                Settings.DensityMulConstant = Density * ProjectionDepthBiasParameters.w;
                Settings.ProjectionDepthBiasParameters = ProjectionDepthBiasParameters.xw;

              #if USE_TRANSMISSION
                if (GBufferData.ShadingModelID == SHADINGMODELID_SUBSURFACE_PROFILE)
                {
                    SSSTransmission = CalcTransmissionThickness(ScreenPosition.xyz, LightPositionAndInvRadius.xyz, GBufferData, Settings);
                }
                else
              #endif
                {
                    SSSTransmission = ManualPCF(ShadowPosition.xy, Settings);
                }
            #endif
        }

    #endif
    
    // 如果不使用PCSS软阴影, 则用ShadowSharpen来调整阴影的强度, 类似Half Lambert的做法.
    #if !USE_PCSS
        Shadow = saturate( (Shadow - 0.5) * ShadowSharpen + 0.5 );
    #endif

    // 过渡阴影. 0是被阴影遮蔽, 1是未被遮蔽. 无需返回颜色, 除非是调制阴影模式需要写入场景颜色.
    float FadedShadow = lerp(1.0f, Square(Shadow), ShadowFadeFraction * PerObjectDistanceFadeFraction);

#if FORWARD_SHADING
    (......)
#else
    // 点光源阴影被写到b通道.
    OutColor.b = EncodeLightAttenuation(FadedShadow);
    OutColor.rga = 1;
    // SSS阴影模拟, 虽然不够准确, 但至少有被阴影遮蔽.
    OutColor.a = OutColor.b;
#endif

#if USE_FADE_PLANE || SUBPIXEL_SHADOW
    // 如果是CSM, 则输出Alpha通道来混合.
    if (bUseFadePlane)
    {
        OutColor.a = BlendFactor;
    }
#endif

    // 调制阴影.
#if MODULATED_SHADOWS
    OutColor.rgb = lerp(ModulatedShadowColor.rgb, float3(1, 1, 1), FadedShadow);
    OutColor.a = 0;
#endif
}

(......)

以上可知,对ScreenShadowMaskTexture执行绘制时,是在屏幕空间进行的,并且ScreenShadowMask会被叠加多次(视光源的阴影实例个数而定),不同的通道存储了不同类型的阴影信息。

5.6.5.5 RenderLight

本小节将分析有了光源和ScreenShadowMaskTexture信息之后如何使用它们。直接进入RenderLight分析阴影相关的逻辑:

void FDeferredShadingSceneRenderer::RenderLight(FRHICommandList& RHICmdList, const FLightSceneInfo* LightSceneInfo, IPooledRenderTarget* ScreenShadowMaskTexture, ...)
{
    (......)

    // 遍历所有view
    for (int32 ViewIndex = 0; ViewIndex < Views.Num(); ViewIndex++)
    {
        (......)
        
        // 平行光.
        if (LightSceneInfo->Proxy->GetLightType() == LightType_Directional)
        {
            (......)
            
            else
            {
                FDeferredLightPS::FPermutationDomain PermutationVector;
                
                (......)
                
                // 声明光照像素着色器FDeferredLightPS实例.
                TShaderMapRef< FDeferredLightPS > PixelShader( View.ShaderMap, PermutationVector );
                GraphicsPSOInit.BoundShaderState.PixelShaderRHI = PixelShader.GetPixelShader();
                (......)
                SetGraphicsPipelineState(RHICmdList, GraphicsPSOInit);
                
                // 将ScreenShadowMaskTexture绑定到像素着色器中.
                PixelShader->SetParameters(RHICmdList, View, LightSceneInfo, ScreenShadowMaskTexture, (bHairLighting) ? &RenderLightParams : nullptr);
            }

            (......)

            // 绘制全屏幕矩形, 以便在屏幕空间计算平行光光照.
            DrawRectangle(
                RHICmdList,
                0, 0,
                View.ViewRect.Width(), View.ViewRect.Height(),
                View.ViewRect.Min.X, View.ViewRect.Min.Y,
                View.ViewRect.Width(), View.ViewRect.Height(),
                View.ViewRect.Size(),
                FSceneRenderTargets::Get(RHICmdList).GetBufferSizeXY(),
                VertexShader,
                EDRF_UseTriangleOptimization);
        }
        else // 非平行光.
        {
            (......)
        }
    }
}

上面可知,是通过调用PixelShader->SetParametersScreenShadowMaskTexture绑定到shader中,下面分析其绑定过程:

// Engine\Source\Runtime\Renderer\Private\LightRendering.cpp

void FDeferredLightPS::SetParameters(FRHICommandList& RHICmdList, const FSceneView& View, const FLightSceneInfo* LightSceneInfo, IPooledRenderTarget* ScreenShadowMaskTexture, ...)
{
    FRHIPixelShader* ShaderRHI = RHICmdList.GetBoundPixelShader();
    SetParametersBase(RHICmdList, ShaderRHI, View, ScreenShadowMaskTexture, LightSceneInfo->Proxy->GetIESTextureResource(), RenderLightParams);
    
    (......)
}

void FDeferredLightPS::SetParametersBase(FRHICommandList& RHICmdList, FRHIPixelShader* ShaderRHI, const FSceneView& View, IPooledRenderTarget* ScreenShadowMaskTexture, ...)
{
    if(LightAttenuationTexture.IsBound())
    {
        // 绑定ScreenShadowMaskTexture到LightAttenuationTexture
        SetTextureParameter(
            RHICmdList,
            ShaderRHI,
            LightAttenuationTexture,
            LightAttenuationTextureSampler,
            TStaticSamplerState<SF_Point,AM_Wrap,AM_Wrap,AM_Wrap>::GetRHI(),
            ScreenShadowMaskTexture ? ScreenShadowMaskTexture->GetRenderTargetItem().ShaderResourceTexture : GWhiteTexture->TextureRHI
        );
    }

    (......)
}

从上面可知,光源的阴影遮蔽纹理在shader的名字叫:LightAttenuationTexture,采用器名字叫:LightAttenuationTextureSampler。

5.6.5.6 阴影Shader逻辑

搜索所有Shader文件的关键字LightAttenuationTexture,不难在common.ush中找到相关的逻辑:

// Engine\Shaders\Private\Common.ush

(......)

// 声明阴影遮蔽变量.
Texture2D        LightAttenuationTexture;
SamplerState    LightAttenuationTextureSampler;

(......)

// 获取像素的光照衰减(即阴影)的平方.
float4 GetPerPixelLightAttenuation(float2 UV)
{
    return Square(Texture2DSampleLevel(LightAttenuationTexture, LightAttenuationTextureSampler, UV, 0));
}

(......)

继续追踪GetPerPixelLightAttenuation接口,最终发现唯一的一处调用,在DeferredLightPixelShaders.usf中:

// Engine\Shaders\Private\DeferredLightPixelShaders.usf

(.....)

void DeferredLightPixelMain(
#if LIGHT_SOURCE_SHAPE > 0
    float4 InScreenPosition : TEXCOORD0,
#else
    float2 ScreenUV            : TEXCOORD0,
    float3 ScreenVector        : TEXCOORD1,
#endif
    float4 SVPos            : SV_POSITION,
    out float4 OutColor        : SV_Target0
    )
{
    const float2 PixelPos = SVPos.xy;
    OutColor = 0;

    (......)

    const float SceneDepth = CalcSceneDepth(InputParams.ScreenUV);
    FDeferredLightData LightData = SetupLightDataForStandardDeferred();

    (......)
    
    float SurfaceShadow = 1.0f;
    // 注意此处调用了GetPerPixelLightAttenuation(InputParams.ScreenUV)作为GetDynamicLighting的float4 LightAttenuation实参.
    const float4 Radiance = GetDynamicLighting(DerivedParams.WorldPosition, DerivedParams.CameraVector, ScreenSpaceData.GBuffer, ScreenSpaceData.AmbientOcclusion, ScreenSpaceData.GBuffer.ShadingModelID, LightData, GetPerPixelLightAttenuation(InputParams.ScreenUV), Dither, uint2(InputParams.PixelPos), RectTexture, SurfaceShadow);
    const float  Attenuation = ComputeLightProfileMultiplier(DerivedParams.WorldPosition, DeferredLightUniforms.Position, -DeferredLightUniforms.Direction, DeferredLightUniforms.Tangent);

    OutColor += (Radiance * Attenuation) * OpaqueVisibility;

    (......)

    OutColor.rgba *= GetExposure();
}

注意上面的GetDynamicLighting的形参float4 LightAttenuation正是调用了GetPerPixelLightAttenuation(InputParams.ScreenUV)进行赋值。要理解这个参数的具体计算逻辑,还得深入GetDynamicLighting

// Engine\Shaders\Private\DeferredLightingCommon.ush

float4 GetDynamicLighting(..., float4 LightAttenuation, ..., inout float SurfaceShadow)
{
    // 此处调用了GetDynamicLightingSplit(分析见下面)
    FDeferredLightingSplit SplitLighting = GetDynamicLightingSplit(
        WorldPosition, CameraVector, GBuffer, AmbientOcclusion, ShadingModelID, 
        LightData, LightAttenuation, Dither, SVPos, SourceTexture,
        SurfaceShadow);

    return SplitLighting.SpecularLighting + SplitLighting.DiffuseLighting;
}

FDeferredLightingSplit GetDynamicLightingSplit(..., float4 LightAttenuation, ..., inout float SurfaceShadow)
{
    (......)

    BRANCH
    if( LightMask > 0 )
    {
        FShadowTerms Shadow;
        Shadow.SurfaceShadow = AmbientOcclusion;
        
        (......)
        
        // 获取阴影数据项, 这里会传入LightAttenuation, 后面会继续追踪GetShadowTerms.
        GetShadowTerms(GBuffer, LightData, WorldPosition, L, LightAttenuation, Dither, Shadow);
        
        SurfaceShadow = Shadow.SurfaceShadow;

        if( Shadow.SurfaceShadow + Shadow.TransmissionShadow > 0 )
        {
            const bool bNeedsSeparateSubsurfaceLightAccumulation = UseSubsurfaceProfile(GBuffer.ShadingModelID);
            float3 LightColor = LightData.Color;

            (......)

            float3 LightingDiffuse = Diffuse_Lambert( GBuffer.DiffuseColor ) * Lighting;
            LightAccumulator_AddSplit(LightAccumulator, LightingDiffuse, 0.0f, 0, LightColor * LightMask * Shadow.SurfaceShadow, bNeedsSeparateSubsurfaceLightAccumulation);
            
            (......)
        }
    }

    return LightAccumulator_GetResultSplit(LightAccumulator);
}

void GetShadowTerms(FGBufferData GBuffer, FDeferredLightData LightData, float3 WorldPosition, float3 L, float4 LightAttenuation, float Dither, inout FShadowTerms Shadow)
{
    float ContactShadowLength = 0.0f;
    const float ContactShadowLengthScreenScale = View.ClipToView[1][1] * GBuffer.Depth;

    BRANCH
    if (LightData.ShadowedBits)
    {
        // 重映射光照衰减缓冲. (具体参见ShadowRendering.cpp)
        // LightAttenuation的数据布局:
        // LightAttenuation.x: 全景平行光阴影.
        // LightAttenuation.y: 全景平行光SSS阴影.
        // LightAttenuation.z: 光照函数+逐物体阴影.
        // LightAttenuation.w: 逐物体SSS阴影.

        // 从近似的GBuffer通道获取静态阴影.
        float UsesStaticShadowMap = dot(LightData.ShadowMapChannelMask, float4(1, 1, 1, 1));
        // 恢复静态阴影.
        float StaticShadowing = lerp(1, dot(GBuffer.PrecomputedShadowFactors, LightData.ShadowMapChannelMask), UsesStaticShadowMap);

        if (LightData.bRadialLight) // 径向光源
        {
            // 恢复阴影数据.
            // 表面阴影 = (光照函数+逐物体阴影) * 静态阴影.
            Shadow.SurfaceShadow = LightAttenuation.z * StaticShadowing;
            Shadow.TransmissionShadow = LightAttenuation.w * StaticShadowing;
            Shadow.TransmissionThickness = LightAttenuation.w;
        }
        else // 非径向光源(平行光)
        {
            // Remapping the light attenuation buffer (see ShadowRendering.cpp)
            // Also fix up the fade between dynamic and static shadows
            // to work with plane splits rather than spheres.

            float DynamicShadowFraction = DistanceFromCameraFade(GBuffer.Depth, LightData, WorldPosition, View.WorldCameraOrigin);
            // For a directional light, fade between static shadowing and the whole scene dynamic shadowing based on distance + per object shadows
            Shadow.SurfaceShadow = lerp(LightAttenuation.x, StaticShadowing, DynamicShadowFraction);
            // Fade between SSS dynamic shadowing and static shadowing based on distance
            Shadow.TransmissionShadow = min(lerp(LightAttenuation.y, StaticShadowing, DynamicShadowFraction), LightAttenuation.w);

            Shadow.SurfaceShadow *= LightAttenuation.z;
            Shadow.TransmissionShadow *= LightAttenuation.z;

            // Need this min or backscattering will leak when in shadow which cast by non perobject shadow(Only for directional light)
            Shadow.TransmissionThickness = min(LightAttenuation.y, LightAttenuation.w);
        }

        FLATTEN
        if (LightData.ShadowedBits > 1 && LightData.ContactShadowLength > 0)
        {
            ContactShadowLength = LightData.ContactShadowLength * (LightData.ContactShadowLengthInWS ? 1.0f : ContactShadowLengthScreenScale);
        }
    }

    // 接触阴影.
#if SUPPORT_CONTACT_SHADOWS
    if ((LightData.ShadowedBits < 2 && (GBuffer.ShadingModelID == SHADINGMODELID_HAIR))
        || GBuffer.ShadingModelID == SHADINGMODELID_EYE)
    {
        ContactShadowLength = 0.2 * ContactShadowLengthScreenScale;
    }

    #if MATERIAL_CONTACT_SHADOWS
        ContactShadowLength = 0.2 * ContactShadowLengthScreenScale;
    #endif

    BRANCH
    if (ContactShadowLength > 0.0)
    {
        float StepOffset = Dither - 0.5;
        float ContactShadow = ShadowRayCast( WorldPosition + View.PreViewTranslation, L, ContactShadowLength, 8, StepOffset );
        
        Shadow.SurfaceShadow *= ContactShadow;

        FLATTEN
        if( GBuffer.ShadingModelID == SHADINGMODELID_HAIR ||  GBuffer.ShadingModelID == SHADINGMODELID_EYE  )
        {
            const bool bUseComplexTransmittance = (LightData.HairTransmittance.ScatteringComponent & HAIR_COMPONENT_MULTISCATTER) > 0;
            if (!bUseComplexTransmittance)
            {
                Shadow.TransmissionShadow *= ContactShadow;
            }
        }
        else
            Shadow.TransmissionShadow *= ContactShadow * 0.5 + 0.5;
    }
#endif

    Shadow.HairTransmittance = LightData.HairTransmittance;
    Shadow.HairTransmittance.OpaqueVisibility = Shadow.SurfaceShadow;
}

在计算表面阴影时,直接采样一次LightAttenuationTexture的值,恢复出各类阴影的参数,最后应用于光照计算中。

5.6.5.7 阴影应用总结

阴影应用阶段可以关注几个重要步骤和额外说明:

  • 每个开启了阴影且拥有有效阴影的光源都会渲染一次ScreenShadowMaskTexture。

  • 渲染ScreenShadowMaskTexture前,会调用ClearShadowMask清理ScreenShadowMaskTexture,保证ScreenShadowMaskTexture是原始状态(全白)。

  • 调用RenderShadowProjections,将光源下的所有阴影实例投射并叠加到屏幕空间的ScreenShadowMaskTexture。这样做的目的是类似于延迟着色,将多个阴影提前合并到View的视图空间,以便后续光照阶段只需要采样一次即可得到阴影信息,提升渲染效率。缺点是要多一个Pass来合并光源的阴影,增加了Draw Call,也增加了少量的显存消耗。

  • ScreenShadowMaskTexture除了常规的深度图阴影,还可能包含距离场阴影、次表面阴影、透明体积阴影、胶囊体阴影、高度场阴影、头发阴影、RT阴影等类型。它的每个通道都有特殊的用途,用于细分和存储不同类型的阴影信息。

  • 渲染ScreenShadowMaskTexture的shader在过滤阴影图时,支持三种过滤方式:无过滤、PCSS和自定义PCF。

  • 在光照计算的shader中,ScreenShadowMaskTexture被绑定到LightAttenuationTexture(光照衰减纹理)的变量中,亦即C++层的ScreenShadowMaskTexture其实就是光照Shader的LightAttenuationTexture。

  • 在光照Shader文件DeferredLightPixelShaders.usf中,计算动态光照时,只调用一次了GetPerPixelLightAttenuation()的值(亦即只采样一次LightAttenuationTexture),即可计算出物体表面受阴影影响的光照结果。

5.6.6 UE阴影总结

UE阴影系统的复杂度大大超乎了笔者原有的想象,阴影篇章只不过是分析了阴影的主要渲染流程和基础技术,不包含其他类型的阴影,都已经有2万多字的篇幅了,远远超出了原先的设想和规划。

下图总结了阴影渲染的主要流程和关键调用:

InitDynamicShadows

RenderShadowDepthMaps

RenderShadowProjections

GetPerPixelLightAttenuation

GetShadowTerms

GetDynamicLighting

初始化阴影

渲染阴影图

渲染ScreenShadowMaskTexture

采样LightAttenuationTexture

计算表面阴影

计算表面光照

输出结果

上面最出彩的莫过于增加了ScreenShadowMaskTexture的渲染步骤,充分利用GBuffer的几何数据、特殊混合模式和精细的通道分配,提前将光源复杂的阴影数据渲染到屏幕空间的光照衰减纹理中,以便在光照计算时,只需要采样一次光照衰减纹理即可重建出表面阴影。

需要特意指出的是,增加额外的Pass来渲染ScreenShadowMaskTexture会增加Draw Call,增加显存消耗,对于简单光影的场景,可能会带来性能的下降。

5.7 本篇总结

本篇主要阐述了UE的直接光影的渲染流程和主要算法,使得读者对光影的渲染有着大致的理解,至于更多技术细节和原理,需要读者自己去研读UE源码发掘。

本篇篇幅达到5万多字,是目前本系列文章最长的一篇,但也是撰写时间最短的一篇,5月果然是高效的月份。

恰逢昨天(2021.5.27),千呼万唤使出来的UE5终于发布了预览版,笔者第一时间下载并简略体验了一下,领略UE5的次世代光影的魅力。

UE5炫酷的启动画面。

笔者兴许会抽时间撰写一篇UE5特辑,剖析其使用的Lumen和Nanite的技术原理和实现细节。

5.7.1 本篇思考

按惯例,本篇也布置一些小思考,以助理解和加深UE BasePass和LightingPass的掌握和理解:

  • 请简述UE的光源的类型和主要渲染流程。
  • 请简述UE的阴影的类型和主要渲染流程。
  • 请修复UE官方版本中透明物体在区域光下无法被照亮的问题。
  • 扩充UE的灯光通道数量。
  • 请实现逐网格的虚拟光源,使得每个网格可以被单独打光,并支持不透明、Masked、半透明物体。

特别说明

  • 感谢所有参考文献的作者,部分图片来自参考文献和网络,侵删。
  • 本系列文章为笔者原创,只发表在博客园上,欢迎分享本文链接,但未经同意,不允许转载!
  • 系列文章,未完待续,完整目录请戳内容纲目
  • 系列文章,未完待续,完整目录请戳内容纲目
  • 系列文章,未完待续,完整目录请戳内容纲目

参考文献

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值