// Copyright Epic Games, Inc. All Rights Reserved. /*============================================================================= VirtualShadowMapProjection.cpp =============================================================================*/ #include "VirtualShadowMapProjection.h" #include "VirtualShadowMapVisualizationData.h" #include "CoreMinimal.h" #include "Stats/Stats.h" #include "RHI.h" #include "RenderResource.h" #include "RendererInterface.h" #include "Shader.h" #include "StaticBoundShaderState.h" #include "SceneUtils.h" #include "RHIStaticStates.h" #include "LightSceneInfo.h" #include "GlobalShader.h" #include "SceneRenderTargetParameters.h" #include "ShadowRendering.h" #include "DeferredShadingRenderer.h" #include "PixelShaderUtils.h" #include "ShadowRendering.h" #include "SceneRendering.h" #include "VirtualShadowMapClipmap.h" #include "HairStrands/HairStrandsData.h" static TAutoConsoleVariable CVarScreenRayLength( TEXT( "r.Shadow.Virtual.ScreenRayLength" ), 0.015f, TEXT( "Length of the screen space shadow trace (smart shadow bias) before the virtual shadow map lookup." ), ECVF_Scalability | ECVF_RenderThreadSafe ); static TAutoConsoleVariable CVarNormalBias( TEXT( "r.Shadow.Virtual.NormalBias" ), 0.5f, TEXT( "Receiver offset along surface normal for shadow lookup. Scaled by distance to camera." ) TEXT( "Higher values avoid artifacts on surfaces nearly parallel to the light, but also visibility offset shadows and increase the chance of hitting unmapped pages." ), ECVF_Scalability | ECVF_RenderThreadSafe ); TAutoConsoleVariable CVarVirtualShadowOnePassProjection( TEXT("r.Shadow.Virtual.OnePassProjection"), 0, TEXT("Single pass projects all local VSMs culled with the light grid. Used in conjunction with clustered deferred shading."), ECVF_Scalability | ECVF_RenderThreadSafe ); static TAutoConsoleVariable CVarSMRTRayCountLocal( TEXT( "r.Shadow.Virtual.SMRT.RayCountLocal" ), 7, TEXT( "Ray count for shadow map tracing of local lights. 0 = disabled." ), ECVF_Scalability | ECVF_RenderThreadSafe ); static TAutoConsoleVariable CVarSMRTSamplesPerRayLocal( TEXT( "r.Shadow.Virtual.SMRT.SamplesPerRayLocal" ), 8, TEXT( "Shadow map samples per ray for local lights" ), ECVF_Scalability | ECVF_RenderThreadSafe ); static TAutoConsoleVariable CVarSMRTMaxRayAngleFromLight( TEXT( "r.Shadow.Virtual.SMRT.MaxRayAngleFromLight" ), 0.03f, TEXT( "Max angle (in radians) a ray is allowed to span from the light's perspective for local lights." ) TEXT( "Smaller angles limit the screen space size of shadow penumbra. " ) TEXT( "Larger angles lead to more noise. " ), ECVF_RenderThreadSafe ); static TAutoConsoleVariable CVarSMRTRayCountDirectional( TEXT( "r.Shadow.Virtual.SMRT.RayCountDirectional" ), 7, TEXT( "Ray count for shadow map tracing of directional lights. 0 = disabled." ), ECVF_Scalability | ECVF_RenderThreadSafe ); static TAutoConsoleVariable CVarSMRTSamplesPerRayDirectional( TEXT( "r.Shadow.Virtual.SMRT.SamplesPerRayDirectional" ), 8, TEXT( "Shadow map samples per ray for directional lights" ), ECVF_Scalability | ECVF_RenderThreadSafe ); static TAutoConsoleVariable CVarSMRTRayLengthScaleDirectional( TEXT( "r.Shadow.Virtual.SMRT.RayLengthScaleDirectional" ), 1.5f, TEXT( "Length of ray to shoot for directional lights, scaled by distance to camera." ) TEXT( "Shorter rays limit the screen space size of shadow penumbra. " ) TEXT( "Longer rays require more samples to avoid shadows disconnecting from contact points. " ), ECVF_RenderThreadSafe ); static TAutoConsoleVariable CVarSMRTAdaptiveRayCount( TEXT( "r.Shadow.Virtual.SMRT.AdaptiveRayCount" ), 1, TEXT( "Shoot fewer rays in fully shadowed and unshadowed regions. Currently only supported with OnePassProjection. " ), ECVF_Scalability | ECVF_RenderThreadSafe ); static TAutoConsoleVariable CVarSMRTTexelDitherScaleDirectional( TEXT( "r.Shadow.Virtual.SMRT.TexelDitherScaleDirectional" ), 2.0f, TEXT( "Applies a dither to the shadow map ray casts for directional lights to help hide aliasing due to insufficient shadow resolution.\n" ) TEXT( "Setting this too high can cause shadows light leaks near occluders." ), ECVF_Scalability | ECVF_RenderThreadSafe ); static TAutoConsoleVariable CVarSMRTWorldDitherScaleDirectional( TEXT( "r.Shadow.Virtual.SMRT.WorldDitherScaleDirectional" ), 0.0f, TEXT( "Applies a world." ), ECVF_Scalability | ECVF_RenderThreadSafe ); TAutoConsoleVariable CVarSMRTExtrapolateWithSlope( TEXT("r.Shadow.Virtual.SMRT.ExtrapolateWithSlope"), 1, TEXT("Use slope-based extrapolation behind occluders. This can increase the quality of shadow penumbra on surfaces aligned with the light direction."), ECVF_Scalability | ECVF_RenderThreadSafe ); static TAutoConsoleVariable CVarForcePerLightShadowMaskClear( TEXT( "r.Shadow.Virtual.ForcePerLightShadowMaskClear" ), 0, TEXT( "" ), ECVF_RenderThreadSafe ); const TCHAR* ToString(EVirtualShadowMapProjectionInputType In) { switch (In) { case EVirtualShadowMapProjectionInputType::HairStrands: return TEXT("HairStrands"); case EVirtualShadowMapProjectionInputType::GBuffer: return Strata::IsStrataEnabled() ? TEXT("Strata") : TEXT("GBuffer"); } return TEXT("Invalid"); } // Composite denoised shadow projection mask onto the light's shadow mask // Basically just a copy shader with a special blend mode class FVirtualShadowMapProjectionCompositePS : public FGlobalShader { DECLARE_GLOBAL_SHADER(FVirtualShadowMapProjectionCompositePS); SHADER_USE_PARAMETER_STRUCT(FVirtualShadowMapProjectionCompositePS, FGlobalShader); BEGIN_SHADER_PARAMETER_STRUCT(FParameters, ) SHADER_PARAMETER_RDG_TEXTURE(Texture2D, InputShadowFactor) RENDER_TARGET_BINDING_SLOTS() END_SHADER_PARAMETER_STRUCT() static bool ShouldCompilePermutation(const FGlobalShaderPermutationParameters& Parameters) { return DoesPlatformSupportNanite(Parameters.Platform); } static void ModifyCompilationEnvironment(const FGlobalShaderPermutationParameters& Parameters, FShaderCompilerEnvironment& OutEnvironment) { // Required right now due to where the shader function lives, but not actually used FVirtualShadowMapArray::SetShaderDefines(OutEnvironment); } }; IMPLEMENT_GLOBAL_SHADER(FVirtualShadowMapProjectionCompositePS, "/Engine/Private/VirtualShadowMaps/VirtualShadowMapProjection.usf", "VirtualShadowMapCompositePS", SF_Pixel); void CompositeVirtualShadowMapMask( FRDGBuilder& GraphBuilder, const FIntRect ScissorRect, const FRDGTextureRef Input, bool bDirectionalLight, FRDGTextureRef OutputShadowMaskTexture) { auto ShaderMap = GetGlobalShaderMap(GMaxRHIFeatureLevel); FVirtualShadowMapProjectionCompositePS::FParameters* PassParameters = GraphBuilder.AllocParameters(); PassParameters->InputShadowFactor = Input; PassParameters->RenderTargets[0] = FRenderTargetBinding(OutputShadowMaskTexture, ERenderTargetLoadAction::ELoad); FRHIBlendState* BlendState = FProjectedShadowInfo::GetBlendStateForProjection( 0, // ShadowMapChannel bDirectionalLight, // bIsWholeSceneDirectionalShadow, false, // bUseFadePlane false, // bProjectingForForwardShading, false // bMobileModulatedProjections ); auto PixelShader = ShaderMap->GetShader(); ValidateShaderParameters(PixelShader, *PassParameters); FPixelShaderUtils::AddFullscreenPass(GraphBuilder, ShaderMap, RDG_EVENT_NAME("MaskComposite"), PixelShader, PassParameters, ScissorRect, BlendState); } class FVirtualShadowMapProjectionCS : public FGlobalShader { DECLARE_GLOBAL_SHADER(FVirtualShadowMapProjectionCS); SHADER_USE_PARAMETER_STRUCT(FVirtualShadowMapProjectionCS, FGlobalShader) class FDirectionalLightDim : SHADER_PERMUTATION_BOOL("DIRECTIONAL_LIGHT"); class FSMRTExtrapolateSlopeDim : SHADER_PERMUTATION_BOOL("SMRT_EXTRAPOLATE_WITH_SLOPE"); class FOnePassProjectionDim : SHADER_PERMUTATION_BOOL("ONE_PASS_PROJECTION"); class FHairStrandsDim : SHADER_PERMUTATION_BOOL("HAS_HAIR_STRANDS"); class FVisualizeOutputDim : SHADER_PERMUTATION_BOOL("VISUALIZE_OUTPUT"); // -1 means dynamic count class FSMRTStaticSampleCount : SHADER_PERMUTATION_RANGE_INT("SMRT_TEMPLATE_STATIC_SAMPLES_PER_RAY", -1, 2); using FPermutationDomain = TShaderPermutationDomain< FDirectionalLightDim, FOnePassProjectionDim, FHairStrandsDim, FVisualizeOutputDim, FSMRTExtrapolateSlopeDim, FSMRTStaticSampleCount>; BEGIN_SHADER_PARAMETER_STRUCT(FParameters, ) SHADER_PARAMETER_STRUCT_INCLUDE(FVirtualShadowMapSamplingParameters, SamplingParameters) SHADER_PARAMETER_RDG_UNIFORM_BUFFER(FSceneTextureUniformParameters, SceneTexturesStruct) SHADER_PARAMETER_RDG_UNIFORM_BUFFER(FHairStrandsViewUniformParameters, HairStrands) SHADER_PARAMETER_RDG_UNIFORM_BUFFER(FVirtualVoxelParameters, HairStrandsVoxel) SHADER_PARAMETER_RDG_UNIFORM_BUFFER(FStrataGlobalUniformParameters, Strata) SHADER_PARAMETER_STRUCT_REF(FViewUniformShaderParameters, View) SHADER_PARAMETER(FIntVector4, ProjectionRect) SHADER_PARAMETER(float, ScreenRayLength) SHADER_PARAMETER(float, NormalBias) SHADER_PARAMETER(uint32, SMRTRayCount) SHADER_PARAMETER(uint32, SMRTSamplesPerRay) SHADER_PARAMETER(float, SMRTRayLengthScale) SHADER_PARAMETER(float, SMRTCotMaxRayAngleFromLight) SHADER_PARAMETER(float, SMRTTexelDitherScale) SHADER_PARAMETER(float, SMRTWorldDitherScale) SHADER_PARAMETER(uint32, bSMRTUseAdaptiveRayCount) SHADER_PARAMETER(uint32, InputType) SHADER_PARAMETER(uint32, bCullBackfacingPixels) // One pass projection parameters SHADER_PARAMETER_RDG_UNIFORM_BUFFER(FForwardLightData, ForwardLightData) SHADER_PARAMETER_RDG_TEXTURE_UAV(RWTexture2D, OutShadowMaskBits) // Pass per light parameters SHADER_PARAMETER_STRUCT(FLightShaderParameters, Light) SHADER_PARAMETER(int32, LightUniformVirtualShadowMapId) SHADER_PARAMETER_RDG_TEXTURE_UAV(RWTexture2D, OutShadowFactor) // Visualization output SHADER_PARAMETER_RDG_BUFFER_SRV(StructuredBuffer< FPhysicalPageMetaData >, PhysicalPageMetaData) SHADER_PARAMETER(int32, VisualizeModeId) SHADER_PARAMETER(int32, VisualizeVirtualShadowMapId) SHADER_PARAMETER_RDG_TEXTURE_UAV(RWTexture2D, OutVisualize) END_SHADER_PARAMETER_STRUCT() static void ModifyCompilationEnvironment( const FGlobalShaderPermutationParameters& Parameters, FShaderCompilerEnvironment& OutEnvironment ) { FGlobalShader::ModifyCompilationEnvironment(Parameters, OutEnvironment); FVirtualShadowMapArray::SetShaderDefines(OutEnvironment); FForwardLightingParameters::ModifyCompilationEnvironment(Parameters.Platform, OutEnvironment); FPermutationDomain PermutationVector(Parameters.PermutationId); // TODO: We may no longer need this with SM6 requirement, but shouldn't hurt OutEnvironment.CompilerFlags.Add(CFLAG_WaveOperations); OutEnvironment.CompilerFlags.Add(CFLAG_Wave32); if (FDataDrivenShaderPlatformInfo::GetSupportsRealTypes(Parameters.Platform) == ERHIFeatureSupport::RuntimeGuaranteed) { OutEnvironment.CompilerFlags.Add(CFLAG_AllowRealTypes); } } static bool ShouldCompilePermutation(const FGlobalShaderPermutationParameters& Parameters) { FPermutationDomain PermutationVector(Parameters.PermutationId); // Directional lights are always in separate passes as forward light data structure currently // only contains a single directional light. if( PermutationVector.Get< FDirectionalLightDim >() && PermutationVector.Get< FOnePassProjectionDim >() ) { return false; } return DoesPlatformSupportNanite(Parameters.Platform); } }; IMPLEMENT_GLOBAL_SHADER(FVirtualShadowMapProjectionCS, "/Engine/Private/VirtualShadowMaps/VirtualShadowMapProjection.usf", "VirtualShadowMapProjection", SF_Compute); static float GetNormalBiasForShader() { return CVarNormalBias.GetValueOnRenderThread() / 1000.0f; } static void RenderVirtualShadowMapProjectionCommon( FRDGBuilder& GraphBuilder, const FMinimalSceneTextures& SceneTextures, const FViewInfo& View, int32 ViewIndex, FVirtualShadowMapArray& VirtualShadowMapArray, const FIntRect ProjectionRect, EVirtualShadowMapProjectionInputType InputType, FRDGTextureRef OutputTexture, const FLightSceneProxy* LightProxy = nullptr, int32 VirtualShadowMapId = INDEX_NONE) { check(GRHISupportsWaveOperations); // Use hair strands data (i.e., hair voxel tracing) only for Gbuffer input for casting hair shadow onto opaque geometry. const bool bHasHairStrandsData = HairStrands::HasViewHairStrandsData(View); FVirtualShadowMapProjectionCS::FParameters* PassParameters = GraphBuilder.AllocParameters< FVirtualShadowMapProjectionCS::FParameters >(); PassParameters->SamplingParameters = VirtualShadowMapArray.GetSamplingParameters(GraphBuilder); PassParameters->SceneTexturesStruct = SceneTextures.UniformBuffer; PassParameters->View = View.ViewUniformBuffer; PassParameters->ProjectionRect = FIntVector4(ProjectionRect.Min.X, ProjectionRect.Min.Y, ProjectionRect.Max.X, ProjectionRect.Max.Y); PassParameters->ScreenRayLength = CVarScreenRayLength.GetValueOnRenderThread(); PassParameters->NormalBias = GetNormalBiasForShader(); PassParameters->InputType = uint32(InputType); PassParameters->bCullBackfacingPixels = VirtualShadowMapArray.ShouldCullBackfacingPixels() ? 1 : 0; PassParameters->bSMRTUseAdaptiveRayCount = CVarSMRTAdaptiveRayCount.GetValueOnRenderThread() != 0 ? 1 : 0; PassParameters->Strata = Strata::BindStrataGlobalUniformParameters(View); if (bHasHairStrandsData) { PassParameters->HairStrands = HairStrands::BindHairStrandsViewUniformParameters(View); PassParameters->HairStrandsVoxel = HairStrands::BindHairStrandsVoxelUniformParameters(View); } bool bDirectionalLight = false; bool bOnePassProjection = LightProxy == nullptr; if (bOnePassProjection) { // One pass projection PassParameters->ForwardLightData = View.ForwardLightingResources.ForwardLightUniformBuffer; PassParameters->OutShadowMaskBits = GraphBuilder.CreateUAV( OutputTexture ); } else { // Pass per light bDirectionalLight = LightProxy->GetLightType() == LightType_Directional; FLightRenderParameters LightParameters; LightProxy->GetLightShaderParameters(LightParameters); LightParameters.MakeShaderParameters(View.ViewMatrices, PassParameters->Light); PassParameters->LightUniformVirtualShadowMapId = VirtualShadowMapId; PassParameters->OutShadowFactor = GraphBuilder.CreateUAV( OutputTexture ); } if (bDirectionalLight) { PassParameters->SMRTRayCount = CVarSMRTRayCountDirectional.GetValueOnRenderThread(); PassParameters->SMRTSamplesPerRay = CVarSMRTSamplesPerRayDirectional.GetValueOnRenderThread(); PassParameters->SMRTRayLengthScale = CVarSMRTRayLengthScaleDirectional.GetValueOnRenderThread(); PassParameters->SMRTCotMaxRayAngleFromLight = 0.0f; // unused in this path PassParameters->SMRTTexelDitherScale = CVarSMRTTexelDitherScaleDirectional.GetValueOnRenderThread(); PassParameters->SMRTWorldDitherScale = CVarSMRTWorldDitherScaleDirectional.GetValueOnRenderThread(); } else { PassParameters->SMRTRayCount = CVarSMRTRayCountLocal.GetValueOnRenderThread(); PassParameters->SMRTSamplesPerRay = CVarSMRTSamplesPerRayLocal.GetValueOnRenderThread(); PassParameters->SMRTRayLengthScale = 0.0f; // unused in this path PassParameters->SMRTCotMaxRayAngleFromLight = 1.0f / FMath::Tan(CVarSMRTMaxRayAngleFromLight.GetValueOnRenderThread()); PassParameters->SMRTTexelDitherScale = 0.0f; // not yet implemented PassParameters->SMRTWorldDitherScale = 0.0f; // not yet implemented } bool bDebugOutput = false; #if !UE_BUILD_SHIPPING if ( !VirtualShadowMapArray.DebugVisualizationOutput.IsEmpty() && InputType == EVirtualShadowMapProjectionInputType::GBuffer && VirtualShadowMapArray.VisualizeLight.IsValid() ) { const FVirtualShadowMapVisualizationData& VisualizationData = GetVirtualShadowMapVisualizationData(); bDebugOutput = true; PassParameters->VisualizeModeId = VisualizationData.GetActiveModeID(); PassParameters->VisualizeVirtualShadowMapId = VirtualShadowMapArray.VisualizeLight.GetVirtualShadowMapId(); PassParameters->PhysicalPageMetaData = GraphBuilder.CreateSRV( VirtualShadowMapArray.PhysicalPageMetaDataRDG ); PassParameters->OutVisualize = GraphBuilder.CreateUAV( VirtualShadowMapArray.DebugVisualizationOutput[ViewIndex] ); } #endif // If the requested samples per ray matches one of our static permutations, pick that one // Otherwise use the dynamic samples per ray permutation (-1). int StaticSamplesPerRay = PassParameters->SMRTSamplesPerRay == 0 ? PassParameters->SMRTSamplesPerRay : -1; FVirtualShadowMapProjectionCS::FPermutationDomain PermutationVector; PermutationVector.Set< FVirtualShadowMapProjectionCS::FDirectionalLightDim >( bDirectionalLight ); PermutationVector.Set< FVirtualShadowMapProjectionCS::FOnePassProjectionDim >( bOnePassProjection ); PermutationVector.Set< FVirtualShadowMapProjectionCS::FSMRTExtrapolateSlopeDim >( CVarSMRTExtrapolateWithSlope.GetValueOnRenderThread() != 0 ); PermutationVector.Set< FVirtualShadowMapProjectionCS::FHairStrandsDim >( bHasHairStrandsData ); PermutationVector.Set< FVirtualShadowMapProjectionCS::FVisualizeOutputDim >( bDebugOutput ); PermutationVector.Set< FVirtualShadowMapProjectionCS::FSMRTStaticSampleCount >( StaticSamplesPerRay ); auto ComputeShader = View.ShaderMap->GetShader< FVirtualShadowMapProjectionCS >( PermutationVector ); ClearUnusedGraphResources( ComputeShader, PassParameters ); ValidateShaderParameters( ComputeShader, *PassParameters ); const FIntPoint GroupCount = FIntPoint::DivideAndRoundUp( ProjectionRect.Size(), 8 ); FComputeShaderUtils::AddPass( GraphBuilder, RDG_EVENT_NAME("VirtualShadowMapProjection(RayCount:%u(%s),SamplesPerRay:%u,Input:%s%s)", PassParameters->SMRTRayCount, PassParameters->bSMRTUseAdaptiveRayCount ? TEXT("Adaptive") : TEXT("Static"), PassParameters->SMRTSamplesPerRay, ToString(InputType), bDebugOutput ? TEXT(",Debug") : TEXT("")), ComputeShader, PassParameters, FIntVector( GroupCount.X, GroupCount.Y, 1 ) ); } FRDGTextureRef RenderVirtualShadowMapProjectionOnePass( FRDGBuilder& GraphBuilder, const FMinimalSceneTextures& SceneTextures, const FViewInfo& View, int32 ViewIndex, FVirtualShadowMapArray& VirtualShadowMapArray, EVirtualShadowMapProjectionInputType InputType) { FIntRect ProjectionRect = View.ViewRect; const FRDGTextureDesc ShadowMaskDesc = FRDGTextureDesc::Create2D( SceneTextures.Config.Extent, VirtualShadowMapArray.GetPackedShadowMaskFormat(), FClearValueBinding::None, TexCreate_ShaderResource | TexCreate_UAV); FRDGTextureRef ShadowMaskBits = GraphBuilder.CreateTexture(ShadowMaskDesc, InputType == EVirtualShadowMapProjectionInputType::HairStrands ? TEXT("ShadowMaskBits(HairStrands)") : TEXT("ShadowMaskBits(Gbuffer)")); RenderVirtualShadowMapProjectionCommon( GraphBuilder, SceneTextures, View, ViewIndex, VirtualShadowMapArray, ProjectionRect, InputType, ShadowMaskBits); return ShadowMaskBits; } static FRDGTextureRef CreateShadowMaskTexture(FRDGBuilder& GraphBuilder, FIntPoint Extent) { const FLinearColor ClearColor(0.0f, 0.0f, 0.0f, 0.0f); FRDGTextureDesc Desc = FRDGTextureDesc::Create2D( Extent, PF_G16R16, FClearValueBinding(ClearColor), TexCreate_ShaderResource | TexCreate_UAV); FRDGTextureRef Texture = GraphBuilder.CreateTexture(Desc, TEXT("Shadow.Virtual.ShadowMask")); // NOTE: Projection pass writes all relevant pixels, so should not need to clear here if (CVarForcePerLightShadowMaskClear.GetValueOnRenderThread() != 0) { AddClearUAVPass(GraphBuilder, GraphBuilder.CreateUAV(Texture), ClearColor); } return Texture; } void RenderVirtualShadowMapProjection( FRDGBuilder& GraphBuilder, const FMinimalSceneTextures& SceneTextures, const FViewInfo& View, int32 ViewIndex, FVirtualShadowMapArray& VirtualShadowMapArray, const FIntRect ScissorRect, EVirtualShadowMapProjectionInputType InputType, FProjectedShadowInfo* ShadowInfo, FRDGTextureRef OutputShadowMaskTexture) { FRDGTextureRef VirtualShadowMaskTexture = CreateShadowMaskTexture(GraphBuilder, View.ViewRect.Max); RenderVirtualShadowMapProjectionCommon( GraphBuilder, SceneTextures, View, ViewIndex, VirtualShadowMapArray, ScissorRect, InputType, VirtualShadowMaskTexture, ShadowInfo->GetLightSceneInfo().Proxy, ShadowInfo->VirtualShadowMaps[0]->ID); CompositeVirtualShadowMapMask( GraphBuilder, ScissorRect, VirtualShadowMaskTexture, false, // bDirectionalLight OutputShadowMaskTexture); } void RenderVirtualShadowMapProjection( FRDGBuilder& GraphBuilder, const FMinimalSceneTextures& SceneTextures, const FViewInfo& View, int32 ViewIndex, FVirtualShadowMapArray& VirtualShadowMapArray, const FIntRect ScissorRect, EVirtualShadowMapProjectionInputType InputType, const TSharedPtr& Clipmap, FRDGTextureRef OutputShadowMaskTexture) { FRDGTextureRef VirtualShadowMaskTexture = CreateShadowMaskTexture(GraphBuilder, View.ViewRect.Max); RenderVirtualShadowMapProjectionCommon( GraphBuilder, SceneTextures, View, ViewIndex, VirtualShadowMapArray, ScissorRect, InputType, VirtualShadowMaskTexture, Clipmap->GetLightSceneInfo().Proxy, Clipmap->GetVirtualShadowMap()->ID); CompositeVirtualShadowMapMask( GraphBuilder, ScissorRect, VirtualShadowMaskTexture, true, // bDirectionalLight OutputShadowMaskTexture); }