175 KiB
Raw Blame History

title, date, excerpt, tags, rating
title date excerpt tags rating
剖析虚幻渲染体系05- 光源和阴影 2024-02-08 17:48:39

前言

https://www.cnblogs.com/timlly/p/14817455.html

Shader体系

Shader模块层级

第一梯队的shader模块是最底层最基础的模块,这些模块不会引用其它模块,但会被其它很多模块引用。这些模块主要有:

  • BasePassCommon.ush
  • BRDF.ush
  • CapsuleLight.ush
  • Common.ush
  • CommonViewUniformBuffer.ush
  • Definitions.usf
  • FP16Math.ush
  • Platform.ush
  • ShaderVersion.ush
  • LightGridCommon.ush
  • LocalVertexFactoryCommon.ush
  • ShadingCommon.ush
  • ShadowDepthCommon.ush
  • ShadowProjectionCommon.ush
  • SHCommon.ush
  • (......)

第二梯队重要或基础模块会引用第一梯队的基础模块,但也会被其它梯队或模块引用:

  • BasePassVertexCommon.ush
  • CapsuleLightIntegrate.ush
  • DeferredLightingCommon.ush
  • DeferredShadingCommon.ush
  • LocalVertexFactory.ush
  • MaterialTemplate.ush
  • RectLight.ush
  • RectLightIntegrate.ush
  • ShadingModels.ush
  • ShadingModelsMaterial.ush
  • VertexFactoryCommon.ush
  • (......)

最后是第三梯队的模块,重要模块的实现,会引用第一、第二梯队的模块:

  • BasePassPixelShader.usf
  • BasePassVertexShader.usf
  • CapsuleShadowShaders.usf
  • DeferredLightPixelShaders.usf
  • DeferredLightVertexShaders.usf
  • ShadowProjectionPixelShader.usf
  • ShadowProjectionVertexShader.usf
  • (......)

Shader基础模块

  • Platform.ush主要定义了跟图形APIDirectX、OpenGL、Vulkan、Metal和FEATURE_LEVEL相关的宏、变量及工具类接口。
  • Common.ush此模块主要包含了图形API或Feature Level相关的宏、类型、局部变量、静态变量、基础工具接口、常用Shader函数等。
  • Definitions.ush:此模块主要是预先定义了一些常见的宏,防止其它模块引用时出现语法错误。
  • ShadingCommon.ush此模块主要是定义了材质所有ShaderModel并提供了少量相关的工具类接口。
  • ShadingModels.ush:此模块主要是着色模型以及光照计算相关的类型和辅助接口。
  • BasePassCommon.ush此模块定义了BasePass的一些变量、宏定义、插值结构体和工具类接口。
  • ShadingModelsMaterial.ush提供了根据材质和指定参数设置GBuffe的接口。
  • BRDF.ush双向反射分布函数模块提供了很多基础光照算法及相关辅助接口。
  • VertexFactoryCommon.ush此模块主要定义了顶点变换相关的辅助接口。
  • LocalVertexFactoryCommon.ush局部顶点工厂通用模块定义了顶点工厂的数据插值结构体及部分辅助接口。
  • LocalVertexFactory.ush局部顶点工厂模块定义了骨骼蒙皮、顶点着色器等相关的数据类型和接口。
  • BasePassVertexCommon.ush此模块定义了一些BasePass通用的结构体、宏。
    • 需要注意的是核心结构体FGBufferData它是通用的结构体可用于BasePass和LightingPass的VS和PS之间也可用于前向和延迟渲染中。是最大数据集合体,在某些情况下,部分属性才有效,具体见ShadingModelsMaterial.ushSetGBufferForShadingModel
  • DeferredLightingCommon.ush:此模块定义了延迟光照相关的通用的接口、宏、变量、类型等。

BasePass

下面是RenderBasePassRenderBasePassViewParallel并行渲染的逻辑:

bool FDeferredShadingSceneRenderer::RenderBasePass(FRHICommandListImmediate& RHICmdList, FExclusiveDepthStencil::Type BasePassDepthStencilAccess, IPooledRenderTarget* ForwardScreenSpaceShadowMask, bool bParallelBasePass, bool bRenderLightmapDensity)
{
    (......)
    
    FExclusiveDepthStencil::Type BasePassDepthStencilAccess_NoDepthWrite = FExclusiveDepthStencil::Type(BasePassDepthStencilAccess & ~FExclusiveDepthStencil::DepthWrite);
    // 并行模式
    if (bParallelBasePass)
    {
        // 绘制任务等待.
        FScopedCommandListWaitForTasks Flusher(CVarRHICmdFlushRenderThreadTasksBasePass.GetValueOnRenderThread() > 0 || CVarRHICmdFlushRenderThreadTasks.GetValueOnRenderThread() > 0, RHICmdList);
        
        // 遍历所有view, 每个view渲染一次Base Pass.
        for (int32 ViewIndex = 0; ViewIndex < Views.Num(); ViewIndex++)
        {
            FViewInfo& View = Views[ViewIndex];

            // Uniform Buffer
            TUniformBufferRef<FOpaqueBasePassUniformParameters> BasePassUniformBuffer;
            CreateOpaqueBasePassUniformBuffer(RHICmdList, View, ForwardScreenSpaceShadowMask, nullptr, nullptr, nullptr, BasePassUniformBuffer);

            // Render State
            FMeshPassProcessorRenderState DrawRenderState(View, BasePassUniformBuffer);
            SetupBasePassState(BasePassDepthStencilAccess, ViewFamily.EngineShowFlags.ShaderComplexity, DrawRenderState);

            const bool bShouldRenderView = View.ShouldRenderView();
            if (bShouldRenderView)
            {
                Scene->UniformBuffers.UpdateViewUniformBuffer(View);

                // 执行并行渲染.
                RenderBasePassViewParallel(View, RHICmdList, BasePassDepthStencilAccess, DrawRenderState);
            }

            FSceneRenderTargets::Get(RHICmdList).BeginRenderingGBuffer(RHICmdList, ERenderTargetLoadAction::ELoad, ERenderTargetLoadAction::ELoad, BasePassDepthStencilAccess, this->ViewFamily.EngineShowFlags.ShaderComplexity);
            RHICmdList.EndRenderPass();

            (......)
        }
    }

    (......)
}

void FDeferredShadingSceneRenderer::RenderBasePassViewParallel(FViewInfo& View, FRHICommandListImmediate& ParentCmdList, FExclusiveDepthStencil::Type BasePassDepthStencilAccess, const FMeshPassProcessorRenderState& InDrawRenderState)
{
    // 并行绘制的数据: 命令队列, 上下文, 渲染状态等.
    FBasePassParallelCommandListSet ParallelSet(View, ParentCmdList, 
        CVarRHICmdBasePassDeferredContexts.GetValueOnRenderThread() > 0, 
        CVarRHICmdFlushRenderThreadTasksBasePass.GetValueOnRenderThread() == 0 && CVarRHICmdFlushRenderThreadTasks.GetValueOnRenderThread() == 0,
        this,
        BasePassDepthStencilAccess,
        InDrawRenderState);

    // 触发并行绘制指令.
    View.ParallelMeshDrawCommandPasses[EMeshPass::BasePass].DispatchDraw(&ParallelSet, ParentCmdList);
}

RenderBasePass是依靠FScopedCommandListWaitForTasks等待绘制指令完成。

BasePass渲染状态

我们知道BasePass是通过FBasePassMeshProcessor来收集很多shader绑定和绘制参数的从Processor的过程中可以很容易知道BasePass绘制时使用的VS和PS分别是TBasePassVSTBasePassPS。 可知TBasePassVS提供了获取Shader绑定、更改编译环境和只编译指定的排列组合shader等接口此外还拥有反射球、光照图类型等属性。

Lighting

光源基础概念

UE的光源分配由FDeferredShadingSceneRenderer::Render内的bComputeLightGrid变量决定:

void FDeferredShadingSceneRenderer::Render(FRHICommandListImmediate& RHICmdList)
{
    (......)
    bool bComputeLightGrid = false;
    if (!IsSimpleForwardShadingEnabled(ShaderPlatform))
    {
        if (bUseGBuffer)
        {
            bComputeLightGrid = bRenderDeferredLighting;
        }
        else
        {
            bComputeLightGrid = ViewFamily.EngineShowFlags.Lighting;
        }

        bComputeLightGrid |= (
            ShouldRenderVolumetricFog() ||
            ViewFamily.ViewMode != VMI_Lit);
    }
    (......)
}

是否开启光源分配任务由几个因素共同影响不是简单前向着色并且使用了GBuffer的延迟渲染或开启了ViewFamily的光照计算或需要渲染体积雾等。接着调用下面的逻辑执行光源分配

void FDeferredShadingSceneRenderer::Render(FRHICommandListImmediate& RHICmdList)
{
    (......)
    // 有序的光源集合.
    FSortedLightSetSceneInfo SortedLightSet;
    {
        // 收集和排序光源.
        GatherAndSortLights(SortedLightSet);
        // 计算光源格子(分配光源).
        ComputeLightGrid(RHICmdList, bComputeLightGrid, SortedLightSet);
    }
    (......)
}

FSortedLightSetSceneInfo是光源非常重要的基础概念,后续的光源处理都会涉及到它。下面看看它和其它光源相关概念的定义:

// Engine\Source\Runtime\Engine\Public\PrimitiveSceneProxy.h
// 简单动态光源的数据.
class FSimpleLightEntry
{
public:
    FVector Color;
    float Radius;
    float Exponent;
    float VolumetricScatteringIntensity;
    bool bAffectTranslucency;
};

// 简单动态光源的和视图相关的数据.
class FSimpleLightPerViewEntry
{
public:
    FVector Position;
};

// 指向简单光源实例的view相关的数据. 
// 类名应该有误, Instace应该是Instance.
class FSimpleLightInstacePerViewIndexData
{
public:
    uint32 PerViewIndex : 31; // 占31位
    uint32 bHasPerViewData;   // 占1位
};

