// Copyright Epic Games, Inc. All Rights Reserved. /*============================================================================= PostProcessAmbientOcclusionMobile.cpp =============================================================================*/ #include "PostProcess/PostProcessAmbientOcclusionMobile.h" #include "CompositionLighting/PostProcessAmbientOcclusion.h" #include "DataDrivenShaderPlatformInfo.h" #include "ShaderParameterStruct.h" #include "SceneRendering.h" #include "RenderTargetPool.h" #include "PostProcess/SceneFilterRendering.h" #include "PostProcess/SceneRenderTargets.h" #include "SystemTextures.h" #include "ScreenPass.h" #include "ScenePrivate.h" #include "SceneTextureParameters.h" #include "SceneRenderTargetParameters.h" #include "ClearQuad.h" #include "PixelShaderUtils.h" static TAutoConsoleVariable CVarMobileAmbientOcclusion( TEXT("r.Mobile.AmbientOcclusion"), 0, TEXT("Caution: An extra sampler will be occupied in mobile base pass pixel shader after enable the mobile ambient occlusion.\n") TEXT("0: Disable Ambient Occlusion on mobile platform. [default]\n") TEXT("1: Enable Ambient Occlusion on mobile platform.\n"), ECVF_ReadOnly | ECVF_RenderThreadSafe ); static TAutoConsoleVariable CVarMobileAmbientOcclusionTechnique( TEXT("r.Mobile.AmbientOcclusionTechnique"), 0, TEXT("0: GTAO (default).\n") TEXT("1: SSAO.\n"), ECVF_RenderThreadSafe ); static TAutoConsoleVariable CVarMobileGTAOPreIntegratedTextureType( TEXT("r.Mobile.GTAOPreIntegratedTextureType"), 2, TEXT("0: No Texture.\n") TEXT("1: Texture2D LUT.\n") TEXT("2: Volume LUT(Default)."), ECVF_ReadOnly); static TAutoConsoleVariable CVarMobileAmbientOcclusionQuality( TEXT("r.Mobile.AmbientOcclusionQuality"), 1, TEXT("The quality of screen space ambient occlusion on mobile platform.\n") TEXT("0: Disabled.\n") TEXT("1: Low.(Default)\n") TEXT("2: Medium.\n") TEXT("3: High.\n"), ECVF_Scalability | ECVF_RenderThreadSafe ); static TAutoConsoleVariable CVarMobileAmbientOcclusionShaderType( TEXT("r.Mobile.AmbientOcclusionShaderType"), 2, TEXT("0: ComputeShader.\n") TEXT("1: Separate ComputeShader.\n") TEXT("2: PixelShader.\n"), ECVF_RenderThreadSafe ); static TAutoConsoleVariable CVarMobileAmbientOcclusionCompression( TEXT("r.Mobile.AmbientOcclusionCompression"), 1, TEXT("0: Disable framebuffer compression for AO texture.\n") TEXT("1: Enable framebuffer compression for AO texture if supported (Default).\n"), ECVF_RenderThreadSafe ); enum class EMobileAmbientOcclusionShaderType { ComputeShader, SeparateComputeShader, PixelShader }; static EMobileAmbientOcclusionShaderType GetCVarMobileAmbientOcclusionShaderTypeOnRenderThread() { int ShaderType = FMath::Clamp(CVarMobileAmbientOcclusionShaderType.GetValueOnRenderThread(), 0, 2); if (ShaderType == 0 && GetMaxWorkGroupInvocations() < 1024) { ShaderType = 1; } return static_cast(ShaderType); } static bool IsComputeShaderType(EMobileAmbientOcclusionShaderType ShaderType) { return ShaderType != EMobileAmbientOcclusionShaderType::PixelShader; } static bool IsTwoPassShaderType(EMobileAmbientOcclusionShaderType ShaderType) { return ShaderType != EMobileAmbientOcclusionShaderType::ComputeShader; } static TAutoConsoleVariable CVarMobileAmbientOcclusionDepthBoundsTest( TEXT("r.Mobile.AmbientOcclusionDepthBoundsTest"), 1, TEXT("Whether to use depth bounds test to cull distant pixels during AO pass. This option is only valid when pixel shader path is used"), ECVF_RenderThreadSafe); static TAutoConsoleVariable CVarMobileSSAOHalfResolution( TEXT("r.Mobile.SSAOHalfResolution"), 0, TEXT("Whether to calculate SSAO at half resolution.\n") TEXT("0: Disabled.\n") TEXT("1: Half Resolution with bilinear upsample\n") TEXT("2: Half Resolution with 4 tap bilateral upsample\n") TEXT("3: Half Resolution with 9 tap bilateral upsample\n"), ECVF_RenderThreadSafe); // -------------------------------------------------------------------------------------------------------------------- DECLARE_GPU_STAT_NAMED(MobileSSAO, TEXT("SSAO")); // -------------------------------------------------------------------------------------------------------------------- bool IsUsingMobileAmbientOcclusion(EShaderPlatform ShaderPlatform) { static const auto MobileAmbientOcclusionQualityCVar = IConsoleManager::Get().FindTConsoleVariableDataInt(TEXT("r.Mobile.AmbientOcclusionQuality")); return IsMobileAmbientOcclusionEnabled(ShaderPlatform) && MobileAmbientOcclusionQualityCVar->GetValueOnAnyThread() > 0; } // -------------------------------------------------------------------------------------------------------------------- class FGTAOMobile_HorizonSearchIntegral : public FGlobalShader { public: class FLUTTextureTypeDim : SHADER_PERMUTATION_INT("PREINTEGRATED_LUT_TYPE", 3); class FShaderQualityDim : SHADER_PERMUTATION_INT("SHADER_QUALITY", 3); using FCommonPermutationDomain = TShaderPermutationDomain< FLUTTextureTypeDim, FShaderQualityDim>; BEGIN_SHADER_PARAMETER_STRUCT(FParameters, ) SHADER_PARAMETER_STRUCT_REF(FViewUniformShaderParameters, View) SHADER_PARAMETER_EX(FVector4f, ViewRectMin, EShaderPrecisionModifier::Half) SHADER_PARAMETER_EX(FVector4f, DepthBufferSizeAndInvSize, EShaderPrecisionModifier::Half) SHADER_PARAMETER_EX(FVector4f, BufferSizeAndInvSize, EShaderPrecisionModifier::Half) SHADER_PARAMETER_EX(FVector4f, ViewSizeAndInvSize, EShaderPrecisionModifier::Half) SHADER_PARAMETER(FVector4f, FadeRadiusMulAdd_FadeDistance_AttenFactor) SHADER_PARAMETER(FVector4f, WorldRadiusAdj_SinDeltaAngle_CosDeltaAngle_Thickness) SHADER_PARAMETER_RDG_TEXTURE(Texture2D, SceneDepthTexture) SHADER_PARAMETER_SAMPLER(SamplerState, SceneDepthSampler) SHADER_PARAMETER_RDG_TEXTURE(Texture2D, NormalTexture) SHADER_PARAMETER_SAMPLER(SamplerState, NormalSampler) SHADER_PARAMETER_TEXTURE(Texture2D, GTAOPreIntegrated2D) SHADER_PARAMETER_TEXTURE(Texture3D, GTAOPreIntegrated3D) SHADER_PARAMETER_SAMPLER(SamplerState, GTAOPreIntegratedSampler) END_SHADER_PARAMETER_STRUCT() static bool ShouldCompilePermutation(const FGlobalShaderPermutationParameters& Parameters, const FCommonPermutationDomain& CommonPermutationVector) { auto LUTTextureType = CommonPermutationVector.Get(); int32 MobileGTAOPreIntegratedTextureType = CVarMobileGTAOPreIntegratedTextureType.GetValueOnAnyThread(); return IsMobileAmbientOcclusionEnabled(Parameters.Platform) && (MobileGTAOPreIntegratedTextureType == LUTTextureType); } static void ModifyCompilationEnvironment(const FGlobalShaderPermutationParameters& Parameters, FShaderCompilerEnvironment& OutEnvironment) { FGlobalShader::ModifyCompilationEnvironment(Parameters, OutEnvironment); OutEnvironment.SetDefine(TEXT("USE_NORMALBUFFER"), 0); } static FCommonPermutationDomain BuildPermutationVector(int32 LUTTextureType, int32 ShaderQuality) { FCommonPermutationDomain PermutationVector; PermutationVector.Set(LUTTextureType); PermutationVector.Set(ShaderQuality); return PermutationVector; } static void SetupShaderParameters(FParameters& ShaderParameters, FRDGBuilder& GraphBuilder, const FViewInfo& View, const FIntRect& ViewRect, const FIntPoint& DepthBufferSize, const FIntPoint& BufferSize, const FVector4f& FallOffStartEndScaleBias, const FVector4f& WorldRadiusAdjSinCosDeltaAngleThickness, FRDGTextureRef SceneDepthTexture) { const FFinalPostProcessSettings& Settings = View.FinalPostProcessSettings; float FadeRadius = FMath::Max(1.0f, Settings.AmbientOcclusionFadeRadius); float InvFadeRadius = 1.0f / FadeRadius; ShaderParameters.View = View.ViewUniformBuffer; ShaderParameters.ViewRectMin = FVector4f(ViewRect.Min.X, ViewRect.Min.Y, 0.0f, 0.0f); ShaderParameters.DepthBufferSizeAndInvSize = FVector4f(DepthBufferSize.X, DepthBufferSize.Y, 1.0f / DepthBufferSize.X, 1.0f / DepthBufferSize.Y); ShaderParameters.BufferSizeAndInvSize = FVector4f(BufferSize.X, BufferSize.Y, 1.0f / BufferSize.X, 1.0f / BufferSize.Y); ShaderParameters.ViewSizeAndInvSize = FVector4f(ViewRect.Width(), ViewRect.Height(), 1.0f / ViewRect.Width(), 1.0f / ViewRect.Height()); ShaderParameters.FadeRadiusMulAdd_FadeDistance_AttenFactor = FVector4f(InvFadeRadius, -(Settings.AmbientOcclusionFadeDistance - FadeRadius) * InvFadeRadius, Settings.AmbientOcclusionFadeDistance, 2.0f / (FallOffStartEndScaleBias.Y * FallOffStartEndScaleBias.Y)); ShaderParameters.WorldRadiusAdj_SinDeltaAngle_CosDeltaAngle_Thickness = WorldRadiusAdjSinCosDeltaAngleThickness; ShaderParameters.SceneDepthTexture = SceneDepthTexture; ShaderParameters.SceneDepthSampler = TStaticSamplerState::GetRHI(); if (GSystemTextures.GTAOPreIntegrated.IsValid()) { ShaderParameters.GTAOPreIntegrated2D = GSystemTextures.GTAOPreIntegrated->GetRHI(); ShaderParameters.GTAOPreIntegrated3D = GSystemTextures.GTAOPreIntegrated->GetRHI(); ShaderParameters.GTAOPreIntegratedSampler = TStaticSamplerState::GetRHI(); } } FGTAOMobile_HorizonSearchIntegral() = default; FGTAOMobile_HorizonSearchIntegral(const CompiledShaderInitializerType& Initializer) : FGlobalShader(Initializer) {} }; class FGTAOMobile_HorizonSearchIntegralSpatialFilterCS : public FGTAOMobile_HorizonSearchIntegral { using Super = FGTAOMobile_HorizonSearchIntegral; public: // Changing these numbers requires PostProcessAmbientOcclusionMobile.usf to be recompiled. // The maximum thread group is 512 on IOS A9 and A10 and the shared memory is 16K static const uint32 ThreadGroupSizeX = 32; static const uint32 ThreadGroupSizeY = 32; // The number of texels on each axis processed by a single thread group. static const FIntPoint TexelsPerThreadGroup; DECLARE_GLOBAL_SHADER(FGTAOMobile_HorizonSearchIntegralSpatialFilterCS); SHADER_USE_PARAMETER_STRUCT(FGTAOMobile_HorizonSearchIntegralSpatialFilterCS, Super); BEGIN_SHADER_PARAMETER_STRUCT(FParameters, ) SHADER_PARAMETER_STRUCT_INCLUDE(FGTAOMobile_HorizonSearchIntegral::FParameters, Common) SHADER_PARAMETER_EX(FVector4f, Power_Intensity_ScreenPixelsToSearch, EShaderPrecisionModifier::Half) SHADER_PARAMETER_RDG_TEXTURE_UAV(RWTexture2D, OutTexture) END_SHADER_PARAMETER_STRUCT() using FPermutationDomain = TShaderPermutationDomain< Super::FCommonPermutationDomain>; static bool ShouldCompilePermutation(const FGlobalShaderPermutationParameters& Parameters) { FPermutationDomain PermutationVector(Parameters.PermutationId); return Super::ShouldCompilePermutation(Parameters, PermutationVector.Get()); } static void ModifyCompilationEnvironment(const FGlobalShaderPermutationParameters& Parameters, FShaderCompilerEnvironment& OutEnvironment) { Super::ModifyCompilationEnvironment(Parameters, OutEnvironment); OutEnvironment.SetDefine(TEXT("HORIZONSEARCH_INTEGRAL_SPATIALFILTER_COMPUTE_SHADER"), 1u); OutEnvironment.SetDefine(TEXT("THREADGROUP_SIZEX"), ThreadGroupSizeX); OutEnvironment.SetDefine(TEXT("THREADGROUP_SIZEY"), ThreadGroupSizeY); } static FPermutationDomain BuildPermutationVector(int32 LUTTextureType, int32 ShaderQuality) { FPermutationDomain PermutationVector; PermutationVector.Set(Super::BuildPermutationVector(LUTTextureType, ShaderQuality)); return PermutationVector; } }; const FIntPoint FGTAOMobile_HorizonSearchIntegralSpatialFilterCS::TexelsPerThreadGroup(ThreadGroupSizeX, ThreadGroupSizeY); IMPLEMENT_GLOBAL_SHADER(FGTAOMobile_HorizonSearchIntegralSpatialFilterCS, "/Engine/Private/PostProcessAmbientOcclusionMobile.usf", "GTAOHorizonSearchIntegralSpatialFilterCS", SF_Compute); class FGTAOMobile_HorizonSearchIntegralCS : public FGTAOMobile_HorizonSearchIntegral { using Super = FGTAOMobile_HorizonSearchIntegral; public: // Changing these numbers requires PostProcessAmbientOcclusionMobile.usf to be recompiled. // Use smaller thread group for low end devices static const uint32 ThreadGroupSizeX = 16; static const uint32 ThreadGroupSizeY = 8; // The number of texels on each axis processed by a single thread group. static const FIntPoint TexelsPerThreadGroup; DECLARE_GLOBAL_SHADER(FGTAOMobile_HorizonSearchIntegralCS); SHADER_USE_PARAMETER_STRUCT(FGTAOMobile_HorizonSearchIntegralCS, Super); BEGIN_SHADER_PARAMETER_STRUCT(FParameters, ) SHADER_PARAMETER_STRUCT_INCLUDE(FGTAOMobile_HorizonSearchIntegral::FParameters, Common) SHADER_PARAMETER_RDG_TEXTURE_UAV(RWTexture2D, OutTexture) END_SHADER_PARAMETER_STRUCT() using FPermutationDomain = TShaderPermutationDomain< Super::FCommonPermutationDomain>; static bool ShouldCompilePermutation(const FGlobalShaderPermutationParameters& Parameters) { FPermutationDomain PermutationVector(Parameters.PermutationId); return Super::ShouldCompilePermutation(Parameters, PermutationVector.Get()); } static void ModifyCompilationEnvironment(const FGlobalShaderPermutationParameters& Parameters, FShaderCompilerEnvironment& OutEnvironment) { Super::ModifyCompilationEnvironment(Parameters, OutEnvironment); OutEnvironment.SetDefine(TEXT("HORIZONSEARCH_INTEGRAL_COMPUTE_SHADER"), 1u); OutEnvironment.SetDefine(TEXT("THREADGROUP_SIZEX"), ThreadGroupSizeX); OutEnvironment.SetDefine(TEXT("THREADGROUP_SIZEY"), ThreadGroupSizeY); } static FPermutationDomain BuildPermutationVector(int32 LUTTextureType, int32 ShaderQuality) { FPermutationDomain PermutationVector; PermutationVector.Set(Super::BuildPermutationVector(LUTTextureType, ShaderQuality)); return PermutationVector; } }; const FIntPoint FGTAOMobile_HorizonSearchIntegralCS::TexelsPerThreadGroup(ThreadGroupSizeX, ThreadGroupSizeY); IMPLEMENT_GLOBAL_SHADER(FGTAOMobile_HorizonSearchIntegralCS, "/Engine/Private/PostProcessAmbientOcclusionMobile.usf", "GTAOHorizonSearchIntegralCS", SF_Compute); class FGTAOMobile_SpatialFilter : public FGlobalShader { public: BEGIN_SHADER_PARAMETER_STRUCT(FParameters, ) SHADER_PARAMETER_EX(FVector4f, ViewRectMin, EShaderPrecisionModifier::Half) SHADER_PARAMETER_EX(FVector4f, BufferSizeAndInvSize, EShaderPrecisionModifier::Half) SHADER_PARAMETER_EX(FVector4f, ViewSizeAndInvSize, EShaderPrecisionModifier::Half) SHADER_PARAMETER_EX(FVector4f, Power_Intensity_ScreenPixelsToSearch, EShaderPrecisionModifier::Half) SHADER_PARAMETER_RDG_TEXTURE(Texture2D, AOInputTexture) SHADER_PARAMETER_SAMPLER(SamplerState, AOInputSampler) END_SHADER_PARAMETER_STRUCT() static bool ShouldCompilePermutation(const FGlobalShaderPermutationParameters& Parameters) { FPermutationDomain PermutationVector(Parameters.PermutationId); return IsMobileAmbientOcclusionEnabled(Parameters.Platform); } static void ModifyCompilationEnvironment(const FGlobalShaderPermutationParameters& Parameters, FShaderCompilerEnvironment& OutEnvironment) { FGlobalShader::ModifyCompilationEnvironment(Parameters, OutEnvironment); } static void SetupShaderParameters(FParameters& ShaderParameters, const FViewInfo& View, const FIntRect& ViewRect, const FIntPoint& BufferSize, FRDGTextureRef HorizonSearchIntegralTexture) { const FFinalPostProcessSettings& Settings = View.FinalPostProcessSettings; ShaderParameters.ViewRectMin = FVector4f(ViewRect.Min.X, ViewRect.Min.Y, 0.0f, 0.0f); ShaderParameters.BufferSizeAndInvSize = FVector4f(BufferSize.X, BufferSize.Y, 1.0f / BufferSize.X, 1.0f / BufferSize.Y); ShaderParameters.ViewSizeAndInvSize = FVector4f(ViewRect.Width(), ViewRect.Height(), 1.0f / ViewRect.Width(), 1.0f / ViewRect.Height()); ShaderParameters.Power_Intensity_ScreenPixelsToSearch = FVector4f(Settings.AmbientOcclusionPower * 0.5f, Settings.AmbientOcclusionIntensity, 0.0f, 0.0f); ShaderParameters.AOInputTexture = HorizonSearchIntegralTexture; ShaderParameters.AOInputSampler = TStaticSamplerState::GetRHI(); } FGTAOMobile_SpatialFilter() = default; FGTAOMobile_SpatialFilter(const CompiledShaderInitializerType& Initializer) : FGlobalShader(Initializer) {} }; class FGTAOMobile_SpatialFilterCS : public FGTAOMobile_SpatialFilter { using Super = FGTAOMobile_SpatialFilter; public: // Changing these numbers requires PostProcessAmbientOcclusionMobile.usf to be recompiled. // Use smaller thread group for low end devices static const uint32 ThreadGroupSizeX = 16; static const uint32 ThreadGroupSizeY = 8; // The number of texels on each axis processed by a single thread group. static const FIntPoint TexelsPerThreadGroup; DECLARE_GLOBAL_SHADER(FGTAOMobile_SpatialFilterCS); SHADER_USE_PARAMETER_STRUCT(FGTAOMobile_SpatialFilterCS, Super); BEGIN_SHADER_PARAMETER_STRUCT(FParameters, ) SHADER_PARAMETER_STRUCT_INCLUDE(FGTAOMobile_SpatialFilter::FParameters, Common) SHADER_PARAMETER_RDG_TEXTURE_UAV(RWTexture2D, OutTexture) END_SHADER_PARAMETER_STRUCT() static bool ShouldCompilePermutation(const FGlobalShaderPermutationParameters& Parameters) { return Super::ShouldCompilePermutation(Parameters); } static void ModifyCompilationEnvironment(const FGlobalShaderPermutationParameters& Parameters, FShaderCompilerEnvironment& OutEnvironment) { Super::ModifyCompilationEnvironment(Parameters, OutEnvironment); OutEnvironment.SetDefine(TEXT("SPATIALFILTER_COMPUTE_SHADER"), 1u); OutEnvironment.SetDefine(TEXT("THREADGROUP_SIZEX"), ThreadGroupSizeX); OutEnvironment.SetDefine(TEXT("THREADGROUP_SIZEY"), ThreadGroupSizeY); } }; const FIntPoint FGTAOMobile_SpatialFilterCS::TexelsPerThreadGroup(ThreadGroupSizeX, ThreadGroupSizeY); IMPLEMENT_GLOBAL_SHADER(FGTAOMobile_SpatialFilterCS, "/Engine/Private/PostProcessAmbientOcclusionMobile.usf", "GTAOSpatialFilterCS", SF_Compute); class FGTAOMobile_HorizonSearchIntegralPS : public FGTAOMobile_HorizonSearchIntegral { using Super = FGTAOMobile_HorizonSearchIntegral; public: DECLARE_GLOBAL_SHADER(FGTAOMobile_HorizonSearchIntegralPS); SHADER_USE_PARAMETER_STRUCT(FGTAOMobile_HorizonSearchIntegralPS, Super); BEGIN_SHADER_PARAMETER_STRUCT(FParameters, ) SHADER_PARAMETER_STRUCT_INCLUDE(FGTAOMobile_HorizonSearchIntegral::FParameters, Common) RENDER_TARGET_BINDING_SLOTS() END_SHADER_PARAMETER_STRUCT() using FPermutationDomain = TShaderPermutationDomain< Super::FCommonPermutationDomain>; static bool ShouldCompilePermutation(const FGlobalShaderPermutationParameters& Parameters) { FPermutationDomain PermutationVector(Parameters.PermutationId); return Super::ShouldCompilePermutation(Parameters, PermutationVector.Get()); } static void ModifyCompilationEnvironment(const FGlobalShaderPermutationParameters& Parameters, FShaderCompilerEnvironment& OutEnvironment) { Super::ModifyCompilationEnvironment(Parameters, OutEnvironment); OutEnvironment.SetDefine(TEXT("HORIZONSEARCH_INTEGRAL_PIXEL_SHADER"), 1u); OutEnvironment.SetDefine(TEXT("FORCE_DEPTH_TEXTURE_READS"), 1); } static FPermutationDomain BuildPermutationVector(int32 LUTTextureType, int32 ShaderQuality) { FPermutationDomain PermutationVector; PermutationVector.Set(Super::BuildPermutationVector(LUTTextureType, ShaderQuality)); return PermutationVector; } }; IMPLEMENT_GLOBAL_SHADER(FGTAOMobile_HorizonSearchIntegralPS, "/Engine/Private/PostProcessAmbientOcclusionMobile.usf", "GTAOHorizonSearchIntegralPS", SF_Pixel); class FGTAOMobile_SpatialFilterPS : public FGTAOMobile_SpatialFilter { using Super = FGTAOMobile_SpatialFilter; public: DECLARE_GLOBAL_SHADER(FGTAOMobile_SpatialFilterPS); SHADER_USE_PARAMETER_STRUCT(FGTAOMobile_SpatialFilterPS, Super); BEGIN_SHADER_PARAMETER_STRUCT(FParameters, ) SHADER_PARAMETER_STRUCT_INCLUDE(FGTAOMobile_SpatialFilter::FParameters, Common) RENDER_TARGET_BINDING_SLOTS() END_SHADER_PARAMETER_STRUCT() static bool ShouldCompilePermutation(const FGlobalShaderPermutationParameters& Parameters) { return Super::ShouldCompilePermutation(Parameters); } static void ModifyCompilationEnvironment(const FGlobalShaderPermutationParameters& Parameters, FShaderCompilerEnvironment& OutEnvironment) { Super::ModifyCompilationEnvironment(Parameters, OutEnvironment); OutEnvironment.SetDefine(TEXT("SPATIALFILTER_PIXEL_SHADER"), 1u); OutEnvironment.SetDefine(TEXT("FORCE_DEPTH_TEXTURE_READS"), 1); } }; IMPLEMENT_GLOBAL_SHADER(FGTAOMobile_SpatialFilterPS, "/Engine/Private/PostProcessAmbientOcclusionMobile.usf", "GTAOSpatialFilterPS", SF_Pixel); FRDGTextureRef CreateMobileScreenSpaceAOTexture(FRDGBuilder& GraphBuilder, const FSceneTexturesConfig& Config) { bool bGTAO = (CVarMobileAmbientOcclusionTechnique.GetValueOnRenderThread() == 0); const uint32 DownsampleFactor = bGTAO ? 2 : 1; const FIntPoint Extent = FIntPoint::DivideAndRoundUp(Config.Extent, DownsampleFactor); EPixelFormat Format = PF_G8; const EMobileAmbientOcclusionShaderType ShaderType = GetCVarMobileAmbientOcclusionShaderTypeOnRenderThread(); // G8 isn't supported as UAV on Android OpenGLES, fall back to RGBA8 for compute shader usage. if (bGTAO && IsOpenGLPlatform(Config.ShaderPlatform) && IsComputeShaderType(ShaderType) ) { Format = PF_R8G8B8A8; } ETextureCreateFlags TextureCreateFlags = TexCreate_ShaderResource | TexCreate_RenderTargetable; if (IsComputeShaderType(ShaderType)) { TextureCreateFlags |= TexCreate_UAV; } if (UE::PixelFormat::HasCapabilities(Format, EPixelFormatCapabilities::LossyCompressible) && CVarMobileAmbientOcclusionCompression.GetValueOnRenderThread() != 0) { TextureCreateFlags |= TexCreate_LossyCompressionLowBitrate; } return GraphBuilder.CreateTexture(FRDGTextureDesc::Create2D(Extent, Format, FClearValueBinding::Black, TextureCreateFlags), TEXT("ScreenSpaceAO")); } static void RenderGTAO(FRDGBuilder& GraphBuilder, FRDGTextureRef SceneDepthTexture, FRDGTextureRef AmbientOcclusionTexture, const TArray& Views) { static const auto GTAOThicknessBlendCVar = IConsoleManager::Get().FindTConsoleVariableDataFloat(TEXT("r.GTAO.ThicknessBlend")); static const auto GTAOFalloffStartRatioCVar = IConsoleManager::Get().FindTConsoleVariableDataFloat(TEXT("r.GTAO.FalloffStartRatio")); static const auto GTAOFalloffEndCVar = IConsoleManager::Get().FindTConsoleVariableDataFloat(TEXT("r.GTAO.FalloffEnd")); static const auto GTAONumAnglesCVar = IConsoleManager::Get().FindTConsoleVariableDataFloat(TEXT("r.GTAO.NumAngles")); const uint32 DownsampleFactor = 2; const int32 MobileGTAOPreIntegratedTextureType = FMath::Min(CVarMobileGTAOPreIntegratedTextureType.GetValueOnRenderThread(), 2); const int32 MobileAmbientOcclusionQuality = FMath::Min(CVarMobileAmbientOcclusionQuality.GetValueOnRenderThread(), 3); const FIntPoint& DepthBufferSize = SceneDepthTexture->Desc.Extent; const FIntPoint& BufferSize = AmbientOcclusionTexture->Desc.Extent; float FallOffEnd = GTAOFalloffEndCVar ? GTAOFalloffEndCVar->GetValueOnRenderThread() : 200.0f; float FallOffStartRatio = GTAOFalloffStartRatioCVar ? FMath::Clamp(GTAOFalloffStartRatioCVar->GetValueOnRenderThread(), 0.0f, 0.999f) : 0.5f; float FallOffStart = FallOffEnd * FallOffStartRatio; float FallOffStartSq = FallOffStart * FallOffStart; float FallOffEndSq = FallOffEnd * FallOffEnd; float FallOffScale = 1.0f / (FallOffEndSq - FallOffStartSq); float FallOffBias = -FallOffStartSq * FallOffScale; FVector4f FallOffStartEndScaleBias(FallOffStart, FallOffEnd, FallOffScale, FallOffBias); float ThicknessBlend = GTAOThicknessBlendCVar ? GTAOThicknessBlendCVar->GetValueOnRenderThread() : 0.5f; ThicknessBlend = FMath::Clamp(1.0f - (ThicknessBlend*ThicknessBlend), 0.0f, 0.99f); float NumAngles = GTAONumAnglesCVar ? FMath::Clamp(GTAONumAnglesCVar->GetValueOnRenderThread(), 1.0f, 16.0f) : 2; float SinDeltaAngle, CosDeltaAngle; FMath::SinCos(&SinDeltaAngle, &CosDeltaAngle, PI / NumAngles); const EMobileAmbientOcclusionShaderType ShaderType = GetCVarMobileAmbientOcclusionShaderTypeOnRenderThread(); FRDGTextureRef HorizonSearchIntegralTexture{}; FRDGTextureUAVRef HorizonSearchIntegralTextureUAV{}; FRDGTextureUAVRef AmbientOcclusionTextureUAV{}; if (IsComputeShaderType(ShaderType)) { AmbientOcclusionTextureUAV = GraphBuilder.CreateUAV(AmbientOcclusionTexture); } if (IsTwoPassShaderType(ShaderType)) { ETextureCreateFlags TextureCreateFlags = TexCreate_ShaderResource | TexCreate_RenderTargetable; const EPixelFormat PixelFormat = PF_R8G8B8A8; if (UE::PixelFormat::HasCapabilities(PixelFormat, EPixelFormatCapabilities::LossyCompressible) && CVarMobileAmbientOcclusionCompression.GetValueOnRenderThread() != 0) { TextureCreateFlags |= TexCreate_LossyCompressionLowBitrate; } HorizonSearchIntegralTexture = GraphBuilder.CreateTexture(FRDGTextureDesc::Create2D(BufferSize, PixelFormat, FClearValueBinding::Black, TextureCreateFlags | (IsComputeShaderType(ShaderType) ? TexCreate_UAV : TexCreate_None)), TEXT("HorizonSearchIntegralTexture")); if (IsComputeShaderType(ShaderType)) { HorizonSearchIntegralTextureUAV = GraphBuilder.CreateUAV(HorizonSearchIntegralTexture); } } for (int32 ViewIndex = 0; ViewIndex < Views.Num(); ViewIndex++) { const FViewInfo& View = Views[ViewIndex]; const FFinalPostProcessSettings& Settings = View.FinalPostProcessSettings; const FIntRect& ViewRect = FIntRect::DivideAndRoundUp(View.ViewRect, DownsampleFactor); FVector4f WorldRadiusAdjSinCosDeltaAngleThickness(FallOffStartEndScaleBias.Y * DepthBufferSize.Y * View.ViewMatrices.GetProjectionMatrix().M[0][0], SinDeltaAngle, CosDeltaAngle, ThicknessBlend); if (ShaderType == EMobileAmbientOcclusionShaderType::ComputeShader) { FGTAOMobile_HorizonSearchIntegralSpatialFilterCS::FParameters* HorizonSearchIntegralSpatialFilterParameters = GraphBuilder.AllocParameters(); FGTAOMobile_HorizonSearchIntegral::SetupShaderParameters(HorizonSearchIntegralSpatialFilterParameters->Common, GraphBuilder, View, ViewRect, DepthBufferSize, BufferSize, FallOffStartEndScaleBias, WorldRadiusAdjSinCosDeltaAngleThickness, SceneDepthTexture); HorizonSearchIntegralSpatialFilterParameters->Power_Intensity_ScreenPixelsToSearch = FVector4f(Settings.AmbientOcclusionPower * 0.5f, Settings.AmbientOcclusionIntensity, 0.0f, 0.0f); HorizonSearchIntegralSpatialFilterParameters->OutTexture = AmbientOcclusionTextureUAV; auto ComputeShaderPermutationVector = FGTAOMobile_HorizonSearchIntegralSpatialFilterCS::BuildPermutationVector(MobileGTAOPreIntegratedTextureType, MobileAmbientOcclusionQuality - 1); TShaderMapRef ComputeShader(View.ShaderMap, ComputeShaderPermutationVector); FComputeShaderUtils::AddPass( GraphBuilder, RDG_EVENT_NAME("AmbientOcclusion_HorizonSearchIntegralSpatialFilter %dx%d (CS)", ViewRect.Width(), ViewRect.Height()), ERDGPassFlags::Compute | ERDGPassFlags::NeverCull, ComputeShader, HorizonSearchIntegralSpatialFilterParameters, FComputeShaderUtils::GetGroupCount(ViewRect.Size(), FGTAOMobile_HorizonSearchIntegralSpatialFilterCS::TexelsPerThreadGroup)); } else if (ShaderType == EMobileAmbientOcclusionShaderType::PixelShader) { TShaderMapRef VertexShader(View.ShaderMap); FScreenPassRenderTarget HorizonSearchIntegralRT(HorizonSearchIntegralTexture, ViewRect, ViewIndex > 0 ? ERenderTargetLoadAction::ELoad : ERenderTargetLoadAction::EClear); FGTAOMobile_HorizonSearchIntegralPS::FParameters* HorizonSearchIntegralParameters = GraphBuilder.AllocParameters(); FGTAOMobile_HorizonSearchIntegral::SetupShaderParameters(HorizonSearchIntegralParameters->Common, GraphBuilder, View, ViewRect, DepthBufferSize, BufferSize, FallOffStartEndScaleBias, WorldRadiusAdjSinCosDeltaAngleThickness, SceneDepthTexture); HorizonSearchIntegralParameters->RenderTargets[0] = HorizonSearchIntegralRT.GetRenderTargetBinding(); auto HorizonSearchIntegralShaderPermutationVector = FGTAOMobile_HorizonSearchIntegralPS::BuildPermutationVector(MobileGTAOPreIntegratedTextureType, MobileAmbientOcclusionQuality - 1); TShaderMapRef HorizonSearchIntegralShader(View.ShaderMap, HorizonSearchIntegralShaderPermutationVector); ClearUnusedGraphResources(HorizonSearchIntegralShader, HorizonSearchIntegralParameters); GraphBuilder.AddPass( RDG_EVENT_NAME("AmbientOcclusion_HorizonSearchIntegral %dx%d (PS)", ViewRect.Width(), ViewRect.Height()), HorizonSearchIntegralParameters, ERDGPassFlags::Raster | ERDGPassFlags::NeverCull, [VertexShader, HorizonSearchIntegralShader, HorizonSearchIntegralParameters, ViewRect, BufferSize](FRHICommandList& RHICmdList) { RHICmdList.SetViewport(ViewRect.Min.X, ViewRect.Min.Y, 0.0f, ViewRect.Max.X, ViewRect.Max.Y, 1.0f); FGraphicsPipelineStateInitializer GraphicsPSOInit; RHICmdList.ApplyCachedRenderTargets(GraphicsPSOInit); GraphicsPSOInit.BlendState = TStaticBlendState<>::GetRHI(); GraphicsPSOInit.RasterizerState = TStaticRasterizerState<>::GetRHI(); GraphicsPSOInit.DepthStencilState = TStaticDepthStencilState::GetRHI(); GraphicsPSOInit.BoundShaderState.VertexDeclarationRHI = GFilterVertexDeclaration.VertexDeclarationRHI; GraphicsPSOInit.BoundShaderState.VertexShaderRHI = VertexShader.GetVertexShader(); GraphicsPSOInit.BoundShaderState.PixelShaderRHI = HorizonSearchIntegralShader.GetPixelShader(); GraphicsPSOInit.PrimitiveType = PT_TriangleList; SetGraphicsPipelineState(RHICmdList, GraphicsPSOInit, 0); SetShaderParameters(RHICmdList, HorizonSearchIntegralShader, HorizonSearchIntegralShader.GetPixelShader(), *HorizonSearchIntegralParameters); DrawRectangle( RHICmdList, 0, 0, BufferSize.X, BufferSize.Y, ViewRect.Min.X, ViewRect.Min.Y, ViewRect.Width(), ViewRect.Height(), BufferSize, BufferSize, VertexShader, EDRF_UseTriangleOptimization); }); FScreenPassRenderTarget AmbientOcclusionRT(AmbientOcclusionTexture, ViewRect, ViewIndex > 0 ? ERenderTargetLoadAction::ELoad : ERenderTargetLoadAction::EClear); FGTAOMobile_SpatialFilterPS::FParameters* SpatialFilterParameters = GraphBuilder.AllocParameters(); FGTAOMobile_SpatialFilter::SetupShaderParameters(SpatialFilterParameters->Common, View, ViewRect, BufferSize, HorizonSearchIntegralTexture); SpatialFilterParameters->RenderTargets[0] = AmbientOcclusionRT.GetRenderTargetBinding(); TShaderMapRef SpatialFilterShader(View.ShaderMap); ClearUnusedGraphResources(SpatialFilterShader, SpatialFilterParameters); GraphBuilder.AddPass( RDG_EVENT_NAME("AmbientOcclusion_SpatialFilter %dx%d (PS)", ViewRect.Width(), ViewRect.Height()), SpatialFilterParameters, ERDGPassFlags::Raster | ERDGPassFlags::NeverCull, [VertexShader, SpatialFilterShader, SpatialFilterParameters, ViewRect, BufferSize](FRHICommandList& RHICmdList) { RHICmdList.SetViewport(ViewRect.Min.X, ViewRect.Min.Y, 0.0f, ViewRect.Max.X, ViewRect.Max.Y, 1.0f); FGraphicsPipelineStateInitializer GraphicsPSOInit; RHICmdList.ApplyCachedRenderTargets(GraphicsPSOInit); GraphicsPSOInit.BlendState = TStaticBlendState<>::GetRHI(); GraphicsPSOInit.RasterizerState = TStaticRasterizerState<>::GetRHI(); GraphicsPSOInit.DepthStencilState = TStaticDepthStencilState::GetRHI(); GraphicsPSOInit.BoundShaderState.VertexDeclarationRHI = GFilterVertexDeclaration.VertexDeclarationRHI; GraphicsPSOInit.BoundShaderState.VertexShaderRHI = VertexShader.GetVertexShader(); GraphicsPSOInit.BoundShaderState.PixelShaderRHI = SpatialFilterShader.GetPixelShader(); GraphicsPSOInit.PrimitiveType = PT_TriangleList; SetGraphicsPipelineState(RHICmdList, GraphicsPSOInit, 0); SetShaderParameters(RHICmdList, SpatialFilterShader, SpatialFilterShader.GetPixelShader(), *SpatialFilterParameters); DrawRectangle( RHICmdList, 0, 0, BufferSize.X, BufferSize.Y, ViewRect.Min.X, ViewRect.Min.Y, ViewRect.Width(), ViewRect.Height(), BufferSize, BufferSize, VertexShader, EDRF_UseTriangleOptimization); }); } else { FGTAOMobile_HorizonSearchIntegralCS::FParameters* HorizonSearchIntegralParameters = GraphBuilder.AllocParameters(); FGTAOMobile_HorizonSearchIntegral::SetupShaderParameters(HorizonSearchIntegralParameters->Common, GraphBuilder, View, ViewRect, DepthBufferSize, BufferSize, FallOffStartEndScaleBias, WorldRadiusAdjSinCosDeltaAngleThickness, SceneDepthTexture); HorizonSearchIntegralParameters->OutTexture = HorizonSearchIntegralTextureUAV; auto HorizonSearchIntegralShaderPermutationVector = FGTAOMobile_HorizonSearchIntegralCS::BuildPermutationVector(MobileGTAOPreIntegratedTextureType, MobileAmbientOcclusionQuality - 1); TShaderMapRef HorizonSearchIntegralShader(View.ShaderMap, HorizonSearchIntegralShaderPermutationVector); FComputeShaderUtils::AddPass( GraphBuilder, RDG_EVENT_NAME("AmbientOcclusion_HorizonSearchIntegral %dx%d (CS)", ViewRect.Width(), ViewRect.Height()), ERDGPassFlags::Compute | ERDGPassFlags::NeverCull, HorizonSearchIntegralShader, HorizonSearchIntegralParameters, FComputeShaderUtils::GetGroupCount(ViewRect.Size(), FGTAOMobile_HorizonSearchIntegralCS::TexelsPerThreadGroup)); FGTAOMobile_SpatialFilterCS::FParameters* SpatialFilterParameters = GraphBuilder.AllocParameters(); FGTAOMobile_SpatialFilter::SetupShaderParameters(SpatialFilterParameters->Common, View, ViewRect, BufferSize, HorizonSearchIntegralTexture); SpatialFilterParameters->OutTexture = AmbientOcclusionTextureUAV; TShaderMapRef SpatialFilterShader(View.ShaderMap); FComputeShaderUtils::AddPass( GraphBuilder, RDG_EVENT_NAME("AmbientOcclusion_SpatialFilter %dx%d (CS)", ViewRect.Width(), ViewRect.Height()), ERDGPassFlags::Compute | ERDGPassFlags::NeverCull, SpatialFilterShader, SpatialFilterParameters, FComputeShaderUtils::GetGroupCount(ViewRect.Size(), FGTAOMobile_SpatialFilterCS::TexelsPerThreadGroup)); } if (View.ViewState && !View.bStatePrevViewInfoIsReadOnly) { GraphBuilder.QueueTextureExtraction(AmbientOcclusionTexture, &View.ViewState->PrevFrameViewInfo.MobileAmbientOcclusion); } } } // -------------------------------------------------------------------------------------------------------------------- struct FMobileSSAOCommonParameters { TRDGUniformBufferRef SceneTexturesUniformBufferRDG; FScreenPassTextureViewport SceneTexturesViewport; FScreenPassTexture SceneDepth; }; static const uint32 kMobileSSAOParametersArraySize = 5; BEGIN_SHADER_PARAMETER_STRUCT(FMobileSSAOShaderParameters, ) SHADER_PARAMETER_ARRAY(FVector4f, ScreenSpaceAOParams, [kMobileSSAOParametersArraySize]) SHADER_PARAMETER_STRUCT(FScreenPassTextureViewportParameters, AOViewport) SHADER_PARAMETER_STRUCT(FScreenPassTextureViewportParameters, AOSceneViewport) END_SHADER_PARAMETER_STRUCT(); BEGIN_SHADER_PARAMETER_STRUCT(FMobileAmbientOcclusionParameters, RENDERER_API) SHADER_PARAMETER_STRUCT_REF(FViewUniformShaderParameters, View) SHADER_PARAMETER_RDG_UNIFORM_BUFFER(FMobileSceneTextureUniformParameters, SceneTextures) SHADER_PARAMETER_STRUCT_INCLUDE(FHZBParameters, HZBParameters) SHADER_PARAMETER_STRUCT_INCLUDE(FMobileSSAOShaderParameters, SSAOParameters) SHADER_PARAMETER_SAMPLER(SamplerState, SSAO_Sampler) SHADER_PARAMETER(FVector2f, SSAO_DownsampledAOInverseSize) SHADER_PARAMETER(FVector2f, SSAO_SvPositionScaleBias) SHADER_PARAMETER_RDG_TEXTURE(Texture2D, RandomNormalTexture) SHADER_PARAMETER_SAMPLER(SamplerState, RandomNormalTextureSampler) END_SHADER_PARAMETER_STRUCT(); class FMobileAmbientOcclusionPS : public FGlobalShader { public: DECLARE_GLOBAL_SHADER(FMobileAmbientOcclusionPS); SHADER_USE_PARAMETER_STRUCT(FMobileAmbientOcclusionPS, FGlobalShader); class FShaderQualityDim : SHADER_PERMUTATION_INT("SHADER_QUALITY", 5); class FOutputDepth : SHADER_PERMUTATION_BOOL("OUTPUT_DEPTH"); using FPermutationDomain = TShaderPermutationDomain; static void ModifyCompilationEnvironment(const FGlobalShaderPermutationParameters& Parameters, FShaderCompilerEnvironment& OutEnvironment) { FGlobalShader::ModifyCompilationEnvironment(Parameters, OutEnvironment); OutEnvironment.SetDefine(TEXT("SSAO"), 1); OutEnvironment.SetDefine(TEXT("FORCE_DEPTH_TEXTURE_READS"), 1); } static bool ShouldCompilePermutation(const FGlobalShaderPermutationParameters& Parameters) { return IsMobileAmbientOcclusionEnabled(Parameters.Platform); } BEGIN_SHADER_PARAMETER_STRUCT(FParameters, ) SHADER_PARAMETER_STRUCT_INCLUDE(FMobileAmbientOcclusionParameters, SharedParameters) RENDER_TARGET_BINDING_SLOTS() END_SHADER_PARAMETER_STRUCT(); }; IMPLEMENT_GLOBAL_SHADER(FMobileAmbientOcclusionPS, "/Engine/Private/PostProcessAmbientOcclusionMobile.usf", "MainPS", SF_Pixel); class FMobileAmbientOcclusionUpsamplePS : public FGlobalShader { DECLARE_GLOBAL_SHADER(FMobileAmbientOcclusionUpsamplePS); SHADER_USE_PARAMETER_STRUCT(FMobileAmbientOcclusionUpsamplePS, FGlobalShader); class FUpsampleQualityDim : SHADER_PERMUTATION_INT("UPSAMPLE_QUALITY", 3); using FPermutationDomain = TShaderPermutationDomain ; BEGIN_SHADER_PARAMETER_STRUCT(FParameters, ) SHADER_PARAMETER_RDG_UNIFORM_BUFFER(FMobileSceneTextureUniformParameters, SceneTextures) SHADER_PARAMETER_STRUCT_REF(FViewUniformShaderParameters, View) SHADER_PARAMETER_RDG_TEXTURE(Texture2D, AOTexture) SHADER_PARAMETER_SAMPLER(SamplerState, AOSampler) RENDER_TARGET_BINDING_SLOTS() END_SHADER_PARAMETER_STRUCT() static bool ShouldCompilePermutation(const FGlobalShaderPermutationParameters& Parameters) { return IsMobileAmbientOcclusionEnabled(Parameters.Platform); } static void ModifyCompilationEnvironment(const FGlobalShaderPermutationParameters& Parameters, FShaderCompilerEnvironment& OutEnvironment) { OutEnvironment.SetDefine(TEXT("UPSAMPLE_PASS"), 1); } }; IMPLEMENT_GLOBAL_SHADER(FMobileAmbientOcclusionUpsamplePS, "/Engine/Private/PostProcessAmbientOcclusionMobile.usf", "AmbientOcclusionUpsamplePS", SF_Pixel); // -------------------------------------------------------------------------------------------------------------------- static FMobileSSAOCommonParameters GetMobileSSAOCommonParameters( FRDGBuilder& GraphBuilder, const FViewInfo& View, FRDGTextureRef SceneDepthTexture, TRDGUniformBufferRef SceneTexturesUniformBufferRDG) { FMobileSSAOCommonParameters CommonParameters; CommonParameters.SceneTexturesUniformBufferRDG = SceneTexturesUniformBufferRDG; CommonParameters.SceneTexturesViewport = FScreenPassTextureViewport(SceneDepthTexture, View.ViewRect); CommonParameters.SceneDepth = FScreenPassTexture(SceneDepthTexture); return CommonParameters; } static FMobileSSAOShaderParameters GetMobileSSAOShaderParameters( const FViewInfo& View, const FScreenPassTextureViewport& InputViewport, const FScreenPassTextureViewport& OutputViewport, const FScreenPassTextureViewport& SceneViewport) { const FFinalPostProcessSettings& Settings = View.FinalPostProcessSettings; FIntPoint RandomizationSize = GSystemTextures.SSAORandomization->GetDesc().Extent * InputViewport.Extent / OutputViewport.Extent; FVector2D ViewportUVToRandomUV(InputViewport.Extent.X / (float)RandomizationSize.X, InputViewport.Extent.Y / (float)RandomizationSize.Y); // e.g. 4 means the input texture is 4x smaller than the buffer size uint32 ScaleToFullRes = SceneViewport.Extent.X / InputViewport.Extent.X; FIntRect ViewRect = FIntRect::DivideAndRoundUp(View.ViewRect, ScaleToFullRes); float AORadiusInShader = Settings.AmbientOcclusionRadius; float ScaleRadiusInWorldSpace = 1.0f; if (!Settings.AmbientOcclusionRadiusInWS) { // radius is defined in view space in 400 units AORadiusInShader /= 400.0f; ScaleRadiusInWorldSpace = 0.0f; } // /4 is an adjustment for usage with multiple mips float f = FMath::Log2(float(ScaleToFullRes)); float g = pow(Settings.AmbientOcclusionMipScale, f); AORadiusInShader *= pow(Settings.AmbientOcclusionMipScale, FMath::Log2(float(ScaleToFullRes))) / 4.0f; float Ratio = View.UnscaledViewRect.Width() / (float)View.UnscaledViewRect.Height(); // Grab this and pass into shader so we can negate the fov influence of projection on the screen pos. float InvTanHalfFov = View.ViewMatrices.GetProjectionMatrix().M[0][0]; float StaticFraction = FMath::Clamp(Settings.AmbientOcclusionStaticFraction, 0.0f, 1.0f); // clamp to prevent user error float FadeRadius = FMath::Max(1.0f, Settings.AmbientOcclusionFadeRadius); float InvFadeRadius = 1.0f / FadeRadius; FVector2D TemporalOffset(0.0f, 0.0f); if (View.State) { TemporalOffset = (View.State->GetCurrentTemporalAASampleIndex() % 8) * FVector2D(2.48f, 7.52f) / (float)RandomizationSize.X; } const float HzbStepMipLevelFactorValue = FMath::Clamp(FSSAOHelper::GetAmbientOcclusionStepMipLevelFactor(), 0.0f, 100.0f); const float InvAmbientOcclusionDistance = 1.0f / FMath::Max(Settings.AmbientOcclusionDistance_DEPRECATED, KINDA_SMALL_NUMBER); FMobileSSAOShaderParameters Result{}; // /1000 to be able to define the value in that distance Result.ScreenSpaceAOParams[0] = FVector4f(Settings.AmbientOcclusionPower, Settings.AmbientOcclusionBias / 1000.0f, InvAmbientOcclusionDistance, Settings.AmbientOcclusionIntensity); Result.ScreenSpaceAOParams[1] = FVector4f(ViewportUVToRandomUV.X, ViewportUVToRandomUV.Y, AORadiusInShader, Ratio); Result.ScreenSpaceAOParams[2] = FVector4f(ScaleToFullRes, Settings.AmbientOcclusionMipThreshold / ScaleToFullRes, ScaleRadiusInWorldSpace, Settings.AmbientOcclusionMipBlend); Result.ScreenSpaceAOParams[3] = FVector4f(TemporalOffset.X, TemporalOffset.Y, StaticFraction, InvTanHalfFov); Result.ScreenSpaceAOParams[4] = FVector4f(InvFadeRadius, -(Settings.AmbientOcclusionFadeDistance - FadeRadius) * InvFadeRadius, HzbStepMipLevelFactorValue, Settings.AmbientOcclusionFadeDistance); Result.AOViewport = GetScreenPassTextureViewportParameters(OutputViewport); Result.AOSceneViewport = GetScreenPassTextureViewportParameters(SceneViewport); return Result; } static void AddMobileAmbientOcclusionPass( FRDGBuilder& GraphBuilder, const FViewInfo& View, const FMobileSSAOCommonParameters& CommonParameters, bool bHalfResolution, bool bOutputDepth, FScreenPassRenderTarget Output) { check(Output.IsValid()); const FScreenPassTextureViewport InputViewport = CommonParameters.SceneTexturesViewport; const FScreenPassTextureViewport OutputViewport = GetDownscaledViewport(CommonParameters.SceneTexturesViewport, bHalfResolution ? 2 : 1); const bool bDepthBoundsTestEnabled = CVarMobileAmbientOcclusionDepthBoundsTest.GetValueOnRenderThread() && !bHalfResolution && GSupportsDepthBoundsTest && CommonParameters.SceneDepth.IsValid() && CommonParameters.SceneDepth.Texture->Desc.NumSamples == 1; float DepthFar = 0.0f; FDepthStencilBinding DepthStencilBinding(CommonParameters.SceneDepth.Texture, ERenderTargetLoadAction::ELoad, ERenderTargetLoadAction::ELoad, FExclusiveDepthStencil::DepthRead_StencilWrite); if (bDepthBoundsTestEnabled) { const FFinalPostProcessSettings& Settings = View.FinalPostProcessSettings; const FMatrix& ProjectionMatrix = View.ViewMatrices.GetProjectionMatrix(); const FVector4f Far = (FVector4f)ProjectionMatrix.TransformFVector4(FVector4(0, 0, Settings.AmbientOcclusionFadeDistance)); DepthFar = FMath::Min(1.0f, Far.Z / Far.W); static_assert(bool(ERHIZBuffer::IsInverted), "Inverted depth buffer is assumed when setting depth bounds test for AO."); FRenderTargetParameters* ClearParameters = GraphBuilder.AllocParameters(); ClearParameters->RenderTargets[0] = Output.GetRenderTargetBinding(); ClearParameters->RenderTargets.DepthStencil = DepthStencilBinding; GraphBuilder.AddPass( RDG_EVENT_NAME("DepthBounds ClearQuad(%s)", Output.Texture->Name), ClearParameters, ERDGPassFlags::Raster, [OutputViewport, DepthFar](FRHICommandListImmediate& RHICmdList) { // We must clear all pixels that won't be touched by AO shader. FClearQuadCallbacks Callbacks; Callbacks.PSOModifier = [](FGraphicsPipelineStateInitializer& PSOInitializer) { PSOInitializer.bDepthBounds = true; }; Callbacks.PreClear = [DepthFar](FRHICommandList& InRHICmdList) { // This is done by rendering a clear quad over a depth range from AmbientOcclusionFadeDistance to far plane. InRHICmdList.SetDepthBounds(0, DepthFar); // NOTE: Inverted depth }; Callbacks.PostClear = [DepthFar](FRHICommandList& InRHICmdList) { // Set depth bounds test to cover everything from near plane to AmbientOcclusionFadeDistance and run AO pixel shader. InRHICmdList.SetDepthBounds(DepthFar, 1.0f); }; RHICmdList.SetViewport(OutputViewport.Rect.Min.X, OutputViewport.Rect.Min.Y, 0.0f, OutputViewport.Rect.Max.X, OutputViewport.Rect.Max.Y, 1.0f); DrawClearQuad(RHICmdList, FLinearColor::White, Callbacks); }); // Make sure the following pass doesn't clear or ignore the data Output.LoadAction = ERenderTargetLoadAction::ELoad; } FMobileAmbientOcclusionParameters SharedParameters; SharedParameters.View = View.ViewUniformBuffer; SharedParameters.SceneTextures = CommonParameters.SceneTexturesUniformBufferRDG; SharedParameters.HZBParameters = GetHZBParametersForAO(GraphBuilder, View, CommonParameters.SceneTexturesViewport.Extent, EAOTechnique::SSAO); SharedParameters.SSAOParameters = GetMobileSSAOShaderParameters(View, InputViewport, OutputViewport, CommonParameters.SceneTexturesViewport); SharedParameters.SSAO_Sampler = TStaticSamplerState::GetRHI(); SharedParameters.SSAO_SvPositionScaleBias = bHalfResolution ? FVector2f(2, -0.5) : FVector2f(1, 0); SharedParameters.SSAO_DownsampledAOInverseSize = bHalfResolution ? FVector2f(1, 1) / FVector2f(OutputViewport.Extent) : FVector2f(1, 1); SharedParameters.RandomNormalTexture = GraphBuilder.RegisterExternalTexture(GSystemTextures.SSAORandomization, TEXT("SSAORandomization")); SharedParameters.RandomNormalTextureSampler = TStaticSamplerState::GetRHI(); FRDGEventName EventName(TEXT("AmbientOcclusionPS %dx%d"), OutputViewport.Rect.Width(), OutputViewport.Rect.Height()); FMobileAmbientOcclusionPS::FParameters* PassParameters = GraphBuilder.AllocParameters(); PassParameters->SharedParameters = MoveTemp(SharedParameters); PassParameters->RenderTargets[0] = Output.GetRenderTargetBinding(); if (bDepthBoundsTestEnabled) { PassParameters->RenderTargets.DepthStencil = DepthStencilBinding; } const int32 MobileAmbientOcclusionQuality = FMath::Min(CVarMobileAmbientOcclusionQuality.GetValueOnRenderThread(), 3) - 1; FMobileAmbientOcclusionPS::FPermutationDomain PermutationVector; PermutationVector.Set(MobileAmbientOcclusionQuality); PermutationVector.Set(bOutputDepth); TShaderMapRef PixelShader(View.ShaderMap, PermutationVector); TShaderMapRef VertexShader(View.ShaderMap); check(PassParameters); ClearUnusedGraphResources(PixelShader, PassParameters); GraphBuilder.AddPass( MoveTemp(EventName), PassParameters, ERDGPassFlags::Raster, [&View, OutputViewport, InputViewport, VertexShader, PixelShader, PassParameters, bDepthBoundsTestEnabled, DepthFar](FRHICommandListImmediate& RHICmdList) { const FIntRect InputRect = InputViewport.Rect; const FIntPoint InputSize = InputViewport.Extent; const FIntRect OutputRect = OutputViewport.Rect; const FIntPoint OutputSize = OutputRect.Size(); RHICmdList.SetViewport(OutputRect.Min.X, OutputRect.Min.Y, 0.0f, OutputRect.Max.X, OutputRect.Max.Y, 1.0f); FGraphicsPipelineStateInitializer GraphicsPSOInit; RHICmdList.ApplyCachedRenderTargets(GraphicsPSOInit); GraphicsPSOInit.BlendState = TStaticBlendState<>::GetRHI(); GraphicsPSOInit.RasterizerState = TStaticRasterizerState::GetRHI(); GraphicsPSOInit.DepthStencilState = TStaticDepthStencilState::GetRHI(); GraphicsPSOInit.BoundShaderState.VertexDeclarationRHI = GFilterVertexDeclaration.VertexDeclarationRHI; GraphicsPSOInit.BoundShaderState.VertexShaderRHI = VertexShader.GetVertexShader(); GraphicsPSOInit.BoundShaderState.PixelShaderRHI = PixelShader.GetPixelShader(); GraphicsPSOInit.PrimitiveType = PT_TriangleList; GraphicsPSOInit.bDepthBounds = bDepthBoundsTestEnabled; SetGraphicsPipelineState(RHICmdList, GraphicsPSOInit, 0); SetShaderParameters(RHICmdList, PixelShader, PixelShader.GetPixelShader(), *PassParameters); if (bDepthBoundsTestEnabled) { RHICmdList.SetDepthBounds(DepthFar, 1.0f); } DrawPostProcessPass( RHICmdList, 0, 0, OutputSize.X, OutputSize.Y, InputRect.Min.X, InputRect.Min.Y, InputRect.Width(), InputRect.Height(), OutputSize, InputSize, VertexShader, View.StereoViewIndex, false, EDRF_UseTriangleOptimization); if (bDepthBoundsTestEnabled) { RHICmdList.SetDepthBounds(0, 1.0f); } }); } static void AddMobileAmbientOcclusionUpsamplePass( FRDGBuilder& GraphBuilder, const FViewInfo& View, const FMobileSSAOCommonParameters& CommonParameters, int32 UpsampleQuality, FRDGTextureRef SourceTexture, FScreenPassRenderTarget Output) { const bool bDepthBoundsTestEnabled = CVarMobileAmbientOcclusionDepthBoundsTest.GetValueOnRenderThread() && GSupportsDepthBoundsTest && CommonParameters.SceneDepth.IsValid() && CommonParameters.SceneDepth.Texture->Desc.NumSamples == 1; const FScreenPassTextureViewport OutputViewport = CommonParameters.SceneTexturesViewport; float DepthFar = 0.0f; FDepthStencilBinding DepthStencilBinding(CommonParameters.SceneDepth.Texture, ERenderTargetLoadAction::ELoad, ERenderTargetLoadAction::ELoad, FExclusiveDepthStencil::DepthRead_StencilWrite); if (bDepthBoundsTestEnabled) { const FFinalPostProcessSettings& Settings = View.FinalPostProcessSettings; const FMatrix& ProjectionMatrix = View.ViewMatrices.GetProjectionMatrix(); const FVector4f Far = (FVector4f)ProjectionMatrix.TransformFVector4(FVector4(0, 0, Settings.AmbientOcclusionFadeDistance)); DepthFar = FMath::Min(1.0f, Far.Z / Far.W); static_assert(bool(ERHIZBuffer::IsInverted), "Inverted depth buffer is assumed when setting depth bounds test for AO."); FRenderTargetParameters* ClearParameters = GraphBuilder.AllocParameters(); ClearParameters->RenderTargets[0] = Output.GetRenderTargetBinding(); ClearParameters->RenderTargets.DepthStencil = DepthStencilBinding; GraphBuilder.AddPass( RDG_EVENT_NAME("DepthBounds ClearQuad(%s)", Output.Texture->Name), ClearParameters, ERDGPassFlags::Raster, [OutputViewport, DepthFar](FRHICommandListImmediate& RHICmdList) { RHICmdList.SetViewport(OutputViewport.Rect.Min.X, OutputViewport.Rect.Min.Y, 0.0f, OutputViewport.Rect.Max.X, OutputViewport.Rect.Max.Y, 1.0f); // We must clear all pixels that won't be touched by AO shader. FClearQuadCallbacks Callbacks; Callbacks.PSOModifier = [](FGraphicsPipelineStateInitializer& PSOInitializer) { PSOInitializer.bDepthBounds = true; }; Callbacks.PreClear = [DepthFar](FRHICommandList& InRHICmdList) { // This is done by rendering a clear quad over a depth range from AmbientOcclusionFadeDistance to far plane. InRHICmdList.SetDepthBounds(0, DepthFar); // NOTE: Inverted depth }; Callbacks.PostClear = [DepthFar](FRHICommandList& InRHICmdList) { // Set depth bounds test to cover everything from near plane to AmbientOcclusionFadeDistance and run AO pixel shader. InRHICmdList.SetDepthBounds(DepthFar, 1.0f); }; DrawClearQuad(RHICmdList, FLinearColor::White, Callbacks); }); // Make sure the following pass doesn't clear or ignore the data Output.LoadAction = ERenderTargetLoadAction::ELoad; } FMobileAmbientOcclusionUpsamplePS::FParameters* PassParameters = GraphBuilder.AllocParameters(); PassParameters->RenderTargets[0] = Output.GetRenderTargetBinding(); if (bDepthBoundsTestEnabled) { PassParameters->RenderTargets.DepthStencil = DepthStencilBinding; } PassParameters->View = GetShaderBinding(View.ViewUniformBuffer); PassParameters->SceneTextures = CommonParameters.SceneTexturesUniformBufferRDG; PassParameters->AOTexture = SourceTexture; PassParameters->AOSampler = TStaticSamplerState::GetRHI(); typename FMobileAmbientOcclusionUpsamplePS::FPermutationDomain PermutationVector; PermutationVector.Set(UpsampleQuality); TShaderMapRef PixelShader(View.ShaderMap, PermutationVector); TShaderMapRef VertexShader(View.ShaderMap); ClearUnusedGraphResources(PixelShader, PassParameters); GraphBuilder.AddPass( RDG_EVENT_NAME("UpsamplePS Quality %d", UpsampleQuality), PassParameters, ERDGPassFlags::Raster, [PassParameters, &View, OutputViewport, VertexShader, PixelShader, bDepthBoundsTestEnabled, DepthFar](FRHICommandList& RHICmdList) { RHICmdList.SetViewport(OutputViewport.Rect.Min.X, OutputViewport.Rect.Min.Y, 0.0f, OutputViewport.Rect.Max.X, OutputViewport.Rect.Max.Y, 1.0f); FGraphicsPipelineStateInitializer GraphicsPSOInit; FPixelShaderUtils::InitFullscreenPipelineState(RHICmdList, View.ShaderMap, PixelShader, GraphicsPSOInit); GraphicsPSOInit.bDepthBounds = bDepthBoundsTestEnabled; SetGraphicsPipelineState(RHICmdList, GraphicsPSOInit, 0); SetShaderParameters(RHICmdList, PixelShader, PixelShader.GetPixelShader(), *PassParameters); if (bDepthBoundsTestEnabled) { RHICmdList.SetDepthBounds(DepthFar, 1.0f); } FPixelShaderUtils::DrawFullscreenTriangle(RHICmdList); if (bDepthBoundsTestEnabled) { RHICmdList.SetDepthBounds(0.0f, 1.0f); } }); } static void RenderSSAO(FRDGBuilder& GraphBuilder, FRDGTextureRef SceneDepthTexture, FRDGTextureRef AmbientOcclusionTexture, const TArray& Views) { RDG_EVENT_SCOPE_STAT(GraphBuilder, MobileSSAO, "MobileSSAO"); RDG_GPU_STAT_SCOPE(GraphBuilder, MobileSSAO); const int32 HalfResolutionSetting = CVarMobileSSAOHalfResolution.GetValueOnRenderThread(); const bool bHalfResolution = HalfResolutionSetting > 0; const int32 UpsampleQuality = FMath::Clamp(HalfResolutionSetting - 1, 0, 2); const bool bBilateralUpsample = UpsampleQuality > 0; FRDGTextureRef HalfResolutionTexture = nullptr; if (bHalfResolution) { // Bilateral requires 32bit format for AO + depth. const EPixelFormat Format = bBilateralUpsample ? PF_R8G8B8A8 : AmbientOcclusionTexture->Desc.Format; const FIntPoint Extent = GetDownscaledExtent(AmbientOcclusionTexture->Desc.Extent, 2); HalfResolutionTexture = GraphBuilder.CreateTexture( FRDGTextureDesc::Create2D(Extent, Format, FClearValueBinding::None, TexCreate_ShaderResource | TexCreate_RenderTargetable), TEXT("HalfResolutionScreenSpaceAO")); } TRDGUniformBufferRef SceneTexturesUniformBufferRDG = CreateMobileSceneTextureUniformBuffer( GraphBuilder, GetViewFamilyInfo(Views).GetSceneTexturesChecked(), EMobileSceneTextureSetupMode::SceneDepth); for (FViewInfo const& View : Views) { FMobileSSAOCommonParameters Parameters = GetMobileSSAOCommonParameters(GraphBuilder, View, SceneDepthTexture, SceneTexturesUniformBufferRDG); FScreenPassRenderTarget FinalTarget = FScreenPassRenderTarget(AmbientOcclusionTexture, View.ViewRect, ERenderTargetLoadAction::ENoAction); if (HalfResolutionTexture != nullptr) { FScreenPassRenderTarget HalfResolutionTarget = FScreenPassRenderTarget(HalfResolutionTexture, View.ViewRect, ERenderTargetLoadAction::ENoAction); AddMobileAmbientOcclusionPass(GraphBuilder, View, Parameters, bHalfResolution, bBilateralUpsample, HalfResolutionTarget); AddMobileAmbientOcclusionUpsamplePass(GraphBuilder, View, Parameters, UpsampleQuality, HalfResolutionTexture, FinalTarget); } else { AddMobileAmbientOcclusionPass(GraphBuilder, View, Parameters, bHalfResolution, bBilateralUpsample, FinalTarget); } } } // -------------------------------------------------------------------------------------------------------------------- void FMobileSceneRenderer::RenderAmbientOcclusion(FRDGBuilder& GraphBuilder, FRDGTextureRef SceneDepthTexture, FRDGTextureRef AmbientOcclusionTexture) { const int32 Technique = CVarMobileAmbientOcclusionTechnique.GetValueOnRenderThread(); switch (Technique) { case 0: RenderGTAO(GraphBuilder, SceneDepthTexture, AmbientOcclusionTexture, Views); break; case 1: RenderSSAO(GraphBuilder, SceneDepthTexture, AmbientOcclusionTexture, Views); break; } }