diff --git a/.obsidian/plugins/various-complements/data.json b/.obsidian/plugins/various-complements/data.json index acd5c1a..a97cddf 100644 --- a/.obsidian/plugins/various-complements/data.json +++ b/.obsidian/plugins/various-complements/data.json @@ -158,6 +158,14 @@ "lastUpdated": 1707386581416 } } + }, + "ShaderModel添加": { + "ShaderModel添加": { + "internalLink": { + "count": 1, + "lastUpdated": 1707399161946 + } + } } } } \ No newline at end of file diff --git a/03-UnrealEngine/Rendering/RenderingPipeline/向往渲染系列文章阅读笔记/剖析虚幻渲染体系(05)- 光源和阴影.md b/03-UnrealEngine/Rendering/RenderingPipeline/向往渲染系列文章阅读笔记/剖析虚幻渲染体系(05)- 光源和阴影.md index d0ecbb4..ed50724 100644 --- a/03-UnrealEngine/Rendering/RenderingPipeline/向往渲染系列文章阅读笔记/剖析虚幻渲染体系(05)- 光源和阴影.md +++ b/03-UnrealEngine/Rendering/RenderingPipeline/向往渲染系列文章阅读笔记/剖析虚幻渲染体系(05)- 光源和阴影.md @@ -132,6 +132,3585 @@ void FDeferredShadingSceneRenderer::RenderBasePassViewParallel(FViewInfo& View, ## BasePass渲染状态 我们知道BasePass是通过`FBasePassMeshProcessor`来收集很多shader绑定和绘制参数的,从Processor的过程中可以很容易知道,BasePass绘制时使用的VS和PS分别是`TBasePassVS`和`TBasePassPS`。 -可知`TBasePassVS`提供了获取Shader绑定、更改编译环境和只编译指定的排列组合shader等接口,此外还拥有反射球、光照图类型等属性。下面是它的获取shader绑定的代码分析: +可知`TBasePassVS`提供了获取Shader绑定、更改编译环境和只编译指定的排列组合shader等接口,此外还拥有反射球、光照图类型等属性。 + +# Lighting +## 光源基础概念 +UE的光源分配由`FDeferredShadingSceneRenderer::Render`内的`bComputeLightGrid`变量决定: ```c++ -``` \ No newline at end of file +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的光照计算或需要渲染体积雾等。接着调用下面的逻辑执行光源分配: +```c++ +void FDeferredShadingSceneRenderer::Render(FRHICommandListImmediate& RHICmdList) +{ + (......) + // 有序的光源集合. + FSortedLightSetSceneInfo SortedLightSet; + { + // 收集和排序光源. + GatherAndSortLights(SortedLightSet); + // 计算光源格子(分配光源). + ComputeLightGrid(RHICmdList, bComputeLightGrid, SortedLightSet); + } + (......) +} +``` +`FSortedLightSetSceneInfo`是光源非常重要的基础概念,后续的光源处理都会涉及到它。下面看看它和其它光源相关概念的定义: +```c++ +// 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> InstanceData; + // 简单动态光源列表, 和视图有关. + TArray> PerViewData; + // 指向简单光源实例的view相关的数据列表. + TArray> 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 SortedLights; +}; +``` +可以关注的是`FSortedLightSceneInfo`的光源排序键值,最高优先级的属性跟分簇、分块、简单光源相关,其次是灯光通道、阴影、光照函数、光照配置,最低优先级是光源类型。 + +这样排序的依据是:分簇、分块可以通过提前光源裁剪等操作,极大地降低着色消耗和指令数量,放在最高位;简单光源光照逻辑简单很多,指令数也少很多,所有简单光源都可以分簇分块渲染,所以紧随分簇分块之后;剩下的是非简单光照的特性,由于灯光通道是否开启也直接影响很多逻辑,开启了灯光通道会较大地影响渲染效率,故而放在非简单光源的首位;是否开启阴影对渲染性能的影响也非常大,如果开启了阴影,会增加若干个Pass绘制阴影图和衰减纹理,增加渲染纹理切换,故而排次位合情合理;后面的几个键值以此类推。 + +### GatherAndSortLights +```c++ +// Engine\Source\Runtime\Renderer\Private\LightRendering.cpp + +void FDeferredShadingSceneRenderer::GatherAndSortLights(FSortedLightSetSceneInfo& OutSortedLights) +{ + if (bAllowSimpleLights) + { + // 收集简单光源, 详细解析见后面. + GatherSimpleLights(ViewFamily, Views, OutSortedLights.SimpleLights); + } + + FSimpleLightArray &SimpleLights = OutSortedLights.SimpleLights; + TArray &SortedLights = OutSortedLights.SortedLights; + + // SortedLights包含了简单光源和非简单光源, 方便后面排序. + SortedLights.Empty(Scene->Lights.Num() + SimpleLights.InstanceData.Num()); + + bool bDynamicShadows = ViewFamily.EngineShowFlags.DynamicShadows && GetShadowQuality() > 0; + + // 构建可见光源列表. + for (TSparseArray::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: +```c++ +// 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格子中,构建每个视图相关的光源列表和格子。 +```c++ +// 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 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& 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::CreateUniformBufferImmediate(ForwardLightData, UniformBuffer_SingleFrame); + + // 下面使用RDG和Compute Shader去裁剪局部光源和反射探针. + + // 线程组数. + const FIntVector NumGroups = FIntVector::DivideAndRoundUp(FIntVector(LightGridSizeXY.X, LightGridSizeXY.Y, GLightGridSizeZ), LightGridInjectionGroupSize); + + TArray> 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(); + + 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(GLightLinkedListCulling != 0); + // 光源格子注入shader. + TShaderMapRef 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 ComputeShaderCompact(View.ShaderMap); + FLightGridCompactCS::FParameters *PassParametersCompact = GraphBuilder.AllocParameters(); + 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: +```c++ +#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 + +(......) +``` +对于点光源和聚光灯,则需要特殊处理,分别使用球体和圆锥体绘制,以便剔除在光源影响范围之外的像素,所以它们的顶点处理会比平行光复杂一些。 + +![](https://img2020.cnblogs.com/blog/1617944/202105/1617944-20210527125757261-177285983.jpg) + +### DeferredLightPixelShader +DeferredLightPixelShader的入口在DeferredLightPixelShaders.usf: +```c++ +#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 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`,追踪它的逻辑堆栈: +```c++ +// 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`,进入复杂的光照计算逻辑: +```c++ +// 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进行剖析: +```c++ +// 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](https://www.guerrilla-games.com/read/decima-engine-advances-in-lighting-and-aa)提出,它的核心思想在于先拉长调整切线、副切线、光源方向: + +![](https://img2020.cnblogs.com/blog/1617944/202105/1617944-20210527125853255-868399953.jpg) + +直接解析求得最大化的N⋅H�⋅�是比较困难的,但是可以假设一些条件,然后提供有理多项式模拟(N⋅H)2(�⋅�)2: + +![](https://img2020.cnblogs.com/blog/1617944/202105/1617944-20210527125905484-986749331.jpg) + +先用Karis近似法估算出第一项,再用牛顿迭代法求得第二项,然后用它模拟(N⋅H)2(�⋅�)2: + +![](https://img2020.cnblogs.com/blog/1617944/202105/1617944-20210527125917125-541399809.jpg) + +`SphereMaxNoH`对应的实现代码: +```c++ +// 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](https://eheitzresearch.wordpress.com/415-2/),其核心思想在于通过线性预先变换近似任意形体的面光源的BRDF的粗糙度、各向异性以及斜切等特性: + +![](https://img2020.cnblogs.com/blog/1617944/202105/1617944-20210527125954790-2014059921.jpg) + +而且效率比球体、半球体、夹紧余弦光照模型相当,比冯氏光照模型要快: + +![](https://img2020.cnblogs.com/blog/1617944/202105/1617944-20210527130006177-768044047.jpg) + +然后将最耗时的LTC矩阵和缩放预积分到纹理中,分别是LTCMatTexture和LTCAmpTexture: + +![](https://img2020.cnblogs.com/blog/1617944/202105/1617944-20210527130020649-841720934.png) + +不过以上两张纹理是在引擎初始化时运行时算出来的,之后就缓存住并直接使用 + +# Shadow +## 阴影基础类型 +在进入阴影渲染剖析前,先详细理解阴影相关的部分关键概念: +```c++ +// Engine\Source\Runtime\Renderer\Private\ShadowRendering.h + +// 投射阴影信息. +class FProjectedShadowInfo : public FRefCountedObject +{ +public: + typedef TArray PrimitiveArrayType; + + // 渲染阴影时使用的view. + FViewInfo* ShadowDepthView; + // 阴影必须渲染的主view, Null表示独立于view的阴影. + FViewInfo* DependentView; + + // 阴影Uniform Buffer. + TUniformBufferRef ShadowDepthPassUniformBuffer; + TUniformBufferRef 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 > 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 OnePassShadowViewProjectionMatrices; + // 用于点光源渲染cubemap6个面的阴影图使用的视图矩阵. + TArray OnePassShadowViewMatrices; + /** Frustums for each cubemap face, used for object culling one pass point light shadows. */ + TArray 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* 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 DynamicSubjectMeshElements; + TArray DynamicSubjectTranslucentMeshElements; + + TArray SubjectMeshCommandBuildRequests; + + // DynamicSubjectMeshElements数量. + int32 NumDynamicSubjectMeshElements; + // SubjectMeshCommandBuildRequests数量. + int32 NumSubjectMeshCommandBuildRequestElements; + + // 绘制阴影所需的绘制命令/渲染状态等. + FMeshCommandOneFrameArray ShadowDepthPassVisibleCommands; + FParallelMeshDrawCommandPass ShadowDepthPass; + TArray> 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的阴影系统太过复杂,单单它一个,还不足以解决阴影的所有渲染功能。下面继续分析其它基础或关键性类型: +```c++ +// Engine\Source\Runtime\Renderer\Private\SceneRendering.h + +// 视图[不]相关的可见光源信息, 主要是阴影相关的信息. +class FVisibleLightInfo +{ +public: + // 在场景内存堆栈(mem stack)分配和管理的投射阴影信息. + TArray MemStackProjectedShadows; + // 所有可见的投射阴影信息, 由阴影设置阶段输出, 不是所有的都会被渲染. + TArray AllProjectedShadows; + + // 特殊的阴影投射信息, 用于专用的特性, 如投射物/胶囊体阴影/RSM等. + TArray ShadowsToProject; + TArray CapsuleShadowsToProject; + TArray RSMsToProject; + + // 所有可见的投射预阴影. 这些不在场景的内存堆栈中分配和管理, 所以需要用TRefCountPtr引用计数. + TArray,SceneRenderingAllocator> ProjectedPreShadows; + // 被遮挡的逐物体阴影, 为了提交遮挡剔除申请所以需要追踪它们. + TArray OccludedPerObjectShadows; +}; + +// 视图相关的可见光源信息. +class FVisibleLightViewInfo +{ +public: + // 光源能够影响到的可见图元. + TArray VisibleDynamicLitPrimitives; + // 对应FVisibleLightInfo::AllProjectedShadows的阴影可见性映射表. + FSceneBitArray ProjectedShadowVisibilityMap; + // 对应FVisibleLightInfo::AllProjectedShadows的阴影ViewRelevance. + TArray 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之前: +```c++ +void FDeferredShadingSceneRenderer::Render(FRHICommandListImmediate& RHICmdList) +{ + (......) + + RenderPrePass(RHICmdList, ...); + + (......) + + // 阴影渲染Pass. + RenderShadowDepthMaps(RHICmdList); + + (......) + + RenderBasePass(RHICmdList, ...); + + (......) +} +``` +### RenderShadowDepthMaps +下面直接进入`RenderShadowDepthMaps`分析源码: +```c++ +// 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 +```c++ +// 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 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`的逻辑,不过这里主要聚焦在阴影的应用逻辑: +```c++ +void FDeferredShadingSceneRenderer::RenderLights(FRHICommandListImmediate& RHICmdList, FSortedLightSetSceneInfo &SortedLightSet, ...) +{ + (......) + + const TArray &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 ScreenShadowMaskTexture; + TRefCountPtr 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& 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为光源再次渲染屏幕空间的阴影遮蔽纹理,代码如下: +```c++ +// 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 DistanceFieldShadows; // 距离场阴影. + TArray 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 +```c++ +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> 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::GetRHI() : TStaticRasterizerState::GetRHI(); + + // 深度模板状态. + GraphicsPSOInit.bDepthBounds = bDepthBoundsTestEnabled; + if (bDepthBoundsTestEnabled) + { + GraphicsPSOInit.DepthStencilState = TStaticDepthStencilState::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::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); + } + } +} +``` +以上代码调用了几个关键的接口:`GetBlendStateForProjection`和`BindShadowProjectionShaders`,下面对它们进行分析: +```c++ +// 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::GetRHI(); + } + else // 不使用渐隐平面. + { + // CSM首个层级不需要过渡. BO_Min是为了组合多个阴影通道. + BlendState = TStaticBlendState::GetRHI(); + } + } + else // 非全景平行光阴影. + { + if (bMobileModulatedProjections) // 颜色调制阴影. + { + // 颜色调制阴影, 忽略Alpha. + BlendState = TStaticBlendState::GetRHI(); + } + else // 非颜色调制阴影. + { + // BO_Min是为了组合多个阴影通道. + BlendState = TStaticBlendState::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 >(RHICmdList, GraphicsPSOInit, ViewIndex, View, HairVisibilityData, ShadowInfo); + else + BindShaderShaders >(RHICmdList, GraphicsPSOInit, ViewIndex, View, HairVisibilityData, ShadowInfo); + } + else if (ShadowInfo->CascadeSettings.FadePlaneLength > 0) + { + if (ShadowInfo->bTransmission) + { + (......) + } + else + { + switch (Quality) + { + case 1: BindShaderShaders >(RHICmdList, GraphicsPSOInit, ViewIndex, View, HairVisibilityData, ShadowInfo); break; + case 2: BindShaderShaders >(RHICmdList, GraphicsPSOInit, ViewIndex, View, HairVisibilityData, ShadowInfo); break; + case 3: BindShaderShaders >(RHICmdList, GraphicsPSOInit, ViewIndex, View, HairVisibilityData, ShadowInfo); break; + case 4: BindShaderShaders >(RHICmdList, GraphicsPSOInit, ViewIndex, View, HairVisibilityData, ShadowInfo); break; + case 5: BindShaderShaders >(RHICmdList, GraphicsPSOInit, ViewIndex, View, HairVisibilityData, ShadowInfo); break; + default: + check(0); + } + } + } + else + { + if (ShadowInfo->bTransmission) + { + (......) + } + else + { + switch (Quality) + { + case 1: BindShaderShaders >(RHICmdList, GraphicsPSOInit, ViewIndex, View, HairVisibilityData, ShadowInfo); break; + case 2: BindShaderShaders >(RHICmdList, GraphicsPSOInit, ViewIndex, View, HairVisibilityData, ShadowInfo); break; + case 3: BindShaderShaders >(RHICmdList, GraphicsPSOInit, ViewIndex, View, HairVisibilityData, ShadowInfo); break; + case 4: BindShaderShaders >(RHICmdList, GraphicsPSOInit, ViewIndex, View, HairVisibilityData, ShadowInfo); break; + case 5: BindShaderShaders >(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 >(RHICmdList, GraphicsPSOInit, ViewIndex, View, HairVisibilityData, ShadowInfo); + } + else + { + switch (Quality) + { + case 1: BindShaderShaders >(RHICmdList, GraphicsPSOInit, ViewIndex, View, HairVisibilityData, ShadowInfo); break; + case 2: BindShaderShaders >(RHICmdList, GraphicsPSOInit, ViewIndex, View, HairVisibilityData, ShadowInfo); break; + case 3: BindShaderShaders >(RHICmdList, GraphicsPSOInit, ViewIndex, View, HairVisibilityData, ShadowInfo); break; + case 4: BindShaderShaders >(RHICmdList, GraphicsPSOInit, ViewIndex, View, HairVisibilityData, ShadowInfo); break; + case 5: BindShaderShaders >(RHICmdList, GraphicsPSOInit, ViewIndex, View, HairVisibilityData, ShadowInfo); break; + default: + check(0); + } + } + } + } +} + +// 绑定阴影投射VS和PS. +template +static void BindShaderShaders(FRHICommandList& RHICmdList, FGraphicsPipelineStateInitializer& GraphicsPSOInit, int32 ViewIndex, const FViewInfo& View, const FHairStrandsVisibilityData* HairVisibilityData, const FProjectedShadowInfo* ShadowInfo) +{ + TShaderRef VertexShader = View.ShaderMap->GetShader(); + TShaderRef PixelShader = View.ShaderMap->GetShader(); + + // 绑定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逻辑: +```c++ +// 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(RHICmdList, RHICmdList.GetBoundVertexShader(), ViewUniformBuffer); + } + + void SetParameters(FRHICommandList& RHICmdList, const FSceneView& View, const FProjectedShadowInfo*) + { + FGlobalShader::SetParameters(RHICmdList, RHICmdList.GetBoundVertexShader(), View.ViewUniformBuffer); + } +}; + +// 阴影投射PS. +template +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(); + 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::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代码: +```c++ +// 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`: +```c++ +// 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),即可计算出物体表面受阴影影响的光照结果。 \ No newline at end of file