// 简单动态光源列表.
class FSimpleLightArray
{
public:
    // 简单动态光源列表, 和视图无关.
    TArray<FSimpleLightEntry, TMemStackAllocator<>> InstanceData;
    // 简单动态光源列表, 和视图有关.
    TArray<FSimpleLightPerViewEntry, TMemStackAllocator<>> PerViewData;
    // 指向简单光源实例的view相关的数据列表. 
    TArray<FSimpleLightInstacePerViewIndexData, TMemStackAllocator<>> InstancePerViewDataIndices;

public:
    // 获取简单光源的视图相关的数据.
    inline const FSimpleLightPerViewEntry& GetViewDependentData(int32 LightIndex, int32 ViewIndex, int32 NumViews) const
    {
        if (InstanceData.Num() == PerViewData.Num())
        {
            return PerViewData[LightIndex];
        }
        else 
        {
            // 计算视图索引.
            FSimpleLightInstacePerViewIndexData PerViewIndex = InstancePerViewDataIndices[LightIndex];
            const int32 PerViewDataIndex = PerViewIndex.PerViewIndex + ( PerViewIndex.bHasPerViewData ? ViewIndex : 0 );
            return PerViewData[PerViewDataIndex];
        }
    }
};

// Engine\Source\Runtime\Renderer\Private\LightSceneInfo.h
// 用于排序的光源的信息.
struct FSortedLightSceneInfo
{
    union
    {
        // 类似于FMeshDrawCommand的排序键值, 下列数据成员的顺序控制着光源的排序重要性.
        struct
        {
            // 光源类型.
            uint32 LightType : LightType_NumBits;
            // 是否拥有纹理配置
            uint32 bTextureProfile : 1;
            // 是否拥有光照函数.
            uint32 bLightFunction : 1;
            // 是否计算阴影.
            uint32 bShadowed : 1;
            // 是否使用灯光通道.
            uint32 bUsesLightingChannels : 1;
            // 是否非简单光源.
            uint32 bIsNotSimpleLight : 1;
            // 是否不支持延迟分块渲染.
            uint32 bTiledDeferredNotSupported : 1;
            // 是否不支持延迟分簇渲染.
            uint32 bClusteredDeferredNotSupported : 1;
        } Fields;
        // 打包的排序键值.
        int32 Packed;
    } SortKey;

    // 对应的光源场景信息.
    const FLightSceneInfo* LightSceneInfo;
    // 简单光源索引.
    int32 SimpleLightIndex;

    // 非简单光源构造函数.
    explicit FSortedLightSceneInfo(const FLightSceneInfo* InLightSceneInfo)
        : LightSceneInfo(InLightSceneInfo),
        SimpleLightIndex(-1)
    {
        SortKey.Packed = 0;
        SortKey.Fields.bIsNotSimpleLight = 1;
    }

    // 简单光源构造函数.
    explicit FSortedLightSceneInfo(int32 InSimpleLightIndex)
        : LightSceneInfo(nullptr),
        SimpleLightIndex(InSimpleLightIndex)
    {
        SortKey.Packed = 0;
        SortKey.Fields.bIsNotSimpleLight = 0;
    }
};

struct FSortedLightSetSceneInfo
{
    // 简单光源结束索引.
    int SimpleLightsEnd;
    // 分块光照结束索引.
    int TiledSupportedEnd;
    // 分簇光照结束索引.
    int ClusteredSupportedEnd;

    // 首个带阴影图的光源索引.
    int AttenuationLightStart;

    // 简单光源列表.
    FSimpleLightArray SimpleLights;
    // 有序的非简单光源列表.
    TArray<FSortedLightSceneInfo, SceneRenderingAllocator> SortedLights;
};

可以关注的是FSortedLightSceneInfo的光源排序键值,最高优先级的属性跟分簇、分块、简单光源相关,其次是灯光通道、阴影、光照函数、光照配置,最低优先级是光源类型。

这样排序的依据是分簇、分块可以通过提前光源裁剪等操作极大地降低着色消耗和指令数量放在最高位简单光源光照逻辑简单很多指令数也少很多所有简单光源都可以分簇分块渲染所以紧随分簇分块之后剩下的是非简单光照的特性由于灯光通道是否开启也直接影响很多逻辑开启了灯光通道会较大地影响渲染效率故而放在非简单光源的首位是否开启阴影对渲染性能的影响也非常大如果开启了阴影会增加若干个Pass绘制阴影图和衰减纹理增加渲染纹理切换故而排次位合情合理后面的几个键值以此类推。

GatherAndSortLights

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

void FDeferredShadingSceneRenderer::GatherAndSortLights(FSortedLightSetSceneInfo& OutSortedLights)
{
    if (bAllowSimpleLights)
    {
        // 收集简单光源, 详细解析见后面.
        GatherSimpleLights(ViewFamily, Views, OutSortedLights.SimpleLights);
    }
    
    FSimpleLightArray &SimpleLights = OutSortedLights.SimpleLights;
    TArray<FSortedLightSceneInfo, SceneRenderingAllocator> &SortedLights = OutSortedLights.SortedLights;

    // SortedLights包含了简单光源和非简单光源, 方便后面排序.
    SortedLights.Empty(Scene->Lights.Num() + SimpleLights.InstanceData.Num());

    bool bDynamicShadows = ViewFamily.EngineShowFlags.DynamicShadows && GetShadowQuality() > 0;

    // 构建可见光源列表.
    for (TSparseArray<FLightSceneInfoCompact>::TConstIterator LightIt(Scene->Lights); LightIt; ++LightIt)
    {
        const FLightSceneInfoCompact& LightSceneInfoCompact = *LightIt;
        const FLightSceneInfo* const LightSceneInfo = LightSceneInfoCompact.LightSceneInfo;

        if (LightSceneInfo->ShouldRenderLightViewIndependent()
            && !ViewFamily.EngineShowFlags.ReflectionOverride)
        {
            // 检查光源是否在某个vie内可见.
            for (int32 ViewIndex = 0; ViewIndex < Views.Num(); ViewIndex++)
            {
                if (LightSceneInfo->ShouldRenderLight(Views[ViewIndex]))
                {
                    // 创建FSortedLightSceneInfo实例.
                    FSortedLightSceneInfo* SortedLightInfo = new(SortedLights) FSortedLightSceneInfo(LightSceneInfo);

                    // 检测阴影或光照函数.
                    SortedLightInfo->SortKey.Fields.LightType = LightSceneInfoCompact.LightType;
                    SortedLightInfo->SortKey.Fields.bTextureProfile = ViewFamily.EngineShowFlags.TexturedLightProfiles && LightSceneInfo->Proxy->GetIESTextureResource();
                    SortedLightInfo->SortKey.Fields.bShadowed = bDynamicShadows && CheckForProjectedShadows(LightSceneInfo);
                    SortedLightInfo->SortKey.Fields.bLightFunction = ViewFamily.EngineShowFlags.LightFunctions && CheckForLightFunction(LightSceneInfo);
                    SortedLightInfo->SortKey.Fields.bUsesLightingChannels = Views[ViewIndex].bUsesLightingChannels && LightSceneInfo->Proxy->GetLightingChannelMask() != GetDefaultLightingChannelMask();

                    // 非简单光源.
                    SortedLightInfo->SortKey.Fields.bIsNotSimpleLight = 1;

                    // 支持分块或分簇的具体条件: 没有使用光源的附加特性, 也非平行光或矩形光.
                    const bool bTiledOrClusteredDeferredSupported =
                        !SortedLightInfo->SortKey.Fields.bTextureProfile &&
                        !SortedLightInfo->SortKey.Fields.bShadowed &&
                        !SortedLightInfo->SortKey.Fields.bLightFunction &&
                        !SortedLightInfo->SortKey.Fields.bUsesLightingChannels
                        && LightSceneInfoCompact.LightType != LightType_Directional
                        && LightSceneInfoCompact.LightType != LightType_Rect;

                    // 是否不支持分块渲染.
                    SortedLightInfo->SortKey.Fields.bTiledDeferredNotSupported = !(bTiledOrClusteredDeferredSupported && LightSceneInfo->Proxy->IsTiledDeferredLightingSupported());

                    // 是否不支持分簇渲染.
                    SortedLightInfo->SortKey.Fields.bClusteredDeferredNotSupported = !bTiledOrClusteredDeferredSupported;
                    break;
                }
            }
        }
    }
    
    // 加入简单光源.
    for (int32 SimpleLightIndex = 0; SimpleLightIndex < SimpleLights.InstanceData.Num(); SimpleLightIndex++)
    {
        FSortedLightSceneInfo* SortedLightInfo = new(SortedLights) FSortedLightSceneInfo(SimpleLightIndex);
        SortedLightInfo->SortKey.Fields.LightType = LightType_Point;
        SortedLightInfo->SortKey.Fields.bTextureProfile = 0;
        SortedLightInfo->SortKey.Fields.bShadowed = 0;
        SortedLightInfo->SortKey.Fields.bLightFunction = 0;
        SortedLightInfo->SortKey.Fields.bUsesLightingChannels = 0;

        // 简单光源.
        SortedLightInfo->SortKey.Fields.bIsNotSimpleLight = 0;

        // 简单光源都可以被分块或分簇渲染.
        SortedLightInfo->SortKey.Fields.bTiledDeferredNotSupported = 0;
        SortedLightInfo->SortKey.Fields.bClusteredDeferredNotSupported = 0;
    }

    // 光源排序, 无阴影无光照函数的光源优先, 可以避免渲染纹理切换.
    struct FCompareFSortedLightSceneInfo
    {
        FORCEINLINE bool operator()( const FSortedLightSceneInfo& A, const FSortedLightSceneInfo& B ) const
        {
            return A.SortKey.Packed < B.SortKey.Packed;
        }
    };
    SortedLights.Sort( FCompareFSortedLightSceneInfo() );

    // 初始化索引.
    OutSortedLights.SimpleLightsEnd = SortedLights.Num();
    OutSortedLights.TiledSupportedEnd = SortedLights.Num();
    OutSortedLights.ClusteredSupportedEnd = SortedLights.Num();
    OutSortedLights.AttenuationLightStart = SortedLights.Num();

    // 遍历所有待渲染的光源, 构建分簇分块和无阴影光源的范围.
    for (int32 LightIndex = 0; LightIndex < SortedLights.Num(); LightIndex++)
    {
        const FSortedLightSceneInfo& SortedLightInfo = SortedLights[LightIndex];
        const bool bDrawShadows = SortedLightInfo.SortKey.Fields.bShadowed;
        const bool bDrawLightFunction = SortedLightInfo.SortKey.Fields.bLightFunction;
        const bool bTextureLightProfile = SortedLightInfo.SortKey.Fields.bTextureProfile;
        const bool bLightingChannels = SortedLightInfo.SortKey.Fields.bUsesLightingChannels;

        if (SortedLightInfo.SortKey.Fields.bIsNotSimpleLight && OutSortedLights.SimpleLightsEnd == SortedLights.Num())
        {
            // 简单光源的结束索引.
            OutSortedLights.SimpleLightsEnd = LightIndex;
        }

        if (SortedLightInfo.SortKey.Fields.bTiledDeferredNotSupported && OutSortedLights.TiledSupportedEnd == SortedLights.Num())
        {
            // 分块光源的结束索引.
            OutSortedLights.TiledSupportedEnd = LightIndex;
        }

        if (SortedLightInfo.SortKey.Fields.bClusteredDeferredNotSupported && OutSortedLights.ClusteredSupportedEnd == SortedLights.Num())
        {
            // 分簇光源的结束索引.
            OutSortedLights.ClusteredSupportedEnd = LightIndex;
        }

        if (bDrawShadows || bDrawLightFunction || bLightingChannels)
        {
            // 首个带阴影的光源索引.
            OutSortedLights.AttenuationLightStart = LightIndex;
            break;
        }
    }
}

