Files
UnrealEngine/Engine/Source/Runtime/Renderer/Private/PlanarReflectionRendering.cpp
2025-05-18 13:04:45 +08:00

844 lines
35 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
/*=============================================================================
PlanarReflectionRendering.cpp
=============================================================================*/
#include "PlanarReflectionRendering.h"
#include "DataDrivenShaderPlatformInfo.h"
#include "Engine/Scene.h"
#include "SceneInterface.h"
#include "RenderingThread.h"
#include "RHIStaticStates.h"
#include "RendererInterface.h"
#include "Camera/CameraTypes.h"
#include "Shader.h"
#include "TextureResource.h"
#include "StaticBoundShaderState.h"
#include "SceneUtils.h"
#include "ScenePrivateBase.h"
#include "PostProcess/SceneRenderTargets.h"
#include "GlobalShader.h"
#include "SceneRenderTargetParameters.h"
#include "SceneRendering.h"
#include "DeferredShadingRenderer.h"
#include "ScenePrivate.h"
#include "PostProcess/SceneFilterRendering.h"
#include "PostProcess/PostProcessing.h"
#include "LightRendering.h"
#include "Materials/MaterialRenderProxy.h"
#include "Components/SceneCaptureComponent.h"
#include "Components/PlanarReflectionComponent.h"
#include "PlanarReflectionSceneProxy.h"
#include "Containers/ArrayView.h"
#include "PipelineStateCache.h"
#include "ClearQuad.h"
#include "SceneTextureParameters.h"
#include "SceneViewExtension.h"
#include "Substrate/Substrate.h"
#include "SceneRenderBuilder.h"
void SetupPlanarReflectionUniformParameters(const class FSceneView& View, const FPlanarReflectionSceneProxy* ReflectionSceneProxy, FPlanarReflectionUniformParameters& OutParameters)
{
// Degenerate plane causes shader to branch around the reflection lookup
OutParameters.ReflectionPlane.Set(0.0f, 0.0f, 0.0f, 0.0f);
FTexture* PlanarReflectionTextureValue = GBlackTexture;
if (ReflectionSceneProxy && ReflectionSceneProxy->RenderTarget)
{
ensure(ReflectionSceneProxy->ViewRect[0].Min.X >= 0);
const FVector PreViewTranslation = View.ViewMatrices.GetPreViewTranslation();
const FPlane4f TranslatedReflectionPlane(ReflectionSceneProxy->ReflectionPlane.TranslateBy(PreViewTranslation));
// Need to set W separately due to FVector = FPlane, which sets W to 1.0.
OutParameters.ReflectionPlane = TranslatedReflectionPlane;
OutParameters.ReflectionPlane.W = TranslatedReflectionPlane.W;
PlanarReflectionTextureValue = ReflectionSceneProxy->RenderTarget;
FIntPoint BufferSize = ReflectionSceneProxy->RenderTarget->GetSizeXY();
float InvBufferSizeX = 1.0f / BufferSize.X;
float InvBufferSizeY = 1.0f / BufferSize.Y;
FVector2D PlanarReflectionScreenBoundValue(
1 - 2 * 0.5 / ReflectionSceneProxy->ViewRect[0].Width(),
1 - 2 * 0.5 / ReflectionSceneProxy->ViewRect[0].Height());
// Uses hardware's texture unit to reliably clamp UV if the view fill the entire buffer.
if (View.Family->Views.Num() == 1 &&
ReflectionSceneProxy->ViewRect[0].Min == FIntPoint::ZeroValue &&
ReflectionSceneProxy->ViewRect[0].Max == BufferSize)
{
PlanarReflectionScreenBoundValue = FVector2D(1, 1);
}
FVector4f ScreenScaleBiasValue[2] = {
FVector4f(0, 0, 0, 0),
FVector4f(0, 0, 0, 0),
};
for (int32 ViewIndex = 0; ViewIndex < FMath::Min(View.Family->Views.Num(), GMaxPlanarReflectionViews); ViewIndex++)
{
FIntRect ViewRect = ReflectionSceneProxy->ViewRect[ViewIndex];
ScreenScaleBiasValue[ViewIndex] = FVector4f(
ViewRect.Width() * InvBufferSizeX / +2.0f,
ViewRect.Height() * InvBufferSizeY / (-2.0f * GProjectionSignY),
(ViewRect.Width() / 2.0f + ViewRect.Min.X) * InvBufferSizeX,
(ViewRect.Height() / 2.0f + ViewRect.Min.Y) * InvBufferSizeY);
}
OutParameters.PlanarReflectionOrigin = (FVector3f)(PreViewTranslation + ReflectionSceneProxy->PlanarReflectionOrigin); // LWC_TODO: precision loss
OutParameters.PlanarReflectionXAxis = (FVector4f)ReflectionSceneProxy->PlanarReflectionXAxis; // LWC_TODO: precision loss
OutParameters.PlanarReflectionYAxis = (FVector4f)ReflectionSceneProxy->PlanarReflectionYAxis; // LWC_TODO: precision loss
OutParameters.InverseTransposeMirrorMatrix = ReflectionSceneProxy->InverseTransposeMirrorMatrix;
OutParameters.PlanarReflectionParameters = (FVector3f)ReflectionSceneProxy->PlanarReflectionParameters;
OutParameters.PlanarReflectionParameters2 = FVector2f(ReflectionSceneProxy->PlanarReflectionParameters2); // LWC_TODO: Precision loss
OutParameters.bIsStereo = ReflectionSceneProxy->bIsStereo;
OutParameters.PlanarReflectionScreenBound = FVector2f(PlanarReflectionScreenBoundValue); // LWC_TODO: Precision loss
// Instanced stereo needs both view's values available at once
if (ReflectionSceneProxy->bIsStereo || View.Family->Views.Num() == 1)
{
static_assert(UE_ARRAY_COUNT(ReflectionSceneProxy->ProjectionWithExtraFOV) == 2
&& GPlanarReflectionUniformMaxReflectionViews == 2, "Code assumes max 2 planar reflection views.");
OutParameters.ProjectionWithExtraFOV[0] = FMatrix44f(ReflectionSceneProxy->ProjectionWithExtraFOV[0]); // LWC_TODO: Precision loss
OutParameters.ProjectionWithExtraFOV[1] = FMatrix44f(ReflectionSceneProxy->ProjectionWithExtraFOV[1]);
OutParameters.PlanarReflectionScreenScaleBias[0] = ScreenScaleBiasValue[0];
OutParameters.PlanarReflectionScreenScaleBias[1] = ScreenScaleBiasValue[1];
}
else
{
int32 ViewIndex = 0;
for (int32 i = 0; i < View.Family->Views.Num(); i++)
{
if (&View == View.Family->Views[i])
{
ViewIndex = i;
break;
}
}
// Clamp the index to not go out of bounds (can happen for example in split screen with > 2 players).
ViewIndex = FMath::Min(ViewIndex, GPlanarReflectionUniformMaxReflectionViews - 1);
// Make sure the current view's value is at index 0
OutParameters.ProjectionWithExtraFOV[0] = FMatrix44f(ReflectionSceneProxy->ProjectionWithExtraFOV[ViewIndex]); // LWC_TODO: Precision loss?
OutParameters.ProjectionWithExtraFOV[1] = FMatrix44f::Identity;
OutParameters.PlanarReflectionScreenScaleBias[0] = ScreenScaleBiasValue[ViewIndex];
OutParameters.PlanarReflectionScreenScaleBias[1] = FVector4f(0, 0, 0, 0);
}
}
else
{
OutParameters.bIsStereo = false;
}
OutParameters.PlanarReflectionTexture = PlanarReflectionTextureValue->TextureRHI;
OutParameters.PlanarReflectionSampler = PlanarReflectionTextureValue->SamplerStateRHI;
}
IMPLEMENT_GLOBAL_SHADER_PARAMETER_STRUCT(FPlanarReflectionUniformParameters, "PlanarReflectionStruct");
template< bool bEnablePlanarReflectionPrefilter >
class TPrefilterPlanarReflectionPS : public FGlobalShader
{
DECLARE_GLOBAL_SHADER(TPrefilterPlanarReflectionPS);
SHADER_USE_PARAMETER_STRUCT(TPrefilterPlanarReflectionPS, FGlobalShader);
BEGIN_SHADER_PARAMETER_STRUCT(FParameters, )
SHADER_PARAMETER_STRUCT_REF(FViewUniformShaderParameters, View)
SHADER_PARAMETER_STRUCT_REF(FPlanarReflectionUniformParameters, PlanarReflection)
SHADER_PARAMETER_STRUCT_INCLUDE(FSceneTextureShaderParameters, SceneTextures)
SHADER_PARAMETER(float, KernelRadiusY)
SHADER_PARAMETER(float, InvPrefilterRoughnessDistance)
SHADER_PARAMETER_RDG_TEXTURE(Texture2D, SceneColorInputTexture)
SHADER_PARAMETER_SAMPLER(SamplerState, SceneColorInputSampler)
RENDER_TARGET_BINDING_SLOTS()
END_SHADER_PARAMETER_STRUCT()
static bool ShouldCompilePermutation(const FGlobalShaderPermutationParameters& Parameters)
{
return bEnablePlanarReflectionPrefilter ? IsFeatureLevelSupported(Parameters.Platform, ERHIFeatureLevel::SM5) : true;
}
static void ModifyCompilationEnvironment(const FGlobalShaderPermutationParameters& Parameters, FShaderCompilerEnvironment& OutEnvironment)
{
OutEnvironment.SetDefine(TEXT("ENABLE_PLANAR_REFLECTIONS_PREFILTER"), bEnablePlanarReflectionPrefilter);
FGlobalShader::ModifyCompilationEnvironment(Parameters, OutEnvironment);
}
};
IMPLEMENT_SHADER_TYPE(template<>, TPrefilterPlanarReflectionPS<false>, TEXT("/Engine/Private/PlanarReflectionShaders.usf"), TEXT("PrefilterPlanarReflectionPS"), SF_Pixel);
IMPLEMENT_SHADER_TYPE(template<>, TPrefilterPlanarReflectionPS<true>, TEXT("/Engine/Private/PlanarReflectionShaders.usf"), TEXT("PrefilterPlanarReflectionPS"), SF_Pixel);
template<bool bEnablePlanarReflectionPrefilter>
void PrefilterPlanarReflection(
FRDGBuilder& GraphBuilder,
const FViewInfo& View,
FSceneTextureShaderParameters SceneTextures,
const FPlanarReflectionSceneProxy* ReflectionSceneProxy,
FRDGTextureRef SceneColorTexture,
FRDGTextureRef ViewFamilyTexture)
{
using FPrefilterPlanarReflectionPS = TPrefilterPlanarReflectionPS<bEnablePlanarReflectionPrefilter>;
if(View.FeatureLevel >= ERHIFeatureLevel::SM5)
{
SceneColorTexture = AddProcessPlanarReflectionPass(GraphBuilder, View, SceneColorTexture);
}
{
RDG_EVENT_SCOPE(GraphBuilder, "PrefilterPlanarReflection");
// Workaround for a possible driver bug on S7 Adreno, missing planar reflections
const ERenderTargetLoadAction RTLoadAction = IsVulkanMobilePlatform(View.GetShaderPlatform()) ? ERenderTargetLoadAction::EClear : ERenderTargetLoadAction::ENoAction;
const float FilterWidth = View.ViewRect.Width();
auto* PassParameters = GraphBuilder.AllocParameters<typename FPrefilterPlanarReflectionPS::FParameters>();
PassParameters->View = View.ViewUniformBuffer;
{
FPlanarReflectionUniformParameters PlanarReflectionUniformParameters;
SetupPlanarReflectionUniformParameters(View, ReflectionSceneProxy, PlanarReflectionUniformParameters);
PassParameters->PlanarReflection = TUniformBufferRef<FPlanarReflectionUniformParameters>::CreateUniformBufferImmediate(PlanarReflectionUniformParameters, UniformBuffer_SingleFrame);
}
PassParameters->SceneTextures = SceneTextures;
PassParameters->KernelRadiusY = FMath::Clamp(ReflectionSceneProxy->PrefilterRoughness, 0.0f, 0.04f) * 0.5f * FilterWidth;
PassParameters->InvPrefilterRoughnessDistance = 1.0f / FMath::Max(ReflectionSceneProxy->PrefilterRoughnessDistance, DELTA);
PassParameters->SceneColorInputTexture = SceneColorTexture;
PassParameters->SceneColorInputSampler = TStaticSamplerState<SF_Bilinear, AM_Clamp, AM_Clamp, AM_Clamp>::GetRHI();
PassParameters->RenderTargets[0] = FRenderTargetBinding(ViewFamilyTexture, RTLoadAction);
FDeferredLightVS::FPermutationDomain PermutationVector;
PermutationVector.Set<FDeferredLightVS::FRadialLight>(false);
TShaderMapRef<FDeferredLightVS> VertexShader(View.ShaderMap, PermutationVector);
TShaderMapRef<FPrefilterPlanarReflectionPS> PixelShader(View.ShaderMap);
const FIntPoint SceneColorExtent = SceneColorTexture->Desc.Extent;
GraphBuilder.AddPass(
RDG_EVENT_NAME("PrefilterPlanarReflections"),
PassParameters,
ERDGPassFlags::Raster,
[&View, VertexShader, PixelShader, PassParameters, SceneColorExtent](FRDGAsyncTask, FRHICommandList& RHICmdList)
{
RHICmdList.SetViewport(View.ViewRect.Min.X, View.ViewRect.Min.Y, 0.0f, View.ViewRect.Max.X, View.ViewRect.Max.Y, 1.0f);
FGraphicsPipelineStateInitializer GraphicsPSOInit;
RHICmdList.ApplyCachedRenderTargets(GraphicsPSOInit);
GraphicsPSOInit.BlendState = TStaticBlendState<>::GetRHI();
GraphicsPSOInit.RasterizerState = TStaticRasterizerState<FM_Solid, CM_None>::GetRHI();
GraphicsPSOInit.DepthStencilState = TStaticDepthStencilState<false, CF_Always>::GetRHI();
GraphicsPSOInit.BoundShaderState.VertexDeclarationRHI = GFilterVertexDeclaration.VertexDeclarationRHI;
GraphicsPSOInit.BoundShaderState.VertexShaderRHI = VertexShader.GetVertexShader();
GraphicsPSOInit.BoundShaderState.PixelShaderRHI = PixelShader.GetPixelShader();
GraphicsPSOInit.PrimitiveType = PT_TriangleList;
SetGraphicsPipelineState(RHICmdList, GraphicsPSOInit, 0);
FIntPoint UV = View.ViewRect.Min;
FIntPoint UVSize = View.ViewRect.Size();
FDeferredLightVS::FParameters ParametersVS = FDeferredLightVS::GetParameters(View,
0, 0,
View.ViewRect.Width(), View.ViewRect.Height(),
UV.X, UV.Y,
UVSize.X, UVSize.Y,
View.ViewRect.Size(),
SceneColorExtent);
SetShaderParameters(RHICmdList, PixelShader, PixelShader.GetPixelShader(), *PassParameters);
SetShaderParameters(RHICmdList, VertexShader, VertexShader.GetVertexShader(), ParametersVS);
DrawRectangle(
RHICmdList,
0, 0,
View.ViewRect.Width(), View.ViewRect.Height(),
UV.X, UV.Y,
UVSize.X, UVSize.Y,
View.ViewRect.Size(),
SceneColorExtent,
VertexShader,
EDRF_UseTriangleOptimization);
});
}
}
struct FUpdatePlanarReflectionContentsInputs
{
FSceneRenderer* ParentSceneRenderer;
FPlanarReflectionSceneProxy* SceneProxy;
FPlanarReflectionRenderTarget* RenderTarget;
FPlane MirrorPlane;
};
static bool UpdatePlanarReflectionContents_RenderThread(
FRDGBuilder& GraphBuilder,
FSceneRenderer* SceneRenderer,
const FSceneRenderUpdateInputs* SceneUpdateInputs,
const FUpdatePlanarReflectionContentsInputs& Inputs)
{
QUICK_SCOPE_CYCLE_COUNTER(STAT_RenderPlanarReflection);
{
FBox PlanarReflectionBounds = Inputs.SceneProxy->WorldBounds;
bool bIsInAnyFrustum = false;
for (int32 ViewIndex = 0; ViewIndex < Inputs.ParentSceneRenderer->Views.Num(); ++ViewIndex)
{
FViewInfo& View = Inputs.ParentSceneRenderer->Views[ViewIndex];
if (Inputs.MirrorPlane.PlaneDot(View.ViewMatrices.GetViewOrigin()) > 0)
{
if (View.ViewFrustum.IntersectBox(PlanarReflectionBounds.GetCenter(), PlanarReflectionBounds.GetExtent()))
{
bIsInAnyFrustum = true;
break;
}
}
}
if (!bIsInAnyFrustum)
{
return false;
}
bool bIsVisibleInAnyView = true;
for (const FViewInfo& View : Inputs.ParentSceneRenderer->Views)
{
FSceneViewState* ViewState = View.ViewState;
if (ViewState)
{
FIndividualOcclusionHistory& OcclusionHistory = ViewState->PlanarReflectionOcclusionHistories.FindOrAdd(Inputs.SceneProxy->PlanarReflectionId);
// +1 to buffered frames because the query is submitted late into the main frame, but read at the beginning of a reflection capture frame
const int32 NumBufferedFrames = FOcclusionQueryHelpers::GetNumBufferedFrames(SceneRenderer->FeatureLevel) + 1;
// +1 to frame counter because we are operating before the main view's InitViews, which is where OcclusionFrameCounter is incremented
uint32 OcclusionFrameCounter = ViewState->OcclusionFrameCounter + 1;
FRHIRenderQuery* PastQuery = OcclusionHistory.GetPastQuery(OcclusionFrameCounter, NumBufferedFrames);
if (PastQuery)
{
uint64 NumSamples = 0;
QUICK_SCOPE_CYCLE_COUNTER(STAT_PlanarReflectionOcclusionQueryResults);
if (RHIGetRenderQueryResult(PastQuery, NumSamples, true))
{
bIsVisibleInAnyView = NumSamples > 0;
if (bIsVisibleInAnyView)
{
break;
}
}
}
}
}
if (!bIsVisibleInAnyView)
{
return false;
}
}
// Clear the SRV prior to rendering so we don't attempt to read from the target we are writing to.
Inputs.SceneProxy->RenderTarget = nullptr;
// Assign the view rect to the resolved scene renderer view rect for the main scene render pass.
// Do this at the end of the scope since we can early out for mobile pixel reflections. This needs
// to be called after PrepareViewRectsForRendering (which is done within Render for the main path).
ON_SCOPE_EXIT
{
// Update the view rects into the planar reflection proxy.
for (int32 ViewIndex = 0; ViewIndex < SceneRenderer->Views.Num(); ++ViewIndex)
{
// Make sure screen percentage has correctly been set on render thread.
check(SceneRenderer->Views[ViewIndex].ViewRect.Area() > 0);
Inputs.SceneProxy->ViewRect[ViewIndex] = SceneRenderer->Views[ViewIndex].ViewRect;
}
// Re-assign the SRV after rendering.
Inputs.SceneProxy->RenderTarget = Inputs.RenderTarget;
};
const ERHIFeatureLevel::Type FeatureLevel = SceneRenderer->FeatureLevel;
// Make sure we render to the same set of GPUs as the main scene renderer.
if (Inputs.ParentSceneRenderer->ViewFamily.RenderTarget != nullptr)
{
Inputs.RenderTarget->SetActiveGPUMask(Inputs.ParentSceneRenderer->ViewFamily.RenderTarget->GetGPUMask(GraphBuilder.RHICmdList));
}
else
{
Inputs.RenderTarget->SetActiveGPUMask(FRHIGPUMask::GPU0());
}
// Applies late update (if any) to view matrices and re-reflects
if (SceneRenderer->Views.Num() > 1)
{
const FMirrorMatrix MirrorMatrix(Inputs.MirrorPlane);
for (int32 ViewIndex = 0; ViewIndex < SceneRenderer->Views.Num(); ++ViewIndex)
{
FViewInfo& ReflectionViewToUpdate = SceneRenderer->Views[ViewIndex];
// Updates view matrices to match new ViewLocation/ViewRotation, un-reflects
// Normally performed in late update itself, delayed to here to ensure we don't ever re-reflect without first un-reflecting
ReflectionViewToUpdate.UpdateViewMatrix();
// Re-reflects view matrices
ReflectionViewToUpdate.UpdatePlanarReflectionViewMatrix(ReflectionViewToUpdate, MirrorMatrix);
}
}
UE_CLOG(FSceneCaptureLogUtils::bEnableSceneCaptureLogging, LogSceneCapture, Log, TEXT("Running UpdatePlanarReflectionContents_RenderThread."));
SceneRenderer->Render(GraphBuilder, SceneUpdateInputs);
FRDGTextureRef ReflectionOutputTexture = GraphBuilder.RegisterExternalTexture(CreateRenderTarget(Inputs.RenderTarget->TextureRHI, TEXT("ReflectionOutputTexture")));
GraphBuilder.SetTextureAccessFinal(ReflectionOutputTexture, ERHIAccess::SRVGraphics);
FSceneTextureShaderParameters SceneTextureParameters = CreateSceneTextureShaderParameters(GraphBuilder, &SceneRenderer->GetActiveSceneTextures(), SceneRenderer->FeatureLevel, ESceneTextureSetupMode::SceneDepth);
const FMinimalSceneTextures& SceneTextures = SceneRenderer->GetActiveSceneTextures();
for (FViewInfo& View : SceneRenderer->Views)
{
RDG_GPU_MASK_SCOPE(GraphBuilder, View.GPUMask);
if (Inputs.ParentSceneRenderer->Scene->GetShadingPath() == EShadingPath::Deferred)
{
PrefilterPlanarReflection<true>(GraphBuilder, View, SceneTextureParameters, Inputs.SceneProxy, SceneTextures.Color.Resolve, ReflectionOutputTexture);
}
else
{
PrefilterPlanarReflection<false>(GraphBuilder, View, SceneTextureParameters, Inputs.SceneProxy, SceneTextures.Color.Resolve, ReflectionOutputTexture);
}
}
return true;
}
extern void BuildProjectionMatrix(FIntPoint RenderTargetSize, float FOV, float InNearClippingPlane, FMatrix& ProjectionMatrix);
extern TArray<FSceneView*> SetupViewFamilyForSceneCapture(
FSceneViewFamily& ViewFamily,
USceneCaptureComponent* SceneCaptureComponent,
const TArrayView<const FSceneCaptureViewInfo> Views,
float MaxViewDistance,
bool bCaptureSceneColor,
bool bIsPlanarReflection,
FPostProcessSettings* PostProcessSettings,
const FFinalPostProcessSettings* InheritedMainViewPostProcessSettings,
float PostProcessBlendWeight,
const AActor* ViewActor,
int32 CubemapFaceIndex);
extern void SetupSceneViewExtensionsForSceneCapture(
FSceneViewFamily& ViewFamily,
TConstArrayView<FSceneView*> Views);
void FScene::UpdatePlanarReflectionContents(UPlanarReflectionComponent* CaptureComponent, FSceneRenderer& ParentSceneRenderer, ISceneRenderBuilder& SceneRenderBuilder)
{
check(CaptureComponent);
FIntPoint DesiredBufferSize = FSceneRenderer::GetDesiredInternalBufferSize(ParentSceneRenderer.ViewFamily);
FVector2D DesiredPlanarReflectionTextureSizeFloat = FVector2D(DesiredBufferSize.X, DesiredBufferSize.Y) * FMath::Clamp(CaptureComponent->ScreenPercentage / 100.f, 0.25f, 1.f);
FIntPoint DesiredPlanarReflectionTextureSize;
DesiredPlanarReflectionTextureSize.X = FMath::Clamp(FMath::CeilToInt32(DesiredPlanarReflectionTextureSizeFloat.X), 1, static_cast<int32>(DesiredBufferSize.X));
DesiredPlanarReflectionTextureSize.Y = FMath::Clamp(FMath::CeilToInt32(DesiredPlanarReflectionTextureSizeFloat.Y), 1, static_cast<int32>(DesiredBufferSize.Y));
const bool bIsRenderTargetValid = CaptureComponent->RenderTarget != nullptr
&& CaptureComponent->RenderTarget->GetSizeXY() == DesiredPlanarReflectionTextureSize
&& (CaptureComponent->RenderTarget->TextureRHI.IsValid());
FPlanarReflectionRenderTarget* RenderTargetToRelease = nullptr;
FPlanarReflectionRenderTarget* RenderTargetToInit = nullptr;
if (CaptureComponent->RenderTarget != nullptr && !bIsRenderTargetValid)
{
RenderTargetToRelease = CaptureComponent->RenderTarget;
CaptureComponent->RenderTarget = nullptr;
}
if (CaptureComponent->RenderTarget == nullptr)
{
CaptureComponent->RenderTarget = new FPlanarReflectionRenderTarget(DesiredPlanarReflectionTextureSize);
RenderTargetToInit = CaptureComponent->RenderTarget;
}
const FMatrix ComponentTransform = CaptureComponent->GetComponentTransform().ToMatrixWithScale();
FPlane MirrorPlane = FPlane(ComponentTransform.TransformPosition(FVector::ZeroVector), ComponentTransform.TransformVector(FVector(0, 0, 1)));
if (!MirrorPlane.Normalize())
{
MirrorPlane = FPlane(FVector(0, 0, 1), 0);
}
TArray<FSceneCaptureViewInfo, FConcurrentLinearArrayAllocator> SceneCaptureViewInfo;
for (int32 ViewIndex = 0; ViewIndex < ParentSceneRenderer.Views.Num() && ViewIndex < GMaxPlanarReflectionViews; ++ViewIndex)
{
const FViewInfo& View = ParentSceneRenderer.Views[ViewIndex];
FSceneCaptureViewInfo NewView;
FVector2D ViewRectMin = FVector2D(View.UnscaledViewRect.Min.X, View.UnscaledViewRect.Min.Y);
FVector2D ViewRectMax = FVector2D(View.UnscaledViewRect.Max.X, View.UnscaledViewRect.Max.Y);
ViewRectMin *= FMath::Clamp(CaptureComponent->ScreenPercentage / 100.f, 0.25f, 1.f);
ViewRectMax *= FMath::Clamp(CaptureComponent->ScreenPercentage / 100.f, 0.25f, 1.f);
NewView.ViewRect.Min.X = FMath::TruncToInt(ViewRectMin.X);
NewView.ViewRect.Min.Y = FMath::TruncToInt(ViewRectMin.Y);
NewView.ViewRect.Max.X = FMath::CeilToInt(ViewRectMax.X);
NewView.ViewRect.Max.Y = FMath::CeilToInt(ViewRectMax.Y);
// Create a mirror matrix and premultiply the view transform by it
const FMirrorMatrix MirrorMatrix(MirrorPlane);
const FMatrix ViewMatrix(MirrorMatrix * View.ViewMatrices.GetViewMatrix());
const FVector ViewOrigin = ViewMatrix.InverseTransformPosition(FVector::ZeroVector);
const FMatrix ViewRotationMatrix = ViewMatrix.RemoveTranslation();
FMatrix ProjectionMatrix;
if (!View.IsPerspectiveProjection() || (CaptureComponent->ExtraFOV == 0.f && ParentSceneRenderer.Views.Num() > 1))
{
// Prefer exact (potentially uneven) stereo projection matrices when no extra FOV is requested
ProjectionMatrix = View.ViewMatrices.GetProjectionMatrix();
}
else
{
const float HalfFOV = FMath::Atan(1.0f / View.ViewMatrices.GetProjectionMatrix().M[0][0]);
BuildProjectionMatrix(View.UnscaledViewRect.Size(), HalfFOV + FMath::DegreesToRadians(CaptureComponent->ExtraFOV), GNearClippingPlane, ProjectionMatrix);
}
NewView.ViewLocation = View.ViewLocation;
NewView.ViewRotation = View.ViewRotation;
NewView.ViewOrigin = ViewOrigin;
NewView.ViewRotationMatrix = ViewRotationMatrix;
NewView.ProjectionMatrix = ProjectionMatrix;
NewView.StereoPass = View.StereoPass;
NewView.StereoViewIndex = View.StereoViewIndex;
NewView.FOV = View.FOV;
SceneCaptureViewInfo.Add(NewView);
}
FPostProcessSettings PostProcessSettings;
bool bIsMobileMultiViewEnabled = false;
if (ParentSceneRenderer.ViewFamily.Views.Num() > 0)
{
bIsMobileMultiViewEnabled = ParentSceneRenderer.ViewFamily.Views[0]->Aspects.IsMobileMultiViewEnabled();
}
FSceneViewFamilyContext ViewFamily(FSceneViewFamily::ConstructionValues(
CaptureComponent->RenderTarget,
this,
CaptureComponent->ShowFlags)
.SetResolveScene(false)
.SetRealtimeUpdate(true)
.SetRequireMobileMultiView(bIsMobileMultiViewEnabled));
FSceneViewExtensionContext ViewExtensionContext(this);
ViewExtensionContext.bStereoEnabled = true;
ViewFamily.ViewExtensions = GEngine->ViewExtensions->GatherActiveExtensions(ViewExtensionContext);
TArray<FSceneView*> Views = SetupViewFamilyForSceneCapture(
ViewFamily,
CaptureComponent,
SceneCaptureViewInfo, CaptureComponent->MaxViewDistanceOverride,
/* bCaptureSceneColor = */ true,
/* bIsPlanarReflection = */ true,
&PostProcessSettings,
/* InheritedMainViewPostProcessSettings = */ nullptr,
1.0f,
/*ViewActor =*/ nullptr,
/*CubemapFaceIndex =*/ INDEX_NONE);
// Fork main renderer's screen percentage interface to have exactly same settings.
ViewFamily.EngineShowFlags.ScreenPercentage = ParentSceneRenderer.ViewFamily.EngineShowFlags.ScreenPercentage;
ViewFamily.SetScreenPercentageInterface(FSceneRenderer::ForkScreenPercentageInterface(
ParentSceneRenderer.ViewFamily.GetScreenPercentageInterface(), ViewFamily));
for (FSceneView* View : Views)
{
View->GlobalClippingPlane = MirrorPlane;
// Jitter can't be removed completely due to the clipping plane
// Also, this prevents the prefilter pass, which reads from jittered depth, from having to do special handling of it's depth-dependent input
View->bAllowTemporalJitter = false;
View->bRenderSceneTwoSided = CaptureComponent->bRenderSceneTwoSided;
}
const bool bIsStereo = IStereoRendering::IsStereoEyeView(ParentSceneRenderer.Views[0]);
// Call SetupViewFamily & SetupView on scene view extensions before renderer creation
SetupSceneViewExtensionsForSceneCapture(ViewFamily, Views);
for (int32 ViewIndex = 0; ViewIndex < SceneCaptureViewInfo.Num(); ++ViewIndex)
{
CaptureComponent->ProjectionWithExtraFOV[ViewIndex] = SceneCaptureViewInfo[ViewIndex].ProjectionMatrix;
}
FString EventName = TEXT("PlanarReflection");
#if WANTS_DRAW_MESH_EVENTS
if (CaptureComponent->GetOwner())
{
EventName = CaptureComponent->GetOwner()->GetFName().ToString();
}
#endif
SCENE_RENDER_GROUP_SCOPE(SceneRenderBuilder, MoveTemp(EventName), ESceneRenderGroupFlags::None);
if (RenderTargetToRelease)
{
SceneRenderBuilder.AddRenderCommand([RenderTargetToRelease](FRHICommandListImmediate& RHICmdList)
{
RenderTargetToRelease->ReleaseResource();
delete RenderTargetToRelease;
});
RenderTargetToRelease = nullptr;
}
if (RenderTargetToInit)
{
SceneRenderBuilder.AddRenderCommand([RenderTargetToInit] (FRHICommandListImmediate& RHICmdList)
{
RenderTargetToInit->InitResource(RHICmdList);
});
}
const FUpdatePlanarReflectionContentsInputs UpdateInputs
{
.ParentSceneRenderer = &ParentSceneRenderer
, .SceneProxy = CaptureComponent->SceneProxy
, .RenderTarget = CaptureComponent->RenderTarget
, .MirrorPlane = MirrorPlane
};
FSceneRenderer* SceneRenderer = SceneRenderBuilder.CreateSceneRenderer(&ViewFamily);
SceneRenderBuilder.AddRenderer(SceneRenderer,
[
UpdateInputs
, SceneCaptureViewInfo = MoveTemp(SceneCaptureViewInfo)
, bIsStereo
] (FRDGBuilder& GraphBuilder, const FSceneRenderFunctionInputs& Inputs)
{
// Remove the render target on the planar reflection proxy so that this planar reflection is not getting drawn in its own FSceneRenderer.
UpdateInputs.SceneProxy->RenderTarget = nullptr;
for (int32 ViewIndex = 0; ViewIndex < SceneCaptureViewInfo.Num(); ++ViewIndex)
{
UpdateInputs.SceneProxy->ProjectionWithExtraFOV[ViewIndex] = SceneCaptureViewInfo[ViewIndex].ProjectionMatrix;
UpdateInputs.SceneProxy->bIsStereo = bIsStereo;
}
return UpdatePlanarReflectionContents_RenderThread(GraphBuilder, Inputs.Renderer, Inputs.SceneUpdateInputs, UpdateInputs);
});
}
void FScene::AddPlanarReflection(UPlanarReflectionComponent* Component)
{
check(Component->SceneProxy);
PlanarReflections_GameThread.Add(Component);
FPlanarReflectionSceneProxy* SceneProxy = Component->SceneProxy;
FScene* Scene = this;
ENQUEUE_RENDER_COMMAND(FAddPlanarReflectionCommand)(
[SceneProxy, Scene](FRHICommandListImmediate& RHICmdList)
{
Scene->ReflectionSceneData.bRegisteredReflectionCapturesHasChanged = true;
Scene->PlanarReflections.Add(SceneProxy);
});
}
void FScene::RemovePlanarReflection(UPlanarReflectionComponent* Component)
{
check(Component->SceneProxy);
PlanarReflections_GameThread.Remove(Component);
FPlanarReflectionSceneProxy* SceneProxy = Component->SceneProxy;
FScene* Scene = this;
ENQUEUE_RENDER_COMMAND(FRemovePlanarReflectionCommand)(
[SceneProxy, Scene](FRHICommandListImmediate& RHICmdList)
{
Scene->ReflectionSceneData.bRegisteredReflectionCapturesHasChanged = true;
Scene->PlanarReflections.Remove(SceneProxy);
});
}
void FScene::UpdatePlanarReflectionTransform(UPlanarReflectionComponent* Component)
{
check(Component->SceneProxy);
FPlanarReflectionSceneProxy* SceneProxy = Component->SceneProxy;
FMatrix Transform = Component->GetComponentTransform().ToMatrixWithScale();
FScene* Scene = this;
ENQUEUE_RENDER_COMMAND(FUpdatePlanarReflectionCommand)(
[SceneProxy, Transform, Scene](FRHICommandListImmediate& RHICmdList)
{
Scene->ReflectionSceneData.bRegisteredReflectionCapturesHasChanged = true;
SceneProxy->UpdateTransform(Transform);
});
}
class FPlanarReflectionPS : public FGlobalShader
{
DECLARE_GLOBAL_SHADER(FPlanarReflectionPS);
SHADER_USE_PARAMETER_STRUCT(FPlanarReflectionPS, FGlobalShader)
static bool ShouldCompilePermutation(const FGlobalShaderPermutationParameters& Parameters)
{
return IsFeatureLevelSupported(Parameters.Platform, ERHIFeatureLevel::SM5);
}
BEGIN_SHADER_PARAMETER_STRUCT(FParameters, )
SHADER_PARAMETER_STRUCT_INCLUDE(FSceneTextureParameters, SceneTextures)
SHADER_PARAMETER_RDG_UNIFORM_BUFFER(FSubstrateGlobalUniformParameters, Substrate)
SHADER_PARAMETER_STRUCT_REF(FViewUniformShaderParameters, ViewUniformBuffer)
SHADER_PARAMETER_STRUCT_REF(FPlanarReflectionUniformParameters, PlanarReflectionParameters)
RENDER_TARGET_BINDING_SLOTS()
END_SHADER_PARAMETER_STRUCT()
};
IMPLEMENT_GLOBAL_SHADER(FPlanarReflectionPS, "/Engine/Private/PlanarReflectionShaders.usf", "PlanarReflectionPS", SF_Pixel);
bool FDeferredShadingSceneRenderer::HasDeferredPlanarReflections(const FViewInfo& View) const
{
if (View.bIsPlanarReflection || View.bIsReflectionCapture)
{
return false;
}
// Prevent rendering unsupported views when ViewIndex >= GMaxPlanarReflectionViews
// Planar reflections in those views will fallback to other reflection methods
{
int32 ViewIndex = INDEX_NONE;
ViewFamily.Views.Find(&View, ViewIndex);
if (ViewIndex >= GMaxPlanarReflectionViews)
{
return false;
}
}
bool bAnyVisiblePlanarReflections = false;
for (int32 PlanarReflectionIndex = 0; PlanarReflectionIndex < Scene->PlanarReflections.Num(); PlanarReflectionIndex++)
{
FPlanarReflectionSceneProxy* ReflectionSceneProxy = Scene->PlanarReflections[PlanarReflectionIndex];
if (View.GetCullingFrustum().IntersectBox(ReflectionSceneProxy->WorldBounds.GetCenter(), ReflectionSceneProxy->WorldBounds.GetExtent()))
{
bAnyVisiblePlanarReflections = true;
break;
}
}
bool bComposePlanarReflections = Scene->PlanarReflections.Num() > 0 && bAnyVisiblePlanarReflections;
return bComposePlanarReflections;
}
void FDeferredShadingSceneRenderer::RenderDeferredPlanarReflections(FRDGBuilder& GraphBuilder, const FSceneTextureParameters& SceneTextures, const FViewInfo& View, FRDGTextureRef& ReflectionsOutputTexture)
{
check(HasDeferredPlanarReflections(View));
// Allocate planar reflection texture
bool bClearReflectionsOutputTexture = false;
if (!ReflectionsOutputTexture)
{
FRDGTextureDesc Desc = FRDGTextureDesc::Create2D(
SceneTextures.SceneDepthTexture->Desc.Extent,
PF_FloatRGBA, FClearValueBinding(FLinearColor(0, 0, 0, 0)),
TexCreate_ShaderResource | TexCreate_RenderTargetable);
ReflectionsOutputTexture = GraphBuilder.CreateTexture(Desc, TEXT("PlanarReflections"));
bClearReflectionsOutputTexture = true;
}
FPlanarReflectionPS::FParameters* PassParameters = GraphBuilder.AllocParameters<FPlanarReflectionPS::FParameters>();
PassParameters->SceneTextures.SceneDepthTexture = SceneTextures.SceneDepthTexture;
PassParameters->SceneTextures.GBufferATexture = SceneTextures.GBufferATexture;
PassParameters->SceneTextures.GBufferBTexture = SceneTextures.GBufferBTexture;
PassParameters->SceneTextures.GBufferCTexture = SceneTextures.GBufferCTexture;
PassParameters->SceneTextures.GBufferDTexture = SceneTextures.GBufferDTexture;
PassParameters->SceneTextures.GBufferETexture = SceneTextures.GBufferETexture;
PassParameters->SceneTextures.GBufferFTexture = SceneTextures.GBufferFTexture;
PassParameters->SceneTextures.GBufferVelocityTexture = SceneTextures.GBufferVelocityTexture;
PassParameters->Substrate = Substrate::BindSubstrateGlobalUniformParameters(View);
PassParameters->ViewUniformBuffer = View.ViewUniformBuffer;
PassParameters->RenderTargets[0] = FRenderTargetBinding(
ReflectionsOutputTexture, bClearReflectionsOutputTexture ? ERenderTargetLoadAction::EClear : ERenderTargetLoadAction::ELoad);
GraphBuilder.AddPass(
RDG_EVENT_NAME("CompositePlanarReflections"),
PassParameters,
ERDGPassFlags::Raster,
[PassParameters, &View, this](FRHICommandListImmediate& RHICmdList)
{
RHICmdList.SetViewport(View.ViewRect.Min.X, View.ViewRect.Min.Y, 0.0f, View.ViewRect.Max.X, View.ViewRect.Max.Y, 1.0f);
FGraphicsPipelineStateInitializer GraphicsPSOInit;
RHICmdList.ApplyCachedRenderTargets(GraphicsPSOInit);
// Blend over previous reflections in the output target (SSR or planar reflections that have already been rendered)
// Planar reflections win over SSR and reflection environment
//@todo - this is order dependent blending, but ordering is coming from registration order
GraphicsPSOInit.BlendState = TStaticBlendState<CW_RGBA, BO_Add, BF_One, BF_InverseSourceAlpha, BO_Max, BF_One, BF_One>::GetRHI();
GraphicsPSOInit.RasterizerState = TStaticRasterizerState<FM_Solid, CM_None>::GetRHI();
GraphicsPSOInit.DepthStencilState = TStaticDepthStencilState<false, CF_Always>::GetRHI();
for (FPlanarReflectionSceneProxy* ReflectionSceneProxy : Scene->PlanarReflections)
{
if (!View.GetCullingFrustum().IntersectBox(ReflectionSceneProxy->WorldBounds.GetCenter(), ReflectionSceneProxy->WorldBounds.GetExtent()))
{
continue;
}
SCOPED_DRAW_EVENTF(RHICmdList, PlanarReflection, TEXT("PlanarReflection: %s"), ReflectionSceneProxy->OwnerName);
FDeferredLightVS::FPermutationDomain PermutationVector;
PermutationVector.Set<FDeferredLightVS::FRadialLight>(false);
TShaderMapRef<FDeferredLightVS> VertexShader(View.ShaderMap, PermutationVector);
TShaderMapRef<FPlanarReflectionPS> PixelShader(View.ShaderMap);
GraphicsPSOInit.BoundShaderState.VertexDeclarationRHI = GFilterVertexDeclaration.VertexDeclarationRHI;
GraphicsPSOInit.BoundShaderState.VertexShaderRHI = VertexShader.GetVertexShader();
GraphicsPSOInit.BoundShaderState.PixelShaderRHI = PixelShader.GetPixelShader();
GraphicsPSOInit.PrimitiveType = PT_TriangleList;
SetGraphicsPipelineState(RHICmdList, GraphicsPSOInit, 0);
FDeferredLightVS::FParameters ParametersVS = FDeferredLightVS::GetParameters(View);
SetShaderParameters(RHICmdList, VertexShader, VertexShader.GetVertexShader(), ParametersVS);
{
FPlanarReflectionUniformParameters PlanarReflectionUniformParameters;
SetupPlanarReflectionUniformParameters(View, ReflectionSceneProxy, PlanarReflectionUniformParameters);
FPlanarReflectionPS::FParameters ShaderParameters = *PassParameters;
ShaderParameters.PlanarReflectionParameters = CreateUniformBufferImmediate(PlanarReflectionUniformParameters, UniformBuffer_SingleDraw);
SetShaderParameters(RHICmdList, PixelShader, PixelShader.GetPixelShader(), ShaderParameters);
}
DrawRectangle(
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(),
View.GetSceneTexturesConfig().Extent,
VertexShader,
EDRF_UseTriangleOptimization);
}
});
}