// 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" #include "HairStrands/HairStrandsData.h" static TAutoConsoleVariable CVarContactShadowLength( TEXT( "r.Shadow.Virtual.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 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_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_RenderThreadSafe ); static TAutoConsoleVariable CVarSMRTRayCountLocal( TEXT( "r.Shadow.Virtual.SMRT.RayCountLocal" ), 7, TEXT( "Ray count for shadow map tracing of local lights. 0 = disabled." ), ECVF_RenderThreadSafe ); static TAutoConsoleVariable CVarSMRTSamplesPerRayLocal( TEXT( "r.Shadow.Virtual.SMRT.SamplesPerRayLocal" ), 8, TEXT( "Shadow map samples per ray for local lights" ), 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_RenderThreadSafe ); static TAutoConsoleVariable CVarSMRTSamplesPerRayDirectional( TEXT( "r.Shadow.Virtual.SMRT.SamplesPerRayDirectional" ), 8, TEXT( "Shadow map samples per ray for directional lights" ), 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_RenderThreadSafe ); // 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, FRDGTextureRef OutputShadowMaskTexture) { auto ShaderMap = GetGlobalShaderMap(GMaxRHIFeatureLevel); FVirtualShadowMapProjectionCompositePS::FParameters* PassParameters = GraphBuilder.AllocParameters(); PassParameters->InputShadowFactor = Input; 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("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 FSMRTAdaptiveRayCountDim : SHADER_PERMUTATION_BOOL("SMRT_ADAPTIVE_RAY_COUNT"); class FTwoPhysicalTexturesDim : SHADER_PERMUTATION_BOOL("TWO_PHYSICAL_TEXTURES"); class FOnePassProjectionDim : SHADER_PERMUTATION_BOOL("ONE_PASS_PROJECTION"); class FHairStrandsDim : SHADER_PERMUTATION_BOOL("HAS_HAIR_STRANDS"); class FDebugOutputDim : SHADER_PERMUTATION_BOOL("DEBUG_OUTPUT"); using FPermutationDomain = TShaderPermutationDomain< FDirectionalLightDim, FOnePassProjectionDim, FSMRTAdaptiveRayCountDim, FTwoPhysicalTexturesDim, FHairStrandsDim, FDebugOutputDim>; 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_STRUCT_REF(FViewUniformShaderParameters, View) SHADER_PARAMETER(FIntVector4, ProjectionRect) SHADER_PARAMETER(float, ContactShadowLength) SHADER_PARAMETER(float, NormalBias) SHADER_PARAMETER(uint32, SMRTRayCount) SHADER_PARAMETER(uint32, SMRTSamplesPerRay) SHADER_PARAMETER(float, SMRTRayLengthScale) SHADER_PARAMETER(float, SMRTCotMaxRayAngleFromLight) SHADER_PARAMETER(uint32, InputType) // One pass projection parameters SHADER_PARAMETER_STRUCT_REF(FForwardLightData, ForwardLightData) SHADER_PARAMETER_RDG_BUFFER_SRV(StructuredBuffer< uint >, VirtualShadowMapIdRemap) // TODO: Move to VSM UB? Per-view though SHADER_PARAMETER_RDG_TEXTURE_UAV(RWTexture2D< uint >, RWShadowMaskBits) // Pass per light parameters SHADER_PARAMETER_STRUCT(FLightShaderParameters, Light) SHADER_PARAMETER(int32, LightUniformVirtualShadowMapId) SHADER_PARAMETER_RDG_TEXTURE_UAV(RWTexture2D, RWShadowFactor) // Debug output SHADER_PARAMETER_RDG_BUFFER_SRV(StructuredBuffer< FPhysicalPageMetaData >, PhysicalPageMetaData) SHADER_PARAMETER(int32, DebugOutputType) SHADER_PARAMETER(int32, DebugVirtualShadowMapId) SHADER_PARAMETER_RDG_TEXTURE_UAV(RWTexture2D, RWDebug) 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); if (PermutationVector.Get()) { OutEnvironment.CompilerFlags.Add(CFLAG_WaveOperations); } OutEnvironment.CompilerFlags.Add( CFLAG_Wave32 ); 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 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; } extern int32 GVirtualShadowMapAtomicWrites; static void RenderVirtualShadowMapProjectionCommon( FRDGBuilder& GraphBuilder, const FMinimalSceneTextures& SceneTextures, const FViewInfo& View, FVirtualShadowMapArray& VirtualShadowMapArray, const FIntRect ProjectionRect, EVirtualShadowMapProjectionInputType InputType, FRDGTextureRef OutputTexture, const FLightSceneProxy* LightProxy = nullptr, int32 VirtualShadowMapId = INDEX_NONE) { // 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); const bool bAdaptiveRayCount = GRHISupportsWaveOperations && CVarSMRTAdaptiveRayCount.GetValueOnRenderThread() != 0; 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->ContactShadowLength = CVarContactShadowLength.GetValueOnRenderThread(); PassParameters->NormalBias = GetNormalBiasForShader(); PassParameters->InputType = uint32(InputType); 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->ForwardLightDataUniformBuffer; PassParameters->VirtualShadowMapIdRemap = GraphBuilder.CreateSRV( VirtualShadowMapArray.VirtualShadowMapIdRemapRDG[0] ); // FIXME Index proper view PassParameters->RWShadowMaskBits = GraphBuilder.CreateUAV( OutputTexture ); } else { // Pass per light bDirectionalLight = LightProxy->GetLightType() == LightType_Directional; FLightShaderParameters LightParameters; LightProxy->GetLightShaderParameters(LightParameters); PassParameters->Light = LightParameters; PassParameters->LightUniformVirtualShadowMapId = VirtualShadowMapId; PassParameters->RWShadowFactor = 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 } 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()); } bool bDebugOutput = false; #if !UE_BUILD_SHIPPING if ( VirtualShadowMapArray.DebugVisualizationProjectionOutput && InputType == EVirtualShadowMapProjectionInputType::GBuffer ) { bDebugOutput = true; PassParameters->DebugOutputType = VirtualShadowMapArray.DebugOutputType; PassParameters->DebugVirtualShadowMapId = VirtualShadowMapArray.DebugVirtualShadowMapId; PassParameters->PhysicalPageMetaData = GraphBuilder.CreateSRV( VirtualShadowMapArray.PhysicalPageMetaDataRDG ); PassParameters->RWDebug = GraphBuilder.CreateUAV( VirtualShadowMapArray.DebugVisualizationProjectionOutput ); } #endif FVirtualShadowMapProjectionCS::FPermutationDomain PermutationVector; PermutationVector.Set< FVirtualShadowMapProjectionCS::FDirectionalLightDim >( bDirectionalLight ); PermutationVector.Set< FVirtualShadowMapProjectionCS::FOnePassProjectionDim >( bOnePassProjection ); PermutationVector.Set< FVirtualShadowMapProjectionCS::FSMRTAdaptiveRayCountDim >( bAdaptiveRayCount ); PermutationVector.Set< FVirtualShadowMapProjectionCS::FTwoPhysicalTexturesDim >( GVirtualShadowMapAtomicWrites == 0 ); PermutationVector.Set< FVirtualShadowMapProjectionCS::FHairStrandsDim >( bHasHairStrandsData ? 1 : 0 ); PermutationVector.Set< FVirtualShadowMapProjectionCS::FDebugOutputDim >( bDebugOutput ); 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:%s,Input:%s%s)", bAdaptiveRayCount ? TEXT("Adaptive") : TEXT("Static"), (InputType == EVirtualShadowMapProjectionInputType::HairStrands) ? TEXT("HairStrands") : TEXT("GBuffer"), bDebugOutput ? TEXT(",Debug") : TEXT("")), ComputeShader, PassParameters, FIntVector( GroupCount.X, GroupCount.Y, 1 ) ); } FRDGTextureRef RenderVirtualShadowMapProjectionOnePass( FRDGBuilder& GraphBuilder, const FMinimalSceneTextures& SceneTextures, const FViewInfo& View, FVirtualShadowMapArray& VirtualShadowMapArray, EVirtualShadowMapProjectionInputType InputType) { FIntRect ProjectionRect = View.ViewRect; const FRDGTextureDesc ShadowMaskDesc = FRDGTextureDesc::Create2D( SceneTextures.Config.Extent, PF_R32_UINT, FClearValueBinding::None, TexCreate_ShaderResource | TexCreate_UAV ); FRDGTextureRef ShadowMaskBits = GraphBuilder.CreateTexture( ShadowMaskDesc, InputType == EVirtualShadowMapProjectionInputType::HairStrands ? TEXT("ShadowMaskBits(HairStrands)") : TEXT("ShadowMaskBits(Gbuffer)")); RenderVirtualShadowMapProjectionCommon( GraphBuilder, SceneTextures, View, VirtualShadowMapArray, ProjectionRect, InputType, ShadowMaskBits); return ShadowMaskBits; } static FRDGTextureRef CreateShadowFactorTexture(FRDGBuilder& GraphBuilder, FIntPoint Extent) { const FLinearColor ClearColor(0.0f, 0.0f, 0.0f, 0.0f); FRDGTextureDesc Desc = FRDGTextureDesc::Create2D( Extent, PF_R32_FLOAT, FClearValueBinding(ClearColor), TexCreate_ShaderResource | TexCreate_UAV); FRDGTextureRef Texture = GraphBuilder.CreateTexture(Desc, TEXT("Shadow.Virtual.ShadowFactor")); AddClearUAVPass(GraphBuilder, GraphBuilder.CreateUAV(Texture), ClearColor); return Texture; } FRDGTextureRef RenderVirtualShadowMapProjection( FRDGBuilder& GraphBuilder, const FMinimalSceneTextures& SceneTextures, const FViewInfo& View, FVirtualShadowMapArray& VirtualShadowMapArray, const FIntRect ScissorRect, EVirtualShadowMapProjectionInputType InputType, FProjectedShadowInfo* ShadowInfo) { FRDGTextureRef OutputTexture = CreateShadowFactorTexture(GraphBuilder, SceneTextures.Config.Extent); RenderVirtualShadowMapProjectionCommon( GraphBuilder, SceneTextures, View, VirtualShadowMapArray, ScissorRect, InputType, OutputTexture, ShadowInfo->GetLightSceneInfo().Proxy, ShadowInfo->VirtualShadowMaps[0]->ID); return OutputTexture; } FRDGTextureRef RenderVirtualShadowMapProjection( FRDGBuilder& GraphBuilder, const FMinimalSceneTextures& SceneTextures, const FViewInfo& View, FVirtualShadowMapArray& VirtualShadowMapArray, const FIntRect ScissorRect, EVirtualShadowMapProjectionInputType InputType, const TSharedPtr& Clipmap) { FRDGTextureRef OutputTexture = CreateShadowFactorTexture(GraphBuilder, SceneTextures.Config.Extent); RenderVirtualShadowMapProjectionCommon( GraphBuilder, SceneTextures, View, VirtualShadowMapArray, ScissorRect, InputType, OutputTexture, Clipmap->GetLightSceneInfo().Proxy, Clipmap->GetVirtualShadowMap()->ID); return OutputTexture; }