上面代码中可以看到,简单光源都可以被分块或分簇渲染,但对于非简单光源,只有满足以下条件的光源才可被分块或分簇渲染:

  • 没有使用光源的附加特性TextureProfile、LightFunction、LightingChannel
  • 没有开启阴影。
  • 非平行光或矩形光。

整个UE工程源码中只有以下一句才可能使bTiledDeferredLightingSupported为true

// Engine\Source\Runtime\Engine\Private\PointLightSceneProxy.h
class FPointLightSceneProxy : public FLocalLightSceneProxy
{
    FPointLightSceneProxy(const UPointLightComponent* Component)
    ,    SourceLength(Component->SourceLength)
    (......)
    {
        // 是否支持分块渲染.
        bTiledDeferredLightingSupported = (SourceLength == 0.0f);
    }
}

也就是说长度为0的点光源才支持分块渲染其它类型和情况的光源都不支持。光源长度大于0的点光源变成了球形光照模型不再适用于简单光照模型也无法分块渲染。

ComputeLightGrid

ComputeLightGrid作用是在锥体空间frustum space裁剪局部光源和反射探针到3D格子中构建每个视图相关的光源列表和格子。

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

void FDeferredShadingSceneRenderer::ComputeLightGrid(FRHICommandListImmediate& RHICmdList, bool bNeedLightGrid, FSortedLightSetSceneInfo &SortedLightSet)
{
    // 不需要光源格子或不支持SM5及以上, 直接返回.
    if (!bNeedLightGrid || FeatureLevel < ERHIFeatureLevel::SM5)
    {
        for (auto& View : Views)
        {
            View.ForwardLightingResources = GetMinimalDummyForwardLightingResources();
        }
        return;
    }

    {
        SCOPED_DRAW_EVENT(RHICmdList, ComputeLightGrid);

        static const auto AllowStaticLightingVar = IConsoleManager::Get().FindTConsoleVariableDataInt(TEXT("r.AllowStaticLighting"));
        const bool bAllowStaticLighting = (!AllowStaticLightingVar || AllowStaticLightingVar->GetValueOnRenderThread() != 0);
        const bool bAllowFormatConversion = RHISupportsBufferLoadTypeConversion(GMaxRHIShaderPlatform);

        // 是否有view使用前向渲染.
        bool bAnyViewUsesForwardLighting = false;
        for (int32 ViewIndex = 0; ViewIndex < Views.Num(); ViewIndex++)
        {
            const FViewInfo& View = Views[ViewIndex];
            bAnyViewUsesForwardLighting |= View.bTranslucentSurfaceLighting || ShouldRenderVolumetricFog() || View.bHasSingleLayerWaterMaterial;
        }

        // 是否裁剪到格子的光源.
        const bool bCullLightsToGrid = GLightCullingQuality 
            && (ViewFamily.EngineShowFlags.DirectLighting 
            && (IsForwardShadingEnabled(ShaderPlatform) || bAnyViewUsesForwardLighting || IsRayTracingEnabled() || ShouldUseClusteredDeferredShading()));
               
        // Store this flag if lights are injected in the grids, check with 'AreClusteredLightsInLightGrid()'
        bClusteredShadingLightsInLightGrid = bCullLightsToGrid;

        for (int32 ViewIndex = 0; ViewIndex < Views.Num(); ViewIndex++)
        {
            FViewInfo& View = Views[ViewIndex];
            FForwardLightData& ForwardLightData = View.ForwardLightingResources->ForwardLightData;
            ForwardLightData = FForwardLightData();

            TArray<FForwardLocalLightData, SceneRenderingAllocator> ForwardLocalLightData;
            
            float FurthestLight = 1000;

            // Track the end markers for different types
            int32 SimpleLightsEnd = 0;
            int32 ClusteredSupportedEnd = 0;

            // 裁剪光源.
            if (bCullLightsToGrid)
            {
                SimpleLightsEnd = SortedLightSet.SimpleLightsEnd;
                // 1. 插入简单光源.
                if (SimpleLightsEnd > 0)
                {
                    const FSimpleLightArray &SimpleLights = SortedLightSet.SimpleLights;

                    const FFloat16 SimpleLightSourceLength16f = FFloat16(0);
                    FLightingChannels SimpleLightLightingChannels;
                    // 将简单光源应用于所有通道上.
                    SimpleLightLightingChannels.bChannel0 = SimpleLightLightingChannels.bChannel1 = SimpleLightLightingChannels.bChannel2 = true;
                    const uint32 SimpleLightLightingChannelMask = GetLightingChannelMaskForStruct(SimpleLightLightingChannels);

                    // 使用有序的光源列表, 跟踪它们的范围.
                    for (int SortedIndex = 0; SortedIndex < SortedLightSet.SimpleLightsEnd; ++SortedIndex)
                    {
                        int32 SimpleLightIndex = SortedLightSet.SortedLights[SortedIndex].SimpleLightIndex;

                        ForwardLocalLightData.AddUninitialized(1);
                        FForwardLocalLightData& LightData = ForwardLocalLightData.Last();

                        const FSimpleLightEntry& SimpleLight = SimpleLights.InstanceData[SimpleLightIndex];
                        const FSimpleLightPerViewEntry& SimpleLightPerViewData = SimpleLights.GetViewDependentData(SimpleLightIndex, ViewIndex, Views.Num());
                        LightData.LightPositionAndInvRadius = FVector4(SimpleLightPerViewData.Position, 1.0f / FMath::Max(SimpleLight.Radius, KINDA_SMALL_NUMBER));
                        LightData.LightColorAndFalloffExponent = FVector4(SimpleLight.Color, SimpleLight.Exponent);

                        // 简单光源没有阴影.
                        uint32 ShadowMapChannelMask = 0;
                        ShadowMapChannelMask |= SimpleLightLightingChannelMask << 8;

                        LightData.LightDirectionAndShadowMapChannelMask = FVector4(FVector(1, 0, 0), *((float*)&ShadowMapChannelMask));

                        const FFloat16 VolumetricScatteringIntensity16f = FFloat16(SimpleLight.VolumetricScatteringIntensity);
                        const uint32 PackedWInt = ((uint32)SimpleLightSourceLength16f.Encoded) | ((uint32)VolumetricScatteringIntensity16f.Encoded << 16);

                        LightData.SpotAnglesAndSourceRadiusPacked = FVector4(-2, 1, 0, *(float*)&PackedWInt);
                        LightData.LightTangentAndSoftSourceRadius = FVector4(1.0f, 0.0f, 0.0f, 0.0f);
                        LightData.RectBarnDoor = FVector4(0, -2, 0, 0);
                    }
                }
                
                const TArray<FSortedLightSceneInfo, SceneRenderingAllocator>& SortedLights = SortedLightSet.SortedLights;
                ClusteredSupportedEnd = SimpleLightsEnd;
                // 2. 增加其它类型的光源, 追踪支持分簇渲染的末尾索引.
                for (int SortedIndex = SimpleLightsEnd; SortedIndex < SortedLights.Num(); ++SortedIndex)
                {
                    const FSortedLightSceneInfo& SortedLightInfo = SortedLights[SortedIndex];
                    const FLightSceneInfo* const LightSceneInfo = SortedLightInfo.LightSceneInfo;
                    const FLightSceneProxy* LightProxy = LightSceneInfo->Proxy;

                    if (LightSceneInfo->ShouldRenderLight(View)
                        && !ViewFamily.EngineShowFlags.ReflectionOverride)
                    {
                        FLightShaderParameters LightParameters;
                        LightProxy->GetLightShaderParameters(LightParameters);

                        if (LightProxy->IsInverseSquared())
                        {
                            LightParameters.FalloffExponent = 0;
                        }

                        if (View.bIsReflectionCapture)
                        {
                            LightParameters.Color *= LightProxy->GetIndirectLightingScale();
                        }

                        int32 ShadowMapChannel = LightProxy->GetShadowMapChannel();
                        int32 DynamicShadowMapChannel = LightSceneInfo->GetDynamicShadowMapChannel();

                        if (!bAllowStaticLighting)
                        {
                            ShadowMapChannel = INDEX_NONE;
                        }

                        // 静态阴影使用ShadowMapChannel, 动态阴影被打包进光源衰减纹理DynamicShadowMapChannel.
                        uint32 LightTypeAndShadowMapChannelMaskPacked =
                            (ShadowMapChannel == 0 ? 1 : 0) |
                            (ShadowMapChannel == 1 ? 2 : 0) |
                            (ShadowMapChannel == 2 ? 4 : 0) |
                            (ShadowMapChannel == 3 ? 8 : 0) |
                            (DynamicShadowMapChannel == 0 ? 16 : 0) |
                            (DynamicShadowMapChannel == 1 ? 32 : 0) |
                            (DynamicShadowMapChannel == 2 ? 64 : 0) |
                            (DynamicShadowMapChannel == 3 ? 128 : 0);

                        LightTypeAndShadowMapChannelMaskPacked |= LightProxy->GetLightingChannelMask() << 8;
                        LightTypeAndShadowMapChannelMaskPacked |= SortedLightInfo.SortKey.Fields.LightType << 16;

                        // 处理局部光源.
                        if ((SortedLightInfo.SortKey.Fields.LightType == LightType_Point && ViewFamily.EngineShowFlags.PointLights) ||
                            (SortedLightInfo.SortKey.Fields.LightType == LightType_Spot && ViewFamily.EngineShowFlags.SpotLights) ||
                            (SortedLightInfo.SortKey.Fields.LightType == LightType_Rect && ViewFamily.EngineShowFlags.RectLights))
                        {
                            ForwardLocalLightData.AddUninitialized(1);
                            FForwardLocalLightData& LightData = ForwardLocalLightData.Last();

                            // Track the last one to support clustered deferred
                            if (!SortedLightInfo.SortKey.Fields.bClusteredDeferredNotSupported)
                            {
                                ClusteredSupportedEnd = FMath::Max(ClusteredSupportedEnd, ForwardLocalLightData.Num());
                            }
                            const float LightFade = GetLightFadeFactor(View, LightProxy);
                            LightParameters.Color *= LightFade;

                            LightData.LightPositionAndInvRadius = FVector4(LightParameters.Position, LightParameters.InvRadius);
                            LightData.LightColorAndFalloffExponent = FVector4(LightParameters.Color, LightParameters.FalloffExponent);
                            LightData.LightDirectionAndShadowMapChannelMask = FVector4(LightParameters.Direction, *((float*)&LightTypeAndShadowMapChannelMaskPacked));

                            LightData.SpotAnglesAndSourceRadiusPacked = FVector4(LightParameters.SpotAngles.X, LightParameters.SpotAngles.Y, LightParameters.SourceRadius, 0);

                            LightData.LightTangentAndSoftSourceRadius = FVector4(LightParameters.Tangent, LightParameters.SoftSourceRadius);

                            LightData.RectBarnDoor = FVector4(LightParameters.RectLightBarnCosAngle, LightParameters.RectLightBarnLength, 0, 0);

                            float VolumetricScatteringIntensity = LightProxy->GetVolumetricScatteringIntensity();

                            if (LightNeedsSeparateInjectionIntoVolumetricFog(LightSceneInfo, VisibleLightInfos[LightSceneInfo->Id]))
                            {
                                // Disable this lights forward shading volumetric scattering contribution
                                VolumetricScatteringIntensity = 0;
                            }

                            // Pack both values into a single float to keep float4 alignment
                            const FFloat16 SourceLength16f = FFloat16(LightParameters.SourceLength);
                            const FFloat16 VolumetricScatteringIntensity16f = FFloat16(VolumetricScatteringIntensity);
                            const uint32 PackedWInt = ((uint32)SourceLength16f.Encoded) | ((uint32)VolumetricScatteringIntensity16f.Encoded << 16);
                            LightData.SpotAnglesAndSourceRadiusPacked.W = *(float*)&PackedWInt;

                            const FSphere BoundingSphere = LightProxy->GetBoundingSphere();
                            const float Distance = View.ViewMatrices.GetViewMatrix().TransformPosition(BoundingSphere.Center).Z + BoundingSphere.W;
                            FurthestLight = FMath::Max(FurthestLight, Distance);
                        }
                        // 平行光源.
                        else if (SortedLightInfo.SortKey.Fields.LightType == LightType_Directional && ViewFamily.EngineShowFlags.DirectionalLights)
                        {
                            (......)
                        }
                    }
                }
            }

            const int32 NumLocalLightsFinal = ForwardLocalLightData.Num();
            if (ForwardLocalLightData.Num() == 0)
            {
                ForwardLocalLightData.AddZeroed();
            }

            // 更新光源的数据到GPU.
            UpdateDynamicVector4BufferData(ForwardLocalLightData, View.ForwardLightingResources->ForwardLocalLightBuffer);

            const FIntPoint LightGridSizeXY = FIntPoint::DivideAndRoundUp(View.ViewRect.Size(), GLightGridPixelSize);
            ForwardLightData.ForwardLocalLightBuffer = View.ForwardLightingResources->ForwardLocalLightBuffer.SRV;
            ForwardLightData.NumLocalLights = NumLocalLightsFinal;
            ForwardLightData.NumReflectionCaptures = View.NumBoxReflectionCaptures + View.NumSphereReflectionCaptures;
            ForwardLightData.NumGridCells = LightGridSizeXY.X * LightGridSizeXY.Y * GLightGridSizeZ;
            ForwardLightData.CulledGridSize = FIntVector(LightGridSizeXY.X, LightGridSizeXY.Y, GLightGridSizeZ);
            ForwardLightData.MaxCulledLightsPerCell = GMaxCulledLightsPerCell;
            ForwardLightData.LightGridPixelSizeShift = FMath::FloorLog2(GLightGridPixelSize);
            ForwardLightData.SimpleLightsEndIndex = SimpleLightsEnd;
            ForwardLightData.ClusteredDeferredSupportedEndIndex = ClusteredSupportedEnd;

            // Clamp far plane to something reasonable
            float FarPlane = FMath::Min(FMath::Max(FurthestLight, View.FurthestReflectionCaptureDistance), (float)HALF_WORLD_MAX / 5.0f);
            FVector ZParams = GetLightGridZParams(View.NearClippingDistance, FarPlane + 10.f);
            ForwardLightData.LightGridZParams = ZParams;

            const uint64 NumIndexableLights = CHANGE_LIGHTINDEXTYPE_SIZE && !bAllowFormatConversion ? (1llu << (sizeof(FLightIndexType32) * 8llu)) : (1llu << (sizeof(FLightIndexType) * 8llu));

            const int32 NumCells = LightGridSizeXY.X * LightGridSizeXY.Y * GLightGridSizeZ * NumCulledGridPrimitiveTypes;

            if (View.ForwardLightingResources->NumCulledLightsGrid.NumBytes != NumCells * NumCulledLightsGridStride * sizeof(uint32))
            {
                View.ForwardLightingResources->NumCulledLightsGrid.Initialize(sizeof(uint32), NumCells * NumCulledLightsGridStride, PF_R32_UINT);
            }

            if (View.ForwardLightingResources->CulledLightDataGrid.NumBytes != NumCells * GMaxCulledLightsPerCell * LightIndexTypeSize)
            {
                View.ForwardLightingResources->CulledLightDataGrid.Initialize(LightIndexTypeSize, NumCells * GMaxCulledLightsPerCell, LightIndexTypeSize == sizeof(uint16) ? PF_R16_UINT : PF_R32_UINT);
            }

            const bool bShouldCacheTemporaryBuffers = View.ViewState != nullptr;
            FForwardLightingCullingResources LocalCullingResources;
            FForwardLightingCullingResources& ForwardLightingCullingResources = bShouldCacheTemporaryBuffers ? View.ViewState->ForwardLightingCullingResources : LocalCullingResources;

            const uint32 CulledLightLinksElements = NumCells * GMaxCulledLightsPerCell * LightLinkStride;

            ForwardLightData.DummyRectLightSourceTexture = GWhiteTexture->TextureRHI;
            ForwardLightData.NumCulledLightsGrid = View.ForwardLightingResources->NumCulledLightsGrid.SRV;
            ForwardLightData.CulledLightDataGrid = View.ForwardLightingResources->CulledLightDataGrid.SRV;

            // 创建光源数据的Uniform Buffer.
            View.ForwardLightingResources->ForwardLightDataUniformBuffer = TUniformBufferRef<FForwardLightData>::CreateUniformBufferImmediate(ForwardLightData, UniformBuffer_SingleFrame);

            // 下面使用RDG和Compute Shader去裁剪局部光源和反射探针.
            
            // 线程组数.
            const FIntVector NumGroups = FIntVector::DivideAndRoundUp(FIntVector(LightGridSizeXY.X, LightGridSizeXY.Y, GLightGridSizeZ), LightGridInjectionGroupSize);

            TArray<FRHIUnorderedAccessView*, TInlineAllocator<2>> OutUAVs({
                View.ForwardLightingResources->NumCulledLightsGrid.UAV,
                View.ForwardLightingResources->CulledLightDataGrid.UAV });

            RHICmdList.TransitionResources(EResourceTransitionAccess::EWritable, EResourceTransitionPipeline::EGfxToCompute, OutUAVs.GetData(), OutUAVs.Num());
            {
                FRDGBuilder GraphBuilder(RHICmdList);
                {
                    RDG_EVENT_SCOPE(GraphBuilder, "CullLights %ux%ux%u NumLights %u NumCaptures %u",
                        ForwardLightData.CulledGridSize.X,
                        ForwardLightData.CulledGridSize.Y,
                        ForwardLightData.CulledGridSize.Z,
                        ForwardLightData.NumLocalLights,
                        ForwardLightData.NumReflectionCaptures);

                    FRDGBufferRef CulledLightLinksBuffer = GraphBuilder.CreateBuffer(FRDGBufferDesc::CreateBufferDesc(sizeof(uint32), CulledLightLinksElements), TEXT("CulledLightLinks"));
                    FRDGBufferRef StartOffsetGridBuffer = GraphBuilder.CreateBuffer(FRDGBufferDesc::CreateBufferDesc(sizeof(uint32), NumCells), TEXT("StartOffsetGrid"));
                    FRDGBufferRef NextCulledLightLinkBuffer = GraphBuilder.CreateBuffer(FRDGBufferDesc::CreateBufferDesc(sizeof(uint32), 1), TEXT("NextCulledLightLink"));
                    FRDGBufferRef NextCulledLightDataBuffer = GraphBuilder.CreateBuffer(FRDGBufferDesc::CreateBufferDesc(sizeof(uint32), 1), TEXT("NextCulledLightData"));

                    // 创建PassParameters实例.
                    FLightGridInjectionCS::FParameters *PassParameters = GraphBuilder.AllocParameters<FLightGridInjectionCS::FParameters>();

                    PassParameters->View = View.ViewUniformBuffer;
                    PassParameters->ReflectionCapture = View.ReflectionCaptureUniformBuffer;
                    PassParameters->Forward = View.ForwardLightingResources->ForwardLightDataUniformBuffer;
                    PassParameters->RWNumCulledLightsGrid = View.ForwardLightingResources->NumCulledLightsGrid.UAV;
                    PassParameters->RWCulledLightDataGrid = View.ForwardLightingResources->CulledLightDataGrid.UAV;
                    PassParameters->RWNextCulledLightLink = GraphBuilder.CreateUAV(NextCulledLightLinkBuffer, PF_R32_UINT);
                    PassParameters->RWStartOffsetGrid = GraphBuilder.CreateUAV(StartOffsetGridBuffer, PF_R32_UINT);
                    PassParameters->RWCulledLightLinks = GraphBuilder.CreateUAV(CulledLightLinksBuffer, PF_R32_UINT);

                    FLightGridInjectionCS::FPermutationDomain PermutationVector;
                    PermutationVector.Set<FLightGridInjectionCS::FUseLinkedListDim>(GLightLinkedListCulling != 0);
                    // 光源格子注入shader.
                    TShaderMapRef<FLightGridInjectionCS> ComputeShader(View.ShaderMap, PermutationVector);

                    // 处理光源格子.
                    if (GLightLinkedListCulling != 0)
                    {
                        // 清理UAV.
                        AddClearUAVPass(GraphBuilder, PassParameters->RWStartOffsetGrid, 0xFFFFFFFF);
                        AddClearUAVPass(GraphBuilder, PassParameters->RWNextCulledLightLink, 0);
                        AddClearUAVPass(GraphBuilder, GraphBuilder.CreateUAV(NextCulledLightDataBuffer, PF_R32_UINT), 0);
                        // 增加光源格子注入Pass.
                        FComputeShaderUtils::AddPass(GraphBuilder, RDG_EVENT_NAME("LightGridInject:LinkedList"), ComputeShader, PassParameters, NumGroups);

                        {
                            // 光源格子压缩shader.
                            TShaderMapRef<FLightGridCompactCS> ComputeShaderCompact(View.ShaderMap);
                            FLightGridCompactCS::FParameters *PassParametersCompact = GraphBuilder.AllocParameters<FLightGridCompactCS::FParameters>();
                            PassParametersCompact->View = View.ViewUniformBuffer;
                            PassParametersCompact->Forward = View.ForwardLightingResources->ForwardLightDataUniformBuffer;

                            PassParametersCompact->CulledLightLinks = GraphBuilder.CreateSRV(CulledLightLinksBuffer, PF_R32_UINT);
                            PassParametersCompact->RWNumCulledLightsGrid = View.ForwardLightingResources->NumCulledLightsGrid.UAV;
                            PassParametersCompact->RWCulledLightDataGrid = View.ForwardLightingResources->CulledLightDataGrid.UAV;
                            PassParametersCompact->RWNextCulledLightData = GraphBuilder.CreateUAV(NextCulledLightDataBuffer, PF_R32_UINT);
                            PassParametersCompact->StartOffsetGrid = GraphBuilder.CreateSRV(StartOffsetGridBuffer, PF_R32_UINT);

                            // 增加光源格子压缩Pass.
                            FComputeShaderUtils::AddPass(GraphBuilder, RDG_EVENT_NAME("CompactLinks"), ComputeShaderCompact, PassParametersCompact, NumGroups);
                        }
                    }
                    else
                    {
                        RHICmdList.ClearUAVUint(View.ForwardLightingResources->NumCulledLightsGrid.UAV, FUintVector4(0, 0, 0, 0));
                        FComputeShaderUtils::AddPass(GraphBuilder, RDG_EVENT_NAME("LightGridInject:NotLinkedList"), ComputeShader, PassParameters, NumGroups);
                    }
                }
                GraphBuilder.Execute();

                RHICmdList.TransitionResources(EResourceTransitionAccess::EReadable, EResourceTransitionPipeline::EComputeToGfx, OutUAVs.GetData(), OutUAVs.Num());
            }
        }
    }
}

