// Copyright Epic Games, Inc. All Rights Reserved. #include "PostProcess/PostProcessLensFlares.h" #include "DataDrivenShaderPlatformInfo.h" #include "Engine/Engine.h" #include "Engine/Texture2D.h" #include "PostProcess/PostProcessDownsample.h" #include "PixelShaderUtils.h" #include "TextureResource.h" #include "ScenePrivate.h" #include "PostProcess/SceneFilterRendering.h" DECLARE_GPU_STAT(LensFlare); namespace { const int32 GLensFlareQuadsPerInstance = 4; TAutoConsoleVariable CVarLensFlareQuality( TEXT("r.LensFlareQuality"), 2, TEXT(" 0: off but best for performance\n") TEXT(" 1: low quality with good performance\n") TEXT(" 2: good quality (default)\n") TEXT(" 3: very good quality but bad performance"), ECVF_Scalability | ECVF_RenderThreadSafe); // The RDG inputs shared by all lens flare passes. BEGIN_SHADER_PARAMETER_STRUCT(FLensFlarePassParameters, ) SHADER_PARAMETER_RDG_TEXTURE_SRV(Texture2D, InputTexture) SHADER_PARAMETER_STRUCT_INCLUDE(FViewShaderParameters, View) RENDER_TARGET_BINDING_SLOTS() END_SHADER_PARAMETER_STRUCT() class FLensFlareShader : public FGlobalShader { public: static bool ShouldCompilePermutation(const FGlobalShaderPermutationParameters& Parameters) { return true; } static void ModifyCompilationEnvironment(const FGlobalShaderPermutationParameters& Parameters, FShaderCompilerEnvironment& OutEnvironment) { FGlobalShader::ModifyCompilationEnvironment(Parameters, OutEnvironment); OutEnvironment.SetDefine(TEXT("QUADS_PER_INSTANCE"), GLensFlareQuadsPerInstance); } FLensFlareShader() = default; FLensFlareShader(const CompiledShaderInitializerType& Initializer) : FGlobalShader(Initializer) {} }; class FLensFlareBlurVS : public FLensFlareShader { public: DECLARE_GLOBAL_SHADER(FLensFlareBlurVS); SHADER_USE_PARAMETER_STRUCT(FLensFlareBlurVS, FLensFlareShader); BEGIN_SHADER_PARAMETER_STRUCT(FParameters, ) SHADER_PARAMETER_RDG_TEXTURE_NON_PIXEL_SRV(Texture2D, InputTexture) SHADER_PARAMETER_SAMPLER(SamplerState, InputSampler) SHADER_PARAMETER_STRUCT(FScreenPassTextureViewportParameters, Input) SHADER_PARAMETER(FIntPoint, TileCount) SHADER_PARAMETER(uint32, TileSize) SHADER_PARAMETER(float, KernelSize) SHADER_PARAMETER(float, KernelAreaInverse) SHADER_PARAMETER(float, Threshold) SHADER_PARAMETER(float, GuardBandScaleInverse) END_SHADER_PARAMETER_STRUCT() }; IMPLEMENT_GLOBAL_SHADER(FLensFlareBlurVS, "/Engine/Private/PostProcessLensFlares.usf", "LensFlareBlurVS", SF_Vertex); class FLensFlareBlurPS : public FLensFlareShader { public: DECLARE_GLOBAL_SHADER(FLensFlareBlurPS); SHADER_USE_PARAMETER_STRUCT(FLensFlareBlurPS, FLensFlareShader); BEGIN_SHADER_PARAMETER_STRUCT(FParameters, ) SHADER_PARAMETER_TEXTURE(Texture2D, BokehTexture) SHADER_PARAMETER_SAMPLER(SamplerState, BokehSampler) END_SHADER_PARAMETER_STRUCT() }; IMPLEMENT_GLOBAL_SHADER(FLensFlareBlurPS, "/Engine/Private/PostProcessLensFlares.usf", "LensFlareBlurPS", SF_Pixel); class FLensFlareCompositePS : public FLensFlareShader { public: DECLARE_GLOBAL_SHADER(FLensFlareCompositePS); SHADER_USE_PARAMETER_STRUCT(FLensFlareCompositePS, FLensFlareShader); BEGIN_SHADER_PARAMETER_STRUCT(FParameters, ) SHADER_PARAMETER_STRUCT_INCLUDE(FLensFlarePassParameters, Pass) SHADER_PARAMETER_SAMPLER(SamplerState, InputSampler) SHADER_PARAMETER(FLinearColor, FlareColor) END_SHADER_PARAMETER_STRUCT() static bool ShouldCompilePermutation(const FGlobalShaderPermutationParameters& Parameters) { return true; } }; IMPLEMENT_GLOBAL_SHADER(FLensFlareCompositePS, "/Engine/Private/PostProcessLensFlares.usf", "LensFlareCompositePS", SF_Pixel); class FLensFlareCopyBloomPS : public FLensFlareShader { DECLARE_GLOBAL_SHADER(FLensFlareCopyBloomPS); SHADER_USE_PARAMETER_STRUCT(FLensFlareCopyBloomPS, FLensFlareShader); BEGIN_SHADER_PARAMETER_STRUCT(FParameters, ) SHADER_PARAMETER_STRUCT_INCLUDE(FLensFlarePassParameters, Pass) END_SHADER_PARAMETER_STRUCT() }; IMPLEMENT_GLOBAL_SHADER(FLensFlareCopyBloomPS, "/Engine/Private/PostProcessLensFlares.usf", "LensFlareCopyBloomPS", SF_Pixel); } //!namespace ELensFlareQuality GetLensFlareQuality() { return static_cast(FMath::Clamp( CVarLensFlareQuality.GetValueOnRenderThread(), static_cast(ELensFlareQuality::Disabled), static_cast(ELensFlareQuality::MAX) - 1)); } FScreenPassTexture AddLensFlaresPass( FRDGBuilder& GraphBuilder, const FViewInfo& View, const FLensFlareInputs& Inputs) { check(Inputs.Flare.IsValid()); check(Inputs.Bloom.IsValid()); check(Inputs.BokehShapeTexture); check(Inputs.LensFlareCount <= FLensFlareInputs::LensFlareCountMax); check(Inputs.TintColorsPerFlare.Num() == Inputs.LensFlareCount); check(Inputs.BokehSizePercent > 0.0f); check(Inputs.Intensity > 0.0f); check(Inputs.LensFlareCount > 0); RDG_EVENT_SCOPE(GraphBuilder, "LensFlares"); const float PercentToScale = 0.01f; // This constant scales the lens flare blur viewport so that the bokeh shape doesn't clip. However, // changing this constant will not preserve energy properly. The kernel size should also change based // on this constant. This is not implemented in order to provide preserve the look of content. The masking // behavior is also affected by this constant. const float GuardBandScale = 2.0f; const FScreenPassTextureViewport BloomViewport(Inputs.Bloom); const FIntPoint BloomViewportSize(BloomViewport.Rect.Size()); const FScreenPassTextureViewport FlareViewport(Inputs.Flare); const FIntPoint FlareViewSize = FlareViewport.Rect.Size(); FRHIBlendState* AdditiveBlendState = TStaticBlendState::GetRHI(); FRHISamplerState* BilinearClampSampler = TStaticSamplerState::GetRHI(); FRDGTextureRef BlurOutputTexture = nullptr; // Initialize the blur output texture. { const FRDGTextureDesc& InputDesc = Inputs.Flare.TextureSRV->Desc.Texture->Desc; FRDGTextureDesc BlurOutputDesc = FRDGTextureDesc::Create2D( InputDesc.Extent, PF_FloatRGBA, FClearValueBinding(FLinearColor::Transparent), /* InFlags = */ TexCreate_ShaderResource | TexCreate_RenderTargetable); BlurOutputTexture = GraphBuilder.CreateTexture(BlurOutputDesc, TEXT("LensFlareBlur")); } // Lens flare blur pass. Rasterizes a bokeh quad for each pixel on the screen based on the intensity threshold. { const uint32 TileSizeInPixels = 1; const FIntPoint TileCount = FlareViewSize / TileSizeInPixels; const float KernelSize = (Inputs.BokehSizePercent * static_cast(FlareViewSize.X)) * PercentToScale; FLensFlarePassParameters* PassParameters = GraphBuilder.AllocParameters(); PassParameters->InputTexture = Inputs.Flare.TextureSRV; PassParameters->RenderTargets[0] = FRenderTargetBinding(BlurOutputTexture, ERenderTargetLoadAction::EClear); // Setup vertex shader parameters. FLensFlareBlurVS::FParameters VertexParameters; VertexParameters.InputTexture = Inputs.Flare.TextureSRV; VertexParameters.Input = GetScreenPassTextureViewportParameters(FlareViewport); VertexParameters.InputSampler = BilinearClampSampler; VertexParameters.TileCount = TileCount; VertexParameters.TileSize = TileSizeInPixels; VertexParameters.KernelSize = KernelSize; VertexParameters.Threshold = Inputs.Threshold; VertexParameters.KernelAreaInverse = 1.0f / FMath::Max(1.0f, KernelSize * KernelSize); VertexParameters.GuardBandScaleInverse = 1.0f / GuardBandScale; // Setup pixel shader parameters. FLensFlareBlurPS::FParameters PixelParameters; PixelParameters.BokehTexture = Inputs.BokehShapeTexture; PixelParameters.BokehSampler = BilinearClampSampler; TShaderMapRef VertexShader(View.ShaderMap); TShaderMapRef PixelShader(View.ShaderMap); GraphBuilder.AddPass( RDG_EVENT_NAME("LensFlareBlur %dx%d", FlareViewSize.X, FlareViewSize.Y), PassParameters, ERDGPassFlags::Raster, [VertexShader, PixelShader, VertexParameters, PixelParameters, AdditiveBlendState, FlareViewport, TileCount] (FRDGAsyncTask, FRHICommandList& RHICmdList) { // Viewport is the same as the input. RHICmdList.SetViewport( FlareViewport.Rect.Min.X, FlareViewport.Rect.Min.Y, 0.0f, FlareViewport.Rect.Max.X, FlareViewport.Rect.Max.Y, 1.0f); // Apply additive blending pipeline state. FGraphicsPipelineStateInitializer GraphicsPSOInit; RHICmdList.ApplyCachedRenderTargets(GraphicsPSOInit); GraphicsPSOInit.BlendState = AdditiveBlendState; GraphicsPSOInit.RasterizerState = TStaticRasterizerState<>::GetRHI(); GraphicsPSOInit.DepthStencilState = TStaticDepthStencilState::GetRHI(); GraphicsPSOInit.BoundShaderState.VertexDeclarationRHI = GEmptyVertexDeclaration.VertexDeclarationRHI; GraphicsPSOInit.BoundShaderState.VertexShaderRHI = VertexShader.GetVertexShader(); GraphicsPSOInit.BoundShaderState.PixelShaderRHI = PixelShader.GetPixelShader(); GraphicsPSOInit.PrimitiveType = PT_TriangleList; SetGraphicsPipelineState(RHICmdList, GraphicsPSOInit, 0); SetShaderParameters(RHICmdList, VertexShader, VertexShader.GetVertexShader(), VertexParameters); SetShaderParameters(RHICmdList, PixelShader, PixelShader.GetPixelShader(), PixelParameters); // Emit an instanced quad draw call on the order of the number of pixels on the screen. RHICmdList.SetStreamSource(0, nullptr, 0); RHICmdList.DrawPrimitive(0, 2, FMath::DivideAndRoundUp(TileCount.X * TileCount.Y, GLensFlareQuadsPerInstance)); }); } FRDGTextureRef LensFlareTexture = nullptr; // Initialize the lens flare output texture. { const FRDGTextureDesc& InputDesc = Inputs.Bloom.TextureSRV->Desc.Texture->Desc; FRDGTextureDesc LensFlareTextureDesc = FRDGTextureDesc::Create2D( InputDesc.Extent, InputDesc.Format, FClearValueBinding(FLinearColor::Transparent), /* InFlags = */ TexCreate_ShaderResource | TexCreate_RenderTargetable | TexCreate_DisableDCC); LensFlareTexture = GraphBuilder.CreateTexture(LensFlareTextureDesc, TEXT("LensFlareTexture")); } ERenderTargetLoadAction LensFlareLoadAction = ERenderTargetLoadAction::ELoad; if (Inputs.bCompositeWithBloom) { FLensFlareCopyBloomPS::FParameters* CopyPassParameters = GraphBuilder.AllocParameters(); CopyPassParameters->Pass.InputTexture = Inputs.Bloom.TextureSRV; CopyPassParameters->Pass.RenderTargets[0] = FRenderTargetBinding(LensFlareTexture, ERenderTargetLoadAction::ENoAction); FPixelShaderUtils::AddFullscreenPass( GraphBuilder, View.ShaderMap, RDG_EVENT_NAME("LensFlareCopyBloom %dx%d", Inputs.Bloom.ViewRect.Size().X, Inputs.Bloom.ViewRect.Size().Y), View.ShaderMap->GetShader(), CopyPassParameters, Inputs.Bloom.ViewRect); } else { // Clear to transparent black on the first render pass. LensFlareLoadAction = ERenderTargetLoadAction::EClear; } const FIntRect OutputViewRect = BloomViewport.Rect; const FVector2f OutputCenter = FVector2f(OutputViewRect.Min + OutputViewRect.Max) / 2; // Scales normalized flare tint alpha to a viewport scale factor. const float AlphaScale = static_cast(Inputs.LensFlareCount - 1); const float AlphaBias = -AlphaScale * 0.5f; // Term to normalize the color based on the scale of the guard band. const float GuardBandAreaInverse = 1.0f / (GuardBandScale * GuardBandScale); const FLinearColor LensFlareHDRColor = Inputs.TintColor * Inputs.Intensity * GuardBandAreaInverse; // Render a scaled quad for each lens flare, additively blended onto the target. for (uint32 LensFlareIndex = 0; LensFlareIndex < Inputs.LensFlareCount; ++LensFlareIndex) { const FLinearColor LensFlareTint = Inputs.TintColorsPerFlare[LensFlareIndex]; FLensFlareCompositePS::FParameters* PassParameters = GraphBuilder.AllocParameters(); PassParameters->Pass.InputTexture = GraphBuilder.CreateSRV(FRDGTextureSRVDesc(BlurOutputTexture)); PassParameters->Pass.View = View.GetShaderParameters(); PassParameters->Pass.RenderTargets[0] = FRenderTargetBinding(LensFlareTexture, LensFlareLoadAction); PassParameters->InputSampler = BilinearClampSampler; PassParameters->FlareColor = LensFlareHDRColor * LensFlareTint; // Alpha of the tint color is used to derive a scale of the flare quad. const float FinalOutputScale = (LensFlareTint.A * AlphaScale + AlphaBias) * GuardBandScale; const FVector2f QuadSize = FVector2f(OutputViewRect.Size()) * FinalOutputScale; const FVector2f QuadOffset = OutputCenter - 0.5f * QuadSize; TShaderMapRef VertexShader(View.ShaderMap); TShaderMapRef PixelShader(View.ShaderMap); const FScreenPassPipelineState PipelineState(VertexShader, PixelShader, AdditiveBlendState); // This pass rasterizes the lens flare quad scaled and centered within the viewport. GraphBuilder.AddPass( RDG_EVENT_NAME("LensFlare%d", LensFlareIndex), PassParameters, ERDGPassFlags::Raster, [PixelShader, PassParameters, OutputViewRect, FlareViewport, QuadSize, QuadOffset, PipelineState] (FRDGAsyncTask, FRHICommandList& RHICmdList) { RHICmdList.SetViewport(OutputViewRect.Min.X, OutputViewRect.Min.Y, 0.0f, OutputViewRect.Max.X, OutputViewRect.Max.Y, 1.0f); SetScreenPassPipelineState(RHICmdList, PipelineState); SetShaderParameters(RHICmdList, PixelShader, PixelShader.GetPixelShader(), *PassParameters); DrawRectangle( RHICmdList, QuadOffset.X, QuadOffset.Y, QuadSize.X, QuadSize.Y, FlareViewport.Rect.Min.X, FlareViewport.Rect.Min.Y, FlareViewport.Rect.Width(), FlareViewport.Rect.Height(), OutputViewRect.Size(), FlareViewport.Extent, PipelineState.VertexShader, EDRF_Default); }); // All subsequent passes must load. LensFlareLoadAction = ERenderTargetLoadAction::ELoad; } return FScreenPassTexture(LensFlareTexture, OutputViewRect); } bool IsLensFlaresEnabled(const FViewInfo& View) { const ELensFlareQuality LensFlareQuality = GetLensFlareQuality(); const FPostProcessSettings& Settings = View.FinalPostProcessSettings; return (LensFlareQuality != ELensFlareQuality::Disabled && !Settings.LensFlareTint.IsAlmostBlack() && Settings.LensFlareBokehSize > SMALL_NUMBER && Settings.LensFlareIntensity > SMALL_NUMBER); } FScreenPassTexture AddLensFlaresPass( FRDGBuilder& GraphBuilder, const FViewInfo& View, FScreenPassTexture Bloom, FScreenPassTextureSlice QualitySceneDownsample, FScreenPassTextureSlice DefaultSceneDownsample) { ensure(IsLensFlaresEnabled(View)); const FPostProcessSettings& Settings = View.FinalPostProcessSettings; RDG_EVENT_SCOPE_STAT(GraphBuilder, LensFlare, "LensFlare"); RDG_GPU_STAT_SCOPE(GraphBuilder, LensFlare); FRHITexture* BokehTextureRHI = GWhiteTexture->TextureRHI; if (GEngine->DefaultBokehTexture) { FTextureResource* BokehTextureResource = GEngine->DefaultBokehTexture->GetResource(); if (BokehTextureResource && BokehTextureResource->TextureRHI) { BokehTextureRHI = BokehTextureResource->TextureRHI; } } if (Settings.LensFlareBokehShape) { FTextureResource* BokehTextureResource = Settings.LensFlareBokehShape->GetResource(); if (BokehTextureResource && BokehTextureResource->TextureRHI) { BokehTextureRHI = BokehTextureResource->TextureRHI; } } FLensFlareInputs LensFlareInputs; LensFlareInputs.Bloom = FScreenPassTextureSlice::CreateFromScreenPassTexture(GraphBuilder, Bloom); LensFlareInputs.Flare = QualitySceneDownsample; LensFlareInputs.BokehShapeTexture = BokehTextureRHI; LensFlareInputs.TintColorsPerFlare = Settings.LensFlareTints; LensFlareInputs.TintColor = Settings.LensFlareTint; LensFlareInputs.BokehSizePercent = Settings.LensFlareBokehSize; LensFlareInputs.Intensity = Settings.LensFlareIntensity * Settings.BloomIntensity; LensFlareInputs.Threshold = Settings.LensFlareThreshold; // If a bloom output texture isn't available, substitute the half resolution scene color instead, but disable bloom // composition. The pass needs a primary input in order to access the image descriptor and viewport for output. if (!Bloom.IsValid()) { LensFlareInputs.Bloom = DefaultSceneDownsample; LensFlareInputs.bCompositeWithBloom = false; } return AddLensFlaresPass(GraphBuilder, View, LensFlareInputs); }