// Copyright Epic Games, Inc. All Rights Reserved. /*============================================================================= VirtualShadowMapProjection.cpp =============================================================================*/ #include "VirtualShadowMapProjection.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" static TAutoConsoleVariable CVarContactShadowLength( TEXT( "r.Shadow.v.ContactShadowLength" ), 0.02f, TEXT( "Length of the screen space contact shadow trace (smart shadow bias) before the virtual shadow map lookup." ), ECVF_RenderThreadSafe ); static TAutoConsoleVariable CVarNormalOffsetWorld( TEXT( "r.Shadow.v.NormalOffsetWorld" ), 0.1f, TEXT( "World space offset along surface normal for shadow lookup." ) 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_RenderThreadSafe ); static TAutoConsoleVariable CVarVirtualShadowMapDebugProjection( TEXT( "r.Shadow.v.DebugVisualizeProjection" ), 0, TEXT( "Projection pass debug output visualization for use with 'vis VirtSmDebugProj'." ), ECVF_RenderThreadSafe ); TAutoConsoleVariable CVarVirtualShadowOnePassProjection( TEXT("r.Shadow.v.OnePassProjection"), 0, TEXT("Single pass projects all local VSMs culled with the light grid. Used in conjunction with clustered deferred shading."), ECVF_RenderThreadSafe ); static TAutoConsoleVariable CVarSMRTRayCountDirectional( TEXT( "r.Shadow.v.SMRT.RayCountDirectional" ), 0, TEXT( "Ray count for shadow map tracing of directional lights. 0 = disabled." ), ECVF_RenderThreadSafe ); static TAutoConsoleVariable CVarSMRTRayCountSpot( TEXT( "r.Shadow.v.SMRT.RayCountSpot" ), 0, TEXT( "Ray count for shadow map tracing of spot lights. 0 = disabled." ), ECVF_RenderThreadSafe ); static TAutoConsoleVariable CVarSMRTSamplesPerRaySpot( TEXT( "r.Shadow.v.SMRT.SamplesPerRaySpot" ), 16, TEXT( "Shadow map samples per ray for spot lights" ), ECVF_RenderThreadSafe ); static TAutoConsoleVariable CVarSMRTSamplesPerRayDirectional( TEXT( "r.Shadow.v.SMRT.SamplesPerRayDirectional" ), 12, TEXT( "Shadow map samples per ray for directional lights" ), ECVF_RenderThreadSafe ); static TAutoConsoleVariable CVarSMRTRayLengthScaleDirectional( TEXT( "r.Shadow.v.SMRT.RayLengthScaleDirectional" ), 1.0f, 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 ); BEGIN_SHADER_PARAMETER_STRUCT(FProjectionParameters, ) SHADER_PARAMETER_STRUCT_INCLUDE(FVirtualShadowMapSamplingParameters, ProjectionParameters) SHADER_PARAMETER_STRUCT(FLightShaderParameters, Light) SHADER_PARAMETER_RDG_UNIFORM_BUFFER(FSceneTextureUniformParameters, SceneTexturesStruct) SHADER_PARAMETER_STRUCT_REF(FViewUniformShaderParameters, View) SHADER_PARAMETER(int32, VirtualShadowMapId) SHADER_PARAMETER(float, ContactShadowLength) SHADER_PARAMETER(float, NormalOffsetWorld) SHADER_PARAMETER(int32, DebugOutputType) SHADER_PARAMETER(int32, SMRTRayCount) SHADER_PARAMETER(int32, SMRTSamplesPerRay) SHADER_PARAMETER(float, SMRTRayLengthScale) RENDER_TARGET_BINDING_SLOTS() END_SHADER_PARAMETER_STRUCT() class FVirtualShadowMapProjectionSpotPS : public FGlobalShader { DECLARE_GLOBAL_SHADER(FVirtualShadowMapProjectionSpotPS); SHADER_USE_PARAMETER_STRUCT(FVirtualShadowMapProjectionSpotPS, FGlobalShader); class FOutputTypeDim : SHADER_PERMUTATION_ENUM_CLASS("OUTPUT_TYPE", EVirtualShadowMapProjectionOutputType); using FPermutationDomain = TShaderPermutationDomain; using FParameters = FProjectionParameters; static bool ShouldCompilePermutation(const FGlobalShaderPermutationParameters& Parameters) { return DoesPlatformSupportNanite(Parameters.Platform); } static void ModifyCompilationEnvironment(const FGlobalShaderPermutationParameters& Parameters, FShaderCompilerEnvironment& OutEnvironment) { FVirtualShadowMapArray::SetShaderDefines(OutEnvironment); } }; IMPLEMENT_GLOBAL_SHADER(FVirtualShadowMapProjectionSpotPS, "/Engine/Private/VirtualShadowMaps/VirtualShadowMapProjection.usf", "VirtualShadowMapProjectionSpotPS", SF_Pixel); class FVirtualShadowMapProjectionDirectionalPS : public FGlobalShader { DECLARE_GLOBAL_SHADER(FVirtualShadowMapProjectionDirectionalPS); SHADER_USE_PARAMETER_STRUCT(FVirtualShadowMapProjectionDirectionalPS, FGlobalShader); class FOutputTypeDim : SHADER_PERMUTATION_ENUM_CLASS("OUTPUT_TYPE", EVirtualShadowMapProjectionOutputType); using FPermutationDomain = TShaderPermutationDomain; using FParameters = FProjectionParameters; static bool ShouldCompilePermutation(const FGlobalShaderPermutationParameters& Parameters) { return DoesPlatformSupportNanite(Parameters.Platform); } static void ModifyCompilationEnvironment(const FGlobalShaderPermutationParameters& Parameters, FShaderCompilerEnvironment& OutEnvironment) { FVirtualShadowMapArray::SetShaderDefines(OutEnvironment); } }; IMPLEMENT_GLOBAL_SHADER(FVirtualShadowMapProjectionDirectionalPS, "/Engine/Private/VirtualShadowMaps/VirtualShadowMapProjection.usf", "VirtualShadowMapProjectionDirectionalPS", SF_Pixel); BEGIN_SHADER_PARAMETER_STRUCT(FVirtualShadowMapProjectionCompositeParameters, ) SHADER_PARAMETER_RDG_TEXTURE(Texture2D, InputSignal) RENDER_TARGET_BINDING_SLOTS() END_SHADER_PARAMETER_STRUCT() // 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); using FParameters = FVirtualShadowMapProjectionCompositeParameters; 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); // Returns true if temporal denoising should be used static bool AddPass_RenderVirtualShadowMapProjection( const FLightSceneProxy* LightProxy, FRDGBuilder& GraphBuilder, const FViewInfo& View, FVirtualShadowMapArray& VirtualShadowMapArray, int VirtualShadowMapId, const FIntRect ScissorRect, EVirtualShadowMapProjectionOutputType OutputType, const FRenderTargetBinding& RenderTargetBinding, FRHIBlendState* BlendState) { check(ScissorRect.Area() > 0); FGlobalShaderMap* ShaderMap = View.ShaderMap; FProjectionParameters* PassParameters = GraphBuilder.AllocParameters(); VirtualShadowMapArray.SetProjectionParameters(GraphBuilder, PassParameters->ProjectionParameters); PassParameters->SceneTexturesStruct = CreateSceneTextureUniformBuffer(GraphBuilder, View.FeatureLevel, ESceneTextureSetupMode::GBuffers | ESceneTextureSetupMode::SceneDepth); PassParameters->View = View.ViewUniformBuffer; FLightShaderParameters LightParameters; LightProxy->GetLightShaderParameters(LightParameters); PassParameters->Light = LightParameters; PassParameters->VirtualShadowMapId = VirtualShadowMapId; PassParameters->DebugOutputType = CVarVirtualShadowMapDebugProjection.GetValueOnRenderThread(); PassParameters->ContactShadowLength = CVarContactShadowLength.GetValueOnRenderThread(); PassParameters->NormalOffsetWorld = CVarNormalOffsetWorld.GetValueOnRenderThread(); PassParameters->RenderTargets[0] = RenderTargetBinding; if (LightProxy->GetLightType() == LightType_Directional) { PassParameters->SMRTRayCount = CVarSMRTRayCountDirectional.GetValueOnRenderThread(); PassParameters->SMRTSamplesPerRay = CVarSMRTSamplesPerRayDirectional.GetValueOnRenderThread(); PassParameters->SMRTRayLengthScale = CVarSMRTRayLengthScaleDirectional.GetValueOnRenderThread(); FVirtualShadowMapProjectionDirectionalPS::FPermutationDomain PermutationVector; PermutationVector.Set(OutputType); auto PixelShader = ShaderMap->GetShader(PermutationVector); ValidateShaderParameters(PixelShader, *PassParameters); // NOTE: We use SV_Position in the shader, so we don't need separate scissor/view rect FPixelShaderUtils::AddFullscreenPass(GraphBuilder, ShaderMap, (OutputType == EVirtualShadowMapProjectionOutputType::Debug) ? RDG_EVENT_NAME("Debug Directional Projection") : RDG_EVENT_NAME("Directional Projection"), PixelShader, PassParameters, ScissorRect, BlendState); // Use temporal denoising with SMRT return PassParameters->SMRTRayCount > 0; } else { PassParameters->SMRTRayCount = CVarSMRTRayCountSpot.GetValueOnRenderThread(); PassParameters->SMRTSamplesPerRay = CVarSMRTSamplesPerRaySpot.GetValueOnRenderThread(); PassParameters->SMRTRayLengthScale = 0.0f; // Currently unused in this path FVirtualShadowMapProjectionSpotPS::FPermutationDomain PermutationVector; PermutationVector.Set(OutputType); auto PixelShader = ShaderMap->GetShader(PermutationVector); ValidateShaderParameters(PixelShader, *PassParameters); // NOTE: We use SV_Position in the shader, so we don't need separate scissor/view rect FPixelShaderUtils::AddFullscreenPass(GraphBuilder, ShaderMap, (OutputType == EVirtualShadowMapProjectionOutputType::Debug) ? RDG_EVENT_NAME("Debug Spot Projection") : RDG_EVENT_NAME("Spot Projection"), PixelShader, PassParameters, ScissorRect, BlendState); // Use temporal denoising with SMRT return PassParameters->SMRTRayCount > 0; } return false; } static FRDGTextureRef CreateDebugOutput(FRDGBuilder& GraphBuilder, FIntPoint Extent) { FRDGTextureDesc DebugOutputDesc = FRDGTextureDesc::Create2D( Extent, PF_A32B32G32R32F, FClearValueBinding::Transparent, TexCreate_ShaderResource | TexCreate_RenderTargetable); return GraphBuilder.CreateTexture(DebugOutputDesc, TEXT("VirtSmDebugProj")); } // Returns true if temporal denoising should be used static bool RenderVirtualShadowMapProjectionCommon( const FLightSceneProxy* LightProxy, FRDGBuilder& GraphBuilder, const FViewInfo& View, FVirtualShadowMapArray& VirtualShadowMapArray, int32 VirtualShadowMapId, const FIntRect ScissorRect, EVirtualShadowMapProjectionOutputType OutputType, const FRenderTargetBinding& RenderTargetBinding, FRHIBlendState* BlendState) { // Main Pass bool bOutUseTemporalDenoising = AddPass_RenderVirtualShadowMapProjection( LightProxy, GraphBuilder, View, VirtualShadowMapArray, VirtualShadowMapId, ScissorRect, OutputType, RenderTargetBinding, BlendState); // Debug visualization const int32 DebugVisualize = CVarVirtualShadowMapDebugProjection.GetValueOnRenderThread(); if (DebugVisualize > 0) { FRDGTextureRef DebugOutput = CreateDebugOutput(GraphBuilder, View.ViewRect.Max); FRenderTargetBinding DebugRenderTargetBinding(DebugOutput, ERenderTargetLoadAction::EClear); AddPass_RenderVirtualShadowMapProjection( LightProxy, GraphBuilder, View, VirtualShadowMapArray, VirtualShadowMapId, ScissorRect, EVirtualShadowMapProjectionOutputType::Debug, DebugRenderTargetBinding, nullptr); GraphBuilder.QueueTextureExtraction(DebugOutput, &VirtualShadowMapArray.DebugVisualizationProjectionOutput); } return bOutUseTemporalDenoising; } void RenderVirtualShadowMapProjectionForDenoising( FProjectedShadowInfo* ShadowInfo, FRDGBuilder& GraphBuilder, const FViewInfo& View, FVirtualShadowMapArray& VirtualShadowMapArray, const FIntRect ScissorRect, FRDGTextureRef SignalTexture, bool& bOutUseTemporalDenoising) { FRenderTargetBinding RenderTargetBinding(SignalTexture, ERenderTargetLoadAction::EClear); bOutUseTemporalDenoising = RenderVirtualShadowMapProjectionCommon( ShadowInfo->GetLightSceneInfo().Proxy, GraphBuilder, View, VirtualShadowMapArray, ShadowInfo->VirtualShadowMaps[0]->ID, ScissorRect, EVirtualShadowMapProjectionOutputType::Denoiser, RenderTargetBinding, nullptr); } void RenderVirtualShadowMapProjectionForDenoising( const TSharedPtr& Clipmap, FRDGBuilder& GraphBuilder, const FViewInfo& View, FVirtualShadowMapArray& VirtualShadowMapArray, const FIntRect ScissorRect, FRDGTextureRef SignalTexture, bool& bOutUseTemporalDenoising) { FRenderTargetBinding RenderTargetBinding(SignalTexture, ERenderTargetLoadAction::EClear); bOutUseTemporalDenoising = RenderVirtualShadowMapProjectionCommon( Clipmap->GetLightSceneInfo().Proxy, GraphBuilder, View, VirtualShadowMapArray, Clipmap->GetVirtualShadowMap()->ID, ScissorRect, EVirtualShadowMapProjectionOutputType::Denoiser, RenderTargetBinding, nullptr); } void RenderVirtualShadowMapProjection( FProjectedShadowInfo* ShadowInfo, FRDGBuilder& GraphBuilder, const FViewInfo& View, FVirtualShadowMapArray& VirtualShadowMapArray, const FIntRect ScissorRect, FRDGTextureRef ScreenShadowMaskTexture, bool bProjectingForForwardShading) { FRenderTargetBinding RenderTargetBinding(ScreenShadowMaskTexture, ERenderTargetLoadAction::ELoad); FRHIBlendState* BlendState = ShadowInfo->GetBlendStateForProjection(bProjectingForForwardShading, false); RenderVirtualShadowMapProjectionCommon( ShadowInfo->GetLightSceneInfo().Proxy, GraphBuilder, View, VirtualShadowMapArray, ShadowInfo->VirtualShadowMaps[0]->ID, ScissorRect, EVirtualShadowMapProjectionOutputType::ScreenShadowMask, RenderTargetBinding, BlendState); } void RenderVirtualShadowMapProjection( const TSharedPtr& Clipmap, FRDGBuilder& GraphBuilder, const FViewInfo& View, FVirtualShadowMapArray& VirtualShadowMapArray, const FIntRect ScissorRect, FRDGTextureRef ScreenShadowMaskTexture, bool bProjectingForForwardShading) { FRenderTargetBinding RenderTargetBinding(ScreenShadowMaskTexture, ERenderTargetLoadAction::ELoad); // See FProjectedShadowInfo::GetBlendStateForProjection. TODO: Support other modes in this path? FRHIBlendState* BlendState = TStaticBlendState::GetRHI(); RenderVirtualShadowMapProjectionCommon( Clipmap->GetLightSceneInfo().Proxy, GraphBuilder, View, VirtualShadowMapArray, Clipmap->GetVirtualShadowMap()->ID, ScissorRect, EVirtualShadowMapProjectionOutputType::ScreenShadowMask, RenderTargetBinding, BlendState); } void CompositeVirtualShadowMapMask( FRDGBuilder& GraphBuilder, const FIntRect ScissorRect, const FSSDSignalTextures& InputSignal, FRDGTextureRef OutputShadowMaskTexture) { auto ShaderMap = GetGlobalShaderMap(GMaxRHIFeatureLevel); FVirtualShadowMapProjectionCompositeParameters* PassParameters = GraphBuilder.AllocParameters(); PassParameters->InputSignal = InputSignal.Textures[0]; PassParameters->RenderTargets[0] = FRenderTargetBinding(OutputShadowMaskTexture, ERenderTargetLoadAction::ELoad); // See FProjectedShadowInfo::GetBlendStateForProjection, but we don't want any of the special cascade behavior, etc. as this is a post-denoised mask. FRHIBlendState* BlendState = TStaticBlendState::GetRHI(); auto PixelShader = ShaderMap->GetShader(); ValidateShaderParameters(PixelShader, *PassParameters); FPixelShaderUtils::AddFullscreenPass(GraphBuilder, ShaderMap, RDG_EVENT_NAME("Mask Composite"), PixelShader, PassParameters, ScissorRect, BlendState); } class FVirtualShadowMapProjectionCS : public FGlobalShader { DECLARE_GLOBAL_SHADER(FVirtualShadowMapProjectionCS); SHADER_USE_PARAMETER_STRUCT(FVirtualShadowMapProjectionCS, FGlobalShader) BEGIN_SHADER_PARAMETER_STRUCT(FParameters, ) SHADER_PARAMETER_STRUCT_INCLUDE(FVirtualShadowMapSamplingParameters, ProjectionParameters) SHADER_PARAMETER_RDG_UNIFORM_BUFFER(FSceneTextureUniformParameters, SceneTexturesStruct) SHADER_PARAMETER_STRUCT_REF(FViewUniformShaderParameters, View) SHADER_PARAMETER_STRUCT_REF(FForwardLightData, ForwardLightData) SHADER_PARAMETER_RDG_BUFFER_SRV(StructuredBuffer< FVirtualShadowMapProjectionShaderData >, ShadowMapProjectionData) SHADER_PARAMETER_RDG_BUFFER_SRV(StructuredBuffer< uint >, VirtualShadowMapIdRemap) SHADER_PARAMETER_RDG_TEXTURE_UAV(RWTexture2D< uint >, RWShadowMaskBits) SHADER_PARAMETER(uint32, NumDirectionalLightSmInds) SHADER_PARAMETER(float, ContactShadowLength) SHADER_PARAMETER(float, NormalOffsetWorld) SHADER_PARAMETER(int32, DebugOutputType) SHADER_PARAMETER(int32, SMRTRayCount) SHADER_PARAMETER(int32, SMRTSamplesPerRay) SHADER_PARAMETER(float, SMRTRayLengthScale) END_SHADER_PARAMETER_STRUCT() static void ModifyCompilationEnvironment( const FGlobalShaderPermutationParameters& Parameters, FShaderCompilerEnvironment& OutEnvironment ) { FGlobalShader::ModifyCompilationEnvironment(Parameters, OutEnvironment); FVirtualShadowMapArray::SetShaderDefines(OutEnvironment); FForwardLightingParameters::ModifyCompilationEnvironment(Parameters.Platform, OutEnvironment); OutEnvironment.CompilerFlags.Add(CFLAG_WaveOperations); } }; IMPLEMENT_GLOBAL_SHADER(FVirtualShadowMapProjectionCS, "/Engine/Private/VirtualShadowMaps/VirtualShadowMapProjection.usf", "VirtualShadowMapProjection", SF_Compute); void RenderVirtualShadowMapProjection( FRDGBuilder& GraphBuilder, const FMinimalSceneTextures& SceneTextures, const FViewInfo& View, FVirtualShadowMapArray& VirtualShadowMapArray, FRDGTextureRef ShadowMaskBits ) { FVirtualShadowMapProjectionCS::FParameters* PassParameters = GraphBuilder.AllocParameters< FVirtualShadowMapProjectionCS::FParameters >(); VirtualShadowMapArray.SetProjectionParameters( GraphBuilder, PassParameters->ProjectionParameters ); PassParameters->SceneTexturesStruct = SceneTextures.UniformBuffer; PassParameters->View = View.ViewUniformBuffer; PassParameters->ForwardLightData = View.ForwardLightingResources->ForwardLightDataUniformBuffer; PassParameters->VirtualShadowMapIdRemap = GraphBuilder.CreateSRV( VirtualShadowMapArray.VirtualShadowMapIdRemapRDG[0] ); // FIXME Index proper view PassParameters->NumDirectionalLightSmInds = VirtualShadowMapArray.NumDirectionalLights; PassParameters->DebugOutputType = CVarVirtualShadowMapDebugProjection.GetValueOnRenderThread(); PassParameters->ContactShadowLength = CVarContactShadowLength.GetValueOnRenderThread(); PassParameters->NormalOffsetWorld = CVarNormalOffsetWorld.GetValueOnRenderThread(); PassParameters->SMRTRayCount = CVarSMRTRayCountSpot.GetValueOnRenderThread(); PassParameters->SMRTSamplesPerRay = CVarSMRTSamplesPerRaySpot.GetValueOnRenderThread(); PassParameters->SMRTRayLengthScale = 0.0f; // Currently unused in this path PassParameters->RWShadowMaskBits = GraphBuilder.CreateUAV( ShadowMaskBits ); auto ComputeShader = View.ShaderMap->GetShader< FVirtualShadowMapProjectionCS >(); const FIntPoint GroupCount = FIntPoint::DivideAndRoundUp( View.ViewRect.Size(), 8 ); FComputeShaderUtils::AddPass( GraphBuilder, RDG_EVENT_NAME("VirtualShadowMapProjection"), ComputeShader, PassParameters, FIntVector( GroupCount.X, GroupCount.Y, 1 ) ); }