需要注意的是只有前向渲染、使用表面着色的透明通道和分簇延迟渲染才有效。处理光源格子使用了RDG和Compute Shader使用的Shader是FLightGridInjectionCS和FLightGridCompactCS.

LightingPass Shader

DeferredLightVertexShader

DeferredLightVertexShader的入口在DeferredLightVertexShaders.usf

#include "Common.ush"

#if defined(SHADER_RADIAL_LIGHT)
float4 StencilingGeometryPosAndScale;
float4 StencilingConeParameters;    // .x NumSides (0 if not cone), .y NumSlices, .z ConeAngle, .w ConeSphereRadius
float4x4 StencilingConeTransform;
float3 StencilingPreViewTranslation;
#endif

#if defined(SHADER_RADIAL_LIGHT) && SHADER_RADIAL_LIGHT == 0

// 使用全屏方块渲染平行光的VS.
void DirectionalVertexMain(
    in float2 InPosition : ATTRIBUTE0,
    in float2 InUV       : ATTRIBUTE1,
    out float2 OutTexCoord : TEXCOORD0,
    out float3 OutScreenVector : TEXCOORD1,
    out float4 OutPosition : SV_POSITION
    )
{
    // 绘制矩形.
    DrawRectangle(float4(InPosition.xy, 0, 1), InUV, OutPosition, OutTexCoord);
    // 将输出位置转换成屏幕向量.
    OutScreenVector = mul(float4(OutPosition.xy, 1, 0), View.ScreenToTranslatedWorld).xyz;
}
#endif

