--- title: Untitled date: 2024-12-08 12:18:54 excerpt: tags: rating: ⭐ --- # 阴影 - [剖析虚幻渲染体系(05)- 光源和阴影](https://www.cnblogs.com/timlly/p/14817455.html) ## 阴影类型 - **Static Shadow** - **Cascading Shadow Map** - **Per Object Shadow**:可移动组件使用的逐物体阴影应用阴影图到物体的包围盒,因此包围盒必须是精确的。对于骨骼网格,这意味着它们应该有一个物理资产。对于粒子系统,任何固定的边界框必须足够大,以容纳所有的粒子。_在网格的Lighting属性组中,Dynamic Inset Shadow可以开启逐物体阴影,对需要高质量高精度的物体非常有用。_ - **Dynamic Shadow**:可移动光源在所有物体上投射出完全动态的阴影(和光)。这种光源的任何数据不会被烘焙到光照图中,它可以自由地在所有东西上投射动态阴影。静态网格、骨架网格、粒子效果等等将完全从可移动光源投射和接收动态阴影。 - **Capsule Shadow** - **Contact Shadow** - **Distance Field Shadow** ## 相关类型 - ShadowRendering.h **FProjectedShadowInfo**:存储投影阴影先关信息。包含各种变换矩阵、阴影渲染函数以及渲染参数、灯光&场景&图元信息。 - SceneRendering.h FVisibleLightInfo:可见光源信息, 主要是阴影相关的信息. FVisibleLightViewInfo: ## DynamicShadows ### InitDynamicShadows() => CreateDynamicShadows() InitViews() => FSceneRenderer::InitDynamicShadows() PS. UE5.3中相关逻辑移动到***CreateDynamicShadows()*** 中了。InitDynamicShadows() => BeginInitDynamicShadows() => BeginGatherShadowPrimitives() => CreateDynamicShadows() 计算各种灯光类型,之后调用: - CreateWholeSceneProjectedShadow() - AddViewDependentWholeSceneShadowsForView() - SetupInteractionShadows() - ***CreatePerObjectProjectedShadow()*** - InitProjectedShadowVisibility() 丛越文章中的注释: ```c++ void FSceneRenderer::InitDynamicShadows(FRHICommandListImmediate& RHICmdList, FGlobalDynamicIndexBuffer& DynamicIndexBuffer, FGlobalDynamicVertexBuffer& DynamicVertexBuffer, FGlobalDynamicReadBuffer& DynamicReadBuffer) { // 初始化各类标记和数量. const bool bMobile = FeatureLevel < ERHIFeatureLevel::SM5; bool bStaticSceneOnly = false; for (int32 ViewIndex = 0; ViewIndex < Views.Num(); ViewIndex++) { FViewInfo& View = Views[ViewIndex]; bStaticSceneOnly = bStaticSceneOnly || View.bStaticSceneOnly; } const bool bProjectEnablePointLightShadows = Scene->ReadOnlyCVARCache.bEnablePointLightShadows; uint32 NumPointShadowCachesUpdatedThisFrame = 0; uint32 NumSpotShadowCachesUpdatedThisFrame = 0; // 预计算阴影. TArray PreShadows; // 视图关联的全景阴影. TArray ViewDependentWholeSceneShadows; // 视图关联的需要裁剪的全景阴影. TArray ViewDependentWholeSceneShadowsThatNeedCulling; { // 遍历所有光源, 将不同类型的光源加入不同类型的待渲染的阴影列表中. for (TSparseArray::TConstIterator LightIt(Scene->Lights); LightIt; ++LightIt) { const FLightSceneInfoCompact& LightSceneInfoCompact = *LightIt; FLightSceneInfo* LightSceneInfo = LightSceneInfoCompact.LightSceneInfo; FScopeCycleCounter Context(LightSceneInfo->Proxy->GetStatId()); FVisibleLightInfo& VisibleLightInfo = VisibleLightInfos[LightSceneInfo->Id]; // LightOcclusionType有阴影图和光追两种, 如果非阴影图类型, 则忽略. const FLightOcclusionType OcclusionType = GetLightOcclusionType(LightSceneInfoCompact); if (OcclusionType != FLightOcclusionType::Shadowmap) continue; // 如果光源没有开启阴影或阴影质量太小, 则忽略阴影图. if ((LightSceneInfoCompact.bCastStaticShadow || LightSceneInfoCompact.bCastDynamicShadow) && GetShadowQuality() > 0) { // 检测该光源是否在某个view里可见, 如果不可见, 则忽略. bool bIsVisibleInAnyView = false; for (int32 ViewIndex = 0; ViewIndex < Views.Num(); ViewIndex++) { bIsVisibleInAnyView = LightSceneInfo->ShouldRenderLight(Views[ViewIndex]); if (bIsVisibleInAnyView) { break; } } // 所有裁剪条件都通过了, 处理光源的阴影. if (bIsVisibleInAnyView) { // 初始化阴影的各种标记和变量. static const auto AllowStaticLightingVar = IConsoleManager::Get().FindTConsoleVariableDataInt(TEXT("r.AllowStaticLighting")); const bool bAllowStaticLighting = (!AllowStaticLightingVar || AllowStaticLightingVar->GetValueOnRenderThread() != 0); // 是否点光源阴影. 注意矩形光也当做点光源处理. const bool bPointLightShadow = LightSceneInfoCompact.LightType == LightType_Point || LightSceneInfoCompact.LightType == LightType_Rect; // 对不预计算阴影的移动光源只创建全景阴影(whole scene shadow). const bool bShouldCreateShadowForMovableLight = LightSceneInfoCompact.bCastDynamicShadow && (!LightSceneInfo->Proxy->HasStaticShadowing() || !bAllowStaticLighting); const bool bCreateShadowForMovableLight = bShouldCreateShadowForMovableLight && (!bPointLightShadow || bProjectEnablePointLightShadows); // 对带有预计算阴影的尚未构建的光源创建全景阴影. const bool bShouldCreateShadowToPreviewStaticLight = LightSceneInfo->Proxy->HasStaticShadowing() && LightSceneInfoCompact.bCastStaticShadow && !LightSceneInfo->IsPrecomputedLightingValid(); const bool bCreateShadowToPreviewStaticLight = bShouldCreateShadowToPreviewStaticLight && (!bPointLightShadow || bProjectEnablePointLightShadows); // 对需要静态阴影但由于重叠导致没有有效阴影图的光源创建全景阴影. const bool bShouldCreateShadowForOverflowStaticShadowing = LightSceneInfo->Proxy->HasStaticShadowing() && !LightSceneInfo->Proxy->HasStaticLighting() && LightSceneInfoCompact.bCastStaticShadow && LightSceneInfo->IsPrecomputedLightingValid() && LightSceneInfo->Proxy->GetShadowMapChannel() == INDEX_NONE; const bool bCreateShadowForOverflowStaticShadowing = bShouldCreateShadowForOverflowStaticShadowing && (!bPointLightShadow || bProjectEnablePointLightShadows); // 添加点光源的全景阴影. const bool bPointLightWholeSceneShadow = (bShouldCreateShadowForMovableLight || bShouldCreateShadowForOverflowStaticShadowing || bShouldCreateShadowToPreviewStaticLight) && bPointLightShadow; if (bPointLightWholeSceneShadow) { UsedWholeScenePointLightNames.Add(LightSceneInfoCompact.LightSceneInfo->Proxy->GetComponentName()); } // 创建光源的全景阴影. if (bCreateShadowForMovableLight || bCreateShadowToPreviewStaticLight || bCreateShadowForOverflowStaticShadowing) { CreateWholeSceneProjectedShadow(LightSceneInfo, NumPointShadowCachesUpdatedThisFrame, NumSpotShadowCachesUpdatedThisFrame); } // 允许移动和固定的光源创建CSM(级联阴影), 或者是尚未构建的静态光源. if ((!LightSceneInfo->Proxy->HasStaticLighting() && LightSceneInfoCompact.bCastDynamicShadow) || bCreateShadowToPreviewStaticLight) { // 增加视图关联的全景阴影. if( !bMobile || ((LightSceneInfo->Proxy->UseCSMForDynamicObjects() || LightSceneInfo->Proxy->IsMovable()) && (LightSceneInfo == Scene->MobileDirectionalLights[0] || LightSceneInfo == Scene->MobileDirectionalLights[1] || LightSceneInfo == Scene->MobileDirectionalLights[2]))) { AddViewDependentWholeSceneShadowsForView(ViewDependentWholeSceneShadows, ViewDependentWholeSceneShadowsThatNeedCulling, VisibleLightInfo, *LightSceneInfo); } // 处理交互阴影, 此处的交互是指光源和图元之间的影响. 包含PerObject阴影、透明阴影、自阴影等. if( !bMobile || (LightSceneInfo->Proxy->CastsModulatedShadows() && !LightSceneInfo->Proxy->UseCSMForDynamicObjects())) { Scene->FlushAsyncLightPrimitiveInteractionCreation(); // 处理动态图元的交互阴影. for (FLightPrimitiveInteraction* Interaction = LightSceneInfo->GetDynamicInteractionOftenMovingPrimitiveList(false); Interaction; Interaction = Interaction->GetNextPrimitive()) { SetupInteractionShadows(RHICmdList, Interaction, VisibleLightInfo, bStaticSceneOnly, ViewDependentWholeSceneShadows, PreShadows); } // 处理静态图元的交互阴影. for (FLightPrimitiveInteraction* Interaction = LightSceneInfo->GetDynamicInteractionStaticPrimitiveList(false); Interaction; Interaction = Interaction->GetNextPrimitive()) { SetupInteractionShadows(RHICmdList, Interaction, VisibleLightInfo, bStaticSceneOnly, ViewDependentWholeSceneShadows, PreShadows); } } } } } } // 计算投射阴影的可见性. InitProjectedShadowVisibility(RHICmdList); } // 清理旧的预计算阴影, 尝试增加新的到缓存中. UpdatePreshadowCache(FSceneRenderTargets::Get(RHICmdList)); // 收集图元列表, 以绘制不同类型的阴影. GatherShadowPrimitives(PreShadows, ViewDependentWholeSceneShadowsThatNeedCulling, bStaticSceneOnly); // 分配阴影深度渲染纹理. AllocateShadowDepthTargets(RHICmdList); // 收集阴影的动态网格元素, 跟之前剖析的GatherDynamicMeshElements类似. GatherShadowDynamicMeshElements(DynamicIndexBuffer, DynamicVertexBuffer, DynamicReadBuffer); } ``` ### 阴影渲染 # 阴影偏移 可以考虑的Buffer有 - ShadowDepths - CustomDepth # 相关Paas 1. ShadowDepths => RenderShadowDepthMaps() 2. Lights => RenderLights() 1. DirectLighting 1. UnbatchedLights 1. ShadowProjectionOnOpaque # ShadowDepths - FSceneRenderer::RenderShadowDepthMaps():位于CustomDepth之前。 - **RenderVirtualShadowMaps()** - RenderShadowDepthMapAtlases() - SortedShadowsForShadowDepthPass.ShadowMapCubemaps循环。渲染点光源阴影立方体图 - **FProjectedShadowInfo::RenderDepth()** - **FProjectedShadowInfo::RenderTranslucencyDepths** ## RenderDepth MeshDrawProcessor为***FShadowDepthPassMeshProcessor***。渲染的Shader为***ShadowDepthPixelShader.usf*** ```c++ void FProjectedShadowInfo::RenderDepth( FRDGBuilder& GraphBuilder, const FSceneRenderer* SceneRenderer, FRDGTextureRef ShadowDepthTexture, bool bDoParallelDispatch, bool bDoCrossGPUCopy) { #if WANTS_DRAW_MESH_EVENTS FString EventName; if (GetEmitDrawEvents()) { GetShadowTypeNameForDrawEvent(EventName); EventName += FString(TEXT(" ")) + FString::FromInt(ResolutionX) + TEXT("x") + FString::FromInt(ResolutionY); } RDG_EVENT_SCOPE(GraphBuilder, "%s", *EventName); #endif CONDITIONAL_SCOPE_CYCLE_COUNTER(STAT_RenderWholeSceneShadowDepthsTime, bWholeSceneShadow); CONDITIONAL_SCOPE_CYCLE_COUNTER(STAT_RenderPerObjectShadowDepthsTime, !bWholeSceneShadow); QUICK_SCOPE_CYCLE_COUNTER(STAT_RenderShadowDepth); FScene* Scene = SceneRenderer->Scene; const ERHIFeatureLevel::Type FeatureLevel = ShadowDepthView->FeatureLevel; BeginRenderView(GraphBuilder, Scene); FShadowDepthPassParameters* PassParameters = GraphBuilder.AllocParameters(); PassParameters->View = ShadowDepthView->ViewUniformBuffer; PassParameters->RenderTargets.DepthStencil = FDepthStencilBinding( ShadowDepthTexture, ERenderTargetLoadAction::ELoad, ERenderTargetLoadAction::ENoAction, FExclusiveDepthStencil::DepthWrite_StencilNop); if (CacheMode == SDCM_MovablePrimitivesOnly || CacheMode == SDCM_CSMScrolling) { // Copy in depths of static primitives before we render movable primitives. FMeshPassProcessorRenderState DrawRenderState; SetStateForShadowDepth(bOnePassPointLightShadow, bDirectionalLight, DrawRenderState, MeshPassTargetType); CopyCachedShadowMap(GraphBuilder, *ShadowDepthView, SceneRenderer, PassParameters->RenderTargets, DrawRenderState); } PassParameters->VirtualShadowMap = SceneRenderer->VirtualShadowMapArray.GetUniformBuffer(); switch (FSceneInterface::GetShadingPath(FeatureLevel)) { case EShadingPath::Deferred: { auto* ShadowDepthPassParameters = GraphBuilder.AllocParameters(); SetupShadowDepthPassUniformBuffer(this, GraphBuilder, *ShadowDepthView, *ShadowDepthPassParameters);//设置矩阵、Shader等相关ShadowDepthPassParameters参数 PassParameters->DeferredPassUniformBuffer = GraphBuilder.CreateUniformBuffer(ShadowDepthPassParameters); } break; case EShadingPath::Mobile: { auto* ShadowDepthPassParameters = GraphBuilder.AllocParameters(); SetupShadowDepthPassUniformBuffer(this, GraphBuilder, *ShadowDepthView, *ShadowDepthPassParameters);//设置矩阵、Shader等相关ShadowDepthPassParameters参数 PassParameters->MobilePassUniformBuffer = GraphBuilder.CreateUniformBuffer(ShadowDepthPassParameters); } break; default: checkNoEntry(); } ShadowDepthPass.BuildRenderingCommands(GraphBuilder, Scene->GPUScene, PassParameters->InstanceCullingDrawParams); #if WITH_MGPU // Need to fetch GPU mask outside "AddPass", as it's not updated during pass execution FRHIGPUMask GPUMask = GraphBuilder.RHICmdList.GetGPUMask(); #endif if (bDoParallelDispatch) { RDG_WAIT_FOR_TASKS_CONDITIONAL(GraphBuilder, IsShadowDepthPassWaitForTasksEnabled()); GraphBuilder.AddPass( RDG_EVENT_NAME("ShadowDepthPassParallel"), PassParameters, ERDGPassFlags::Raster | ERDGPassFlags::SkipRenderPass, [this, PassParameters #if WITH_MGPU , ShadowDepthTexture, GPUMask, bDoCrossGPUCopy #endif ](const FRDGPass* InPass, FRHICommandListImmediate& RHICmdList) { FShadowParallelCommandListSet ParallelCommandListSet(InPass, RHICmdList, *ShadowDepthView, *this, FParallelCommandListBindings(PassParameters)); ShadowDepthPass.DispatchDraw(&ParallelCommandListSet, RHICmdList, &PassParameters->InstanceCullingDrawParams); #if WITH_MGPU if (bDoCrossGPUCopy) { CopyCachedShadowMapCrossGPU(RHICmdList, ShadowDepthTexture->GetRHI(), GPUMask); } #endif }); } else { GraphBuilder.AddPass( RDG_EVENT_NAME("ShadowDepthPass"), PassParameters, ERDGPassFlags::Raster, [this, PassParameters #if WITH_MGPU , ShadowDepthTexture, GPUMask, bDoCrossGPUCopy #endif ](FRHICommandList& RHICmdList) { SetStateForView(RHICmdList); ShadowDepthPass.DispatchDraw(nullptr, RHICmdList, &PassParameters->InstanceCullingDrawParams); #if WITH_MGPU if (bDoCrossGPUCopy) { CopyCachedShadowMapCrossGPU(RHICmdList, ShadowDepthTexture->GetRHI(), GPUMask); } #endif }); } } ``` ```c++ void SetupShadowDepthPassUniformBuffer( const FProjectedShadowInfo* ShadowInfo, FRDGBuilder& GraphBuilder, const FViewInfo& View, FShadowDepthPassUniformParameters& ShadowDepthPassParameters) { static const auto CSMCachingCVar = IConsoleManager::Get().FindTConsoleVariableDataInt(TEXT("r.Shadow.CSMCaching")); const bool bCSMCachingEnabled = CSMCachingCVar && CSMCachingCVar->GetValueOnAnyThread() != 0; SetupSceneTextureUniformParameters(GraphBuilder, View.GetSceneTexturesChecked(), View.FeatureLevel, ESceneTextureSetupMode::None, ShadowDepthPassParameters.SceneTextures);//设置对应的RT与Texture ShadowDepthPassParameters.ProjectionMatrix = FTranslationMatrix44f(FVector3f(ShadowInfo->PreShadowTranslation - View.ViewMatrices.GetPreViewTranslation())) * ShadowInfo->TranslatedWorldToClipOuterMatrix; // LWC_TDOO: Precision loss? ShadowDepthPassParameters.ViewMatrix = FMatrix44f(ShadowInfo->TranslatedWorldToView); // LWC_TODO: Precision loss // Disable the SlopeDepthBias because we couldn't reconstruct the depth offset if it is not 0.0f when scrolling the cached shadow map. ShadowDepthPassParameters.ShadowParams = FVector4f(ShadowInfo->GetShaderDepthBias(), bCSMCachingEnabled ? 0.0f : ShadowInfo->GetShaderSlopeDepthBias(), ShadowInfo->GetShaderMaxSlopeDepthBias(), ShadowInfo->bOnePassPointLightShadow ? 1 : ShadowInfo->InvMaxSubjectDepth); ShadowDepthPassParameters.bClampToNearPlane = ShadowInfo->ShouldClampToNearPlane() ? 1.0f : 0.0f; if (ShadowInfo->bOnePassPointLightShadow) { check(ShadowInfo->BorderSize == 0); // offset from translated world space to (pre translated) shadow space const FMatrix Translation = FTranslationMatrix(ShadowInfo->PreShadowTranslation - View.ViewMatrices.GetPreViewTranslation()); for (int32 FaceIndex = 0; FaceIndex < 6; FaceIndex++) { ShadowDepthPassParameters.ShadowViewProjectionMatrices[FaceIndex] = FMatrix44f(Translation * ShadowInfo->OnePassShadowViewProjectionMatrices[FaceIndex]); // LWC_TODO: Precision loss? ShadowDepthPassParameters.ShadowViewMatrices[FaceIndex] = FMatrix44f(Translation * ShadowInfo->OnePassShadowViewMatrices[FaceIndex]); } } ShadowDepthPassParameters.bRenderToVirtualShadowMap = false; ShadowDepthPassParameters.VirtualSmPageTable = GraphBuilder.CreateSRV(GSystemTextures.GetDefaultStructuredBuffer(GraphBuilder, sizeof(uint32))); ShadowDepthPassParameters.PackedNaniteViews = GraphBuilder.CreateSRV(GSystemTextures.GetDefaultStructuredBuffer(GraphBuilder, sizeof(Nanite::FPackedView))); ShadowDepthPassParameters.PageRectBounds = GraphBuilder.CreateSRV(GSystemTextures.GetDefaultStructuredBuffer(GraphBuilder, sizeof(FIntVector4))); FRDGTextureRef DepthBufferArray = GraphBuilder.CreateTexture( FRDGTextureDesc::Create2DArray( FIntPoint(4,4), PF_R32_UINT, FClearValueBinding::None, TexCreate_ShaderResource | TexCreate_UAV, 1 ), TEXT("Dummy-OutDepthBuffer") ); ShadowDepthPassParameters.OutDepthBufferArray = GraphBuilder.CreateUAV( DepthBufferArray ); } ``` ## FShadowDepthPassMeshProcessor CullMode:双面物体会使用CM_None,其余的使用 CM_CW或者CM_CCW。 ```c++ bool FShadowDepthPassMeshProcessor::TryAddMeshBatch(const FMeshBatch& RESTRICT MeshBatch, uint64 BatchElementMask, const FPrimitiveSceneProxy* RESTRICT PrimitiveSceneProxy, int32 StaticMeshId, const FMaterialRenderProxy& MaterialRenderProxy, const FMaterial& Material) { const bool bShouldCastShadow = Material.ShouldCastDynamicShadows(); const FMeshDrawingPolicyOverrideSettings OverrideSettings = ComputeMeshOverrideSettings(MeshBatch); const ERasterizerFillMode MeshFillMode = ComputeMeshFillMode(Material, OverrideSettings); const ERasterizerCullMode MeshCullMode = FMeshPassProcessor::ComputeMeshCullMode(Material, OverrideSettings); ERasterizerCullMode FinalCullMode = SetupShadowCullMode(FeatureLevel, MeshPassTargetType, ShadowDepthType, Material, MeshCullMode, PrimitiveSceneProxy->CastsShadowAsTwoSided()); //判断是否投射动态阴影 && 是否在MainPass中渲染 && 是否会走OpaquePass bool bResult = true; if (bShouldCastShadow && ShouldIncludeDomainInMeshPass(Material.GetMaterialDomain()) && ShouldIncludeMaterialInDefaultOpaquePass(Material) && EnumHasAnyFlags(MeshSelectionMask, MeshBatch.VertexFactory->SupportsGPUScene(FeatureLevel) ? EShadowMeshSelection::VSM : EShadowMeshSelection::SM)) { const FMaterialRenderProxy* EffectiveMaterialRenderProxy = &MaterialRenderProxy; const FMaterial* EffectiveMaterial = &Material; const bool bVFTypeSupportsNullPixelShader = MeshBatch.VertexFactory->SupportsNullPixelShader(); const bool bEvaluateWPO = Material.MaterialModifiesMeshPosition_RenderThread() && (!ShouldOptimizedWPOAffectNonNaniteShaderSelection() || PrimitiveSceneProxy->EvaluateWorldPositionOffset()); //取得NullPixelShader以及计算材质中的WPO,之后如果对没有Pixel进行写入且没有改变WPO使用默认材质渲染;否则则采用当前模型的材质。 if (UseDefaultMaterialForShadowDepth(Material, bVFTypeSupportsNullPixelShader, bEvaluateWPO)) { const FMaterialRenderProxy* DefaultProxy = UMaterial::GetDefaultMaterial(MD_Surface)->GetRenderProxy(); const FMaterial* DefaultMaterialResource = DefaultProxy->GetMaterialNoFallback(FeatureLevel); check(DefaultMaterialResource); // Override with the default material for opaque materials that don't modify mesh position. EffectiveMaterialRenderProxy = DefaultProxy; EffectiveMaterial = DefaultMaterialResource; } bResult = Process(MeshBatch, BatchElementMask, StaticMeshId, PrimitiveSceneProxy, *EffectiveMaterialRenderProxy, *EffectiveMaterial, MeshFillMode, FinalCullMode); } return bResult; } ``` ## Shader VertexShader为:ShadowDepthVertexShader.usf,有4种变体: - IMPLEMENT_SHADOW_DEPTH_SHADERMODE_SHADERS(VertexShadowDepth_PerspectiveCorrect); - IMPLEMENT_SHADOW_DEPTH_SHADERMODE_SHADERS(VertexShadowDepth_OutputDepth); - IMPLEMENT_SHADOW_DEPTH_SHADERMODE_SHADERS(VertexShadowDepth_OnePassPointLight); - IMPLEMENT_SHADOW_DEPTH_SHADERMODE_SHADERS(VertexShadowDepth_VirtualShadowMap); PixelShader为:***ShadowDepthPixelShader.usf***,有4种变体: - IMPLEMENT_SHADOWDEPTHPASS_PIXELSHADER_TYPE(PixelShadowDepth_NonPerspectiveCorrect); - IMPLEMENT_SHADOWDEPTHPASS_PIXELSHADER_TYPE(PixelShadowDepth_PerspectiveCorrect); - IMPLEMENT_SHADOWDEPTHPASS_PIXELSHADER_TYPE(PixelShadowDepth_OnePassPointLight); - IMPLEMENT_SHADOWDEPTHPASS_PIXELSHADER_TYPE(PixelShadowDepth_VirtualShadowMap); 使用对应的Shader逻辑位于GetShadowDepthPassShaders()。只有非方向光同时不是OnePass的状态下会使用**PerspectiveCorrect**。 ```c++ void SetShadowDepthOutputs( float4x4 WorldToClipMatrix, float4x4 WorldToShadowMatrix, float4 WorldPosition, float3 WorldVertexNormal, out float4 OutPosition, out float ShadowDepth #if PERSPECTIVE_CORRECT_DEPTH , out float OutDepthBias #endif ) { OutPosition = mul(WorldPosition, WorldToClipMatrix); // Clamp the vertex to the near plane if it is in front of the near plane // This has problems if some vertices of a triangle get clamped and others do not, also causes artifacts with non-ortho projections if (PassStruct.bClampToNearPlane > 0 && OutPosition.z > OutPosition.w) { OutPosition.z = 0.999999f; OutPosition.w = 1.0f; } #if ONEPASS_POINTLIGHT_SHADOW const float3 ViewDirection = -normalize(mul(WorldPosition, WorldToShadowMatrix).xyz); const float3 ViewNormal = mul(float4(WorldVertexNormal,0), WorldToShadowMatrix).xyz; const float NoL = abs(dot(ViewDirection, ViewNormal)); #else const float NoL = abs(dot( float3(WorldToShadowMatrix[0].z, WorldToShadowMatrix[1].z, WorldToShadowMatrix[2].z), WorldVertexNormal)); #endif const float MaxSlopeDepthBias = PassStruct.ShadowParams.z; const float Slope = clamp(abs(NoL) > 0 ? sqrt(saturate(1 - NoL*NoL)) / NoL : MaxSlopeDepthBias, 0, MaxSlopeDepthBias); const float SlopeDepthBias = PassStruct.ShadowParams.y; const float SlopeBias = SlopeDepthBias * Slope; const float ConstantDepthBias = PassStruct.ShadowParams.x; const float DepthBias = SlopeBias + ConstantDepthBias; #if PERSPECTIVE_CORRECT_DEPTH ShadowDepth = OutPosition.z; OutDepthBias = DepthBias; #elif ONEPASS_POINTLIGHT_SHADOW ShadowDepth = 0; //OutPosition.z += DepthBias; #else // Output linear, normalized depth const float InvMaxSubjectDepth = PassStruct.ShadowParams.w; #if PLATFORM_NEEDS_PRECISE_SHADOW_DEPTH precise #endif float AdjustedDepth = ( 1 - OutPosition.z * InvMaxSubjectDepth ) + DepthBias; ShadowDepth = AdjustedDepth; OutPosition.z = AdjustedDepth; #endif } void Main( FVertexFactoryInput Input, #if ONEPASS_POINTLIGHT_SHADOW out FShadowDepthVSToPS OutParameters, #else out FShadowDepthVSToPS OutParameters, #endif #if VIRTUAL_SM_ENABLED out nointerpolation uint PackedPageInfo : TEXCOORD8, #endif out float4 OutPosition : SV_POSITION #if ONEPASS_POINTLIGHT_SHADOW , out uint LayerIndex : SV_RenderTargetArrayIndex #endif #if VIRTUAL_SM_ENABLED // OLA-TODO: this collides with instanced stereo, which thankfully is not used with shadow maps, so should be fine, presumably. , out float4 OutVirtualSmPageClip : SV_ClipDistance #endif // VIRTUAL_SM_ENABLED ) { ResolvedView = ResolveView(); //计算FMaterialVertexParameters,最终获取顶点WorldPosition FVertexFactoryIntermediates VFIntermediates = GetVertexFactoryIntermediates(Input); float4 WorldPos = VertexFactoryGetWorldPosition(Input, VFIntermediates); half3x3 TangentToLocal = VertexFactoryGetTangentToLocal(Input, VFIntermediates); FMaterialVertexParameters VertexParameters = GetMaterialVertexParameters(Input, VFIntermediates, WorldPos.xyz, TangentToLocal); const float3 WorldNormal = VertexFactoryGetWorldNormal(Input, VFIntermediates); WorldPos.xyz += GetMaterialWorldPositionOffset(VertexParameters); #if ONEPASS_POINTLIGHT_SHADOW OutPosition = WorldPos; #if INTERPOLATE_VF_ATTRIBUTES // Masked materials need texture coords to clip OutParameters.FactoryInterpolants = VertexFactoryGetInterpolantsVSToPS(Input, VFIntermediates, VertexParameters); #endif #if INTERPOLATE_POSITION OutParameters.PixelPosition = WorldPos.xyz; #endif LayerIndex = bUseGpuSceneInstancing ? VertexFactoryGetViewIndex(VFIntermediates) : LayerId; OutPosition = mul(WorldPos, PassStruct.ShadowViewProjectionMatrices[LayerIndex]); #else float Dummy; SetShadowDepthOutputs( PassStruct.ProjectionMatrix, PassStruct.ViewMatrix, WorldPos, WorldNormal, OutPosition, #if !PERSPECTIVE_CORRECT_DEPTH Dummy #else OutParameters.ShadowDepth, OutParameters.DepthBias #endif ); #if INTERPOLATE_VF_ATTRIBUTES // Masked materials need texture coords to clip OutParameters.FactoryInterpolants = VertexFactoryGetInterpolantsVSToPS(Input, VFIntermediates, VertexParameters); #endif #if INTERPOLATE_POSITION OutParameters.PixelPosition = WorldPos.xyz; #endif #if !PERSPECTIVE_CORRECT_DEPTH && !COMPILER_SUPPORTS_EMPTY_STRUCTS OutParameters.Dummy = 0; #endif #endif #if VIRTUAL_SM_ENABLED PackedPageInfo = 0; OutVirtualSmPageClip = float4(1.0f, 1.0f, 1.0f, 1.0f); if (PassStruct.bRenderToVirtualShadowMap != 0) { // Get the offset from which we loaded the instance ID uint InstanceIdIndex = VertexFactoryGetInstanceIdLoadIndex(VFIntermediates); PackedPageInfo = InstanceCulling.PageInfoBuffer[InstanceIdIndex]; FPageInfo PageInfo = UnpackPageInfo(PackedPageInfo); TransformToVirtualSmPage(OutPosition, OutVirtualSmPageClip, PageInfo, WorldPos.xyz); } #endif // VIRTUAL_SM_ENABLED } #if POSITION_ONLY void PositionOnlyMain( in FPositionAndNormalOnlyVertexFactoryInput Input, #if ONEPASS_POINTLIGHT_SHADOW out uint LayerIndex : SV_RenderTargetArrayIndex, #endif out FShadowDepthVSToPS OutParameters, #if VIRTUAL_SM_ENABLED out nointerpolation uint PackedPageInfo : TEXCOORD8, #endif out float4 OutPosition : SV_POSITION #if VIRTUAL_SM_ENABLED // OLA-TODO: this collides with instanced stereo, which thankfully is not used with shadow maps, so should be fine, presumably. , out float4 OutVirtualSmPageClip : SV_ClipDistance #endif // VIRTUAL_SM_ENABLED ) { ResolvedView = ResolveView(); float4 WorldPos = VertexFactoryGetWorldPosition(Input); #if INTERPOLATE_VF_ATTRIBUTES OutParameters.FactoryInterpolants = (FVertexFactoryInterpolantsVSToPS)0; #endif float3 WorldNormal = VertexFactoryGetWorldNormal(Input); #if ONEPASS_POINTLIGHT_SHADOW LayerIndex = bUseGpuSceneInstancing ? VertexFactoryGetViewIndex(Input) : LayerId; OutPosition = mul(WorldPos, PassStruct.ShadowViewProjectionMatrices[LayerIndex]); #else // !ONEPASS_POINTLIGHT_SHADOW float ShadowDepth; SetShadowDepthOutputs( PassStruct.ProjectionMatrix, PassStruct.ViewMatrix, WorldPos, WorldNormal, OutPosition, #if PERSPECTIVE_CORRECT_DEPTH OutParameters.ShadowDepth, OutParameters.DepthBias #else ShadowDepth #endif ); #if !PERSPECTIVE_CORRECT_DEPTH && !COMPILER_SUPPORTS_EMPTY_STRUCTS OutParameters.Dummy = 0; #endif #endif // ONEPASS_POINTLIGHT_SHADOW #if INTERPOLATE_POSITION OutParameters.PixelPosition = WorldPos.xyz; #endif #if VIRTUAL_SM_ENABLED PackedPageInfo = 0; OutVirtualSmPageClip = float4(1.0f, 1.0f, 1.0f, 1.0f); if (PassStruct.bRenderToVirtualShadowMap != 0) { // Get the offset from which we loaded the instance ID uint InstanceIdIndex = VertexFactoryGetInstanceIdLoadIndex(Input); // TODO: Maybe offset index to local buffer for this? We may not want to use a global since most passes are not supporting this // Or perhaps both are different, as they are managed somewhat differently anyway, maybe. PackedPageInfo = InstanceCulling.PageInfoBuffer[InstanceIdIndex]; FPageInfo PageInfo = UnpackPageInfo(PackedPageInfo); TransformToVirtualSmPage(OutPosition, OutVirtualSmPageClip, PageInfo, WorldPos.xyz); } #endif // VIRTUAL_SM_ENABLED } #endif // POSITION_ONLY ``` ```c++ void Main( FShadowDepthVSToPS Inputs, #if VIRTUAL_SM_ENABLED nointerpolation uint PackedPageInfo : TEXCOORD8, #endif in float4 SvPosition : SV_Position // after all interpolators #if PERSPECTIVE_CORRECT_DEPTH || COMPILER_METAL ,out float OutDepth : SV_DEPTH #endif ) { ResolvedView = ResolveView(); #if INTERPOLATE_VF_ATTRIBUTES FMaterialPixelParameters MaterialParameters = GetMaterialPixelParameters(Inputs.FactoryInterpolants, SvPosition);//取得材质中的PixelShader相关变量PixelMaterialInputs。 FPixelMaterialInputs PixelMaterialInputs; #if INTERPOLATE_POSITION { float4 ScreenPosition = SvPositionToResolvedScreenPosition(SvPosition); float3 TranslatedWorld = Inputs.PixelPosition.xyz; CalcMaterialParametersEx(MaterialParameters, PixelMaterialInputs, SvPosition, ScreenPosition, 1, TranslatedWorld, TranslatedWorld); } #else CalcMaterialParameters(MaterialParameters, PixelMaterialInputs, SvPosition, 1); #endif // Evaluate the mask for masked materials GetMaterialClippingShadowDepth(MaterialParameters, PixelMaterialInputs); #else ClipLODTransition(SvPosition.xy); #endif #if PERSPECTIVE_CORRECT_DEPTH const float InvMaxSubjectDepth = PassStruct.ShadowParams.w; Inputs.ShadowDepth = 1 - Inputs.ShadowDepth * InvMaxSubjectDepth; Inputs.ShadowDepth += Inputs.DepthBias; OutDepth = saturate(Inputs.ShadowDepth); #elif COMPILER_METAL // Metal fragment shader must not be empty, // so output depth value explicitly if this shader permuation was not discarded OutDepth = SvPosition.z; #endif #if ENABLE_NON_NANITE_VSM && VIRTUAL_TEXTURE_TARGET uint2 vAddress = (uint2)SvPosition.xy; float DeviceZ = SvPosition.z; FPageInfo PageInfo = UnpackPageInfo( PackedPageInfo ); FNaniteView NaniteView = UnpackNaniteView( PassStruct.PackedNaniteViews[ PageInfo.ViewId ] ); FShadowPhysicalPage Page = ShadowDecodePageTable( PassStruct.VirtualSmPageTable[ CalcPageOffset( NaniteView.TargetLayerIndex, NaniteView.TargetMipLevel, vAddress >> VSM_LOG2_PAGE_SIZE ) ] ); if( Page.bThisLODValid ) { uint2 pAddress = Page.PhysicalAddress * VSM_PAGE_SIZE + (vAddress & VSM_PAGE_SIZE_MASK); // If requested, render to the static page const int ArrayIndex = PageInfo.bStaticPage ? GetVirtualShadowMapStaticArrayIndex() : 0; InterlockedMax( PassStruct.OutDepthBufferArray[ uint3( pAddress, ArrayIndex ) ], asuint( DeviceZ ) ); } #endif } ``` # Lights ### Shader ShadowProjectionPixelShader.usf - TShadowProjectionPS: - TDirectionalPercentageCloserShadowProjectionPS:方向光投影 - TSpotPercentageCloserShadowProjectionPS:SpotLight - FOnePassPointShadowProjectionPS(Moible?) ### 相关函数 - FDeferredShadingSceneRenderer::RenderLights() - FDeferredShadingSceneRenderer::RenderDeferredShadowProjections() - FSceneRenderer::RenderShadowProjections() - FProjectedShadowInfo::SetupFrustumForProjection():构建阴影投影4棱椎平面信息。 - FProjectedShadowInfo::SetupProjectionStencilMask(): ## 其他 顺序: RenderCustomDepthPass FSceneRenderer::CreateDynamicShadows => FSceneRenderer::CreatePerObjectProjectedShadow ```c++ if (!IsForwardShadingEnabled(ShaderPlatform)) { // Dynamic shadows are synced later when using the deferred path to make more headroom for tasks. FinishInitDynamicShadows(GraphBuilder, InitViewTaskDatas.DynamicShadows, InstanceCullingManager, ExternalAccessQueue); } ``` ```c++ if (RendererOutput == ERendererOutput::DepthPrepassOnly) { RenderOcclusionLambda(); if (bUpdateNaniteStreaming) { Nanite::GStreamingManager.SubmitFrameStreamingRequests(GraphBuilder); } CopySceneCaptureComponentToTarget(GraphBuilder, SceneTextures, ViewFamilyTexture, ViewFamily, Views); } else { GVRSImageManager.PrepareImageBasedVRS(GraphBuilder, ViewFamily, SceneTextures); if (!IsForwardShadingEnabled(ShaderPlatform)) { // Dynamic shadows are synced later when using the deferred path to make more headroom for tasks. FinishInitDynamicShadows(GraphBuilder, InitViewTaskDatas.DynamicShadows, InstanceCullingManager, ExternalAccessQueue); } // Update groom only visible in shadow if (IsHairStrandsEnabled(EHairStrandsShaderType::All, Scene->GetShaderPlatform()) && RendererOutput == ERendererOutput::FinalSceneColor) { UpdateHairStrandsBookmarkParameters(Scene, Views, HairStrandsBookmarkParameters); // Interpolation for cards/meshes only visible in shadow needs to happen after the shadow jobs are completed const bool bRunHairStrands = HairStrandsBookmarkParameters.HasInstances() && (Views.Num() > 0); if (bRunHairStrands) { RunHairStrandsBookmark(GraphBuilder, EHairStrandsBookmark::ProcessCardsAndMeshesInterpolation_ShadowView, HairStrandsBookmarkParameters); } } // NOTE: The ordering of the lights is used to select sub-sets for different purposes, e.g., those that support clustered deferred. FSortedLightSetSceneInfo& SortedLightSet = *GraphBuilder.AllocObject(); { RDG_CSV_STAT_EXCLUSIVE_SCOPE(GraphBuilder, SortLights); RDG_GPU_STAT_SCOPE(GraphBuilder, SortLights); ComputeLightGridOutput = GatherLightsAndComputeLightGrid(GraphBuilder, bComputeLightGrid, SortedLightSet); } ``` # RenderLights FDeferredShadingSceneRenderer::RenderLights() => RDG_EVENT_SCOPE(GraphBuilder, "UnbatchedLights");//batchedLights为没有LightFunction与没有阴影的灯光。 => for (int32 LightIndex = UnbatchedLightStart; LightIndex < LumenLightStart; LightIndex++) => if (bDrawShadows){...} => ***RenderDeferredShadowProjections()*** => if (bDirectLighting){...} if (bDirectLighting){...} ## RenderDeferredShadowProjections() # 半程阴影 由晨风&Neverwind提出: - 【[UFSH2024]用虚幻引擎5为《幻塔》定制高品质动画流程风格化渲染管线 | 晨风 Neverwind 完美世界游戏】 【精准空降到 07:27】 https://www.bilibili.com/video/BV1rW2LYvEox/?share_source=copy_web&vd_source=fe8142e8e12816535feaeabd6f6cdc8e&t=447 **阴影Setup阶段**: ```c++ if 启用半程阴影: { 额外进行一次CreatePerObjectProjectedShadow() { 处理阴影光照Matrix //猜测: //在ProjectedShadowInfo->SetupPerObjectProjection()中调整,FProjectedShadowInfo.TranslatedWorldToView。 //在LightSceneInfo->Proxy->GetPerObjectProjectedShadowInitializer(Bounds, ShadowInitializer)之后,修改WorldTolight。 向阴影信息写入IsHalfViewShadowFlag//猜测:在FProjectedShadowInfo中添加判断Flag并且写入。 用新的光源方向画Atlas //猜测:给带有对应Flog的DirectionLigh多创建一个对应的Atlas? } } ``` 截图代码(半程阴影修改LightDirection逻辑): ```c++ if(PrimitiveSceneinfo->Proxy->IsToonDisableSelfShadow()) { ... for (int32 ViewIndex = 0; ViewIndex < Views.Num(); ViewIndex++) { const FViewInfo& View = Views[ViewIndex]; const FMatrix& ViewMatrix = View.ShadowViewMatrices.GetViewMatrix() FVector LightDirection = Lightsceneinfo->Proxy- GetDirection(); const FVector CameraDirection = ViewMatrix.GetColumn(2).GetSafeNonmal() float LightViewBlendFactor = PrimitiveSceneinfo->Proxy->GetToonHalfVienShadowFactor(); Fvector HalfViewLightDir = (LightDirection + ( 1 - LightViewBlendFactor) + CameraDirection * LightViewBlendFactor).GetSafeNormal(); FMatrix FinalCombineMatrix = FNnverseRotationMatrix(HalfViewLightDir.Rotation()) ShadowInitializer.WorldTolight = FinalCombineMatrix; } ... } ``` PS.很有可能需要创建2个Atlas。Atlas的创建位于***FSceneRenderer::AllocateCachedShadowDepthTargets()***。数据存储在***SortedShadowsForShadowDepthPass.ShadowMapAtlases***中。大致由FSceneRenderer::FinishInitDynamicShadows()调用。 **阴影Projection阶段**: //此阶段需要屏蔽角色投射到自己的非半程阴影 //和角色投射到场景中会跟随视角移动的阴影 ```c++ if(Toon材质,且没有程阴影Flag的阴影 &&非Toon材质但有半程阴影Flag的阴影) { 屏蔽此阴影 } ```