// Copyright 1998-2015 Epic Games, Inc. All Rights Reserved. /*============================================================================= ScreenSpaceReflections.cpp: Post processing Screen Space Reflections implementation. =============================================================================*/ #include "RendererPrivate.h" #include "ScenePrivate.h" #include "SceneFilterRendering.h" #include "PostProcessing.h" #include "ScreenSpaceReflections.h" #include "PostProcessTemporalAA.h" #include "PostProcessAmbientOcclusion.h" #include "SceneUtils.h" static TAutoConsoleVariable CVarSSRQuality( TEXT("r.SSR.Quality"), 3, TEXT("Whether to use screen space reflections and at what quality setting.\n") TEXT("(limits the setting in the post process settings which has a different scale)\n") TEXT("(costs performance, adds more visual realism but the technique has limits)\n") TEXT(" 0: off (default)\n") TEXT(" 1: low (no glossy)\n") TEXT(" 2: medium (no glossy)\n") TEXT(" 3: high (glossy/using roughness, few samples)\n") TEXT(" 4: very high (likely too slow for real-time)"), ECVF_Scalability | ECVF_RenderThreadSafe); static TAutoConsoleVariable CVarSSRTemporal( TEXT("r.SSR.Temporal"), 0, TEXT("Defines if we use the temporal smoothing for the screen space reflection\n") TEXT(" 0 is off (for debugging), 1 is on (default)"), ECVF_RenderThreadSafe); bool DoScreenSpaceReflections(const FViewInfo& View) { if(!View.Family->EngineShowFlags.ScreenSpaceReflections) { return false; } if(!View.State) { // not view state (e.g. thumbnail rendering?), no HZB (no screen space reflections or occlusion culling) return false; } int SSRQuality = CVarSSRQuality.GetValueOnRenderThread(); if(SSRQuality <= 0) { return false; } if(View.FinalPostProcessSettings.ScreenSpaceReflectionIntensity < 1.0f) { return false; } return true; } /** * Encapsulates the post processing screen space reflections pixel shader. * @param SSRQuality 0:Visualize Mask */ template class FPostProcessScreenSpaceReflectionsPS : public FGlobalShader { DECLARE_SHADER_TYPE(FPostProcessScreenSpaceReflectionsPS, Global); static bool ShouldCache(EShaderPlatform Platform) { return IsFeatureLevelSupported(Platform, ERHIFeatureLevel::SM4); } static void ModifyCompilationEnvironment(EShaderPlatform Platform, FShaderCompilerEnvironment& OutEnvironment) { FGlobalShader::ModifyCompilationEnvironment(Platform, OutEnvironment); OutEnvironment.SetDefine( TEXT("PREV_FRAME_COLOR"), PrevFrame ); OutEnvironment.SetDefine( TEXT("SSR_QUALITY"), SSRQuality ); } /** Default constructor. */ FPostProcessScreenSpaceReflectionsPS() {} public: FPostProcessPassParameters PostprocessParameter; FDeferredPixelShaderParameters DeferredParameters; FShaderParameter SSRParams; /** Initialization constructor. */ FPostProcessScreenSpaceReflectionsPS(const ShaderMetaType::CompiledShaderInitializerType& Initializer) : FGlobalShader(Initializer) { PostprocessParameter.Bind(Initializer.ParameterMap); DeferredParameters.Bind(Initializer.ParameterMap); SSRParams.Bind(Initializer.ParameterMap, TEXT("SSRParams")); } void SetParameters(const FRenderingCompositePassContext& Context) { const FFinalPostProcessSettings& Settings = Context.View.FinalPostProcessSettings; const FPixelShaderRHIParamRef ShaderRHI = GetPixelShader(); FGlobalShader::SetParameters(Context.RHICmdList, ShaderRHI, Context.View); PostprocessParameter.SetPS(ShaderRHI, Context, TStaticSamplerState::GetRHI()); DeferredParameters.Set(Context.RHICmdList, ShaderRHI, Context.View); { float MaxRoughness = FMath::Clamp(Context.View.FinalPostProcessSettings.ScreenSpaceReflectionMaxRoughness, 0.01f, 1.0f); // f(x) = x * Scale + Bias // f(MaxRoughness) = 0 // f(MaxRoughness/2) = 1 float RoughnessMaskScale = -2.0f / MaxRoughness; RoughnessMaskScale *= SSRQuality < 3 ? 2.0f : 1.0f; FLinearColor Value( FMath::Clamp(Context.View.FinalPostProcessSettings.ScreenSpaceReflectionIntensity * 0.01f, 0.0f, 1.0f), RoughnessMaskScale, 0, 0); SetShaderValue(Context.RHICmdList, ShaderRHI, SSRParams, Value); } } // FShader interface. virtual bool Serialize(FArchive& Ar) override { bool bShaderHasOutdatedParameters = FGlobalShader::Serialize(Ar); Ar << PostprocessParameter << DeferredParameters << SSRParams; return bShaderHasOutdatedParameters; } }; // Typedef is necessary because the C preprocessor thinks the comma in the template parameter list is a comma in the macro parameter list. #define IMPLEMENT_REFLECTION_PIXELSHADER_TYPE(A, B) \ typedef FPostProcessScreenSpaceReflectionsPS FPostProcessScreenSpaceReflectionsPS##A##B; \ IMPLEMENT_SHADER_TYPE(template<>,FPostProcessScreenSpaceReflectionsPS##A##B,TEXT("ScreenSpaceReflections"),TEXT("ScreenSpaceReflectionsPS"),SF_Pixel) IMPLEMENT_REFLECTION_PIXELSHADER_TYPE(0,0); IMPLEMENT_REFLECTION_PIXELSHADER_TYPE(0,1); IMPLEMENT_REFLECTION_PIXELSHADER_TYPE(1,1); IMPLEMENT_REFLECTION_PIXELSHADER_TYPE(0,2); IMPLEMENT_REFLECTION_PIXELSHADER_TYPE(1,2); IMPLEMENT_REFLECTION_PIXELSHADER_TYPE(0,3); IMPLEMENT_REFLECTION_PIXELSHADER_TYPE(1,3); IMPLEMENT_REFLECTION_PIXELSHADER_TYPE(0,4); IMPLEMENT_REFLECTION_PIXELSHADER_TYPE(1,4); // -------------------------------------------------------- // @param Quality usually in 0..100 range, default is 50 // @return see CVarSSRQuality, never 0 static int32 ComputeSSRQuality(float Quality) { int32 Ret; if(Quality >= 60.0f) { Ret = (Quality >= 80.0f) ? 4 : 3; } else { Ret = (Quality >= 40.0f) ? 2 : 1; } int SSRQualityCVar = FMath::Max(0, CVarSSRQuality.GetValueOnRenderThread()); return FMath::Min(Ret, SSRQualityCVar); } void FRCPassPostProcessScreenSpaceReflections::Process(FRenderingCompositePassContext& Context) { SCOPED_DRAW_EVENT(Context.RHICmdList, ScreenSpaceReflections); const FSceneView& View = Context.View; const auto FeatureLevel = Context.GetFeatureLevel(); const FSceneRenderTargetItem& DestRenderTarget = PassOutputs[0].RequestSurface(Context); // Set the view family's render target/viewport. SetRenderTarget(Context.RHICmdList, DestRenderTarget.TargetableTexture, FTextureRHIRef()); Context.RHICmdList.Clear(true, FLinearColor(0, 0, 0, 0), false, 1.0f, false, 0, FIntRect()); Context.SetViewportAndCallRHI(View.ViewRect); // set the state Context.RHICmdList.SetBlendState(TStaticBlendState<>::GetRHI()); Context.RHICmdList.SetRasterizerState(TStaticRasterizerState<>::GetRHI()); Context.RHICmdList.SetDepthStencilState(TStaticDepthStencilState::GetRHI()); int SSRQuality = ComputeSSRQuality(View.FinalPostProcessSettings.ScreenSpaceReflectionQuality); SSRQuality = FMath::Clamp(SSRQuality, 1, 4); uint32 iPreFrame = bPrevFrame ? 1 : 0; if (View.Family->EngineShowFlags.VisualizeSSR) { iPreFrame = 0; SSRQuality = 0; } TShaderMapRef< FPostProcessVS > VertexShader(Context.GetShaderMap()); #define CASE(A, B) \ case (A + 2 * (B + 3 * 0 )): \ { \ TShaderMapRef< FPostProcessScreenSpaceReflectionsPS > PixelShader(Context.GetShaderMap()); \ static FGlobalBoundShaderState BoundShaderState; \ SetGlobalBoundShaderState(Context.RHICmdList, FeatureLevel, BoundShaderState, GFilterVertexDeclaration.VertexDeclarationRHI, *VertexShader, *PixelShader); \ VertexShader->SetParameters(Context); \ PixelShader->SetParameters(Context); \ }; \ break switch (iPreFrame + 2 * (SSRQuality + 3 * 0)) { CASE(0,0); CASE(0,1); CASE(1,1); CASE(0,2); CASE(1,2); CASE(0,3); CASE(1,3); CASE(0,4); CASE(1,4); default: check(!"Missing case in FRCPassPostProcessScreenSpaceReflections"); } #undef CASE // Draw a quad mapping scene color to the view's render target DrawRectangle( Context.RHICmdList, 0, 0, View.ViewRect.Width(), View.ViewRect.Height(), View.ViewRect.Min.X, View.ViewRect.Min.Y, View.ViewRect.Width(), View.ViewRect.Height(), View.ViewRect.Size(), GSceneRenderTargets.GetBufferSizeXY(), *VertexShader, EDRF_UseTriangleOptimization); Context.RHICmdList.CopyToResolveTarget(DestRenderTarget.TargetableTexture, DestRenderTarget.ShaderResourceTexture, false, FResolveParams()); } FPooledRenderTargetDesc FRCPassPostProcessScreenSpaceReflections::ComputeOutputDesc(EPassOutputId InPassOutputId) const { FPooledRenderTargetDesc Ret(FPooledRenderTargetDesc::Create2DDesc(GSceneRenderTargets.GetBufferSizeXY(), PF_FloatRGBA, TexCreate_None, TexCreate_RenderTargetable, false)); Ret.DebugName = TEXT("ScreenSpaceReflections"); return Ret; } void ScreenSpaceReflections(FRHICommandListImmediate& RHICmdList, FViewInfo& View, TRefCountPtr& SSROutput) { FRenderingCompositePassContext CompositeContext(RHICmdList, View); FPostprocessContext Context( CompositeContext.Graph, View ); FSceneViewState* ViewState = (FSceneViewState*)Context.View.State; FRenderingCompositePass* SceneColorInput = Context.Graph.RegisterPass( new FRCPassPostProcessInput( GSceneRenderTargets.GetSceneColor() ) ); FRenderingCompositePass* HZBInput = Context.Graph.RegisterPass( new FRCPassPostProcessInput( View.HZB ) ); bool bPrevFrame = 0; if( ViewState && ViewState->TemporalAAHistoryRT && !Context.View.bCameraCut ) { SceneColorInput = Context.Graph.RegisterPass( new FRCPassPostProcessInput( ViewState->TemporalAAHistoryRT ) ); bPrevFrame = 1; } { FRenderingCompositePass* TracePass = Context.Graph.RegisterPass( new FRCPassPostProcessScreenSpaceReflections( bPrevFrame ) ); TracePass->SetInput( ePId_Input0, SceneColorInput ); TracePass->SetInput( ePId_Input1, HZBInput ); Context.FinalOutput = FRenderingCompositeOutputRef( TracePass ); } const bool bTemporalFilter = View.FinalPostProcessSettings.AntiAliasingMethod != AAM_TemporalAA || CVarSSRTemporal.GetValueOnRenderThread() != 0; if( ViewState && bTemporalFilter ) { { FRenderingCompositeOutputRef HistoryInput; if( ViewState && ViewState->SSRHistoryRT && !Context.View.bCameraCut ) { HistoryInput = Context.Graph.RegisterPass( new FRCPassPostProcessInput( ViewState->SSRHistoryRT ) ); } else { // No history, use black HistoryInput = Context.Graph.RegisterPass(new FRCPassPostProcessInput(GSystemTextures.BlackDummy)); } FRenderingCompositePass* TemporalAAPass = Context.Graph.RegisterPass( new FRCPassPostProcessSSRTemporalAA ); TemporalAAPass->SetInput( ePId_Input0, Context.FinalOutput ); TemporalAAPass->SetInput( ePId_Input1, HistoryInput ); //TemporalAAPass->SetInput( ePId_Input2, VelocityInput ); Context.FinalOutput = FRenderingCompositeOutputRef( TemporalAAPass ); } if( ViewState ) { FRenderingCompositePass* HistoryOutput = Context.Graph.RegisterPass( new FRCPassPostProcessOutput( &ViewState->SSRHistoryRT ) ); HistoryOutput->SetInput( ePId_Input0, Context.FinalOutput ); Context.FinalOutput = FRenderingCompositeOutputRef( HistoryOutput ); } } { FRenderingCompositePass* ReflectionOutput = Context.Graph.RegisterPass( new FRCPassPostProcessOutput( &SSROutput ) ); ReflectionOutput->SetInput( ePId_Input0, Context.FinalOutput ); Context.FinalOutput = FRenderingCompositeOutputRef( ReflectionOutput ); } CompositeContext.Root->AddDependency( Context.FinalOutput ); CompositeContext.Process(TEXT("ReflectionEnvironments")); }