#if FEATURE_LEVEL >= FEATURE_LEVEL_SM4 && SHADER_RADIAL_LIGHT == 1
// 使用近似边界几何体绘制点光源或聚光灯的VS.
void RadialVertexMain(
    in uint InVertexId : SV_VertexID,
    in float3 InPosition : ATTRIBUTE0,
    out float4 OutScreenPosition : TEXCOORD0,
    out float4 OutPosition : SV_POSITION
    )
{
    float3 WorldPosition;
    uint NumSides = StencilingConeParameters.x;
    // 圆锥体形状
    if (NumSides != 0)
    {
        float SphereRadius = StencilingConeParameters.w;
        float ConeAngle = StencilingConeParameters.z;

        // 锥体顶点着色.
        const float InvCosRadiansPerSide = 1.0f / cos(PI / (float)NumSides);
        // 使用Cos(Theta)=邻边(Adjacent)/斜边(Hypotenuse)的公式来求圆锥末端沿圆锥Z轴的距离.
        const float ZRadius = SphereRadius * cos(ConeAngle);
        const float TanConeAngle = tan(ConeAngle);

        uint NumSlices = StencilingConeParameters.y;
        uint CapIndexStart = NumSides * NumSlices;
        // 生成圆锥形的顶点
        if (InVertexId < CapIndexStart)
        {
            uint SliceIndex = InVertexId / NumSides;
            uint SideIndex = InVertexId % NumSides;

            const float CurrentAngle = SideIndex * 2 * PI / (float)NumSides;
            const float DistanceDownConeDirection = ZRadius * SliceIndex / (float)(NumSlices - 1);
            // 使用Tan(Theta)=对边(Opposite)/邻边(Adjacent)的公式来求解这个切片的半径.
            // 提高有效半径,使圆的边缘位于圆锥体上, 从而代替顶点.
            const float SliceRadius = DistanceDownConeDirection * TanConeAngle * InvCosRadiansPerSide;
            // 在圆锥的局部空间创建一个位置在XY平面上形成一个圆并沿Z轴偏移.
            const float3 LocalPosition = float3(ZRadius * SliceIndex / (float)(NumSlices - 1), SliceRadius * sin(CurrentAngle), SliceRadius * cos(CurrentAngle));
            // 转换到世界空间并应用pre-view translation因为这些顶点将与一个已删除pre-view translation的着色器一起使用.
            WorldPosition = mul(float4(LocalPosition, 1), StencilingConeTransform).xyz + StencilingPreViewTranslation;
        }
        else
        {
            // 为球帽生成顶点.
            const float CapRadius = ZRadius * tan(ConeAngle);

            uint VertexId = InVertexId - CapIndexStart;
            uint SliceIndex = VertexId / NumSides;
            uint SideIndex = VertexId % NumSides;

            const float UnadjustedSliceRadius = CapRadius * SliceIndex / (float)(NumSlices - 1);
            // 提高有效半径,使圆的边缘位于圆锥体上, 从而代替顶点.
            const float SliceRadius = UnadjustedSliceRadius * InvCosRadiansPerSide;
            // 用勾股定理(Pythagorean theorem)求出这个切片的Z轴距离.
            const float ZDistance = sqrt(SphereRadius * SphereRadius - UnadjustedSliceRadius * UnadjustedSliceRadius);

            const float CurrentAngle = SideIndex * 2 * PI / (float)NumSides;
            const float3 LocalPosition = float3(ZDistance, SliceRadius * sin(CurrentAngle), SliceRadius * cos(CurrentAngle));
            WorldPosition = mul(float4(LocalPosition, 1), StencilingConeTransform).xyz + StencilingPreViewTranslation;
        }
    }
    else // 球形.
    {
        WorldPosition = InPosition * StencilingGeometryPosAndScale.w + StencilingGeometryPosAndScale.xyz;
    }

    OutScreenPosition = OutPosition = mul(float4(WorldPosition, 1), View.TranslatedWorldToClip);
}
#endif

(......)

对于点光源和聚光灯,则需要特殊处理,分别使用球体和圆锥体绘制,以便剔除在光源影响范围之外的像素,所以它们的顶点处理会比平行光复杂一些。

DeferredLightPixelShader

DeferredLightPixelShader的入口在DeferredLightPixelShaders.usf

#define SUPPORT_CONTACT_SHADOWS 1

#include "Common.ush"
#include "DeferredShadingCommon.ush"
#include "DeferredLightingCommon.ush"

(......)

#if USE_ATMOSPHERE_TRANSMITTANCE
#include "/Engine/Private/SkyAtmosphereCommon.ush"
#endif

// 输入参数.
struct FInputParams
{
    float2 PixelPos;
    float4 ScreenPosition;
    float2 ScreenUV;
    float3 ScreenVector;
};

// 派生参数.
struct FDerivedParams
{
    float3 CameraVector;
    float3 WorldPosition;
};

// 获取派生参数.
FDerivedParams GetDerivedParams(in FInputParams Input, in float SceneDepth)
{
    FDerivedParams Out;
#if LIGHT_SOURCE_SHAPE > 0
    // With a perspective projection, the clip space position is NDC * Clip.w
    // With an orthographic projection, clip space is the same as NDC
    float2 ClipPosition = Input.ScreenPosition.xy / Input.ScreenPosition.w * (View.ViewToClip[3][3] < 1.0f ? SceneDepth : 1.0f);
    Out.WorldPosition = mul(float4(ClipPosition, SceneDepth, 1), View.ScreenToWorld).xyz;
    Out.CameraVector = normalize(Out.WorldPosition - View.WorldCameraOrigin);
#else
    Out.WorldPosition = Input.ScreenVector * SceneDepth + View.WorldCameraOrigin;
    Out.CameraVector = normalize(Input.ScreenVector);
#endif
    return Out;
}

// 创建并设置延迟光照数据结构FDeferredLightData.
FDeferredLightData SetupLightDataForStandardDeferred()
{
    FDeferredLightData LightData;
    
    LightData.Position = DeferredLightUniforms.Position;
    LightData.InvRadius = DeferredLightUniforms.InvRadius;
    LightData.Color = DeferredLightUniforms.Color;
    LightData.FalloffExponent = DeferredLightUniforms.FalloffExponent;
    LightData.Direction = DeferredLightUniforms.Direction;
    LightData.Tangent = DeferredLightUniforms.Tangent;
    LightData.SpotAngles = DeferredLightUniforms.SpotAngles;
    LightData.SourceRadius = DeferredLightUniforms.SourceRadius;
    LightData.SourceLength = DeferredLightUniforms.SourceLength;
    LightData.SoftSourceRadius = DeferredLightUniforms.SoftSourceRadius;
    LightData.SpecularScale = DeferredLightUniforms.SpecularScale;
    LightData.ContactShadowLength = abs(DeferredLightUniforms.ContactShadowLength);
    LightData.ContactShadowLengthInWS = DeferredLightUniforms.ContactShadowLength < 0.0f;
    LightData.DistanceFadeMAD = DeferredLightUniforms.DistanceFadeMAD;
    LightData.ShadowMapChannelMask = DeferredLightUniforms.ShadowMapChannelMask;
    LightData.ShadowedBits = DeferredLightUniforms.ShadowedBits;

    LightData.bInverseSquared = INVERSE_SQUARED_FALLOFF;
    LightData.bRadialLight = LIGHT_SOURCE_SHAPE > 0;
    LightData.bSpotLight = LIGHT_SOURCE_SHAPE > 0;
    LightData.bRectLight = LIGHT_SOURCE_SHAPE == 2;
    
    LightData.RectLightBarnCosAngle = DeferredLightUniforms.RectLightBarnCosAngle;
    LightData.RectLightBarnLength = DeferredLightUniforms.RectLightBarnLength;

    LightData.HairTransmittance = InitHairTransmittanceData();
    return LightData;
}

// 灯光通道纹理.
Texture2D<uint> LightingChannelsTexture;
// 获取灯光通道掩码.
uint GetLightingChannelMask(float2 UV)
{
    uint2 IntegerUV = UV * View.BufferSizeAndInvSize.xy;
    return LightingChannelsTexture.Load(uint3(IntegerUV, 0)).x;
}

float GetExposure()
{
#if USE_PREEXPOSURE
    return View.PreExposure;
#else
    return 1;
#endif
}

(......)


#if USE_HAIR_LIGHTING == 0 || USE_HAIR_LIGHTING == 1

// 延迟灯光像素着色器主入口.
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;

    // Convert input data (directional/local light)
    FInputParams InputParams = (FInputParams)0;
    InputParams.PixelPos        = SVPos.xy;
#if LIGHT_SOURCE_SHAPE > 0
    InputParams.ScreenPosition    = InScreenPosition;
    // 计算特殊光源的屏幕UV.
    InputParams.ScreenUV        = InScreenPosition.xy / InScreenPosition.w * View.ScreenPositionScaleBias.xy + View.ScreenPositionScaleBias.wz;
    InputParams.ScreenVector    = 0;
#else
    InputParams.ScreenPosition    = 0;
    InputParams.ScreenUV        = ScreenUV;
    InputParams.ScreenVector    = ScreenVector;
#endif

    // 获取屏幕空间的数据, 包含GBuffer和AO.
    FScreenSpaceData ScreenSpaceData = GetScreenSpaceData(InputParams.ScreenUV);

    // 只有ShadingModelID不为0的像素才是延迟着色.
    BRANCH if( ScreenSpaceData.GBuffer.ShadingModelID > 0 
        // 检测灯光通道是否重合.
        #if USE_LIGHTING_CHANNELS
        && (GetLightingChannelMask(InputParams.ScreenUV) & DeferredLightUniforms.LightingChannelMask)
        #endif
        )
    {
        const float OpaqueVisibility = 1.0f;

        // 计算场景深度值.
        const float SceneDepth = CalcSceneDepth(InputParams.ScreenUV);
        // 获取派生数据.
        const FDerivedParams DerivedParams = GetDerivedParams(InputParams, SceneDepth);

        // 设置延迟光源数据.
        FDeferredLightData LightData = SetupLightDataForStandardDeferred();

        // 获取抖动.
        float Dither = InterleavedGradientNoise(InputParams.PixelPos, View.StateFrameIndexMod8 );

        // 从光源的源纹理获取矩形纹理.
        FRectTexture RectTexture = InitRectTexture(DeferredLightUniforms.SourceTexture);
        float SurfaceShadow = 1.0f;
        // 计算动态光照.
        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;

        // 大气透射.
    #if USE_ATMOSPHERE_TRANSMITTANCE
        OutColor.rgb *= GetAtmosphericLightTransmittance(SVPos, InputParams.ScreenUV, DeferredLightUniforms.Direction.xyz);
    #endif
    }

    // RGB:SceneColor Specular and Diffuse
    // A:Non Specular SceneColor Luminance
    // So we need PreExposure for both color and alpha
    OutColor.rgba *= GetExposure();
}
#endif
(......)

上面就是延迟光源的像素着色过程,看起来 很简单?非也,里边有两个重要的接口蕴含了大量的逻辑。 第一个是相对简单的GetScreenSpaceData,追踪它的逻辑堆栈:

// Engine\Shaders\Private\DeferredShadingCommon.ush

// 获取屏幕空间的数据.
FScreenSpaceData GetScreenSpaceData(float2 UV, bool bGetNormalizedNormal = true)
{
    FScreenSpaceData Out;

    // 获取GBuffer数据.
    Out.GBuffer = GetGBufferData(UV, bGetNormalizedNormal);
    // 获取屏幕空间AO.
    float4 ScreenSpaceAO = Texture2DSampleLevel(SceneTexturesStruct.ScreenSpaceAOTexture, SceneTexturesStruct.ScreenSpaceAOTextureSampler, UV, 0);

    // 注意AO只取r通道.
    Out.AmbientOcclusion = ScreenSpaceAO.r;

    return Out;
}

// 获取指定屏幕空间UV的GBuffer数据.
FGBufferData GetGBufferData(float2 UV, bool bGetNormalizedNormal = true)
{
    (......)
    
    // 从GBuffer纹理中采样数据.
    float4 GBufferA = Texture2DSampleLevel(SceneTexturesStruct.GBufferATexture, SceneTexturesStruct.GBufferATextureSampler, UV, 0);
    float4 GBufferB = Texture2DSampleLevel(SceneTexturesStruct.GBufferBTexture, SceneTexturesStruct.GBufferBTextureSampler, UV, 0);
    float4 GBufferC = Texture2DSampleLevel(SceneTexturesStruct.GBufferCTexture, SceneTexturesStruct.GBufferCTextureSampler, UV, 0);
    float4 GBufferD = Texture2DSampleLevel(SceneTexturesStruct.GBufferDTexture, SceneTexturesStruct.GBufferDTextureSampler, UV, 0);
    float CustomNativeDepth = Texture2DSampleLevel(SceneTexturesStruct.CustomDepthTexture, SceneTexturesStruct.CustomDepthTextureSampler, UV, 0).r;

    int2 IntUV = (int2)trunc(UV * View.BufferSizeAndInvSize.xy);
    // 自定义模板值.
    uint CustomStencil = SceneTexturesStruct.CustomStencilTexture.Load(int3(IntUV, 0)) STENCIL_COMPONENT_SWIZZLE;
    
    (......)

    // 静态光.
    #if ALLOW_STATIC_LIGHTING
        float4 GBufferE = Texture2DSampleLevel(SceneTexturesStruct.GBufferETexture, SceneTexturesStruct.GBufferETextureSampler, UV, 0);
    #else
        float4 GBufferE = 1;
    #endif

    // 切线.
    #if GBUFFER_HAS_TANGENT
        float4 GBufferF = Texture2DSampleLevel(SceneTexturesStruct.GBufferFTexture, SceneTexturesStruct.GBufferFTextureSampler, UV, 0);
    #else
        float4 GBufferF = 0.5f;
    #endif 

    // 速度.
    #if WRITES_VELOCITY_TO_GBUFFER
        float4 GBufferVelocity = Texture2DSampleLevel(SceneTexturesStruct.GBufferVelocityTexture, SceneTexturesStruct.GBufferVelocityTextureSampler, UV, 0);
    #else
        float4 GBufferVelocity = 0;
    #endif
#endif

    // 深度.
    float SceneDepth = CalcSceneDepth(UV);
    
    // 解码纹理值到对应的结构体.
    return DecodeGBufferData(GBufferA, GBufferB, GBufferC, GBufferD, GBufferE, GBufferF, GBufferVelocity, CustomNativeDepth, CustomStencil, SceneDepth, bGetNormalizedNormal, CheckerFromSceneColorUV(UV));
}

// 解码纹理值到对应的结构体.
FGBufferData DecodeGBufferData(float4 InGBufferA, float4 InGBufferB, float4 InGBufferC, float4 InGBufferD, float4 InGBufferE, float4 InGBufferF, float4 InGBufferVelocity, float CustomNativeDepth, uint CustomStencil, float SceneDepth, bool bGetNormalizedNormal, bool bChecker)
{
    FGBufferData GBuffer;

    // 法线.
    GBuffer.WorldNormal = DecodeNormal( InGBufferA.xyz );
    if(bGetNormalizedNormal)
    {
        GBuffer.WorldNormal = normalize(GBuffer.WorldNormal);
    }

    // 逐物体, 金属度, 高光度, 粗糙度.
    GBuffer.PerObjectGBufferData = InGBufferA.a;  
    GBuffer.Metallic    = InGBufferB.r;
    GBuffer.Specular    = InGBufferB.g;
    GBuffer.Roughness    = InGBufferB.b;
    // 着色模型ID.
    GBuffer.ShadingModelID = DecodeShadingModelId(InGBufferB.a);
    GBuffer.SelectiveOutputMask = DecodeSelectiveOutputMask(InGBufferB.a);

    // 基础色.
    GBuffer.BaseColor = DecodeBaseColor(InGBufferC.rgb);

    // AO和非直接光.
#if ALLOW_STATIC_LIGHTING
    GBuffer.GBufferAO = 1;
    GBuffer.IndirectIrradiance = DecodeIndirectIrradiance(InGBufferC.a);
#else
    GBuffer.GBufferAO = InGBufferC.a;
    GBuffer.IndirectIrradiance = 1;
#endif

    // 自定义数据.
    GBuffer.CustomData = !(GBuffer.SelectiveOutputMask & SKIP_CUSTOMDATA_MASK) ? InGBufferD : 0;

    // 场景或自定义的深度模板.
    GBuffer.PrecomputedShadowFactors = !(GBuffer.SelectiveOutputMask & SKIP_PRECSHADOW_MASK) ? InGBufferE :  ((GBuffer.SelectiveOutputMask & ZERO_PRECSHADOW_MASK) ? 0 :  1);
    GBuffer.CustomDepth = ConvertFromDeviceZ(CustomNativeDepth);
    GBuffer.CustomStencil = CustomStencil;
    GBuffer.Depth = SceneDepth;

    // 保持初始的基本值.
    GBuffer.StoredBaseColor = GBuffer.BaseColor;
    GBuffer.StoredMetallic = GBuffer.Metallic;
    GBuffer.StoredSpecular = GBuffer.Specular;

    (......)

    // 派生自BaseColor, Metalness, Specular的数据.
    {
        GBuffer.SpecularColor = ComputeF0(GBuffer.Specular, GBuffer.BaseColor, GBuffer.Metallic);

        if (UseSubsurfaceProfile(GBuffer.ShadingModelID))
        {
            AdjustBaseColorAndSpecularColorForSubsurfaceProfileLighting(GBuffer.BaseColor, GBuffer.SpecularColor, GBuffer.Specular, bChecker);
        }

        GBuffer.DiffuseColor = GBuffer.BaseColor - GBuffer.BaseColor * GBuffer.Metallic;

        (......)
    }

    // 切线.
#if GBUFFER_HAS_TANGENT
    GBuffer.WorldTangent = DecodeNormal(InGBufferF.rgb);
    GBuffer.Anisotropy = InGBufferF.a * 2.0f - 1.0f;

    if(bGetNormalizedNormal)
    {
        GBuffer.WorldTangent = normalize(GBuffer.WorldTangent);
    }
#else
    GBuffer.WorldTangent = 0;
    GBuffer.Anisotropy = 0;
#endif

    // 速度.
    GBuffer.Velocity = !(GBuffer.SelectiveOutputMask & SKIP_VELOCITY_MASK) ? InGBufferVelocity : 0;

    return GBuffer;
}

解码GBuffer的过程大致是从GBuffer纹理中采样数据再进行二次加工并存储到FGBufferData中然后FGBufferData的实例又存储到FScreenSpaceData中。以便后续的光照计算中直接访问也可以防止多次采样纹理造成GPU和显存之间的IO瓶颈和GPU Cache命中率降低。 第一个是非常复杂的GetDynamicLighting,进入复杂的光照计算逻辑:

// Engine\Shaders\Private\DeferredLightingCommon.ush
// 计算动态光照.
float4 GetDynamicLighting(
    float3 WorldPosition, float3 CameraVector, FGBufferData GBuffer, float AmbientOcclusion, uint ShadingModelID, 
    FDeferredLightData LightData, float4 LightAttenuation, float Dither, uint2 SVPos, FRectTexture SourceTexture,
    inout float SurfaceShadow)
{
    FDeferredLightingSplit SplitLighting = GetDynamicLightingSplit(
        WorldPosition, CameraVector, GBuffer, AmbientOcclusion, ShadingModelID, 
        LightData, LightAttenuation, Dither, SVPos, SourceTexture,
        SurfaceShadow);

    return SplitLighting.SpecularLighting + SplitLighting.DiffuseLighting;
}

// 计算动态光照, 分离了漫反射和高光项.
FDeferredLightingSplit GetDynamicLightingSplit(
    float3 WorldPosition, float3 CameraVector, FGBufferData GBuffer, float AmbientOcclusion, uint ShadingModelID, 
    FDeferredLightData LightData, float4 LightAttenuation, float Dither, uint2 SVPos, FRectTexture SourceTexture,
    inout float SurfaceShadow)
{
    FLightAccumulator LightAccumulator = (FLightAccumulator)0;

    float3 V = -CameraVector;
    float3 N = GBuffer.WorldNormal;
    BRANCH if( GBuffer.ShadingModelID == SHADINGMODELID_CLEAR_COAT && CLEAR_COAT_BOTTOM_NORMAL)
    {
        const float2 oct1 = ((float2(GBuffer.CustomData.a, GBuffer.CustomData.z) * 2) - (256.0/255.0)) + UnitVectorToOctahedron(GBuffer.WorldNormal);
        N = OctahedronToUnitVector(oct1);            
    }
    
    float3 L = LightData.Direction;    // Already normalized
    float3 ToLight = L;
    
    float LightMask = 1;
    // 获取辐射光源的衰减.
    if (LightData.bRadialLight)
    {
        LightMask = GetLocalLightAttenuation( WorldPosition, LightData, ToLight, L );
    }

    LightAccumulator.EstimatedCost += 0.3f;        // running the PixelShader at all has a cost

    BRANCH
    // 只计算有效强度的表面.
    if( LightMask > 0 )
    {
        // 处理表面阴影.
        FShadowTerms Shadow;
        Shadow.SurfaceShadow = AmbientOcclusion;
        Shadow.TransmissionShadow = 1;
        Shadow.TransmissionThickness = 1;
        Shadow.HairTransmittance.Transmittance = 1;
        Shadow.HairTransmittance.OpaqueVisibility = 1;
        // 计算表面阴影数据.
        GetShadowTerms(GBuffer, LightData, WorldPosition, L, LightAttenuation, Dither, Shadow);
        SurfaceShadow = Shadow.SurfaceShadow;

        LightAccumulator.EstimatedCost += 0.3f;        // add the cost of getting the shadow terms

        BRANCH
        // 如果受阴影后的光照强度和透射强度之和>0才继续计算光照.
        if( Shadow.SurfaceShadow + Shadow.TransmissionShadow > 0 )
        {
            const bool bNeedsSeparateSubsurfaceLightAccumulation = UseSubsurfaceProfile(GBuffer.ShadingModelID);
            float3 LightColor = LightData.Color;

        #if NON_DIRECTIONAL_DIRECT_LIGHTING // 非平行直接光
            float Lighting;

            if( LightData.bRectLight ) // 矩形光
            {
                FRect Rect = GetRect( ToLight, LightData );

                // 积分矩形光照.
                Lighting = IntegrateLight( Rect, SourceTexture);
            }
            else // 胶囊光
            {
                FCapsuleLight Capsule = GetCapsule( ToLight, LightData );

                // 积分胶囊光照.
                Lighting = IntegrateLight( Capsule, LightData.bInverseSquared );
            }

            float3 LightingDiffuse = Diffuse_Lambert( GBuffer.DiffuseColor ) * Lighting;
            LightAccumulator_AddSplit(LightAccumulator, LightingDiffuse, 0.0f, 0, LightColor * LightMask * Shadow.SurfaceShadow, bNeedsSeparateSubsurfaceLightAccumulation);
        #else // 平行直接光
            FDirectLighting Lighting;

            if (LightData.bRectLight)
            {
                FRect Rect = GetRect( ToLight, LightData );

                #if REFERENCE_QUALITY
                    Lighting = IntegrateBxDF( GBuffer, N, V, Rect, Shadow, SourceTexture, SVPos );
                #else
                    Lighting = IntegrateBxDF( GBuffer, N, V, Rect, Shadow, SourceTexture);
                #endif
            }
            else
            {
                FCapsuleLight Capsule = GetCapsule( ToLight, LightData );

                #if REFERENCE_QUALITY
                    Lighting = IntegrateBxDF( GBuffer, N, V, Capsule, Shadow, SVPos );
                #else
                    Lighting = IntegrateBxDF( GBuffer, N, V, Capsule, Shadow, LightData.bInverseSquared );
                #endif
            }

            Lighting.Specular *= LightData.SpecularScale;
                
            LightAccumulator_AddSplit( LightAccumulator, Lighting.Diffuse, Lighting.Specular, Lighting.Diffuse, LightColor * LightMask * Shadow.SurfaceShadow, bNeedsSeparateSubsurfaceLightAccumulation );
            LightAccumulator_AddSplit( LightAccumulator, Lighting.Transmission, 0.0f, Lighting.Transmission, LightColor * LightMask * Shadow.TransmissionShadow, bNeedsSeparateSubsurfaceLightAccumulation );

            LightAccumulator.EstimatedCost += 0.4f;        // add the cost of the lighting computations (should sum up to 1 form one light)
        #endif
        }
    }

    return LightAccumulator_GetResultSplit(LightAccumulator);
}

上面的代码中在NON_DIRECTIONAL_DIRECT_LIGHTING为0的分支下会根据是否矩形光和质量参考(REFERENCE_QUALITY)来进入4种不同的BxDF接口下面就以非矩形光非质量参考版本的IntegrateBxDF进行剖析

// Engine\Shaders\Private\CapsuleLightIntegrate.ush

// 积分胶囊光照.
FDirectLighting IntegrateBxDF( FGBufferData GBuffer, half3 N, half3 V, FCapsuleLight Capsule, FShadowTerms Shadow, bool bInverseSquared )
{
    float NoL;
    float Falloff;
    float LineCosSubtended = 1;

    // Clip to horizon
    //float NoP0 = dot( N, Capsule.LightPos[0] );
    //float NoP1 = dot( N, Capsule.LightPos[1] );
    //if( NoP0 < 0 ) Capsule.LightPos[0] = (  Capsule.LightPos[0] * NoP1 - Capsule.LightPos[1] * NoP0 ) / ( NoP1 - NoP0);
    //if( NoP1 < 0 ) Capsule.LightPos[1] = ( -Capsule.LightPos[0] * NoP1 + Capsule.LightPos[1] * NoP0 ) / (-NoP1 + NoP0);

    BRANCH
    // 处理衰减, N和L的点积.
    if( Capsule.Length > 0 ) // 如果是有效胶囊体, 则只需线段积分(具体公式见5.4.2.2)
    {
        LineIrradiance( N, Capsule.LightPos[0], Capsule.LightPos[1], Capsule.DistBiasSqr, LineCosSubtended, Falloff, NoL );
    }
    else // 光源当成一个点
    {
        float DistSqr = dot( Capsule.LightPos[0], Capsule.LightPos[0] );
        Falloff = rcp( DistSqr + Capsule.DistBiasSqr );

        float3 L = Capsule.LightPos[0] * rsqrt( DistSqr );
        NoL = dot( N, L );
    }

    // 胶囊半径>0, 当球体盖帽来调整N和L的点积. 
    if( Capsule.Radius > 0 )
    {
        // TODO Use capsule area?
        float SinAlphaSqr = saturate( Pow2( Capsule.Radius ) * Falloff );
        NoL = SphereHorizonCosWrap( NoL, SinAlphaSqr );
    }

    NoL = saturate( NoL );
    Falloff = bInverseSquared ? Falloff : 1;

    // 调整有效长度的光源方向.
    float3 ToLight = Capsule.LightPos[0];
    if( Capsule.Length > 0 )
    {
        float3 R = reflect( -V, N );

        ToLight = ClosestPointLineToRay( Capsule.LightPos[0], Capsule.LightPos[1], Capsule.Length, R );
    }

    float DistSqr = dot( ToLight, ToLight );
    float InvDist = rsqrt( DistSqr );
    float3 L = ToLight * InvDist;
    
    GBuffer.Roughness = max( GBuffer.Roughness, View.MinRoughness );
    float a = Pow2( GBuffer.Roughness );
    
    // 根据上面的信息构建区域光信息.
    FAreaLight AreaLight;
    AreaLight.SphereSinAlpha = saturate( Capsule.Radius * InvDist * (1 - a) );
    AreaLight.SphereSinAlphaSoft = saturate( Capsule.SoftRadius * InvDist );
    AreaLight.LineCosSubtended = LineCosSubtended;
    AreaLight.FalloffColor = 1;
    AreaLight.Rect = (FRect)0;
    AreaLight.bIsRect = false;
    AreaLight.Texture = InitRectTexture(DummyRectLightTextureForCapsuleCompilerWarning);

    // 积分区域光
    return IntegrateBxDF( GBuffer, N, V, L, Falloff, NoL, AreaLight, Shadow );
}

// Engine\Shaders\Private\ShadingModels.ush

FDirectLighting IntegrateBxDF( FGBufferData GBuffer, half3 N, half3 V, half3 L, float Falloff, float NoL, FAreaLight AreaLight, FShadowTerms Shadow )
{
    // 根据不同的着色模型进入不同的光照BxDF.
    switch( GBuffer.ShadingModelID )
    {
        case SHADINGMODELID_DEFAULT_LIT:
        case SHADINGMODELID_SINGLELAYERWATER:
        case SHADINGMODELID_THIN_TRANSLUCENT:
            // 默认光照
            return DefaultLitBxDF( GBuffer, N, V, L, Falloff, NoL, AreaLight, Shadow );
        case SHADINGMODELID_SUBSURFACE:
            // 次表面散射
            return SubsurfaceBxDF( GBuffer, N, V, L, Falloff, NoL, AreaLight, Shadow );
        case SHADINGMODELID_PREINTEGRATED_SKIN:
            // 预积分皮肤
            return PreintegratedSkinBxDF( GBuffer, N, V, L, Falloff, NoL, AreaLight, Shadow );
        case SHADINGMODELID_CLEAR_COAT:
            // 清漆
            return ClearCoatBxDF( GBuffer, N, V, L, Falloff, NoL, AreaLight, Shadow );
        case SHADINGMODELID_SUBSURFACE_PROFILE:
            // 次表面散射配置
            return SubsurfaceProfileBxDF( GBuffer, N, V, L, Falloff, NoL, AreaLight, Shadow );
        case SHADINGMODELID_TWOSIDED_FOLIAGE:
            // 双面
            return TwoSidedBxDF( GBuffer, N, V, L, Falloff, NoL, AreaLight, Shadow );
        case SHADINGMODELID_HAIR:
            // 头发
            return HairBxDF( GBuffer, N, V, L, Falloff, NoL, AreaLight, Shadow );
        case SHADINGMODELID_CLOTH:
            // 布料
            return ClothBxDF( GBuffer, N, V, L, Falloff, NoL, AreaLight, Shadow );
        case SHADINGMODELID_EYE:
            // 眼睛
            return EyeBxDF( GBuffer, N, V, L, Falloff, NoL, AreaLight, Shadow );
        default:
            return (FDirectLighting)0;
    }
}

RectGGXApproxLTC

ShaderModel的高光计算SphereMaxNoH是调整模拟区域光后的各种向量之间的点积,由论文DECIMA ENGINE: ADVANCES IN LIGHTING AND AA提出,它的核心思想在于先拉长调整切线、副切线、光源方向:

直接解析求得最大化的N⋅H<EFBFBD><EFBFBD>是比较困难的但是可以假设一些条件然后提供有理多项式模拟(N⋅H)2(<28><EFBFBD>)2

先用Karis近似法估算出第一项再用牛顿迭代法求得第二项然后用它模拟(N⋅H)2(<28><EFBFBD>)2

SphereMaxNoH对应的实现代码:

// Engine\Shaders\Private\BRDF.ush

void SphereMaxNoH( inout BxDFContext Context, float SinAlpha, bool bNewtonIteration )
{
    if( SinAlpha > 0 )
    {
        float CosAlpha = sqrt( 1 - Pow2( SinAlpha ) );
    
        float RoL = 2 * Context.NoL * Context.NoV - Context.VoL;
        if( RoL >= CosAlpha )
        {
            Context.NoH = 1;
            Context.XoH = 0;
            Context.YoH = 0;
            Context.VoH = abs( Context.NoV );
        }
        else
        {
            float rInvLengthT = SinAlpha * rsqrt( 1 - RoL*RoL );
            float NoTr = rInvLengthT * ( Context.NoV - RoL * Context.NoL );
            float VoTr = rInvLengthT * ( 2 * Context.NoV*Context.NoV - 1 - RoL * Context.VoL );

            if (bNewtonIteration)
            {
                // dot( cross(N,L), V )
                float NxLoV = sqrt( saturate( 1 - Pow2(Context.NoL) - Pow2(Context.NoV) - Pow2(Context.VoL) + 2 * Context.NoL * Context.NoV * Context.VoL ) );

                float NoBr = rInvLengthT * NxLoV;
                float VoBr = rInvLengthT * NxLoV * 2 * Context.NoV;

                float NoLVTr = Context.NoL * CosAlpha + Context.NoV + NoTr;
                float VoLVTr = Context.VoL * CosAlpha + 1   + VoTr;

                float p = NoBr   * VoLVTr;
                float q = NoLVTr * VoLVTr;
                float s = VoBr   * NoLVTr;

                float xNum = q * ( -0.5 * p + 0.25 * VoBr * NoLVTr );
                float xDenom = p*p + s * (s - 2*p) + NoLVTr * ( (Context.NoL * CosAlpha + Context.NoV) * Pow2(VoLVTr) + q * (-0.5 * (VoLVTr + Context.VoL * CosAlpha) - 0.5) );
                float TwoX1 = 2 * xNum / ( Pow2(xDenom) + Pow2(xNum) );
                float SinTheta = TwoX1 * xDenom;
                float CosTheta = 1.0 - TwoX1 * xNum;
                NoTr = CosTheta * NoTr + SinTheta * NoBr;
                VoTr = CosTheta * VoTr + SinTheta * VoBr;
            }

            Context.NoL = Context.NoL * CosAlpha + NoTr; // dot( N, L * CosAlpha + T * SinAlpha )
            Context.VoL = Context.VoL * CosAlpha + VoTr;

            float InvLenH = rsqrt( 2 + 2 * Context.VoL );
            Context.NoH = saturate( ( Context.NoL + Context.NoV ) * InvLenH );
            Context.VoH = saturate( InvLenH + InvLenH * Context.VoL );
        }
    }
}

以上实现出自论文Real-Time Polygonal-Light Shading with Linearly Transformed Cosines其核心思想在于通过线性预先变换近似任意形体的面光源的BRDF的粗糙度、各向异性以及斜切等特性

而且效率比球体、半球体、夹紧余弦光照模型相当,比冯氏光照模型要快:

然后将最耗时的LTC矩阵和缩放预积分到纹理中分别是LTCMatTexture和LTCAmpTexture

不过以上两张纹理是在引擎初始化时运行时算出来的,之后就缓存住并直接使用

Shadow

阴影基础类型

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

// 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;
    };
};

阴影初始化总结

用于前面花费很多笔墨和小节来详细剖析阴影初始化的具体步骤和细节,难免会让很多童鞋望而生畏,那么本节就简洁扼要地总结阴影初始化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交互数据提升缓存命中率。
  • 不同粒度的遮挡剔除。

阴影渲染

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

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

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渲染不透明阴影和半透明阴影。

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);
    }
}

阴影应用

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
    }
}

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中,此外,还可能包含距离场阴影、子像素阴影、透明体积阴影、胶囊体阴影、高度场阴影、头发阴影等类型。

FProjectedShadowInfo::RenderProjection

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);
}

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
}

(......)

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

注意上面的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的值恢复出各类阴影的参数最后应用于光照计算中。

阴影应用总结

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

  • 每个开启了阴影且拥有有效阴影的光源都会渲染一次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即可计算出物体表面受阴影影响的光照结果。