3489 lines
154 KiB
C++
3489 lines
154 KiB
C++
// Copyright Epic Games, Inc. All Rights Reserved.
|
|
|
|
/*=============================================================================
|
|
LightRendering.cpp: Light rendering implementation.
|
|
=============================================================================*/
|
|
|
|
#include "LightRendering.h"
|
|
#include "RendererModule.h"
|
|
#include "DeferredShadingRenderer.h"
|
|
#include "ScenePrivate.h"
|
|
#include "PostProcess/SceneFilterRendering.h"
|
|
#include "PipelineStateCache.h"
|
|
#include "ClearQuad.h"
|
|
#include "Engine/SubsurfaceProfile.h"
|
|
#include "ShowFlags.h"
|
|
#include "VisualizeTexture.h"
|
|
#include "RayTracing/RaytracingOptions.h"
|
|
#include "SceneTextureParameters.h"
|
|
#include "HairStrands/HairStrandsRendering.h"
|
|
#include "ScreenPass.h"
|
|
#include "SkyAtmosphereRendering.h"
|
|
#include "VolumetricCloudRendering.h"
|
|
#include "Substrate/Substrate.h"
|
|
#include "VirtualShadowMaps/VirtualShadowMapProjection.h"
|
|
#include "HairStrands/HairStrandsData.h"
|
|
#include "AnisotropyRendering.h"
|
|
#include "Engine/SubsurfaceProfile.h"
|
|
#include "Shadows/ShadowSceneRenderer.h"
|
|
#include "RenderCore.h"
|
|
#include "BasePassRendering.h"
|
|
#include "MobileBasePassRendering.h"
|
|
#include "TranslucentLighting.h"
|
|
#include "MegaLights/MegaLights.h"
|
|
#include "LightFunctionAtlas.h"
|
|
#include "HeterogeneousVolumes/HeterogeneousVolumes.h"
|
|
#include "Materials/MaterialRenderProxy.h"
|
|
#include "RHIResourceUtils.h"
|
|
#include "RectLightSceneProxy.h"
|
|
#include "Shadows/FirstPersonSelfShadow.h"
|
|
|
|
#include "PSOPrecacheValidation.h"
|
|
|
|
using namespace LightFunctionAtlas;
|
|
|
|
// ENABLE_DEBUG_DISCARD_PROP is used to test the lighting code by allowing to discard lights to see how performance scales
|
|
// It ought never to be enabled in a shipping build, and is probably only really useful when woring on the shading code.
|
|
#if !(UE_BUILD_SHIPPING || UE_BUILD_TEST)
|
|
#define ENABLE_DEBUG_DISCARD_PROP 1
|
|
#else // (UE_BUILD_SHIPPING || UE_BUILD_TEST)
|
|
#define ENABLE_DEBUG_DISCARD_PROP 0
|
|
#endif // !(UE_BUILD_SHIPPING || UE_BUILD_TEST)
|
|
|
|
DECLARE_GPU_DRAWCALL_STAT(Lights);
|
|
|
|
IMPLEMENT_TYPE_LAYOUT(FLightFunctionSharedParameters);
|
|
IMPLEMENT_TYPE_LAYOUT(FStencilingGeometryShaderParameters);
|
|
IMPLEMENT_TYPE_LAYOUT(FOnePassPointShadowProjectionShaderParameters);
|
|
IMPLEMENT_TYPE_LAYOUT(FShadowProjectionShaderParameters);
|
|
|
|
IMPLEMENT_GLOBAL_SHADER_PARAMETER_STRUCT(FDeferredLightUniformStruct, "DeferredLightUniforms");
|
|
|
|
DECLARE_DWORD_COUNTER_STAT(TEXT("VSM Light Projections (Local One Pass Fast)"), STAT_VSMLocalProjectionOnePassFast, STATGROUP_ShadowRendering);
|
|
|
|
static const TCHAR* DeferredLightGlobalPSOCollectorName = TEXT("DeferredLightGlobalPSOCollector");
|
|
|
|
extern int32 GUseTranslucentLightingVolumes;
|
|
|
|
static int32 GAllowDepthBoundsTest = 1;
|
|
static FAutoConsoleVariableRef CVarAllowDepthBoundsTest(
|
|
TEXT("r.AllowDepthBoundsTest"),
|
|
GAllowDepthBoundsTest,
|
|
TEXT("If true, use enable depth bounds test when rendering deferred lights.")
|
|
);
|
|
|
|
static int32 bAllowSimpleLights = 1;
|
|
static FAutoConsoleVariableRef CVarAllowSimpleLights(
|
|
TEXT("r.AllowSimpleLights"),
|
|
bAllowSimpleLights,
|
|
TEXT("If true, we allow simple (ie particle) lights")
|
|
);
|
|
|
|
static TAutoConsoleVariable<int32> CVarRayTracingOcclusion(
|
|
TEXT("r.RayTracing.Shadows"),
|
|
0,
|
|
TEXT("0: use traditional rasterized shadow map (default)\n")
|
|
TEXT("1: use ray tracing shadows"),
|
|
ECVF_RenderThreadSafe | ECVF_Scalability);
|
|
|
|
static int32 GShadowRayTracingSamplesPerPixel = -1;
|
|
static FAutoConsoleVariableRef CVarShadowRayTracingSamplesPerPixel(
|
|
TEXT("r.RayTracing.Shadows.SamplesPerPixel"),
|
|
GShadowRayTracingSamplesPerPixel,
|
|
TEXT("Sets the samples-per-pixel for directional light occlusion (default = 1)"),
|
|
ECVF_RenderThreadSafe);
|
|
|
|
static TAutoConsoleVariable<int32> CVarShadowUseDenoiser(
|
|
TEXT("r.Shadow.Denoiser"),
|
|
2,
|
|
TEXT("Choose the denoising algorithm.\n")
|
|
TEXT(" 0: Disabled (default);\n")
|
|
TEXT(" 1: Forces the default denoiser of the renderer;\n")
|
|
TEXT(" 2: GScreenSpaceDenoiser witch may be overriden by a third party plugin.\n"),
|
|
ECVF_RenderThreadSafe);
|
|
|
|
static TAutoConsoleVariable<int32> CVarMaxShadowDenoisingBatchSize(
|
|
TEXT("r.Shadow.Denoiser.MaxBatchSize"), 4,
|
|
TEXT("Maximum number of shadow to denoise at the same time."),
|
|
ECVF_RenderThreadSafe);
|
|
|
|
static TAutoConsoleVariable<int32> CVarMaxShadowRayTracingBatchSize(
|
|
TEXT("r.RayTracing.Shadows.MaxBatchSize"), 8,
|
|
TEXT("Maximum number of shadows to trace at the same time."),
|
|
ECVF_RenderThreadSafe);
|
|
|
|
static TAutoConsoleVariable<int32> CVarAllowClearLightSceneExtentsOnly(
|
|
TEXT("r.AllowClearLightSceneExtentsOnly"), 1,
|
|
TEXT(""),
|
|
ECVF_RenderThreadSafe);
|
|
|
|
static TAutoConsoleVariable<int32> CVarRayTracingShadowsDirectionalLight(
|
|
TEXT("r.RayTracing.Shadows.Lights.Directional"),
|
|
1,
|
|
TEXT("Enables ray tracing shadows for directional lights (default = 1)"),
|
|
ECVF_RenderThreadSafe);
|
|
|
|
static TAutoConsoleVariable<int32> CVarRayTracingShadowsPointLight(
|
|
TEXT("r.RayTracing.Shadows.Lights.Point"),
|
|
1,
|
|
TEXT("Enables ray tracing shadows for point lights (default = 1)"),
|
|
ECVF_RenderThreadSafe);
|
|
|
|
static TAutoConsoleVariable<int32> CVarRayTracingShadowsSpotLight(
|
|
TEXT("r.RayTracing.Shadows.Lights.Spot"),
|
|
1,
|
|
TEXT("Enables ray tracing shadows for spot lights (default = 1)"),
|
|
ECVF_RenderThreadSafe);
|
|
|
|
static TAutoConsoleVariable<int32> CVarRayTracingShadowsRectLight(
|
|
TEXT("r.RayTracing.Shadows.Lights.Rect"),
|
|
1,
|
|
TEXT("Enables ray tracing shadows for rect light (default = 1)"),
|
|
ECVF_RenderThreadSafe);
|
|
|
|
static TAutoConsoleVariable<int32> CVarAppliedLightFunctionOnHair(
|
|
TEXT("r.HairStrands.LightFunction"),
|
|
1,
|
|
TEXT("Enables Light function on hair"),
|
|
ECVF_RenderThreadSafe);
|
|
|
|
static TAutoConsoleVariable<int32> CVarOnePassProjectionSkipScreenShadowMask(
|
|
TEXT("r.Shadow.Virtual.OnePassProjection.SkipScreenShadowMask"),
|
|
1,
|
|
TEXT("Allows skipping the screen space shadow mask entirely when only a virtual shadow map would write into it.\n")
|
|
TEXT("Should generally be left enabled outside of debugging."),
|
|
ECVF_RenderThreadSafe);
|
|
|
|
static TAutoConsoleVariable<float> CVarContactShadowsOverrideLength(
|
|
TEXT("r.ContactShadows.OverrideLength"),
|
|
-1.0f,
|
|
TEXT("Allows overriding the contact shadow length for all directional lights.\n")
|
|
TEXT("Disabled when < 0.\n")
|
|
TEXT("Should generally be left disabled outside of debugging."),
|
|
ECVF_RenderThreadSafe);
|
|
|
|
static TAutoConsoleVariable<bool> CVarContactShadowsOverrideLengthInWS(
|
|
TEXT("r.ContactShadows.OverrideLengthInWS"),
|
|
false,
|
|
TEXT("Whether r.ContactShadows.OverrideLength is in world space units or in screen space units."),
|
|
ECVF_RenderThreadSafe);
|
|
|
|
static TAutoConsoleVariable<float> CVarContactShadowsOverrideShadowCastingIntensity(
|
|
TEXT("r.ContactShadows.OverrideShadowCastingIntensity"),
|
|
-1.0f,
|
|
TEXT("Allows overriding the contact shadow casting intensity for all directional lights.\n")
|
|
TEXT("Disabled when < 0.\n")
|
|
TEXT("Should generally be left disabled outside of debugging."),
|
|
ECVF_RenderThreadSafe);
|
|
|
|
static TAutoConsoleVariable<float> CVarContactShadowsOverrideNonShadowCastingIntensity(
|
|
TEXT("r.ContactShadows.OverrideNonShadowCastingIntensity"),
|
|
-1.0f,
|
|
TEXT("Allows overriding the contact shadow non casting intensity for all directional lights.\n")
|
|
TEXT("Disabled when < 0.\n")
|
|
TEXT("Should generally be left disabled outside of debugging."),
|
|
ECVF_RenderThreadSafe);
|
|
|
|
static TAutoConsoleVariable<int32> CVarHairStrandsAllowOneTransmittancePass(
|
|
TEXT("r.HairStrands.Lighting.AllowOneTransmittancePass"),
|
|
1,
|
|
TEXT("Allows one transmittance pass for hair strands lighting to have better performance (experimental).\n"),
|
|
ECVF_RenderThreadSafe);
|
|
|
|
#if ENABLE_DEBUG_DISCARD_PROP
|
|
static float GDebugLightDiscardProp = 0.0f;
|
|
static FAutoConsoleVariableRef CVarDebugLightDiscardProp(
|
|
TEXT("r.DebugLightDiscardProp"),
|
|
GDebugLightDiscardProp,
|
|
TEXT("[0,1]: Proportion of lights to discard for debug/performance profiling purposes.")
|
|
);
|
|
#endif // ENABLE_DEBUG_DISCARD_PROP
|
|
|
|
#if RHI_RAYTRACING
|
|
|
|
static bool ShouldRenderRayTracingShadowsForLightType(ELightComponentType LightType)
|
|
{
|
|
switch(LightType)
|
|
{
|
|
case LightType_Directional:
|
|
return !!CVarRayTracingShadowsDirectionalLight.GetValueOnRenderThread();
|
|
case LightType_Point:
|
|
return !!CVarRayTracingShadowsPointLight.GetValueOnRenderThread();
|
|
case LightType_Spot:
|
|
return !!CVarRayTracingShadowsSpotLight.GetValueOnRenderThread();
|
|
case LightType_Rect:
|
|
return !!CVarRayTracingShadowsRectLight.GetValueOnRenderThread();
|
|
default:
|
|
return true;
|
|
}
|
|
}
|
|
|
|
bool ShouldRenderRayTracingShadows(const FSceneViewFamily& ViewFamily)
|
|
{
|
|
return ShouldRenderRayTracingEffect(
|
|
(CVarRayTracingOcclusion.GetValueOnRenderThread() > 0),
|
|
ERayTracingPipelineCompatibilityFlags::FullPipeline | ERayTracingPipelineCompatibilityFlags::Inline,
|
|
ViewFamily
|
|
);
|
|
}
|
|
|
|
bool ShouldRenderRayTracingShadowsForLight(const FSceneViewFamily& ViewFamily, ELightComponentType LightType, ECastRayTracedShadow::Type CastRayTracedShadow)
|
|
{
|
|
if ( !ShouldRenderRayTracingEffect(true, ERayTracingPipelineCompatibilityFlags::FullPipeline | ERayTracingPipelineCompatibilityFlags::Inline, ViewFamily)
|
|
|| !ShouldRenderRayTracingShadowsForLightType(LightType))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
switch (CastRayTracedShadow)
|
|
{
|
|
case ECastRayTracedShadow::Enabled:
|
|
return true;
|
|
|
|
case ECastRayTracedShadow::UseProjectSetting:
|
|
return ShouldRenderRayTracingShadows(ViewFamily);
|
|
|
|
default:
|
|
return false;
|
|
}
|
|
}
|
|
|
|
bool ShouldRenderRayTracingShadowsForLight(const FSceneViewFamily& ViewFamily, const FLightSceneProxy& LightProxy)
|
|
{
|
|
return ShouldRenderRayTracingShadowsForLight(ViewFamily, ELightComponentType(LightProxy.GetLightType()), LightProxy.CastsRaytracedShadow());
|
|
}
|
|
|
|
bool ShouldRenderRayTracingShadowsForLight(const FSceneViewFamily& ViewFamily, const FLightSceneInfoCompact& LightInfo)
|
|
{
|
|
return ShouldRenderRayTracingShadowsForLight(ViewFamily, ELightComponentType(LightInfo.LightType), LightInfo.CastRaytracedShadow);
|
|
}
|
|
#endif // RHI_RAYTRACING
|
|
|
|
void GetLightContactShadowParameters(const FLightSceneProxy* Proxy, float& OutLength, bool& bOutLengthInWS, float& OutCastingIntensity, float& OutNonCastingIntensity)
|
|
{
|
|
OutLength = Proxy->GetContactShadowLength();
|
|
bOutLengthInWS = Proxy->IsContactShadowLengthInWS();
|
|
OutCastingIntensity = Proxy->GetContactShadowCastingIntensity();
|
|
OutNonCastingIntensity = Proxy->GetContactShadowNonCastingIntensity();
|
|
|
|
if (CVarContactShadowsOverrideLength.GetValueOnAnyThread() >= 0.0f)
|
|
{
|
|
OutLength = CVarContactShadowsOverrideLength.GetValueOnAnyThread();
|
|
bOutLengthInWS = CVarContactShadowsOverrideLengthInWS.GetValueOnAnyThread();
|
|
}
|
|
|
|
if (CVarContactShadowsOverrideShadowCastingIntensity.GetValueOnAnyThread() >= 0.0f)
|
|
{
|
|
OutCastingIntensity = CVarContactShadowsOverrideShadowCastingIntensity.GetValueOnAnyThread();
|
|
}
|
|
|
|
if (CVarContactShadowsOverrideNonShadowCastingIntensity.GetValueOnAnyThread() >= 0.0f)
|
|
{
|
|
OutNonCastingIntensity = CVarContactShadowsOverrideNonShadowCastingIntensity.GetValueOnAnyThread();
|
|
}
|
|
|
|
if (!bOutLengthInWS)
|
|
{
|
|
// Multiply by 2 for screen space in order to preserve old values after introducing multiply by View.ClipToView[1][1] in shader.
|
|
OutLength *= 2.0f;
|
|
}
|
|
}
|
|
|
|
void FLightFunctionSharedParameters::Bind(const FShaderParameterMap& ParameterMap)
|
|
{
|
|
LightFunctionParameters.Bind(ParameterMap, TEXT("LightFunctionParameters"));
|
|
}
|
|
|
|
FVector4f FLightFunctionSharedParameters::GetLightFunctionSharedParameters(const FLightSceneInfo* LightSceneInfo, float ShadowFadeFraction)
|
|
{
|
|
bool bIsSpotLight = LightSceneInfo->Proxy->GetLightType() == LightType_Spot;
|
|
const bool bIsPointLight = LightSceneInfo->Proxy->GetLightType() == LightType_Point;
|
|
float TanOuterAngle = bIsSpotLight ? FMath::Tan(LightSceneInfo->Proxy->GetOuterConeAngle()) : 1.0f;
|
|
|
|
if (LightSceneInfo->Proxy->GetLightType() == LightType_Rect)
|
|
{
|
|
// Rect light can have a spot like perspective projection
|
|
FRectLightSceneProxy* RectLightProxy = (FRectLightSceneProxy*)LightSceneInfo->Proxy;
|
|
if (RectLightProxy->LightFunctionConeAngleTangent > 0.0f)
|
|
{
|
|
bIsSpotLight = true;
|
|
TanOuterAngle = RectLightProxy->LightFunctionConeAngleTangent;
|
|
}
|
|
else
|
|
{
|
|
bIsSpotLight = false;
|
|
TanOuterAngle = 0.0f;
|
|
}
|
|
}
|
|
|
|
return FVector4f(TanOuterAngle, ShadowFadeFraction, bIsSpotLight ? 1.0f : 0.0f, bIsPointLight ? 1.0f : 0.0f);
|
|
}
|
|
|
|
class FStencilConeIndexBuffer : public FIndexBuffer
|
|
{
|
|
public:
|
|
// A side is a line of vertices going from the cone's origin to the edge of its SphereRadius
|
|
static const int32 NumSides = 18;
|
|
// A slice is a circle of vertices in the cone's XY plane
|
|
static const int32 NumSlices = 12;
|
|
|
|
static const uint32 NumVerts = NumSides * NumSlices * 2;
|
|
|
|
void InitRHI(FRHICommandListBase& RHICmdList) override
|
|
{
|
|
TArray<uint16> Indices;
|
|
|
|
Indices.Empty((NumSlices - 1) * NumSides * 12);
|
|
// Generate triangles for the vertices of the cone shape
|
|
for (int32 SliceIndex = 0; SliceIndex < NumSlices - 1; SliceIndex++)
|
|
{
|
|
for (int32 SideIndex = 0; SideIndex < NumSides; SideIndex++)
|
|
{
|
|
const int32 CurrentIndex = SliceIndex * NumSides + SideIndex % NumSides;
|
|
const int32 NextSideIndex = SliceIndex * NumSides + (SideIndex + 1) % NumSides;
|
|
const int32 NextSliceIndex = (SliceIndex + 1) * NumSides + SideIndex % NumSides;
|
|
const int32 NextSliceAndSideIndex = (SliceIndex + 1) * NumSides + (SideIndex + 1) % NumSides;
|
|
|
|
Indices.Add(CurrentIndex);
|
|
Indices.Add(NextSideIndex);
|
|
Indices.Add(NextSliceIndex);
|
|
Indices.Add(NextSliceIndex);
|
|
Indices.Add(NextSideIndex);
|
|
Indices.Add(NextSliceAndSideIndex);
|
|
}
|
|
}
|
|
|
|
// Generate triangles for the vertices of the spherical cap
|
|
const int32 CapIndexStart = NumSides * NumSlices;
|
|
|
|
for (int32 SliceIndex = 0; SliceIndex < NumSlices - 1; SliceIndex++)
|
|
{
|
|
for (int32 SideIndex = 0; SideIndex < NumSides; SideIndex++)
|
|
{
|
|
const int32 CurrentIndex = SliceIndex * NumSides + SideIndex % NumSides + CapIndexStart;
|
|
const int32 NextSideIndex = SliceIndex * NumSides + (SideIndex + 1) % NumSides + CapIndexStart;
|
|
const int32 NextSliceIndex = (SliceIndex + 1) * NumSides + SideIndex % NumSides + CapIndexStart;
|
|
const int32 NextSliceAndSideIndex = (SliceIndex + 1) * NumSides + (SideIndex + 1) % NumSides + CapIndexStart;
|
|
|
|
Indices.Add(CurrentIndex);
|
|
Indices.Add(NextSliceIndex);
|
|
Indices.Add(NextSideIndex);
|
|
Indices.Add(NextSideIndex);
|
|
Indices.Add(NextSliceIndex);
|
|
Indices.Add(NextSliceAndSideIndex);
|
|
}
|
|
}
|
|
|
|
NumIndices = Indices.Num();
|
|
|
|
// Create index buffer. Fill buffer with initial data upon creation
|
|
IndexBufferRHI = UE::RHIResourceUtils::CreateIndexBufferFromArray(RHICmdList, TEXT("FStencilConeIndexBuffer"), EBufferUsageFlags::Static, MakeConstArrayView(Indices));
|
|
}
|
|
|
|
int32 GetIndexCount() const { return NumIndices; }
|
|
|
|
protected:
|
|
int32 NumIndices;
|
|
};
|
|
|
|
/** The stencil cone index buffer. */
|
|
TGlobalResource<FStencilConeIndexBuffer> GStencilConeIndexBuffer;
|
|
|
|
/**
|
|
* Vertex buffer for a cone. It holds zero'd out data since the actual math is done on the shader
|
|
*/
|
|
class FStencilConeVertexBuffer : public FVertexBuffer
|
|
{
|
|
public:
|
|
static const int32 NumVerts = FStencilConeIndexBuffer::NumSides * FStencilConeIndexBuffer::NumSlices * 2;
|
|
|
|
/**
|
|
* Initialize the RHI for this rendering resource
|
|
*/
|
|
void InitRHI(FRHICommandListBase& RHICmdList) override
|
|
{
|
|
TArray<FVector4f> Verts;
|
|
Verts.Empty(NumVerts);
|
|
for (int32 s = 0; s < NumVerts; s++)
|
|
{
|
|
Verts.Emplace(0, 0, 0, 0);
|
|
}
|
|
|
|
VertexBufferRHI = UE::RHIResourceUtils::CreateVertexBufferFromArray(RHICmdList, TEXT("FStencilConeVertexBuffer"), EBufferUsageFlags::Static, MakeConstArrayView(Verts));
|
|
}
|
|
|
|
int32 GetVertexCount() const { return NumVerts; }
|
|
};
|
|
|
|
/** The (dummy) stencil cone vertex buffer. */
|
|
TGlobalResource<FStencilConeVertexBuffer> GStencilConeVertexBuffer;
|
|
|
|
void FStencilingGeometryShaderParameters::Bind(const FShaderParameterMap& ParameterMap)
|
|
{
|
|
StencilGeometryPosAndScale.Bind(ParameterMap, TEXT("StencilingGeometryPosAndScale"));
|
|
StencilConeParameters.Bind(ParameterMap, TEXT("StencilingConeParameters"));
|
|
StencilConeTransform.Bind(ParameterMap, TEXT("StencilingConeTransform"));
|
|
}
|
|
|
|
void FStencilingGeometryShaderParameters::Set(FRHIBatchedShaderParameters& BatchedParameters, const FVector4f& InStencilingGeometryPosAndScale) const
|
|
{
|
|
const FParameters P = GetParameters(InStencilingGeometryPosAndScale);
|
|
SetShaderValue(BatchedParameters, StencilGeometryPosAndScale, P.StencilingGeometryPosAndScale);
|
|
SetShaderValue(BatchedParameters, StencilConeParameters, P.StencilingConeParameters);
|
|
}
|
|
|
|
void FStencilingGeometryShaderParameters::Set(FRHIBatchedShaderParameters& BatchedParameters, const FSceneView& View, const FLightSceneInfo* LightSceneInfo) const
|
|
{
|
|
const FParameters P = GetParameters(View, LightSceneInfo);
|
|
if (LightSceneInfo->Proxy->GetLightType() == LightType_Point ||
|
|
LightSceneInfo->Proxy->GetLightType() == LightType_Rect)
|
|
{
|
|
SetShaderValue(BatchedParameters, StencilGeometryPosAndScale, P.StencilingGeometryPosAndScale);
|
|
SetShaderValue(BatchedParameters, StencilConeParameters, P.StencilingConeParameters);
|
|
}
|
|
else if (LightSceneInfo->Proxy->GetLightType() == LightType_Spot)
|
|
{
|
|
SetShaderValue(BatchedParameters, StencilConeTransform, P.StencilingConeTransform);
|
|
SetShaderValue(BatchedParameters, StencilConeParameters, P.StencilingConeParameters);
|
|
}
|
|
}
|
|
|
|
FStencilingGeometryShaderParameters::FParameters FStencilingGeometryShaderParameters::GetParameters(const FVector4f& InStencilingGeometryPosAndScale)
|
|
{
|
|
FParameters Out;
|
|
Out.StencilingGeometryPosAndScale = InStencilingGeometryPosAndScale;
|
|
Out.StencilingConeParameters = FVector4f(0.0f, 0.0f, 0.0f, 0.0f);
|
|
Out.StencilingConeTransform = FMatrix44f::Identity;
|
|
return Out;
|
|
}
|
|
|
|
FStencilingGeometryShaderParameters::FParameters FStencilingGeometryShaderParameters::GetParameters(const FSceneView& View, const FLightSceneInfo* LightSceneInfo)
|
|
{
|
|
FParameters Out;
|
|
if (LightSceneInfo->Proxy->GetLightType() == LightType_Point ||
|
|
LightSceneInfo->Proxy->GetLightType() == LightType_Rect)
|
|
{
|
|
StencilingGeometry::GStencilSphereVertexBuffer.CalcTransform(Out.StencilingGeometryPosAndScale, LightSceneInfo->Proxy->GetBoundingSphere(), View.ViewMatrices.GetPreViewTranslation());
|
|
Out.StencilingConeParameters = FVector4f(0.0f, 0.0f, 0.0f, 0.0f);
|
|
Out.StencilingConeTransform = FMatrix44f::Identity;
|
|
}
|
|
else if (LightSceneInfo->Proxy->GetLightType() == LightType_Spot)
|
|
{
|
|
const FMatrix WorldToTranslatedWorld = FTranslationMatrix(View.ViewMatrices.GetPreViewTranslation());
|
|
Out.StencilingGeometryPosAndScale = FVector4f(0.0f, 0.0f, 0.0f, 0.0f);
|
|
Out.StencilingConeTransform = FMatrix44f(LightSceneInfo->Proxy->GetLightToWorld() * WorldToTranslatedWorld);
|
|
Out.StencilingConeParameters =
|
|
FVector4f(
|
|
FStencilConeIndexBuffer::NumSides,
|
|
FStencilConeIndexBuffer::NumSlices,
|
|
LightSceneInfo->Proxy->GetOuterConeAngle(),
|
|
LightSceneInfo->Proxy->GetRadius());
|
|
}
|
|
return Out;
|
|
}
|
|
|
|
bool FDeferredLightVS::ShouldCompilePermutation(const FGlobalShaderPermutationParameters& Parameters)
|
|
{
|
|
FPermutationDomain PermutationVector(Parameters.PermutationId);
|
|
if (PermutationVector.Get<FRadialLight>())
|
|
{
|
|
return IsFeatureLevelSupported(Parameters.Platform, ERHIFeatureLevel::SM5) || IsMobileDeferredShadingEnabled(Parameters.Platform) || MobileLocalLightsBufferEnabled(Parameters.Platform);
|
|
}
|
|
// used with FPrefilterPlanarReflectionPS on mobile
|
|
return true;
|
|
}
|
|
|
|
void FDeferredLightVS::ModifyCompilationEnvironment(const FGlobalShaderPermutationParameters& Parameters, FShaderCompilerEnvironment& OutEnvironment)
|
|
{
|
|
FGlobalShader::ModifyCompilationEnvironment(Parameters, OutEnvironment);
|
|
}
|
|
|
|
FDrawFullScreenRectangleParameters FDeferredLightVS::GetFullScreenRectParameters(
|
|
float X, float Y,
|
|
float SizeX, float SizeY,
|
|
float U, float V,
|
|
float SizeU, float SizeV,
|
|
FIntPoint InTargetSize,
|
|
FIntPoint InTextureSize)
|
|
{
|
|
FDrawFullScreenRectangleParameters Out;
|
|
Out.PosScaleBias = FVector4f(SizeX, SizeY, X, Y);
|
|
Out.UVScaleBias = FVector4f(SizeU, SizeV, U, V);
|
|
Out.InvTargetSizeAndTextureSize = FVector4f(
|
|
1.0f / InTargetSize.X, 1.0f / InTargetSize.Y,
|
|
1.0f / InTextureSize.X, 1.0f / InTextureSize.Y);
|
|
return Out;
|
|
}
|
|
|
|
FDeferredLightVS::FParameters FDeferredLightVS::GetParameters(const FViewInfo& View,
|
|
float X, float Y,
|
|
float SizeX, float SizeY,
|
|
float U, float V,
|
|
float SizeU, float SizeV,
|
|
FIntPoint TargetSize,
|
|
FIntPoint TextureSize,
|
|
bool bBindViewUniform)
|
|
{
|
|
FParameters Out;
|
|
if (bBindViewUniform)
|
|
{
|
|
Out.View = View.ViewUniformBuffer;
|
|
}
|
|
Out.Geometry = FStencilingGeometryShaderParameters::GetParameters(FVector4f(0, 0, 0, 0));
|
|
Out.FullScreenRect = GetFullScreenRectParameters(X, Y, SizeX, SizeY, U, V, SizeU, SizeV, TargetSize, TextureSize);
|
|
return Out;
|
|
}
|
|
|
|
FDeferredLightVS::FParameters FDeferredLightVS::GetParameters(const FViewInfo& View, bool bBindViewUniform)
|
|
{
|
|
FParameters Out;
|
|
if (bBindViewUniform)
|
|
{
|
|
Out.View = View.ViewUniformBuffer;
|
|
}
|
|
Out.Geometry = FStencilingGeometryShaderParameters::GetParameters(FVector4f(0, 0, 0, 0));
|
|
Out.FullScreenRect = GetFullScreenRectParameters(
|
|
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);
|
|
|
|
return Out;
|
|
}
|
|
|
|
FDeferredLightVS::FParameters FDeferredLightVS::GetParameters(const FViewInfo& View, const FSphere& LightBounds, bool bBindViewUniform)
|
|
{
|
|
FVector4f StencilingSpherePosAndScale;
|
|
StencilingGeometry::GStencilSphereVertexBuffer.CalcTransform(StencilingSpherePosAndScale, LightBounds, View.ViewMatrices.GetPreViewTranslation());
|
|
|
|
FParameters Out;
|
|
if (bBindViewUniform)
|
|
{
|
|
Out.View = View.ViewUniformBuffer;
|
|
}
|
|
Out.Geometry = FStencilingGeometryShaderParameters::GetParameters((FVector4f)StencilingSpherePosAndScale); // LWC_TODO: Precision loss
|
|
Out.FullScreenRect = GetFullScreenRectParameters(0, 0, 0, 0, 0, 0, 0, 0, FIntPoint(1, 1), FIntPoint(1, 1));
|
|
return Out;
|
|
}
|
|
|
|
FDeferredLightVS::FParameters FDeferredLightVS::GetParameters(const FViewInfo& View, const FLightSceneInfo* LightSceneInfo, bool bBindViewUniform)
|
|
{
|
|
FParameters Out;
|
|
if (bBindViewUniform)
|
|
{
|
|
Out.View = View.ViewUniformBuffer;
|
|
}
|
|
Out.Geometry = FStencilingGeometryShaderParameters::GetParameters(View, LightSceneInfo);
|
|
Out.FullScreenRect = GetFullScreenRectParameters(0, 0, 0, 0, 0, 0, 0, 0, FIntPoint(1, 1), FIntPoint(1, 1));
|
|
return Out;
|
|
}
|
|
|
|
static void RenderLight(
|
|
FRDGBuilder& GraphBuilder,
|
|
const FScene* Scene,
|
|
const FViewInfo& View,
|
|
const FMinimalSceneTextures& SceneTextures,
|
|
const FLightSceneInfo* LightSceneInfo,
|
|
FRDGTextureRef ScreenShadowMaskTexture,
|
|
FRDGTextureRef LightingChannelsTexture,
|
|
bool bRenderOverlap,
|
|
bool bCloudShadow,
|
|
const bool bCanLightUsesAtlasForUnbatchedLight = false,
|
|
TRDGUniformBufferRef<FVirtualShadowMapUniformParameters> VirtualShadowMapUniformBuffer = nullptr,
|
|
FRDGTextureRef ShadowMaskBits = nullptr,
|
|
int32 VirtualShadowMapId = INDEX_NONE);
|
|
|
|
FDeferredLightUniformStruct GetDeferredLightParameters(const FSceneView& View, const FLightSceneInfo& LightSceneInfo, bool bUseLightFunctionAtlas, uint32 LightFlags)
|
|
{
|
|
FDeferredLightUniformStruct Out;
|
|
|
|
FLightRenderParameters LightParameters;
|
|
LightSceneInfo.Proxy->GetLightShaderParameters(LightParameters, LightFlags);
|
|
LightParameters.MakeShaderParameters(View.ViewMatrices, View.GetLastEyeAdaptationExposure(), Out.LightParameters);
|
|
|
|
const bool bIsRayTracedLight = ShouldRenderRayTracingShadowsForLight(*View.Family, *LightSceneInfo.Proxy);
|
|
|
|
const FVector2D FadeParams = LightSceneInfo.Proxy->GetDirectionalLightDistanceFadeParameters(View.GetFeatureLevel(), !bIsRayTracedLight && LightSceneInfo.IsPrecomputedLightingValid(), View.MaxShadowCascades);
|
|
|
|
// use MAD for efficiency in the shader
|
|
Out.DistanceFadeMAD = FVector2f(FadeParams.Y, -FadeParams.X * FadeParams.Y);
|
|
|
|
int32 ShadowMapChannel = LightSceneInfo.Proxy->GetShadowMapChannel();
|
|
|
|
const bool bAllowStaticLighting = IsStaticLightingAllowed();
|
|
|
|
if (!bAllowStaticLighting)
|
|
{
|
|
ShadowMapChannel = INDEX_NONE;
|
|
Out.ShadowMapChannelMask = FVector4f(0);
|
|
}
|
|
else
|
|
{
|
|
Out.ShadowMapChannelMask = FVector4f(
|
|
ShadowMapChannel == 0 ? 1 : 0,
|
|
ShadowMapChannel == 1 ? 1 : 0,
|
|
ShadowMapChannel == 2 ? 1 : 0,
|
|
ShadowMapChannel == 3 ? 1 : 0);
|
|
}
|
|
|
|
const bool bDynamicShadows = View.Family->EngineShowFlags.DynamicShadows && GetShadowQuality() > 0;
|
|
const bool bHasLightFunction = LightSceneInfo.Proxy->GetLightFunctionMaterial() != NULL && (!bUseLightFunctionAtlas || !LightSceneInfo.Proxy->HasValidLightFunctionAtlasSlot());
|
|
Out.ShadowedBits = LightSceneInfo.Proxy->CastsStaticShadow() || bHasLightFunction ? 1 : 0;
|
|
Out.ShadowedBits |= LightSceneInfo.Proxy->CastsDynamicShadow() && View.Family->EngineShowFlags.DynamicShadows ? 3 : 0;
|
|
|
|
Out.VolumetricScatteringIntensity = LightSceneInfo.Proxy->GetVolumetricScatteringIntensity();
|
|
|
|
static auto* ContactShadowsCVar = IConsoleManager::Get().FindTConsoleVariableDataInt(TEXT("r.ContactShadows"));
|
|
|
|
Out.ContactShadowLength = 0;
|
|
Out.ContactShadowCastingIntensity = 1.0f;
|
|
Out.ContactShadowNonCastingIntensity = 0.0f;
|
|
|
|
if (ContactShadowsCVar && ContactShadowsCVar->GetValueOnRenderThread() != 0 && View.Family->EngineShowFlags.ContactShadows)
|
|
{
|
|
float ContactShadowLength;
|
|
bool bContactShadowLengthInWS;
|
|
float ContactShadowCastingIntensity;
|
|
float ContactShadowNonCastingIntensity;
|
|
GetLightContactShadowParameters(LightSceneInfo.Proxy, ContactShadowLength, bContactShadowLengthInWS, ContactShadowCastingIntensity, ContactShadowNonCastingIntensity);
|
|
|
|
Out.ContactShadowLength = ContactShadowLength;
|
|
// Sign indicates if contact shadow length is in world space or screen space.
|
|
Out.ContactShadowLength *= bContactShadowLengthInWS ? -1.0f : 1.0f;
|
|
Out.ContactShadowCastingIntensity = ContactShadowCastingIntensity;
|
|
Out.ContactShadowNonCastingIntensity = ContactShadowNonCastingIntensity;
|
|
}
|
|
|
|
// When rendering reflection captures, the direct lighting of the light is actually the indirect specular from the main view
|
|
if (View.bIsReflectionCapture)
|
|
{
|
|
Out.LightParameters.Color *= LightSceneInfo.Proxy->GetIndirectLightingScale();
|
|
}
|
|
|
|
const ELightComponentType LightType = (ELightComponentType)LightSceneInfo.Proxy->GetLightType();
|
|
if ((LightType == LightType_Point || LightType == LightType_Spot || LightType == LightType_Rect) && View.IsPerspectiveProjection())
|
|
{
|
|
Out.LightParameters.Color *= GetLightFadeFactor(View, LightSceneInfo.Proxy);
|
|
}
|
|
|
|
Out.LightingChannelMask = LightSceneInfo.Proxy->GetLightingChannelMask();
|
|
|
|
// Ensure the light falloff exponent is set to 0 so that lighting shaders handle it as inverse-squared attenuated light
|
|
if (LightSceneInfo.Proxy->IsInverseSquared())
|
|
{
|
|
Out.LightParameters.FalloffExponent = 0.0f;
|
|
}
|
|
return Out;
|
|
}
|
|
|
|
FDeferredLightUniformStruct GetSimpleDeferredLightParameters(
|
|
const FSceneView& View,
|
|
const FSimpleLightEntry& SimpleLight,
|
|
const FVector& LightWorldPosition)
|
|
{
|
|
FDeferredLightUniformStruct Out;
|
|
Out.ShadowMapChannelMask = FVector4f(0, 0, 0, 0);
|
|
Out.DistanceFadeMAD = FVector2f(0, 0);
|
|
Out.ContactShadowLength = 0.0f;
|
|
Out.ContactShadowCastingIntensity = 1.f;
|
|
Out.ContactShadowNonCastingIntensity = 0.f;
|
|
Out.VolumetricScatteringIntensity = SimpleLight.VolumetricScatteringIntensity;
|
|
Out.ShadowedBits = 0;
|
|
Out.LightingChannelMask = 0;
|
|
|
|
Out.LightParameters.TranslatedWorldPosition = FVector3f(LightWorldPosition + View.ViewMatrices.GetPreViewTranslation());
|
|
Out.LightParameters.InvRadius = 1.0f / FMath::Max(SimpleLight.Radius, KINDA_SMALL_NUMBER);
|
|
Out.LightParameters.Color = (FVector3f)SimpleLight.Color * FLightRenderParameters::GetLightExposureScale(View.GetLastEyeAdaptationExposure(), SimpleLight.InverseExposureBlend);
|
|
Out.LightParameters.FalloffExponent = SimpleLight.Exponent;
|
|
Out.LightParameters.Direction = FVector3f(1, 0, 0);
|
|
Out.LightParameters.Tangent = FVector3f(1, 0, 0);
|
|
Out.LightParameters.SpotAngles = FVector2f(-2, 1);
|
|
Out.LightParameters.SpecularScale = SimpleLight.SpecularScale;
|
|
Out.LightParameters.DiffuseScale = SimpleLight.DiffuseScale;
|
|
Out.LightParameters.SourceRadius = 0.0f;
|
|
Out.LightParameters.SoftSourceRadius = 0.0f;
|
|
Out.LightParameters.SourceLength = 0.0f;
|
|
Out.LightParameters.RectLightBarnCosAngle = 0;
|
|
Out.LightParameters.RectLightBarnLength = -2.0f;
|
|
Out.LightParameters.RectLightAtlasUVOffset = FVector2f::ZeroVector;
|
|
Out.LightParameters.RectLightAtlasUVScale = FVector2f::ZeroVector;
|
|
Out.LightParameters.RectLightAtlasMaxLevel = FLightRenderParameters::GetRectLightAtlasInvalidMIPLevel();
|
|
Out.LightParameters.IESAtlasIndex = INDEX_NONE;
|
|
Out.LightParameters.LightFunctionAtlasLightIndex = 0;
|
|
Out.LightParameters.bAffectsTranslucentLighting = 0;
|
|
return Out;
|
|
}
|
|
FDeferredLightUniformStruct GetSimpleDeferredLightParameters(
|
|
const FSceneView& View,
|
|
const FSimpleLightEntry& SimpleLight,
|
|
const FSimpleLightPerViewEntry& SimpleLightPerViewData)
|
|
{
|
|
return GetSimpleDeferredLightParameters(View, SimpleLight, SimpleLightPerViewData.Position);
|
|
}
|
|
|
|
FLightOcclusionType GetLightOcclusionType(const FLightSceneProxy& Proxy, const FSceneViewFamily& ViewFamily)
|
|
{
|
|
bool bUseRaytracing = false;
|
|
#if RHI_RAYTRACING
|
|
bUseRaytracing = ShouldRenderRayTracingShadowsForLight(ViewFamily, Proxy);
|
|
#endif
|
|
EMegaLightsMode MegaLightsMode = MegaLights::GetMegaLightsMode(ViewFamily, Proxy.GetLightType(), Proxy.AllowMegaLights(), Proxy.GetMegaLightsShadowMethod());
|
|
|
|
if (MegaLightsMode != EMegaLightsMode::Disabled)
|
|
{
|
|
return MegaLightsMode == EMegaLightsMode::EnabledVSM ? FLightOcclusionType::MegaLightsVSM : FLightOcclusionType::MegaLights;
|
|
}
|
|
|
|
return bUseRaytracing ? FLightOcclusionType::Raytraced : FLightOcclusionType::Shadowmap;
|
|
}
|
|
|
|
FLightOcclusionType GetLightOcclusionType(const FLightSceneInfoCompact& LightInfo, const FSceneViewFamily& ViewFamily)
|
|
{
|
|
bool bUseRaytracing = false;
|
|
#if RHI_RAYTRACING
|
|
bUseRaytracing = ShouldRenderRayTracingShadowsForLight(ViewFamily, LightInfo);
|
|
#endif
|
|
EMegaLightsMode MegaLightsMode = MegaLights::GetMegaLightsMode(ViewFamily, LightInfo.LightType, LightInfo.bAllowMegaLights, LightInfo.MegaLightsShadowMethod);
|
|
|
|
if (MegaLightsMode != EMegaLightsMode::Disabled)
|
|
{
|
|
return MegaLightsMode == EMegaLightsMode::EnabledVSM ? FLightOcclusionType::MegaLightsVSM : FLightOcclusionType::MegaLights;
|
|
}
|
|
|
|
return bUseRaytracing ? FLightOcclusionType::Raytraced : FLightOcclusionType::Shadowmap;
|
|
}
|
|
|
|
float GetLightFadeFactor(const FSceneView& View, const FLightSceneProxy* Proxy)
|
|
{
|
|
// Distance fade
|
|
FSphere Bounds = Proxy->GetBoundingSphere();
|
|
|
|
const float DistanceSquared = (Bounds.Center - View.ViewMatrices.GetViewOrigin()).SizeSquared();
|
|
extern float GMinScreenRadiusForLights;
|
|
float SizeFade = FMath::Square(FMath::Min(0.0002f, GMinScreenRadiusForLights / Bounds.W) * View.LODDistanceFactor) * DistanceSquared;
|
|
SizeFade = FMath::Clamp(6.0f - 6.0f * SizeFade, 0.0f, 1.0f);
|
|
|
|
extern float GLightMaxDrawDistanceScale;
|
|
float ProxyMaxDist = Proxy->GetMaxDrawDistance();
|
|
float ScaledMaxDist = ProxyMaxDist * GLightMaxDrawDistanceScale;
|
|
// NOTE: Feels like we should scale fade range by GLightMaxDrawDistanceScale as well, but would change legacy behavior
|
|
float Range = Proxy->GetFadeRange();
|
|
float DistanceFade = ProxyMaxDist > 0.0f ? (ScaledMaxDist - FMath::Sqrt(DistanceSquared)) / Range : 1.0f;
|
|
DistanceFade = FMath::Clamp(DistanceFade, 0.0f, 1.0f);
|
|
return SizeFade * DistanceFade;
|
|
}
|
|
|
|
void StencilingGeometry::DrawSphere(FRHICommandList& RHICmdList, uint32 InstanceCount)
|
|
{
|
|
RHICmdList.SetStreamSource(0, StencilingGeometry::GStencilSphereVertexBuffer.VertexBufferRHI, 0);
|
|
RHICmdList.DrawIndexedPrimitive(StencilingGeometry::GStencilSphereIndexBuffer.IndexBufferRHI, 0, 0,
|
|
StencilingGeometry::GStencilSphereVertexBuffer.GetVertexCount(), 0,
|
|
StencilingGeometry::GStencilSphereIndexBuffer.GetIndexCount() / 3, InstanceCount);
|
|
}
|
|
|
|
void StencilingGeometry::DrawSphere(FRHICommandList& RHICmdList)
|
|
{
|
|
DrawSphere(RHICmdList, 1);
|
|
}
|
|
|
|
void StencilingGeometry::DrawVectorSphere(FRHICommandList& RHICmdList)
|
|
{
|
|
RHICmdList.SetStreamSource(0, StencilingGeometry::GStencilSphereVectorBuffer.VertexBufferRHI, 0);
|
|
RHICmdList.DrawIndexedPrimitive(StencilingGeometry::GStencilSphereIndexBuffer.IndexBufferRHI, 0, 0,
|
|
StencilingGeometry::GStencilSphereVectorBuffer.GetVertexCount(), 0,
|
|
StencilingGeometry::GStencilSphereIndexBuffer.GetIndexCount() / 3, 1);
|
|
}
|
|
|
|
void StencilingGeometry::DrawCone(FRHICommandList& RHICmdList)
|
|
{
|
|
// No Stream Source needed since it will generate vertices on the fly
|
|
RHICmdList.SetStreamSource(0, GStencilConeVertexBuffer.VertexBufferRHI, 0);
|
|
|
|
RHICmdList.DrawIndexedPrimitive(GStencilConeIndexBuffer.IndexBufferRHI, 0, 0,
|
|
FStencilConeIndexBuffer::NumVerts, 0, GStencilConeIndexBuffer.GetIndexCount() / 3, 1);
|
|
}
|
|
|
|
/** The stencil sphere vertex buffer. */
|
|
TGlobalResource<StencilingGeometry::TStencilSphereVertexBuffer<18, 12, FVector4f> > StencilingGeometry::GStencilSphereVertexBuffer;
|
|
TGlobalResource<StencilingGeometry::TStencilSphereVertexBuffer<18, 12, FVector3f> > StencilingGeometry::GStencilSphereVectorBuffer;
|
|
|
|
/** The stencil sphere index buffer. */
|
|
TGlobalResource<StencilingGeometry::TStencilSphereIndexBuffer<18, 12> > StencilingGeometry::GStencilSphereIndexBuffer;
|
|
|
|
TGlobalResource<StencilingGeometry::TStencilSphereVertexBuffer<4, 4, FVector4f> > StencilingGeometry::GLowPolyStencilSphereVertexBuffer;
|
|
TGlobalResource<StencilingGeometry::TStencilSphereIndexBuffer<4, 4> > StencilingGeometry::GLowPolyStencilSphereIndexBuffer;
|
|
|
|
// Implement a version for directional lights, and a version for point / spot lights
|
|
IMPLEMENT_GLOBAL_SHADER(FDeferredLightVS, "/Engine/Private/DeferredLightVertexShaders.usf", "VertexMain", SF_Vertex);
|
|
|
|
class FDeferredLightHairVS : public FGlobalShader
|
|
{
|
|
DECLARE_SHADER_TYPE(FDeferredLightHairVS, Global);
|
|
SHADER_USE_PARAMETER_STRUCT(FDeferredLightHairVS, FGlobalShader);
|
|
|
|
BEGIN_SHADER_PARAMETER_STRUCT(FParameters, )
|
|
SHADER_PARAMETER_RDG_UNIFORM_BUFFER(FHairStrandsViewUniformParameters, HairStrands)
|
|
END_SHADER_PARAMETER_STRUCT()
|
|
|
|
static bool ShouldCompilePermutation(const FGlobalShaderPermutationParameters& Parameters)
|
|
{
|
|
return IsFeatureLevelSupported(Parameters.Platform, ERHIFeatureLevel::SM5);
|
|
}
|
|
|
|
static void ModifyCompilationEnvironment(const FGlobalShaderPermutationParameters& Parameters, FShaderCompilerEnvironment& OutEnvironment)
|
|
{
|
|
FGlobalShader::ModifyCompilationEnvironment(Parameters, OutEnvironment);
|
|
OutEnvironment.SetDefine(TEXT("SHADER_HAIR"), 1);
|
|
}
|
|
};
|
|
|
|
IMPLEMENT_GLOBAL_SHADER(FDeferredLightHairVS, "/Engine/Private/DeferredLightVertexShaders.usf", "HairVertexMain", SF_Vertex);
|
|
|
|
enum class ELightSourceShape
|
|
{
|
|
Directional,
|
|
Capsule,
|
|
Rect,
|
|
|
|
MAX
|
|
};
|
|
|
|
|
|
/** A pixel shader for rendering the light in a deferred pass. */
|
|
class FDeferredLightPS : public FGlobalShader
|
|
{
|
|
DECLARE_SHADER_TYPE(FDeferredLightPS, Global)
|
|
SHADER_USE_PARAMETER_STRUCT(FDeferredLightPS, FGlobalShader);
|
|
|
|
class FSourceShapeDim : SHADER_PERMUTATION_ENUM_CLASS("LIGHT_SOURCE_SHAPE", ELightSourceShape);
|
|
class FSourceTextureDim : SHADER_PERMUTATION_BOOL("USE_SOURCE_TEXTURE");
|
|
class FIESProfileDim : SHADER_PERMUTATION_BOOL("USE_IES_PROFILE");
|
|
class FLightFunctionAtlasDim: SHADER_PERMUTATION_BOOL("USE_LIGHT_FUNCTION_ATLAS");
|
|
class FVisualizeCullingDim : SHADER_PERMUTATION_BOOL("VISUALIZE_LIGHT_CULLING");
|
|
class FLightingChannelsDim : SHADER_PERMUTATION_BOOL("USE_LIGHTING_CHANNELS");
|
|
class FTransmissionDim : SHADER_PERMUTATION_BOOL("USE_TRANSMISSION");
|
|
class FHairLighting : SHADER_PERMUTATION_INT("USE_HAIR_LIGHTING", 2);
|
|
class FHairComplexTransmittance: SHADER_PERMUTATION_BOOL("USE_HAIR_COMPLEX_TRANSMITTANCE");
|
|
class FAtmosphereTransmittance : SHADER_PERMUTATION_BOOL("USE_ATMOSPHERE_TRANSMITTANCE");
|
|
class FCloudTransmittance : SHADER_PERMUTATION_BOOL("USE_CLOUD_TRANSMITTANCE");
|
|
class FAnistropicMaterials : SHADER_PERMUTATION_BOOL("SUPPORTS_ANISOTROPIC_MATERIALS");
|
|
class FSubstrateTileType : SHADER_PERMUTATION_INT("SUBSTRATE_TILETYPE", 4);
|
|
class FVirtualShadowMapMask : SHADER_PERMUTATION_BOOL("USE_VIRTUAL_SHADOW_MAP_MASK");
|
|
|
|
using FPermutationDomain = TShaderPermutationDomain<
|
|
FSourceShapeDim,
|
|
FSourceTextureDim,
|
|
FIESProfileDim,
|
|
FLightFunctionAtlasDim,
|
|
FVisualizeCullingDim,
|
|
FLightingChannelsDim,
|
|
FTransmissionDim,
|
|
FHairLighting,
|
|
FHairComplexTransmittance,
|
|
FAtmosphereTransmittance,
|
|
FCloudTransmittance,
|
|
FAnistropicMaterials,
|
|
FSubstrateTileType,
|
|
FVirtualShadowMapMask>;
|
|
|
|
BEGIN_SHADER_PARAMETER_STRUCT(FParameters, )
|
|
SHADER_PARAMETER_RDG_UNIFORM_BUFFER(FSceneTextureUniformParameters, SceneTextures)
|
|
SHADER_PARAMETER_RDG_UNIFORM_BUFFER(FHairStrandsViewUniformParameters, HairStrands)
|
|
SHADER_PARAMETER_RDG_UNIFORM_BUFFER(FSubstrateGlobalUniformParameters, Substrate)
|
|
SHADER_PARAMETER_STRUCT_INCLUDE(FVolumetricCloudShadowAOParameters, CloudShadowAO)
|
|
SHADER_PARAMETER_STRUCT_INCLUDE(FLightCloudTransmittanceParameters, CloudShadow)
|
|
SHADER_PARAMETER_RDG_UNIFORM_BUFFER(FForwardLightUniformParameters, ForwardLightStruct)
|
|
SHADER_PARAMETER(uint32, CloudShadowEnabled)
|
|
SHADER_PARAMETER(uint32, HairTransmittanceBufferMaxCount)
|
|
SHADER_PARAMETER(uint32, HairShadowMaskValid)
|
|
SHADER_PARAMETER(FVector4f, ShadowChannelMask)
|
|
SHADER_PARAMETER_RDG_TEXTURE(Texture2D, LightAttenuationTexture)
|
|
SHADER_PARAMETER_SAMPLER(SamplerState, LightAttenuationTextureSampler)
|
|
SHADER_PARAMETER_RDG_TEXTURE(Texture2D, LightingChannelsTexture)
|
|
SHADER_PARAMETER_SAMPLER(SamplerState, LightingChannelsSampler)
|
|
SHADER_PARAMETER_RDG_BUFFER_SRV(Buffer, HairTransmittanceBuffer)
|
|
SHADER_PARAMETER_RDG_TEXTURE(Texture2D, ScreenShadowMaskSubPixelTexture)
|
|
SHADER_PARAMETER_STRUCT_REF(FViewUniformShaderParameters, View)
|
|
SHADER_PARAMETER_RDG_UNIFORM_BUFFER(FDeferredLightUniformStruct, DeferredLight)
|
|
SHADER_PARAMETER_RDG_UNIFORM_BUFFER(FLightFunctionAtlasGlobalParameters, LightFunctionAtlas)
|
|
// For virtual shadow map mask
|
|
SHADER_PARAMETER_RDG_UNIFORM_BUFFER(FVirtualShadowMapUniformParameters, VirtualShadowMap)
|
|
SHADER_PARAMETER(int32, VirtualShadowMapId)
|
|
SHADER_PARAMETER(int32, LightSceneId)
|
|
SHADER_PARAMETER_RDG_TEXTURE(Texture2D, ShadowMaskBits)
|
|
// Heterogeneous Volume data
|
|
//SHADER_PARAMETER_RDG_UNIFORM_BUFFER(FOrthoVoxelGridUniformBufferParameters, OrthoGridUniformBuffer)
|
|
//SHADER_PARAMETER_RDG_UNIFORM_BUFFER(FFrustumVoxelGridUniformBufferParameters, FrustumGridUniformBuffer)
|
|
SHADER_PARAMETER_RDG_UNIFORM_BUFFER(FAdaptiveVolumetricShadowMapUniformBufferParameters, AVSM)
|
|
RENDER_TARGET_BINDING_SLOTS()
|
|
END_SHADER_PARAMETER_STRUCT()
|
|
|
|
static bool ShouldCompilePermutation(const FGlobalShaderPermutationParameters& Parameters)
|
|
{
|
|
FPermutationDomain PermutationVector(Parameters.PermutationId);
|
|
|
|
// Build FVisualizeCullingDim permutation only for a restricted number of case, as they don't impact the 'estimated cost' of lighting
|
|
if (PermutationVector.Get< FVisualizeCullingDim >() && (
|
|
PermutationVector.Get< FSourceTextureDim >() ||
|
|
PermutationVector.Get< FIESProfileDim >() ||
|
|
PermutationVector.Get< FLightFunctionAtlasDim >() ||
|
|
PermutationVector.Get< FTransmissionDim >() ||
|
|
PermutationVector.Get< FHairLighting >() ||
|
|
PermutationVector.Get< FHairComplexTransmittance >() ||
|
|
PermutationVector.Get< FAtmosphereTransmittance >() ||
|
|
PermutationVector.Get< FCloudTransmittance >() ||
|
|
PermutationVector.Get< FAnistropicMaterials >() ||
|
|
PermutationVector.Get< FVirtualShadowMapMask >()))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if (PermutationVector.Get< FSourceShapeDim >() == ELightSourceShape::Directional && PermutationVector.Get< FIESProfileDim >())
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if (PermutationVector.Get< FSourceShapeDim >() != ELightSourceShape::Directional && (PermutationVector.Get<FAtmosphereTransmittance>() || PermutationVector.Get<FCloudTransmittance>()))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
// Directional lights don't support virtual shadow map mask one pass projection, as they are always full screen lit and not part of the light grid
|
|
if (PermutationVector.Get< FSourceShapeDim >() == ELightSourceShape::Directional && PermutationVector.Get< FVirtualShadowMapMask >())
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if( PermutationVector.Get< FSourceShapeDim >() != ELightSourceShape::Rect && PermutationVector.Get< FSourceTextureDim >())
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if (PermutationVector.Get< FHairLighting >() && PermutationVector.Get< FTransmissionDim >())
|
|
{
|
|
return false;
|
|
}
|
|
|
|
// (Hair Lighting == 1) requires FHairComplexTransmittance
|
|
if (PermutationVector.Get<FHairLighting>() == 1 && !PermutationVector.Get<FDeferredLightPS::FHairComplexTransmittance>())
|
|
{
|
|
return false;
|
|
}
|
|
|
|
const bool bNeedComplexTransmittanceSupport = IsHairStrandsSupported(EHairStrandsShaderType::All, Parameters.Platform);
|
|
if (PermutationVector.Get<FHairLighting>() == 0 && PermutationVector.Get<FDeferredLightPS::FHairComplexTransmittance>() && !bNeedComplexTransmittanceSupport)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if (PermutationVector.Get<FDeferredLightPS::FAnistropicMaterials>())
|
|
{
|
|
if (Substrate::IsSubstrateEnabled())
|
|
{
|
|
return false;
|
|
}
|
|
|
|
// Anisotropic materials do not currently support rect lights
|
|
if (PermutationVector.Get<FSourceShapeDim>() == ELightSourceShape::Rect || PermutationVector.Get<FSourceTextureDim>())
|
|
{
|
|
return false;
|
|
}
|
|
|
|
// (Hair Lighting == 1) has its own BxDF and anisotropic BRDF is only for DefaultLit and ClearCoat materials.
|
|
if (PermutationVector.Get<FHairLighting>() == 1)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if (!FDataDrivenShaderPlatformInfo::GetSupportsAnisotropicMaterials(Parameters.Platform))
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
if (!DoesPlatformSupportVirtualShadowMaps(Parameters.Platform) && PermutationVector.Get<FVirtualShadowMapMask>() != 0)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if (!Substrate::IsSubstrateEnabled() && PermutationVector.Get<FSubstrateTileType>() != 0)
|
|
{
|
|
return false;
|
|
}
|
|
return IsFeatureLevelSupported(Parameters.Platform, ERHIFeatureLevel::SM5);
|
|
}
|
|
|
|
static FPermutationDomain RemapPermutation(FPermutationDomain PermutationVector)
|
|
{
|
|
// Build FVisualizeCullingDim permutation only for a restricted number of case, as they don't impact the 'estimated cost' of lighting
|
|
if (PermutationVector.Get< FVisualizeCullingDim >())
|
|
{
|
|
PermutationVector.Set< FSourceTextureDim >(false);
|
|
PermutationVector.Set< FIESProfileDim >(false);
|
|
PermutationVector.Set< FLightFunctionAtlasDim >(false);
|
|
PermutationVector.Set< FTransmissionDim >(false);
|
|
PermutationVector.Set< FHairLighting >(false);
|
|
PermutationVector.Set< FAtmosphereTransmittance >(false);
|
|
PermutationVector.Set< FCloudTransmittance >(false);
|
|
PermutationVector.Set< FAnistropicMaterials >(false);
|
|
PermutationVector.Set< FVirtualShadowMapMask >(false);
|
|
PermutationVector.Set< FHairComplexTransmittance >(false);
|
|
}
|
|
|
|
return PermutationVector;
|
|
}
|
|
|
|
static EShaderPermutationPrecacheRequest ShouldPrecachePermutation(const FGlobalShaderPermutationParameters& Parameters)
|
|
{
|
|
FPermutationDomain PermutationVector(Parameters.PermutationId);
|
|
if (PermutationVector.Get<FVisualizeCullingDim>())
|
|
{
|
|
return EShaderPermutationPrecacheRequest::NotPrecached;
|
|
}
|
|
|
|
return FGlobalShader::ShouldPrecachePermutation(Parameters);
|
|
}
|
|
|
|
static void ModifyCompilationEnvironment(const FGlobalShaderPermutationParameters& Parameters, FShaderCompilerEnvironment& OutEnvironment)
|
|
{
|
|
FPermutationDomain PermutationVector(Parameters.PermutationId);
|
|
|
|
FGlobalShader::ModifyCompilationEnvironment(Parameters, OutEnvironment);
|
|
if (PermutationVector.Get< FVirtualShadowMapMask >() != 0)
|
|
{
|
|
FVirtualShadowMapArray::SetShaderDefines(OutEnvironment);
|
|
FForwardLightingParameters::ModifyCompilationEnvironment(Parameters.Platform, OutEnvironment);
|
|
}
|
|
|
|
OutEnvironment.SetDefine(TEXT("USE_ADAPTIVE_VOLUMETRIC_SHADOW_MAP"), ShouldHeterogeneousVolumesCastShadows());
|
|
|
|
// FXC is too slow at compiling Substrate lighting shaders
|
|
if (Substrate::IsSubstrateEnabled() && FDataDrivenShaderPlatformInfo::GetSupportsDxc(Parameters.Platform))
|
|
{
|
|
OutEnvironment.CompilerFlags.Add(CFLAG_ForceDXC);
|
|
}
|
|
}
|
|
|
|
static EShaderCompileJobPriority GetOverrideJobPriority()
|
|
{
|
|
// FDeferredLightPS *can* take up to 11s
|
|
return EShaderCompileJobPriority::ExtraHigh;
|
|
}
|
|
|
|
void SetParameters(FRHIBatchedShaderParameters& BatchedParameters, const FDeferredLightUniformStruct& DeferredLightUniformsValue)
|
|
{
|
|
SetUniformBufferParameterImmediate(BatchedParameters, GetUniformBufferParameter<FDeferredLightUniformStruct>(), DeferredLightUniformsValue);
|
|
}
|
|
};
|
|
|
|
IMPLEMENT_GLOBAL_SHADER(FDeferredLightPS, "/Engine/Private/DeferredLightPixelShaders.usf", "DeferredLightPixelMain", SF_Pixel);
|
|
|
|
/** Shader used to visualize stationary light overlap. */
|
|
class FDeferredLightOverlapPS : public FGlobalShader
|
|
{
|
|
DECLARE_SHADER_TYPE(FDeferredLightOverlapPS, Global)
|
|
SHADER_USE_PARAMETER_STRUCT(FDeferredLightOverlapPS, FGlobalShader);
|
|
|
|
class FRadialAttenuation : SHADER_PERMUTATION_BOOL("RADIAL_ATTENUATION");
|
|
using FPermutationDomain = TShaderPermutationDomain<FRadialAttenuation>;
|
|
|
|
BEGIN_SHADER_PARAMETER_STRUCT(FParameters, )
|
|
SHADER_PARAMETER_STRUCT_REF(FViewUniformShaderParameters, View)
|
|
SHADER_PARAMETER_RDG_UNIFORM_BUFFER(FSceneTextureUniformParameters, SceneTextures)
|
|
SHADER_PARAMETER_RDG_UNIFORM_BUFFER(FDeferredLightUniformStruct, DeferredLight)
|
|
SHADER_PARAMETER(float, bHasValidChannel)
|
|
RENDER_TARGET_BINDING_SLOTS()
|
|
END_SHADER_PARAMETER_STRUCT()
|
|
|
|
public:
|
|
|
|
static bool ShouldCompilePermutation(const FGlobalShaderPermutationParameters& Parameters)
|
|
{
|
|
return IsFeatureLevelSupported(Parameters.Platform, ERHIFeatureLevel::SM5);
|
|
}
|
|
|
|
static void ModifyCompilationEnvironment(const FGlobalShaderPermutationParameters& Parameters, FShaderCompilerEnvironment& OutEnvironment)
|
|
{
|
|
FGlobalShader::ModifyCompilationEnvironment(Parameters, OutEnvironment);
|
|
}
|
|
};
|
|
|
|
IMPLEMENT_GLOBAL_SHADER(FDeferredLightOverlapPS, "/Engine/Private/StationaryLightOverlapShaders.usf", "OverlapPixelMain", SF_Pixel);
|
|
|
|
/** Gathers simple lights from visible primtives in the passed in views. */
|
|
void FSceneRenderer::GatherSimpleLights(const FSceneViewFamily& ViewFamily, const TArray<FViewInfo>& Views, FSimpleLightArray& SimpleLights)
|
|
{
|
|
TArray<const FPrimitiveSceneInfo*, SceneRenderingAllocator> PrimitivesWithSimpleLights;
|
|
|
|
// Gather visible primitives from all views that might have simple lights
|
|
for (int32 ViewIndex = 0; ViewIndex < Views.Num(); ViewIndex++)
|
|
{
|
|
const FViewInfo& View = Views[ViewIndex];
|
|
for (int32 PrimitiveIndex = 0; PrimitiveIndex < View.VisibleDynamicPrimitivesWithSimpleLights.Num(); PrimitiveIndex++)
|
|
{
|
|
const FPrimitiveSceneInfo* PrimitiveSceneInfo = View.VisibleDynamicPrimitivesWithSimpleLights[PrimitiveIndex];
|
|
|
|
// TArray::AddUnique is slow, but not expecting many entries in PrimitivesWithSimpleLights
|
|
PrimitivesWithSimpleLights.AddUnique(PrimitiveSceneInfo);
|
|
}
|
|
}
|
|
|
|
// Gather simple lights from the primitives
|
|
for (int32 PrimitiveIndex = 0; PrimitiveIndex < PrimitivesWithSimpleLights.Num(); PrimitiveIndex++)
|
|
{
|
|
const FPrimitiveSceneInfo* Primitive = PrimitivesWithSimpleLights[PrimitiveIndex];
|
|
Primitive->Proxy->GatherSimpleLights(ViewFamily, SimpleLights);
|
|
}
|
|
}
|
|
|
|
/** Gets a readable light name for use with a draw event. */
|
|
void FSceneRenderer::GetLightNameForDrawEvent(const FLightSceneProxy* LightProxy, FString& LightNameWithLevel)
|
|
{
|
|
#if WANTS_DRAW_MESH_EVENTS
|
|
if (GetEmitDrawEvents())
|
|
{
|
|
FString FullLevelName = LightProxy->GetLevelName().ToString();
|
|
const int32 LastSlashIndex = FullLevelName.Find(TEXT("/"), ESearchCase::CaseSensitive, ESearchDir::FromEnd);
|
|
|
|
if (LastSlashIndex != INDEX_NONE)
|
|
{
|
|
// Trim the leading path before the level name to make it more readable
|
|
// The level FName was taken directly from the Outermost UObject, otherwise we would do this operation on the game thread
|
|
FullLevelName.MidInline(LastSlashIndex + 1, FullLevelName.Len() - (LastSlashIndex + 1), EAllowShrinking::No);
|
|
}
|
|
|
|
LightNameWithLevel = FullLevelName + TEXT(".") + LightProxy->GetOwnerNameOrLabel();
|
|
}
|
|
#endif
|
|
}
|
|
|
|
extern int32 GbEnableAsyncComputeTranslucencyLightingVolumeClear;
|
|
|
|
uint32 GetShadowQuality();
|
|
|
|
static bool LightRequiresDenoiser(const FLightSceneInfo& LightSceneInfo)
|
|
{
|
|
ELightComponentType LightType = ELightComponentType(LightSceneInfo.Proxy->GetLightType());
|
|
if (LightType == LightType_Directional)
|
|
{
|
|
return LightSceneInfo.Proxy->GetLightSourceAngle() > 0;
|
|
}
|
|
else if (LightType == LightType_Point || LightType == LightType_Spot)
|
|
{
|
|
return LightSceneInfo.Proxy->GetSourceRadius() > 0;
|
|
}
|
|
else if (LightType == LightType_Rect)
|
|
{
|
|
return true;
|
|
}
|
|
else
|
|
{
|
|
check(0);
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool FSceneRenderer::AllowSimpleLights() const
|
|
{
|
|
return bAllowSimpleLights == 1;
|
|
}
|
|
|
|
bool CanLightUsesAtlasForUnbatchedLight(ERHIFeatureLevel::Type FeatureLevel, const FLightSceneProxy* Proxy)
|
|
{
|
|
if (!Proxy)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
// For now, we prevent directional light to use the light function atlas because atlas tiles needs to be repeatable.
|
|
// And if a texcoordinate node is not scale as a integer multiplier of the uv in [0,1] then the tile will become visible.
|
|
if (Proxy->GetLightType() == LightType_Directional)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
// Material shoud also be compatible with light function atlas, i.e. not sample depth or world position.
|
|
// We do not check that for other systems (translucent, water, volume fog, clustered, Lumen),
|
|
// because light functions were never available there before the atlas. So those LF aare still added into the atlas.
|
|
// => If a material is designed to be used with those systems, light function mateirla it must be made be compliant from the start.
|
|
const FMaterialRenderProxy* MaterialRenderProxy = Proxy->GetLightFunctionMaterial();
|
|
if (MaterialRenderProxy)
|
|
{
|
|
const FMaterial& LFMaterial = Proxy->GetLightFunctionMaterial()->GetIncompleteMaterialWithFallback(FeatureLevel);
|
|
|
|
return LFMaterial.MaterialIsLightFunctionAtlasCompatible_RenderThread();
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
void FSceneRenderer::UpdateLightFunctionAtlasTaskFunction()
|
|
{
|
|
TRACE_CPUPROFILER_EVENT_SCOPE(UpdateLightFunctionAtlas);
|
|
SCOPED_NAMED_EVENT_TEXT("UpdateLightFunctionAtlas", FColor::Yellow);
|
|
|
|
for (auto LightIt = Scene->Lights.CreateConstIterator(); LightIt; ++LightIt)
|
|
{
|
|
const FLightSceneInfoCompact& LightSceneInfoCompact = *LightIt;
|
|
|
|
LightFunctionAtlas.UpdateRegisterLightSceneInfo(LightSceneInfoCompact.LightSceneInfo);
|
|
}
|
|
|
|
// Update the light function atlas according to registered lights and views
|
|
LightFunctionAtlas.UpdateLightFunctionAtlas(Views);
|
|
}
|
|
|
|
void FSceneRenderer::GatherAndSortLights(FSortedLightSetSceneInfo& OutSortedLights, bool bShadowedLightsInClustered)
|
|
{
|
|
TRACE_CPUPROFILER_EVENT_SCOPE(GatherAndSortLights);
|
|
|
|
if (AllowSimpleLights())
|
|
{
|
|
GatherSimpleLights(ViewFamily, Views, OutSortedLights.SimpleLights);
|
|
}
|
|
FSimpleLightArray& SimpleLights = OutSortedLights.SimpleLights;
|
|
TArray<FSortedLightSceneInfo, SceneRenderingAllocator>& SortedLights = OutSortedLights.SortedLights;
|
|
|
|
// NOTE: we allocate space also for simple lights such that they can be referenced in the same sorted range
|
|
SortedLights.Empty(Scene->Lights.Num() + SimpleLights.InstanceData.Num());
|
|
|
|
bool bAnyViewbUsesLightingChannels = false;
|
|
|
|
for (int32 ViewIndex = 0; ViewIndex < Views.Num(); ViewIndex++)
|
|
{
|
|
if (Views[ViewIndex].bUsesLightingChannels)
|
|
{
|
|
bAnyViewbUsesLightingChannels = true;
|
|
}
|
|
}
|
|
|
|
const bool bUseLightFunctionAtlas = LightFunctionAtlas::IsEnabled(*Scene, ELightFunctionAtlasSystem::DeferredLighting);
|
|
bool bDynamicShadows = ViewFamily.EngineShowFlags.DynamicShadows && GetShadowQuality() > 0;
|
|
|
|
#if ENABLE_DEBUG_DISCARD_PROP
|
|
int32 Total = Scene->Lights.Num() + SimpleLights.InstanceData.Num();
|
|
int32 NumToKeep = int32(float(Total) * (1.0f - GDebugLightDiscardProp));
|
|
const float DebugDiscardStride = float(NumToKeep) / float(Total);
|
|
float DebugDiscardCounter = 0.0f;
|
|
#endif // ENABLE_DEBUG_DISCARD_PROP
|
|
// Build a list of visible lights.
|
|
for (auto LightIt = Scene->Lights.CreateConstIterator(); LightIt; ++LightIt)
|
|
{
|
|
const FLightSceneInfoCompact& LightSceneInfoCompact = *LightIt;
|
|
const FLightSceneInfo* const LightSceneInfo = LightSceneInfoCompact.LightSceneInfo;
|
|
|
|
#if ENABLE_DEBUG_DISCARD_PROP
|
|
{
|
|
int32 PrevCounter = int32(DebugDiscardCounter);
|
|
DebugDiscardCounter += DebugDiscardStride;
|
|
if (PrevCounter >= int32(DebugDiscardCounter))
|
|
{
|
|
continue;
|
|
}
|
|
}
|
|
#endif // ENABLE_DEBUG_DISCARD_PROP
|
|
|
|
if (LightSceneInfo->ShouldRenderLightViewIndependent()
|
|
// Reflection override skips direct specular because it tends to be blindingly bright with a perfectly smooth surface
|
|
&& !ViewFamily.EngineShowFlags.ReflectionOverride)
|
|
{
|
|
// Check if the light is visible in any of the views.
|
|
for (int32 ViewIndex = 0; ViewIndex < Views.Num(); ViewIndex++)
|
|
{
|
|
if (LightSceneInfo->ShouldRenderLight(Views[ViewIndex]))
|
|
{
|
|
FSortedLightSceneInfo* SortedLightInfo = new(SortedLights) FSortedLightSceneInfo(LightSceneInfo);
|
|
|
|
// Check for shadows and light functions.
|
|
SortedLightInfo->SortKey.Fields.LightType = LightSceneInfoCompact.LightType;
|
|
SortedLightInfo->SortKey.Fields.bShadowed = bDynamicShadows && CheckForProjectedShadows(LightSceneInfo);
|
|
SortedLightInfo->SortKey.Fields.bLightFunction = ViewFamily.EngineShowFlags.LightFunctions && CheckForLightFunction(LightSceneInfo);
|
|
SortedLightInfo->SortKey.Fields.bUsesLightingChannels = LightSceneInfo->Proxy->GetLightingChannelMask() != GetDefaultLightingChannelMask();
|
|
|
|
// Cached once here and the material is never queried again later.
|
|
SortedLightInfo->bIsCompatibleWithLightFunctionAtlas = CanLightUsesAtlasForUnbatchedLight(FeatureLevel, LightSceneInfo->Proxy);
|
|
|
|
// These are not simple lights.
|
|
SortedLightInfo->SortKey.Fields.bIsNotSimpleLight = 1;
|
|
|
|
// Lights handled by Mega Lights
|
|
const bool bHandledByMegaLights = MegaLights::GetMegaLightsMode(ViewFamily, LightSceneInfoCompact.LightType, LightSceneInfoCompact.bAllowMegaLights, LightSceneInfoCompact.MegaLightsShadowMethod) != EMegaLightsMode::Disabled;
|
|
|
|
const bool bCastsFirstPersonSelfShadow = LightCastsFirstPersonSelfShadow(*LightSceneInfo);
|
|
|
|
// NOTE: bClusteredDeferredSupported==false means "lights cannot be batched" (tiled or clustered). When false, light will go the slower unbatched render path.
|
|
// Tiled and clustered deferred lighting only support certain lights that don't use any additional features (like shadow or light function not compatible with the atlas.)
|
|
// And also that are not directional (mostly because it doesn't make so much sense to insert them into every grid cell in the universe).
|
|
// In the forward case one directional light gets put into its own variables, and in the deferred case it gets a full-screen pass.
|
|
// Usually it'll have shadows and stuff anyway.
|
|
// Contact shadow are not supported. Same for first person self shadow.
|
|
const bool bClusteredDeferredSupported =
|
|
(!SortedLightInfo->SortKey.Fields.bShadowed || bShadowedLightsInClustered) &&
|
|
(!SortedLightInfo->SortKey.Fields.bLightFunction || (bUseLightFunctionAtlas && SortedLightInfo->bIsCompatibleWithLightFunctionAtlas)) // If not compatible with the LightFunctionAtlas, light with light function materials must go the unbatched route.
|
|
&& LightSceneInfoCompact.LightType != LightType_Directional
|
|
&& LightSceneInfo->Proxy->GetContactShadowLength() == 0
|
|
&& !bCastsFirstPersonSelfShadow
|
|
&& !bHandledByMegaLights;
|
|
|
|
// Track feature available accross all lights
|
|
if (SortedLightInfo->SortKey.Fields.LightType == LightType_Rect) { OutSortedLights.bHasRectLights = true; }
|
|
if (SortedLightInfo->SortKey.Fields.bUsesLightingChannels) { OutSortedLights.bHasLightChannels = true; }
|
|
if (SortedLightInfo->SortKey.Fields.bLightFunction) { OutSortedLights.bHasLightFunctions = true; }
|
|
if (bCastsFirstPersonSelfShadow) { OutSortedLights.bHasFirstPersonSelfShadowLights = true; }
|
|
|
|
SortedLightInfo->SortKey.Fields.bClusteredDeferredNotSupported = !bClusteredDeferredSupported;
|
|
|
|
if (bHandledByMegaLights)
|
|
{
|
|
SortedLightInfo->SortKey.Fields.LightSceneId = LightSceneInfo->Id & LIGHT_ID_MASK;
|
|
SortedLightInfo->SortKey.Fields.bHandledByMegaLights = true;
|
|
}
|
|
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
// Add the simple lights also
|
|
for (int32 SimpleLightIndex = 0; SimpleLightIndex < SimpleLights.InstanceData.Num(); SimpleLightIndex++)
|
|
{
|
|
#if ENABLE_DEBUG_DISCARD_PROP
|
|
{
|
|
int32 PrevCounter = int32(DebugDiscardCounter);
|
|
DebugDiscardCounter += DebugDiscardStride;
|
|
if (PrevCounter >= int32(DebugDiscardCounter))
|
|
{
|
|
continue;
|
|
}
|
|
}
|
|
#endif // ENABLE_DEBUG_DISCARD_PROP
|
|
|
|
FSortedLightSceneInfo* SortedLightInfo = new(SortedLights) FSortedLightSceneInfo(SimpleLightIndex);
|
|
SortedLightInfo->SortKey.Fields.LightType = LightType_Point;
|
|
SortedLightInfo->SortKey.Fields.bShadowed = 0;
|
|
SortedLightInfo->SortKey.Fields.bLightFunction = 0;
|
|
SortedLightInfo->SortKey.Fields.bUsesLightingChannels = 0;
|
|
|
|
// These are simple lights.
|
|
SortedLightInfo->SortKey.Fields.bIsNotSimpleLight = 0;
|
|
|
|
// Simple lights are ok to use with tiled and clustered deferred lighting
|
|
SortedLightInfo->SortKey.Fields.bClusteredDeferredNotSupported = 0;
|
|
SortedLightInfo->SortKey.Fields.bHandledByMegaLights = 0;
|
|
}
|
|
|
|
// Sort non-shadowed, non-light function lights first to avoid render target switches.
|
|
struct FCompareFSortedLightSceneInfo
|
|
{
|
|
FORCEINLINE bool operator()( const FSortedLightSceneInfo& A, const FSortedLightSceneInfo& B ) const
|
|
{
|
|
return A.SortKey.Packed < B.SortKey.Packed;
|
|
}
|
|
};
|
|
SortedLights.Sort( FCompareFSortedLightSceneInfo() );
|
|
|
|
// Scan and find ranges.
|
|
OutSortedLights.SimpleLightsEnd = SortedLights.Num();
|
|
OutSortedLights.ClusteredSupportedEnd = SortedLights.Num();
|
|
OutSortedLights.UnbatchedLightStart = SortedLights.Num();
|
|
OutSortedLights.MegaLightsLightStart = SortedLights.Num();
|
|
|
|
// Iterate over all lights to be rendered and build ranges for tiled deferred and unshadowed lights
|
|
for (int32 LightIndex = 0; LightIndex < SortedLights.Num(); LightIndex++)
|
|
{
|
|
const FSortedLightSceneInfo& SortedLightInfo = SortedLights[LightIndex];
|
|
const bool bDrawShadows = SortedLightInfo.SortKey.Fields.bShadowed;
|
|
const bool bLightingChannels = bAnyViewbUsesLightingChannels && SortedLightInfo.SortKey.Fields.bUsesLightingChannels;
|
|
|
|
// Do not schedule unbatched lights if the atlas is used and enabled.
|
|
// Keep in mind that when clustered shading is enabled, lights with light function are clustered compatible so are pushed in to the clustered path (if a supported light type).
|
|
// Otherwise, those light are pushed in the non-shadowed non-lightfunction render path that still supports light function atlas sampling.
|
|
const bool bDrawLightFunction = SortedLightInfo.SortKey.Fields.bLightFunction && (!bUseLightFunctionAtlas || !SortedLightInfo.bIsCompatibleWithLightFunctionAtlas);
|
|
|
|
if (SortedLightInfo.SortKey.Fields.bHandledByMegaLights && OutSortedLights.MegaLightsLightStart == SortedLights.Num())
|
|
{
|
|
// Mark the first index that needs to be rendered
|
|
OutSortedLights.MegaLightsLightStart = LightIndex;
|
|
}
|
|
|
|
if (SortedLightInfo.SortKey.Fields.bIsNotSimpleLight && OutSortedLights.SimpleLightsEnd == SortedLights.Num())
|
|
{
|
|
// Mark the first index to not be simple
|
|
OutSortedLights.SimpleLightsEnd = LightIndex;
|
|
}
|
|
|
|
if (SortedLightInfo.SortKey.Fields.bClusteredDeferredNotSupported && OutSortedLights.ClusteredSupportedEnd == SortedLights.Num())
|
|
{
|
|
// Mark the first index to not support clustered deferred
|
|
OutSortedLights.ClusteredSupportedEnd = LightIndex;
|
|
}
|
|
|
|
if ((bDrawShadows || bDrawLightFunction || bLightingChannels)
|
|
&& SortedLightInfo.SortKey.Fields.bClusteredDeferredNotSupported
|
|
&& !SortedLightInfo.SortKey.Fields.bHandledByMegaLights
|
|
&& OutSortedLights.UnbatchedLightStart == SortedLights.Num())
|
|
{
|
|
OutSortedLights.UnbatchedLightStart = LightIndex;
|
|
}
|
|
}
|
|
|
|
// Make sure no obvious things went wrong!
|
|
check(OutSortedLights.ClusteredSupportedEnd >= OutSortedLights.SimpleLightsEnd);
|
|
check(OutSortedLights.UnbatchedLightStart >= OutSortedLights.ClusteredSupportedEnd);
|
|
|
|
if (OutSortedLights.UnbatchedLightStart > OutSortedLights.MegaLightsLightStart)
|
|
{
|
|
OutSortedLights.UnbatchedLightStart = OutSortedLights.MegaLightsLightStart;
|
|
}
|
|
}
|
|
|
|
FHairStrandsTransmittanceMaskData CreateDummyHairStrandsTransmittanceMaskData(FRDGBuilder& GraphBuilder, FGlobalShaderMap* ShaderMap);
|
|
|
|
void FDeferredShadingSceneRenderer::RenderLights(
|
|
FRDGBuilder& GraphBuilder,
|
|
FMinimalSceneTextures& SceneTextures,
|
|
FRDGTextureRef LightingChannelsTexture,
|
|
const FSortedLightSetSceneInfo& SortedLightSet)
|
|
{
|
|
const bool bUseHairLighting = HairStrands::HasViewHairStrandsData(Views);
|
|
#if RHI_RAYTRACING
|
|
const bool bEnableRayTracing = true;
|
|
#else
|
|
const bool bEnableRayTracing = false;
|
|
#endif // RHI_RAYTRACING
|
|
const bool bUseLightFunctionAtlas = LightFunctionAtlas::IsEnabled(*Scene, ELightFunctionAtlasSystem::DeferredLighting);
|
|
|
|
RDG_EVENT_SCOPE_STAT(GraphBuilder, Lights, "Lights");
|
|
RDG_GPU_STAT_SCOPE(GraphBuilder, Lights);
|
|
|
|
SCOPED_NAMED_EVENT(FDeferredShadingSceneRenderer_RenderLights, FColor::Emerald);
|
|
SCOPE_CYCLE_COUNTER(STAT_LightingDrawTime);
|
|
SCOPE_CYCLE_COUNTER(STAT_LightRendering);
|
|
|
|
const TArray<FSortedLightSceneInfo, SceneRenderingAllocator>& SortedLights = SortedLightSet.SortedLights;
|
|
const int32 SimpleLightsEnd = SortedLightSet.SimpleLightsEnd;
|
|
const int32 UnbatchedLightStart = SortedLightSet.UnbatchedLightStart;
|
|
const int32 MegaLightsLightStart = SortedLightSet.MegaLightsLightStart;
|
|
|
|
FHairStrandsTransmittanceMaskData DummyTransmittanceMaskData;
|
|
if (bUseHairLighting && Views.Num() > 0)
|
|
{
|
|
DummyTransmittanceMaskData = CreateDummyHairStrandsTransmittanceMaskData(GraphBuilder, Views[0].ShaderMap);
|
|
}
|
|
|
|
{
|
|
RDG_EVENT_SCOPE(GraphBuilder, "DirectLighting");
|
|
|
|
// SUBSTRATE_TODO move right after stencil clear so that it is also common with EnvLight pass
|
|
if (ViewFamily.EngineShowFlags.DirectLighting && Substrate::IsSubstrateEnabled())
|
|
{
|
|
// Update the stencil buffer, marking simple/complex Substrate material only once for all the following passes.
|
|
Substrate::AddSubstrateStencilPass(GraphBuilder, Views, SceneTextures);
|
|
}
|
|
|
|
// The shadow scene renderer is not optional in the deferred renderer
|
|
FShadowSceneRenderer& ShadowSceneRenderer = GetSceneExtensionsRenderers().GetRenderer<FShadowSceneRenderer>();
|
|
|
|
if(ViewFamily.EngineShowFlags.DirectLighting)
|
|
{
|
|
ShadowSceneRenderer.RenderVirtualShadowMapProjectionMaskBits(GraphBuilder, SceneTextures);
|
|
|
|
RDG_EVENT_SCOPE(GraphBuilder, "BatchedLights");
|
|
INC_DWORD_STAT_BY(STAT_NumBatchedLights, UnbatchedLightStart);
|
|
|
|
// Currently they have a special path anyway in case of standard deferred so always skip the simple lights
|
|
int32 StandardDeferredStart = SortedLightSet.SimpleLightsEnd;
|
|
|
|
bool bRenderSimpleLightsStandardDeferred = SortedLightSet.SimpleLights.InstanceData.Num() > 0;
|
|
|
|
UE_CLOG(ShouldUseClusteredDeferredShading() && !AreLightsInLightGrid(), LogRenderer, Warning,
|
|
TEXT("Clustered deferred shading is enabled, but lights were not injected in grid, falling back to other methods (hint 'r.LightCulling.Quality' may cause this)."));
|
|
|
|
// True if the clustered shading is enabled and the feature level is there, and that the light grid had lights injected.
|
|
if (ShouldUseClusteredDeferredShading() && AreLightsInLightGrid())
|
|
{
|
|
// Tell the trad. deferred that the clustered deferred capable lights are taken care of.
|
|
// This includes the simple lights
|
|
StandardDeferredStart = SortedLightSet.ClusteredSupportedEnd;
|
|
// Tell the trad. deferred that the simple lights are spoken for.
|
|
bRenderSimpleLightsStandardDeferred = false;
|
|
|
|
AddClusteredDeferredShadingPass(GraphBuilder, SceneTextures, SortedLightSet, ShadowSceneRenderer.VirtualShadowMapMaskBits, ShadowSceneRenderer.VirtualShadowMapMaskBitsHairStrands, LightingChannelsTexture);
|
|
}
|
|
|
|
if (bRenderSimpleLightsStandardDeferred)
|
|
{
|
|
RenderSimpleLightsStandardDeferred(GraphBuilder, SceneTextures, SortedLightSet.SimpleLights);
|
|
}
|
|
|
|
// Draw non-shadowed non-light function lights without changing render targets between them
|
|
for (int32 ViewIndex = 0, ViewCount = Views.Num(); ViewIndex < ViewCount; ++ViewIndex)
|
|
{
|
|
const FViewInfo& View = Views[ViewIndex];
|
|
RDG_EVENT_SCOPE_CONDITIONAL(GraphBuilder, ViewCount > 1, "View%d", ViewIndex);
|
|
SCOPED_GPU_MASK(GraphBuilder.RHICmdList, View.GPUMask);
|
|
|
|
for (int32 LightIndex = StandardDeferredStart; LightIndex < UnbatchedLightStart; LightIndex++)
|
|
{
|
|
// Render the light to the scene color buffer, using a 1x1 white texture as input
|
|
const FSortedLightSceneInfo& SortedLightInfo = SortedLights[LightIndex];
|
|
const FLightSceneInfo* LightSceneInfo = SortedLightInfo.LightSceneInfo;
|
|
RenderLight(GraphBuilder, Scene, View, SceneTextures, LightSceneInfo, nullptr, LightingChannelsTexture,
|
|
false /*bRenderOverlap*/, false /*bCloudShadow*/, SortedLightInfo.bIsCompatibleWithLightFunctionAtlas);
|
|
}
|
|
}
|
|
|
|
// Add a special version when hair rendering is enabled for getting lighting on hair.
|
|
if (bUseHairLighting)
|
|
{
|
|
FRDGTextureRef NullScreenShadowMaskSubPixelTexture = nullptr;
|
|
for (int32 ViewIndex = 0, ViewCount = Views.Num(); ViewIndex < ViewCount; ++ViewIndex)
|
|
{
|
|
FViewInfo& View = Views[ViewIndex];
|
|
if (HairStrands::HasViewHairStrandsData(View))
|
|
{
|
|
// Draw non-shadowed non-light function lights without changing render targets between them
|
|
for (int32 LightIndex = StandardDeferredStart; LightIndex < UnbatchedLightStart; LightIndex++)
|
|
{
|
|
const FSortedLightSceneInfo& SortedLightInfo = SortedLights[LightIndex];
|
|
const FLightSceneInfo* LightSceneInfo = SortedLightInfo.LightSceneInfo;
|
|
RenderLightForHair(GraphBuilder, View, SceneTextures, LightSceneInfo,
|
|
NullScreenShadowMaskSubPixelTexture, LightingChannelsTexture, DummyTransmittanceMaskData,
|
|
false /*bForwardRendering*/, SortedLightInfo.bIsCompatibleWithLightFunctionAtlas);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
{
|
|
RDG_EVENT_SCOPE(GraphBuilder, "UnbatchedLights");
|
|
|
|
const int32 DenoiserMode = CVarShadowUseDenoiser.GetValueOnRenderThread();
|
|
|
|
const IScreenSpaceDenoiser* DefaultDenoiser = IScreenSpaceDenoiser::GetDefaultDenoiser();
|
|
const IScreenSpaceDenoiser* DenoiserToUse = DenoiserMode == 1 ? DefaultDenoiser : GScreenSpaceDenoiser;
|
|
|
|
TArray<FRDGTextureRef, SceneRenderingAllocator> PreprocessedShadowMaskTextures;
|
|
TArray<FRDGTextureRef, SceneRenderingAllocator> PreprocessedShadowMaskSubPixelTextures;
|
|
|
|
const int32 MaxDenoisingBatchSize = FMath::Clamp(CVarMaxShadowDenoisingBatchSize.GetValueOnRenderThread(), 1, IScreenSpaceDenoiser::kMaxBatchSize);
|
|
const int32 MaxRTShadowBatchSize = CVarMaxShadowRayTracingBatchSize.GetValueOnRenderThread();
|
|
const bool bDoShadowDenoisingBatching = DenoiserMode != 0 && MaxDenoisingBatchSize > 1;
|
|
|
|
//#dxr_todo: support multiview for the batching case
|
|
const bool bDoShadowBatching = (bDoShadowDenoisingBatching || MaxRTShadowBatchSize > 1) && Views.Num() == 1;
|
|
|
|
// Optimizations: batches all shadow ray tracing denoising. Definitely could be smarter to avoid high VGPR pressure if this entire
|
|
// function was converted to render graph, and want least intrusive change as possible. So right now it trades render target memory pressure
|
|
// for denoising perf.
|
|
if (bEnableRayTracing && bDoShadowBatching)
|
|
{
|
|
const uint32 ViewIndex = 0;
|
|
FViewInfo& View = Views[ViewIndex];
|
|
|
|
const int32 NumShadowedLights = MegaLightsLightStart - UnbatchedLightStart;
|
|
// Allocate PreprocessedShadowMaskTextures once so QueueTextureExtraction can deferred write.
|
|
{
|
|
if (!View.bStatePrevViewInfoIsReadOnly)
|
|
{
|
|
View.ViewState->PrevFrameViewInfo.ShadowHistories.Empty();
|
|
View.ViewState->PrevFrameViewInfo.ShadowHistories.Reserve(NumShadowedLights);
|
|
}
|
|
|
|
PreprocessedShadowMaskTextures.SetNum(NumShadowedLights);
|
|
}
|
|
|
|
PreprocessedShadowMaskTextures.SetNum(NumShadowedLights);
|
|
|
|
if (HairStrands::HasViewHairStrandsData(View))
|
|
{
|
|
PreprocessedShadowMaskSubPixelTextures.SetNum(NumShadowedLights);
|
|
}
|
|
}
|
|
|
|
const bool bDirectLighting = ViewFamily.EngineShowFlags.DirectLighting;
|
|
|
|
FFirstPersonSelfShadowInputs FirstPersonSelfShadowInputs;
|
|
if (SortedLightSet.bHasFirstPersonSelfShadowLights && ShouldRenderFirstPersonSelfShadow(ViewFamily))
|
|
{
|
|
FirstPersonSelfShadowInputs = CreateFirstPersonSelfShadowInputs(GraphBuilder, Views, SceneTextures);
|
|
}
|
|
|
|
FRDGTextureRef SharedScreenShadowMaskTexture = nullptr;
|
|
FRDGTextureRef SharedScreenShadowMaskSubPixelTexture = nullptr;
|
|
|
|
// Draw shadowed and light function lights
|
|
auto UnbatchedLightsPass = [&](bool bIsHairPass)
|
|
{
|
|
for (int32 LightIndex = UnbatchedLightStart; LightIndex < MegaLightsLightStart; LightIndex++)
|
|
{
|
|
const FSortedLightSceneInfo& SortedLightInfo = SortedLights[LightIndex];
|
|
const FLightSceneInfo& LightSceneInfo = *SortedLightInfo.LightSceneInfo;
|
|
const FLightSceneProxy& LightSceneProxy = *LightSceneInfo.Proxy;
|
|
const FVisibleLightInfo& VisibleLightInfo = VisibleLightInfos[LightSceneInfo.Id];
|
|
const FLightOcclusionType OcclusionType = GetLightOcclusionType(LightSceneProxy, ViewFamily);
|
|
|
|
const bool bDrawShadows = SortedLightInfo.SortKey.Fields.bShadowed;
|
|
const bool bDrawLightFunction = SortedLightInfo.SortKey.Fields.bLightFunction && (!bUseLightFunctionAtlas || !SortedLightInfo.bIsCompatibleWithLightFunctionAtlas);
|
|
const bool bDrawPreviewIndicator = ViewFamily.EngineShowFlags.PreviewShadowsIndicator && !LightSceneInfo.IsPrecomputedLightingValid() && LightSceneProxy.HasStaticShadowing();
|
|
const bool bDrawHairShadow = bDrawShadows && bUseHairLighting;
|
|
const bool bUseHairDeepShadow = bDrawShadows && bUseHairLighting && LightSceneProxy.CastsHairStrandsDeepShadow();
|
|
const bool bRunHairLighting = bUseHairLighting && (bIsHairPass || OcclusionType == FLightOcclusionType::Raytraced);
|
|
const bool bDrawFirstPersonSelfShadow = bDrawShadows && !bIsHairPass && ShouldRenderFirstPersonSelfShadowForLight(*this, ViewFamily, Views, LightSceneInfo);
|
|
|
|
bool bUsedShadowMaskTexture = false;
|
|
|
|
bool bElideScreenShadowMask = false;
|
|
bool bElideScreenShadowMaskSubPixel = false;
|
|
|
|
// Raytraced shadow light for hair are handled/interleaved with regular light, as the raytraced shadow masks
|
|
// for gbuffer & hair are computed by the same single pass.
|
|
if (bIsHairPass && OcclusionType == FLightOcclusionType::Raytraced)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
FScopeCycleCounter Context(LightSceneProxy.GetStatId());
|
|
|
|
FRDGTextureRef ScreenShadowMaskTexture = nullptr;
|
|
FRDGTextureRef ScreenShadowMaskSubPixelTexture = nullptr;
|
|
|
|
if (bDrawShadows || bDrawLightFunction || bDrawPreviewIndicator)
|
|
{
|
|
// In certain cases we can skip creating the screen shadow mask texture
|
|
// In particular right now this is true if we are doing one pass projection with only a virtual shadow map
|
|
// with no light functions, as in that case we can directly sample the shadow mask bits in the lighting shader.
|
|
bElideScreenShadowMask =
|
|
CVarOnePassProjectionSkipScreenShadowMask.GetValueOnRenderThread() != 0 &&
|
|
ShadowSceneRenderer.UsePackedShadowMaskBits() &&
|
|
OcclusionType == FLightOcclusionType::Shadowmap &&
|
|
!(bDirectLighting && bDrawLightFunction) &&
|
|
!bDrawPreviewIndicator &&
|
|
SortedLightInfo.SortKey.Fields.LightType != LightType_Directional &&
|
|
VisibleLightInfo.VirtualShadowMapId != INDEX_NONE && // Not a directional light, so no per-view clipmaps
|
|
VisibleLightInfo.ContainsOnlyVirtualShadowMaps() &&
|
|
!bDrawFirstPersonSelfShadow;
|
|
bElideScreenShadowMaskSubPixel = bElideScreenShadowMask && CVarHairStrandsAllowOneTransmittancePass.GetValueOnRenderThread() > 0;
|
|
|
|
if (!SharedScreenShadowMaskTexture || !SharedScreenShadowMaskSubPixelTexture)
|
|
{
|
|
const FRDGTextureDesc SharedScreenShadowMaskTextureDesc(FRDGTextureDesc::Create2D(SceneTextures.Config.Extent, PF_B8G8R8A8, FClearValueBinding::White, TexCreate_RenderTargetable | TexCreate_ShaderResource | GFastVRamConfig.ScreenSpaceShadowMask));
|
|
|
|
if (!SharedScreenShadowMaskTexture && !bElideScreenShadowMask)
|
|
{
|
|
SharedScreenShadowMaskTexture = GraphBuilder.CreateTexture(SharedScreenShadowMaskTextureDesc, TEXT("ShadowMaskTexture"));
|
|
}
|
|
if (!SharedScreenShadowMaskSubPixelTexture && bRunHairLighting && !bElideScreenShadowMaskSubPixel)
|
|
{
|
|
SharedScreenShadowMaskSubPixelTexture = GraphBuilder.CreateTexture(SharedScreenShadowMaskTextureDesc, TEXT("ShadowMaskSubPixelTexture"));
|
|
}
|
|
}
|
|
ScreenShadowMaskTexture = bElideScreenShadowMask ? nullptr : SharedScreenShadowMaskTexture;
|
|
ScreenShadowMaskSubPixelTexture = bElideScreenShadowMaskSubPixel ? nullptr : SharedScreenShadowMaskSubPixelTexture;
|
|
}
|
|
|
|
FString LightNameWithLevel;
|
|
GetLightNameForDrawEvent(&LightSceneProxy, LightNameWithLevel);
|
|
RDG_EVENT_SCOPE(GraphBuilder, "%s", *LightNameWithLevel);
|
|
|
|
if (bDrawShadows)
|
|
{
|
|
INC_DWORD_STAT(STAT_NumShadowedLights);
|
|
|
|
// Inline ray traced shadow batching, launches shadow batches when needed
|
|
// reduces memory overhead while keeping shadows batched to optimize costs
|
|
#if RHI_RAYTRACING
|
|
{
|
|
const uint32 ViewIndex = 0;
|
|
FViewInfo& View = Views[ViewIndex];
|
|
|
|
IScreenSpaceDenoiser::FShadowRayTracingConfig RayTracingConfig;
|
|
RayTracingConfig.RayCountPerPixel = GShadowRayTracingSamplesPerPixel > -1? GShadowRayTracingSamplesPerPixel : LightSceneProxy.GetSamplesPerPixel();
|
|
|
|
const bool bDenoiserCompatible = !LightRequiresDenoiser(LightSceneInfo) || IScreenSpaceDenoiser::EShadowRequirements::PenumbraAndClosestOccluder == DenoiserToUse->GetShadowRequirements(View, LightSceneInfo, RayTracingConfig);
|
|
|
|
const bool bWantsBatchedShadow = OcclusionType == FLightOcclusionType::Raytraced &&
|
|
bDoShadowBatching &&
|
|
bDenoiserCompatible &&
|
|
SortedLightInfo.SortKey.Fields.bShadowed;
|
|
|
|
// determine if this light doesn't yet have a precomputed shadow and execute a batch to amortize costs if one is needed
|
|
if (bWantsBatchedShadow &&
|
|
(PreprocessedShadowMaskTextures.Num() == 0 || !PreprocessedShadowMaskTextures[LightIndex - UnbatchedLightStart]))
|
|
{
|
|
RDG_EVENT_SCOPE(GraphBuilder, "ShadowBatch");
|
|
TStaticArray<IScreenSpaceDenoiser::FShadowVisibilityParameters, IScreenSpaceDenoiser::kMaxBatchSize> DenoisingQueue;
|
|
TStaticArray<int32, IScreenSpaceDenoiser::kMaxBatchSize> LightIndices;
|
|
|
|
FSceneTextureParameters SceneTextureParameters = GetSceneTextureParameters(GraphBuilder, SceneTextures.UniformBuffer);
|
|
|
|
int32 ProcessShadows = 0;
|
|
|
|
const auto QuickOffDenoisingBatch = [&]
|
|
{
|
|
int32 InputParameterCount = 0;
|
|
for (int32 i = 0; i < IScreenSpaceDenoiser::kMaxBatchSize; i++)
|
|
{
|
|
InputParameterCount += DenoisingQueue[i].LightSceneInfo != nullptr ? 1 : 0;
|
|
}
|
|
|
|
check(InputParameterCount >= 1);
|
|
|
|
TStaticArray<IScreenSpaceDenoiser::FShadowVisibilityOutputs, IScreenSpaceDenoiser::kMaxBatchSize> Outputs;
|
|
|
|
RDG_EVENT_SCOPE(GraphBuilder, "%s%s(Shadow BatchSize=%d) %dx%d",
|
|
DenoiserToUse != DefaultDenoiser ? TEXT("ThirdParty ") : TEXT(""),
|
|
DenoiserToUse->GetDebugName(),
|
|
InputParameterCount,
|
|
View.ViewRect.Width(), View.ViewRect.Height());
|
|
|
|
DenoiserToUse->DenoiseShadowVisibilityMasks(
|
|
GraphBuilder,
|
|
View,
|
|
&View.PrevViewInfo,
|
|
SceneTextureParameters,
|
|
DenoisingQueue,
|
|
InputParameterCount,
|
|
Outputs);
|
|
|
|
for (int32 i = 0; i < InputParameterCount; i++)
|
|
{
|
|
const FLightSceneInfo* LocalLightSceneInfo = DenoisingQueue[i].LightSceneInfo;
|
|
|
|
int32 LocalLightIndex = LightIndices[i];
|
|
FRDGTextureRef& RefDestination = PreprocessedShadowMaskTextures[LocalLightIndex - UnbatchedLightStart];
|
|
check(RefDestination == nullptr);
|
|
RefDestination = Outputs[i].Mask;
|
|
DenoisingQueue[i].LightSceneInfo = nullptr;
|
|
}
|
|
}; // QuickOffDenoisingBatch
|
|
|
|
// Ray trace shadows of lights, and quick off denoising batch.
|
|
for (int32 LightBatchIndex = LightIndex; LightBatchIndex < MegaLightsLightStart; LightBatchIndex++)
|
|
{
|
|
const FSortedLightSceneInfo& BatchSortedLightInfo = SortedLights[LightBatchIndex];
|
|
const FLightSceneInfo& BatchLightSceneInfo = *BatchSortedLightInfo.LightSceneInfo;
|
|
|
|
// Denoiser does not support texture rect light important sampling.
|
|
const bool bBatchDrawShadows = BatchSortedLightInfo.SortKey.Fields.bShadowed;
|
|
|
|
if (!bBatchDrawShadows)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
const FLightOcclusionType BatchOcclusionType = GetLightOcclusionType(*BatchLightSceneInfo.Proxy, ViewFamily);
|
|
if (BatchOcclusionType != FLightOcclusionType::Raytraced)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
const bool bRequiresDenoiser = LightRequiresDenoiser(BatchLightSceneInfo) && DenoiserMode > 0;
|
|
|
|
IScreenSpaceDenoiser::FShadowRayTracingConfig BatchRayTracingConfig;
|
|
BatchRayTracingConfig.RayCountPerPixel = GShadowRayTracingSamplesPerPixel > -1 ? GShadowRayTracingSamplesPerPixel : BatchLightSceneInfo.Proxy->GetSamplesPerPixel();
|
|
|
|
IScreenSpaceDenoiser::EShadowRequirements DenoiserRequirements = bRequiresDenoiser ?
|
|
DenoiserToUse->GetShadowRequirements(View, BatchLightSceneInfo, BatchRayTracingConfig) :
|
|
IScreenSpaceDenoiser::EShadowRequirements::Bailout;
|
|
|
|
// Not worth batching and increase memory pressure if the denoiser do not support this ray tracing config.
|
|
// TODO: add suport for batch with multiple SPP.
|
|
if (bRequiresDenoiser && DenoiserRequirements != IScreenSpaceDenoiser::EShadowRequirements::PenumbraAndClosestOccluder)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
// Ray trace the shadow.
|
|
//#dxr_todo: support multiview for the batching case
|
|
FRDGTextureRef RayTracingShadowMaskTexture;
|
|
{
|
|
FRDGTextureDesc Desc = FRDGTextureDesc::Create2D(
|
|
SceneTextures.Config.Extent,
|
|
PF_FloatRGBA,
|
|
FClearValueBinding::Black,
|
|
TexCreate_ShaderResource | TexCreate_RenderTargetable | TexCreate_UAV);
|
|
RayTracingShadowMaskTexture = GraphBuilder.CreateTexture(Desc, TEXT("RayTracingOcclusion"));
|
|
}
|
|
|
|
FRDGTextureRef RayDistanceTexture;
|
|
{
|
|
FRDGTextureDesc Desc = FRDGTextureDesc::Create2D(
|
|
SceneTextures.Config.Extent,
|
|
PF_R16F,
|
|
FClearValueBinding::Black,
|
|
TexCreate_ShaderResource | TexCreate_RenderTargetable | TexCreate_UAV);
|
|
RayDistanceTexture = GraphBuilder.CreateTexture(Desc, TEXT("RayTracingOcclusionDistance"));
|
|
}
|
|
|
|
FRDGTextureRef SubPixelRayTracingShadowMaskTexture = nullptr;
|
|
FRDGTextureUAV* SubPixelRayTracingShadowMaskUAV = nullptr;
|
|
if (bUseHairLighting)
|
|
{
|
|
FRDGTextureDesc Desc = FRDGTextureDesc::Create2D(
|
|
SceneTextures.Config.Extent,
|
|
PF_FloatRGBA,
|
|
FClearValueBinding::Black,
|
|
TexCreate_ShaderResource | TexCreate_RenderTargetable | TexCreate_UAV);
|
|
SubPixelRayTracingShadowMaskTexture = GraphBuilder.CreateTexture(Desc, TEXT("SubPixelRayTracingOcclusion"));
|
|
SubPixelRayTracingShadowMaskUAV = GraphBuilder.CreateUAV(FRDGTextureUAVDesc(SubPixelRayTracingShadowMaskTexture));
|
|
}
|
|
|
|
FString BatchLightNameWithLevel;
|
|
GetLightNameForDrawEvent(BatchLightSceneInfo.Proxy, BatchLightNameWithLevel);
|
|
|
|
FRDGTextureUAV* RayTracingShadowMaskUAV = GraphBuilder.CreateUAV(FRDGTextureUAVDesc(RayTracingShadowMaskTexture));
|
|
FRDGTextureUAV* RayHitDistanceUAV = GraphBuilder.CreateUAV(FRDGTextureUAVDesc(RayDistanceTexture));
|
|
{
|
|
RDG_EVENT_SCOPE(GraphBuilder, "%s", *BatchLightNameWithLevel);
|
|
|
|
// Ray trace the shadow cast by opaque geometries on to hair strands geometries
|
|
// Note: No denoiser is required on this output, as the hair strands are geometrically noisy, which make it hard to denoise
|
|
RenderRayTracingShadows(
|
|
GraphBuilder,
|
|
SceneTextureParameters,
|
|
View,
|
|
BatchLightSceneInfo,
|
|
BatchRayTracingConfig,
|
|
DenoiserRequirements,
|
|
LightingChannelsTexture,
|
|
RayTracingShadowMaskUAV,
|
|
RayHitDistanceUAV,
|
|
SubPixelRayTracingShadowMaskUAV);
|
|
|
|
if (HairStrands::HasViewHairStrandsData(View))
|
|
{
|
|
FRDGTextureRef& RefDestination = PreprocessedShadowMaskSubPixelTextures[LightBatchIndex - UnbatchedLightStart];
|
|
check(RefDestination == nullptr);
|
|
RefDestination = SubPixelRayTracingShadowMaskTexture;
|
|
}
|
|
}
|
|
|
|
bool bBatchFull = false;
|
|
|
|
if (bRequiresDenoiser)
|
|
{
|
|
// Queue the ray tracing output for shadow denoising.
|
|
for (int32 i = 0; i < IScreenSpaceDenoiser::kMaxBatchSize; i++)
|
|
{
|
|
if (DenoisingQueue[i].LightSceneInfo == nullptr)
|
|
{
|
|
DenoisingQueue[i].LightSceneInfo = &BatchLightSceneInfo;
|
|
DenoisingQueue[i].RayTracingConfig = RayTracingConfig;
|
|
DenoisingQueue[i].InputTextures.Mask = RayTracingShadowMaskTexture;
|
|
DenoisingQueue[i].InputTextures.ClosestOccluder = RayDistanceTexture;
|
|
LightIndices[i] = LightBatchIndex;
|
|
|
|
// If queue for this light type is full, quick of the batch.
|
|
if ((i + 1) == MaxDenoisingBatchSize)
|
|
{
|
|
QuickOffDenoisingBatch();
|
|
bBatchFull = true;
|
|
}
|
|
break;
|
|
}
|
|
else
|
|
{
|
|
check((i - 1) < IScreenSpaceDenoiser::kMaxBatchSize);
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
PreprocessedShadowMaskTextures[LightBatchIndex - UnbatchedLightStart] = RayTracingShadowMaskTexture;
|
|
}
|
|
|
|
// terminate batch if we filled a denoiser batch or hit our max light batch
|
|
ProcessShadows++;
|
|
if (bBatchFull || ProcessShadows == MaxRTShadowBatchSize)
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
|
|
// Ensures all denoising queues are processed.
|
|
if (DenoisingQueue[0].LightSceneInfo)
|
|
{
|
|
QuickOffDenoisingBatch();
|
|
}
|
|
}
|
|
} // end inline batched raytraced shadow
|
|
|
|
if (PreprocessedShadowMaskTextures.Num() > 0 && PreprocessedShadowMaskTextures[LightIndex - UnbatchedLightStart])
|
|
{
|
|
const uint32 ShadowMaskIndex = LightIndex - UnbatchedLightStart;
|
|
ScreenShadowMaskTexture = PreprocessedShadowMaskTextures[ShadowMaskIndex];
|
|
PreprocessedShadowMaskTextures[ShadowMaskIndex] = nullptr;
|
|
|
|
// Sub-pixel shadow for hair strands geometries
|
|
if (bRunHairLighting && ShadowMaskIndex < uint32(PreprocessedShadowMaskSubPixelTextures.Num()))
|
|
{
|
|
ScreenShadowMaskSubPixelTexture = PreprocessedShadowMaskSubPixelTextures[ShadowMaskIndex];
|
|
PreprocessedShadowMaskSubPixelTextures[ShadowMaskIndex] = nullptr;
|
|
}
|
|
|
|
// Inject deep shadow mask if the light supports it
|
|
if (bUseHairDeepShadow)
|
|
{
|
|
RenderHairStrandsDeepShadowMask(GraphBuilder, Views, &LightSceneInfo, VisibleLightInfos, ScreenShadowMaskTexture);
|
|
}
|
|
}
|
|
else
|
|
#endif // RHI_RAYTRACING
|
|
if (OcclusionType == FLightOcclusionType::Raytraced)
|
|
{
|
|
FSceneTextureParameters SceneTextureParameters = GetSceneTextureParameters(GraphBuilder, SceneTextures.UniformBuffer);
|
|
|
|
FRDGTextureRef RayTracingShadowMaskTexture;
|
|
{
|
|
FRDGTextureDesc Desc = FRDGTextureDesc::Create2D(
|
|
SceneTextures.Config.Extent,
|
|
PF_FloatRGBA,
|
|
FClearValueBinding::Black,
|
|
TexCreate_ShaderResource | TexCreate_RenderTargetable | TexCreate_UAV);
|
|
RayTracingShadowMaskTexture = GraphBuilder.CreateTexture(Desc, TEXT("RayTracingOcclusion"));
|
|
}
|
|
|
|
FRDGTextureRef RayDistanceTexture;
|
|
{
|
|
FRDGTextureDesc Desc = FRDGTextureDesc::Create2D(
|
|
SceneTextures.Config.Extent,
|
|
PF_R16F,
|
|
FClearValueBinding::Black,
|
|
TexCreate_ShaderResource | TexCreate_RenderTargetable | TexCreate_UAV);
|
|
RayDistanceTexture = GraphBuilder.CreateTexture(Desc, TEXT("RayTracingOcclusionDistance"));
|
|
}
|
|
|
|
FRDGTextureUAV* RayTracingShadowMaskUAV = GraphBuilder.CreateUAV(FRDGTextureUAVDesc(RayTracingShadowMaskTexture));
|
|
FRDGTextureUAV* RayHitDistanceUAV = GraphBuilder.CreateUAV(FRDGTextureUAVDesc(RayDistanceTexture));
|
|
|
|
FRDGTextureRef SubPixelRayTracingShadowMaskTexture = nullptr;
|
|
FRDGTextureUAV* SubPixelRayTracingShadowMaskUAV = nullptr;
|
|
if (bRunHairLighting)
|
|
{
|
|
FRDGTextureDesc Desc = FRDGTextureDesc::Create2D(
|
|
SceneTextures.Config.Extent,
|
|
PF_FloatRGBA,
|
|
FClearValueBinding::Black,
|
|
TexCreate_ShaderResource | TexCreate_RenderTargetable | TexCreate_UAV);
|
|
SubPixelRayTracingShadowMaskTexture = GraphBuilder.CreateTexture(Desc, TEXT("SubPixelRayTracingOcclusion"));
|
|
SubPixelRayTracingShadowMaskUAV = GraphBuilder.CreateUAV(FRDGTextureUAVDesc(SubPixelRayTracingShadowMaskTexture));
|
|
}
|
|
|
|
FRDGTextureRef RayTracingShadowMaskTileTexture;
|
|
{
|
|
FRDGTextureDesc Desc = FRDGTextureDesc::Create2D(
|
|
SceneTextures.Config.Extent,
|
|
PF_FloatRGBA,
|
|
FClearValueBinding::Black,
|
|
TexCreate_ShaderResource | TexCreate_RenderTargetable | TexCreate_UAV);
|
|
RayTracingShadowMaskTileTexture = GraphBuilder.CreateTexture(Desc, TEXT("RayTracingOcclusionTile"));
|
|
}
|
|
|
|
bool bIsMultiview = Views.Num() > 0;
|
|
|
|
for (FViewInfo& View : Views)
|
|
{
|
|
RDG_GPU_MASK_SCOPE(GraphBuilder, View.GPUMask);
|
|
|
|
IScreenSpaceDenoiser::FShadowRayTracingConfig RayTracingConfig;
|
|
RayTracingConfig.RayCountPerPixel = GShadowRayTracingSamplesPerPixel > -1 ? GShadowRayTracingSamplesPerPixel : LightSceneProxy.GetSamplesPerPixel();
|
|
|
|
IScreenSpaceDenoiser::EShadowRequirements DenoiserRequirements = IScreenSpaceDenoiser::EShadowRequirements::Bailout;
|
|
if (DenoiserMode != 0 && LightRequiresDenoiser(LightSceneInfo))
|
|
{
|
|
DenoiserRequirements = DenoiserToUse->GetShadowRequirements(View, LightSceneInfo, RayTracingConfig);
|
|
}
|
|
|
|
RenderRayTracingShadows(
|
|
GraphBuilder,
|
|
SceneTextureParameters,
|
|
View,
|
|
LightSceneInfo,
|
|
RayTracingConfig,
|
|
DenoiserRequirements,
|
|
LightingChannelsTexture,
|
|
RayTracingShadowMaskUAV,
|
|
RayHitDistanceUAV,
|
|
SubPixelRayTracingShadowMaskUAV);
|
|
|
|
if (DenoiserRequirements != IScreenSpaceDenoiser::EShadowRequirements::Bailout)
|
|
{
|
|
TStaticArray<IScreenSpaceDenoiser::FShadowVisibilityParameters, IScreenSpaceDenoiser::kMaxBatchSize> InputParameters;
|
|
TStaticArray<IScreenSpaceDenoiser::FShadowVisibilityOutputs, IScreenSpaceDenoiser::kMaxBatchSize> Outputs;
|
|
|
|
InputParameters[0].InputTextures.Mask = RayTracingShadowMaskTexture;
|
|
InputParameters[0].InputTextures.ClosestOccluder = RayDistanceTexture;
|
|
InputParameters[0].LightSceneInfo = &LightSceneInfo;
|
|
InputParameters[0].RayTracingConfig = RayTracingConfig;
|
|
|
|
int32 InputParameterCount = 1;
|
|
|
|
RDG_EVENT_SCOPE(GraphBuilder, "%s%s(Shadow BatchSize=%d) %dx%d",
|
|
DenoiserToUse != DefaultDenoiser ? TEXT("ThirdParty ") : TEXT(""),
|
|
DenoiserToUse->GetDebugName(),
|
|
InputParameterCount,
|
|
View.ViewRect.Width(), View.ViewRect.Height());
|
|
|
|
DenoiserToUse->DenoiseShadowVisibilityMasks(
|
|
GraphBuilder,
|
|
View,
|
|
&View.PrevViewInfo,
|
|
SceneTextureParameters,
|
|
InputParameters,
|
|
InputParameterCount,
|
|
Outputs);
|
|
|
|
if (bIsMultiview)
|
|
{
|
|
AddDrawTexturePass(GraphBuilder, View, Outputs[0].Mask, RayTracingShadowMaskTileTexture, View.ViewRect.Min, View.ViewRect.Min, View.ViewRect.Size());
|
|
ScreenShadowMaskTexture = RayTracingShadowMaskTileTexture;
|
|
}
|
|
else
|
|
{
|
|
ScreenShadowMaskTexture = Outputs[0].Mask;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
ScreenShadowMaskTexture = RayTracingShadowMaskTexture;
|
|
}
|
|
|
|
if (HairStrands::HasViewHairStrandsData(View))
|
|
{
|
|
ScreenShadowMaskSubPixelTexture = SubPixelRayTracingShadowMaskTexture;
|
|
}
|
|
}
|
|
|
|
// Inject deep shadow mask if the light supports it
|
|
if (bUseHairDeepShadow)
|
|
{
|
|
RenderHairStrandsShadowMask(GraphBuilder, Views, &LightSceneInfo, VisibleLightInfos, false /*bForwardShading*/, ScreenShadowMaskTexture);
|
|
}
|
|
}
|
|
else // (OcclusionType == FOcclusionType::Shadowmap)
|
|
{
|
|
const auto ClearShadowMask = [&](FRDGTextureRef InScreenShadowMaskTexture)
|
|
{
|
|
// Clear light attenuation for local lights with a quad covering their extents
|
|
const bool bClearLightScreenExtentsOnly = CVarAllowClearLightSceneExtentsOnly.GetValueOnRenderThread() && SortedLightInfo.SortKey.Fields.LightType != LightType_Directional;
|
|
|
|
if (bClearLightScreenExtentsOnly)
|
|
{
|
|
FRenderTargetParameters* PassParameters = GraphBuilder.AllocParameters<FRenderTargetParameters>();
|
|
PassParameters->RenderTargets[0] = FRenderTargetBinding(InScreenShadowMaskTexture, ERenderTargetLoadAction::ENoAction);
|
|
|
|
GraphBuilder.AddPass(
|
|
RDG_EVENT_NAME("ClearQuad"),
|
|
PassParameters,
|
|
ERDGPassFlags::Raster,
|
|
[this, &LightSceneProxy](FRDGAsyncTask, FRHICommandList& RHICmdList)
|
|
{
|
|
for (int32 ViewIndex = 0; ViewIndex < Views.Num(); ViewIndex++)
|
|
{
|
|
const FViewInfo& View = Views[ViewIndex];
|
|
SCOPED_GPU_MASK(RHICmdList, View.GPUMask);
|
|
|
|
FIntRect ScissorRect;
|
|
if (!LightSceneProxy.GetScissorRect(ScissorRect, View, View.ViewRect))
|
|
{
|
|
ScissorRect = View.ViewRect;
|
|
}
|
|
|
|
if (ScissorRect.Min.X < ScissorRect.Max.X && ScissorRect.Min.Y < ScissorRect.Max.Y)
|
|
{
|
|
RHICmdList.SetViewport(ScissorRect.Min.X, ScissorRect.Min.Y, 0.0f, ScissorRect.Max.X, ScissorRect.Max.Y, 1.0f);
|
|
DrawClearQuad(RHICmdList, true, FLinearColor(1, 1, 1, 1), false, 0, false, 0);
|
|
}
|
|
else
|
|
{
|
|
LightSceneProxy.GetScissorRect(ScissorRect, View, View.ViewRect);
|
|
}
|
|
}
|
|
});
|
|
}
|
|
else
|
|
{
|
|
AddClearRenderTargetPass(GraphBuilder, InScreenShadowMaskTexture);
|
|
}
|
|
};
|
|
|
|
// Skip clearing if it doesn't exist (due to being elided by VSM projection)
|
|
if (ScreenShadowMaskTexture)
|
|
{
|
|
ClearShadowMask(ScreenShadowMaskTexture);
|
|
}
|
|
if (ScreenShadowMaskSubPixelTexture)
|
|
{
|
|
ClearShadowMask(ScreenShadowMaskSubPixelTexture);
|
|
}
|
|
|
|
if (bIsHairPass)
|
|
{
|
|
RenderDeferredShadowProjections(GraphBuilder, SceneTextures, &LightSceneInfo, nullptr, ScreenShadowMaskSubPixelTexture);
|
|
}
|
|
else
|
|
{
|
|
RenderDeferredShadowProjections(GraphBuilder, SceneTextures, &LightSceneInfo, ScreenShadowMaskTexture, nullptr);
|
|
}
|
|
}
|
|
|
|
// First person self shadow
|
|
if (bDrawFirstPersonSelfShadow)
|
|
{
|
|
RenderFirstPersonSelfShadow(GraphBuilder, *this, Views, ScreenShadowMaskTexture, FirstPersonSelfShadowInputs, LightSceneInfo);
|
|
}
|
|
|
|
bUsedShadowMaskTexture = true;
|
|
} // if (bDrawShadows)
|
|
|
|
// Render light function to the attenuation buffer.
|
|
if (bDirectLighting)
|
|
{
|
|
if (bDrawLightFunction)
|
|
{
|
|
if (bIsHairPass)
|
|
{
|
|
if (CVarAppliedLightFunctionOnHair.GetValueOnRenderThread() > 0 && ScreenShadowMaskSubPixelTexture)
|
|
{
|
|
const bool bLightFunctionRendered = RenderLightFunction(GraphBuilder, SceneTextures, &LightSceneInfo, ScreenShadowMaskSubPixelTexture, bDrawShadows, false, true);
|
|
bUsedShadowMaskTexture |= bLightFunctionRendered;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
const bool bLightFunctionRendered = RenderLightFunction(GraphBuilder, SceneTextures, &LightSceneInfo, ScreenShadowMaskTexture, bDrawShadows, false, false);
|
|
bUsedShadowMaskTexture |= bLightFunctionRendered;
|
|
}
|
|
}
|
|
|
|
if (bDrawPreviewIndicator)
|
|
{
|
|
bUsedShadowMaskTexture |= RenderPreviewShadowsIndicator(GraphBuilder, SceneTextures, &LightSceneInfo, ScreenShadowMaskTexture, bUsedShadowMaskTexture, false);
|
|
}
|
|
|
|
if (!bDrawShadows)
|
|
{
|
|
INC_DWORD_STAT(STAT_NumLightFunctionOnlyLights);
|
|
}
|
|
}
|
|
|
|
// If we never rendered into the mask, don't attempt to read from it.
|
|
if (!bUsedShadowMaskTexture)
|
|
{
|
|
ScreenShadowMaskTexture = nullptr;
|
|
ScreenShadowMaskSubPixelTexture = nullptr;
|
|
}
|
|
|
|
// Render the light to the scene color buffer, conditionally using the attenuation buffer or a 1x1 white texture as input
|
|
if (bDirectLighting && !bIsHairPass)
|
|
{
|
|
for (int32 ViewIndex = 0, ViewCount = Views.Num(); ViewIndex < ViewCount; ++ViewIndex)
|
|
{
|
|
const FViewInfo& View = Views[ViewIndex];
|
|
|
|
// If the light elided the screen space shadow mask, sample directly from the packed shadow mask
|
|
int32 VirtualShadowMapId = INDEX_NONE;
|
|
if (bElideScreenShadowMask)
|
|
{
|
|
INC_DWORD_STAT(STAT_VSMLocalProjectionOnePassFast);
|
|
VirtualShadowMapId = VisibleLightInfo.GetVirtualShadowMapId(&View);
|
|
}
|
|
|
|
RDG_EVENT_SCOPE_CONDITIONAL(GraphBuilder, ViewCount > 1, "View%d", ViewIndex);
|
|
SCOPED_GPU_MASK(GraphBuilder.RHICmdList, View.GPUMask);
|
|
RenderLight(
|
|
GraphBuilder, Scene, View, SceneTextures, &LightSceneInfo,
|
|
VirtualShadowMapId != INDEX_NONE ? nullptr : ScreenShadowMaskTexture, LightingChannelsTexture, false /*bRenderOverlap*/, true /*bCloudShadow*/, SortedLightInfo.bIsCompatibleWithLightFunctionAtlas,
|
|
VirtualShadowMapArray.GetUniformBuffer(ViewIndex), ShadowSceneRenderer.VirtualShadowMapMaskBits, VirtualShadowMapId);
|
|
}
|
|
}
|
|
|
|
if (bRunHairLighting)
|
|
{
|
|
for (int32 ViewIndex = 0, ViewCount = Views.Num(); ViewIndex < ViewCount; ++ViewIndex)
|
|
{
|
|
FViewInfo& View = Views[ViewIndex];
|
|
|
|
if (HairStrands::HasViewHairStrandsData(View))
|
|
{
|
|
// If the light elided the screen space shadow mask, sample directly from the packed shadow mask
|
|
// Note: this is only used when VSM one pass is enable AND hair one pass transmittance is enabled.
|
|
// Hence the condition using bElideScreenShadowMaskSubPixel, instead of bElideScreenShadowMask for
|
|
// computing VirtualShadowMapId
|
|
int32 VirtualShadowMapId = INDEX_NONE;
|
|
if (bElideScreenShadowMaskSubPixel)
|
|
{
|
|
INC_DWORD_STAT(STAT_VSMLocalProjectionOnePassFast);
|
|
VirtualShadowMapId = VisibleLightInfo.GetVirtualShadowMapId(&View);
|
|
}
|
|
|
|
FHairStrandsTransmittanceMaskData TransmittanceMaskData;
|
|
FRDGTextureRef HairShadowMask = nullptr;
|
|
if (bDrawHairShadow && VirtualShadowMapId != INDEX_NONE)
|
|
{
|
|
TransmittanceMaskData.TransmittanceMask = ShadowSceneRenderer.HairTransmittanceMaskBits;
|
|
HairShadowMask = nullptr;
|
|
check(ScreenShadowMaskSubPixelTexture == nullptr);
|
|
}
|
|
else if (bDrawHairShadow)
|
|
{
|
|
TransmittanceMaskData = RenderHairStrandsTransmittanceMask(GraphBuilder, View, ViewIndex, &LightSceneInfo, false, ScreenShadowMaskSubPixelTexture);
|
|
HairShadowMask = ScreenShadowMaskSubPixelTexture;
|
|
}
|
|
if (TransmittanceMaskData.TransmittanceMask == nullptr)
|
|
{
|
|
TransmittanceMaskData = DummyTransmittanceMaskData;
|
|
}
|
|
|
|
// Note: ideally the light should still be evaluated for hair when not casting shadow, but for preserving the old behavior, and not adding
|
|
// any perf. regression, we disable this light for hair rendering
|
|
RenderLightForHair(
|
|
GraphBuilder, View, SceneTextures, &LightSceneInfo,
|
|
VirtualShadowMapId != INDEX_NONE ? nullptr : HairShadowMask, LightingChannelsTexture, TransmittanceMaskData,
|
|
false /*bForwardRendering*/, SortedLightInfo.bIsCompatibleWithLightFunctionAtlas,
|
|
VirtualShadowMapArray.GetUniformBuffer(ViewIndex), ShadowSceneRenderer.VirtualShadowMapMaskBitsHairStrands, VirtualShadowMapId);
|
|
}
|
|
}
|
|
}
|
|
}};
|
|
|
|
// Two seperate light loop:
|
|
// * For GBuffer inputs (and hair input for light having RT shadows)
|
|
// * For Hair inputs if any hair data are present
|
|
UnbatchedLightsPass(false/*bIsHairPass*/);
|
|
if (bUseHairLighting)
|
|
{
|
|
RDG_EVENT_SCOPE(GraphBuilder, "UnbatchedLights(Hair)");
|
|
UnbatchedLightsPass(true/*bIsHairPass*/);
|
|
}
|
|
}
|
|
}
|
|
|
|
GraphBuilder.FlushSetupQueue();
|
|
}
|
|
|
|
static void RenderLightArrayForOverlapViewmode(
|
|
FRDGBuilder& GraphBuilder,
|
|
const FScene* Scene,
|
|
const TArray<FViewInfo>& Views,
|
|
const FMinimalSceneTextures& SceneTextures,
|
|
FRDGTextureRef LightingChannelsTexture,
|
|
const TSparseArray<FLightSceneInfoCompact, TAlignedSparseArrayAllocator<alignof(FLightSceneInfoCompact)>>& LightArray)
|
|
{
|
|
for (auto LightIt = LightArray.CreateConstIterator(); LightIt; ++LightIt)
|
|
{
|
|
const FLightSceneInfoCompact& LightSceneInfoCompact = *LightIt;
|
|
const FLightSceneInfo* LightSceneInfo = LightSceneInfoCompact.LightSceneInfo;
|
|
|
|
// Nothing to do for black lights.
|
|
if (LightSceneInfoCompact.Color.IsAlmostBlack())
|
|
{
|
|
continue;
|
|
}
|
|
|
|
// Only render shadow casting stationary lights
|
|
if (!LightSceneInfo->Proxy->HasStaticShadowing() ||
|
|
LightSceneInfo->Proxy->HasStaticLighting() ||
|
|
!LightSceneInfo->Proxy->CastsStaticShadow())
|
|
{
|
|
continue;
|
|
}
|
|
|
|
// Check if the light is visible in any of the views.
|
|
for (const FViewInfo& View : Views)
|
|
{
|
|
SCOPED_GPU_MASK(GraphBuilder.RHICmdList, View.GPUMask);
|
|
RenderLight(GraphBuilder, Scene, View, SceneTextures, LightSceneInfo, nullptr, LightingChannelsTexture, true /*bRenderOverlap*/, false /*bCloudShadow*/, false/*bIsCompatibleWithLightFunctionAtlas*/);
|
|
}
|
|
}
|
|
}
|
|
|
|
void FDeferredShadingSceneRenderer::RenderStationaryLightOverlap(
|
|
FRDGBuilder& GraphBuilder,
|
|
const FMinimalSceneTextures& SceneTextures,
|
|
FRDGTextureRef LightingChannelsTexture)
|
|
{
|
|
if (Scene->bIsEditorScene)
|
|
{
|
|
// Clear to discard base pass values in scene color since we didn't skip that, to have valid scene depths
|
|
AddClearRenderTargetPass(GraphBuilder, SceneTextures.Color.Target, FLinearColor::Black);
|
|
|
|
RenderLightArrayForOverlapViewmode(GraphBuilder, Scene, Views, SceneTextures, LightingChannelsTexture, Scene->Lights);
|
|
|
|
//Note: making use of FScene::InvisibleLights, which contains lights that haven't been added to the scene in the same way as visible lights
|
|
// So code called by RenderLightArrayForOverlapViewmode must be careful what it accesses
|
|
RenderLightArrayForOverlapViewmode(GraphBuilder, Scene, Views, SceneTextures, LightingChannelsTexture, Scene->InvisibleLights);
|
|
}
|
|
}
|
|
|
|
static void InternalSetBoundingGeometryRasterizerState(FGraphicsPipelineStateInitializer& GraphicsPSOInit, bool bReverseCulling, bool bCameraInsideLightGeometry)
|
|
{
|
|
if (bCameraInsideLightGeometry)
|
|
{
|
|
// Render backfaces with depth tests disabled since the camera is inside (or close to inside) the light geometry
|
|
GraphicsPSOInit.RasterizerState = bReverseCulling ? TStaticRasterizerState<FM_Solid, CM_CW>::GetRHI() : TStaticRasterizerState<FM_Solid, CM_CCW>::GetRHI();
|
|
}
|
|
else
|
|
{
|
|
// Render frontfaces with depth tests on to get the speedup from HiZ since the camera is outside the light geometry
|
|
GraphicsPSOInit.RasterizerState = bReverseCulling ? TStaticRasterizerState<FM_Solid, CM_CCW>::GetRHI() : TStaticRasterizerState<FM_Solid, CM_CW>::GetRHI();
|
|
}
|
|
}
|
|
|
|
template<ECompareFunction CompareFunction>
|
|
static uint32 InternalSetBoundingGeometryDepthState(FGraphicsPipelineStateInitializer& GraphicsPSOInit, ESubstrateTileType TileType)
|
|
{
|
|
// bCameraInsideLightGeometry = true -> CompareFunction = Always
|
|
// bCameraInsideLightGeometry = false -> CompareFunction = CF_DepthNearOrEqual
|
|
uint32 StencilRef = 0u;
|
|
if (TileType != ESubstrateTileType::ECount)
|
|
{
|
|
check(Substrate::IsSubstrateEnabled());
|
|
switch (TileType)
|
|
{
|
|
case ESubstrateTileType::ESimple : StencilRef = Substrate::StencilBit_Fast; GraphicsPSOInit.DepthStencilState = TStaticDepthStencilState<false, CompareFunction, true, CF_Equal, SO_Keep, SO_Keep, SO_Keep, true, CF_Equal, SO_Keep, SO_Keep, SO_Keep, Substrate::StencilBit_Fast, 0x0>::GetRHI(); break;
|
|
case ESubstrateTileType::ESingle : StencilRef = Substrate::StencilBit_Single; GraphicsPSOInit.DepthStencilState = TStaticDepthStencilState<false, CompareFunction, true, CF_Equal, SO_Keep, SO_Keep, SO_Keep, true, CF_Equal, SO_Keep, SO_Keep, SO_Keep, Substrate::StencilBit_Single, 0x0>::GetRHI(); break;
|
|
case ESubstrateTileType::EComplex: StencilRef = Substrate::StencilBit_Complex; GraphicsPSOInit.DepthStencilState = TStaticDepthStencilState<false, CompareFunction, true, CF_Equal, SO_Keep, SO_Keep, SO_Keep, true, CF_Equal, SO_Keep, SO_Keep, SO_Keep, Substrate::StencilBit_Complex, 0x0>::GetRHI(); break;
|
|
case ESubstrateTileType::EComplexSpecial: StencilRef = Substrate::StencilBit_ComplexSpecial; GraphicsPSOInit.DepthStencilState = TStaticDepthStencilState<false, CompareFunction, true, CF_Equal, SO_Keep, SO_Keep, SO_Keep, true, CF_Equal, SO_Keep, SO_Keep, SO_Keep, Substrate::StencilBit_ComplexSpecial, 0x0>::GetRHI(); break;
|
|
default: check(false);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
GraphicsPSOInit.DepthStencilState = TStaticDepthStencilState<false, CompareFunction>::GetRHI();
|
|
}
|
|
return StencilRef;
|
|
}
|
|
|
|
/** Sets up rasterizer and depth state for rendering bounding geometry in a deferred pass. */
|
|
static uint32 SetBoundingGeometryRasterizerAndDepthState(FGraphicsPipelineStateInitializer& GraphicsPSOInit, bool bReverseCulling, bool bCameraInsideLightGeometry, ESubstrateTileType TileType)
|
|
{
|
|
uint32 StencilRef = 0u;
|
|
InternalSetBoundingGeometryRasterizerState(GraphicsPSOInit, bReverseCulling, bCameraInsideLightGeometry);
|
|
if (bCameraInsideLightGeometry)
|
|
{
|
|
StencilRef = InternalSetBoundingGeometryDepthState<CF_Always>(GraphicsPSOInit, TileType);
|
|
}
|
|
else
|
|
{
|
|
StencilRef = InternalSetBoundingGeometryDepthState<CF_DepthNearOrEqual>(GraphicsPSOInit, TileType);
|
|
}
|
|
|
|
return StencilRef;
|
|
}
|
|
|
|
// Use DBT to allow work culling on shadow lights
|
|
static void CalculateLightNearFarDepthFromBounds(const FViewInfo& View, const FSphere& LightBounds, float& NearDepth, float& FarDepth)
|
|
{
|
|
const FMatrix ViewProjection = View.ViewMatrices.GetViewProjectionMatrix();
|
|
const FVector ViewDirection = View.GetViewDirection();
|
|
|
|
// push camera relative bounds center along view vec by its radius
|
|
const FVector FarPoint = LightBounds.Center + LightBounds.W * ViewDirection;
|
|
const FVector4 FarPoint4 = FVector4(FarPoint, 1.f);
|
|
const FVector4 FarPoint4Clip = ViewProjection.TransformFVector4(FarPoint4);
|
|
FarDepth = FarPoint4Clip.Z / FarPoint4Clip.W;
|
|
|
|
// pull camera relative bounds center along -view vec by its radius
|
|
const FVector NearPoint = LightBounds.Center - LightBounds.W * ViewDirection;
|
|
const FVector4 NearPoint4 = FVector4(NearPoint, 1.f);
|
|
const FVector4 NearPoint4Clip = ViewProjection.TransformFVector4(NearPoint4);
|
|
NearDepth = NearPoint4Clip.Z / NearPoint4Clip.W;
|
|
|
|
// negative means behind view, but we use a NearClipPlane==1.f depth
|
|
|
|
if (NearPoint4Clip.W < 0)
|
|
NearDepth = 1;
|
|
|
|
if (FarPoint4Clip.W < 0)
|
|
FarDepth = 1;
|
|
|
|
NearDepth = FMath::Clamp(NearDepth, 0.0f, 1.0f);
|
|
FarDepth = FMath::Clamp(FarDepth, 0.0f, 1.0f);
|
|
|
|
}
|
|
|
|
static TRDGUniformBufferRef<FDeferredLightUniformStruct> CreateDeferredLightUniformBuffer(FRDGBuilder& GraphBuilder, const FViewInfo& View, const FLightSceneInfo& LightSceneInfo)
|
|
{
|
|
auto* DeferredLightStruct = GraphBuilder.AllocParameters<FDeferredLightUniformStruct>();
|
|
*DeferredLightStruct = GetDeferredLightParameters(View, LightSceneInfo, LightFunctionAtlas::IsEnabled(View, ELightFunctionAtlasSystem::DeferredLighting));
|
|
return GraphBuilder.CreateUniformBuffer(DeferredLightStruct);
|
|
}
|
|
|
|
static TRDGUniformBufferRef<FDeferredLightUniformStruct> CreateDeferredLightUniformBuffer(FRDGBuilder& GraphBuilder, const FViewInfo& View, const FSimpleLightEntry& SimpleLight, const FVector& SimpleLightPosition)
|
|
{
|
|
auto* DeferredLightStruct = GraphBuilder.AllocParameters<FDeferredLightUniformStruct>();
|
|
*DeferredLightStruct = GetSimpleDeferredLightParameters(View, SimpleLight, SimpleLightPosition);
|
|
return GraphBuilder.CreateUniformBuffer(DeferredLightStruct);
|
|
}
|
|
|
|
static FDeferredLightPS::FParameters GetDeferredLightPSParameters(
|
|
FRDGBuilder& GraphBuilder,
|
|
const FScene* Scene,
|
|
const FViewInfo& View,
|
|
const FLightSceneInfo* LightSceneInfo,
|
|
FRDGTextureRef SceneColorTexture,
|
|
FRDGTextureRef SceneDepthTexture,
|
|
TRDGUniformBufferRef<FSceneTextureUniformParameters> SceneTexturesUniformBuffer,
|
|
TRDGUniformBufferRef<FHairStrandsViewUniformParameters> HairStrandsUniformBuffer,
|
|
FRDGTextureRef ShadowMaskTexture,
|
|
FRDGTextureRef LightingChannelsTexture,
|
|
bool bCloudShadow,
|
|
TRDGUniformBufferRef<FVirtualShadowMapUniformParameters> VirtualShadowMapUniformBuffer = nullptr,
|
|
FRDGTextureRef ShadowMaskBits = nullptr,
|
|
int32 VirtualShadowMapId = INDEX_NONE)
|
|
{
|
|
FDeferredLightPS::FParameters Out;
|
|
|
|
const ELightComponentType LightType = (ELightComponentType)LightSceneInfo->Proxy->GetLightType();
|
|
const bool bIsDirectional = LightType == LightType_Directional;
|
|
|
|
FRDGTextureRef WhiteDummy = GSystemTextures.GetWhiteDummy(GraphBuilder);
|
|
FRDGBufferRef BufferDummy = GSystemTextures.GetDefaultBuffer(GraphBuilder, 4, 0u);
|
|
FRDGBufferSRVRef BufferDummySRV = GraphBuilder.CreateSRV(BufferDummy, PF_R32_UINT);
|
|
|
|
// PS - General parameters
|
|
const FVolumetricCloudRenderSceneInfo* CloudInfo = bCloudShadow ? Scene->GetVolumetricCloudSceneInfo() : nullptr;
|
|
Out.SceneTextures = SceneTexturesUniformBuffer;
|
|
Out.HairStrands = HairStrandsUniformBuffer;
|
|
Out.ForwardLightStruct = View.ForwardLightingResources.ForwardLightUniformBuffer;
|
|
Out.Substrate = Substrate::BindSubstrateGlobalUniformParameters(View);
|
|
Out.LightingChannelsTexture = LightingChannelsTexture ? LightingChannelsTexture : WhiteDummy;
|
|
Out.LightingChannelsSampler = TStaticSamplerState<SF_Point, AM_Clamp, AM_Clamp, AM_Clamp>::GetRHI();
|
|
Out.CloudShadowAO = GetCloudShadowAOParameters(GraphBuilder, View, CloudInfo);
|
|
Out.CloudShadowEnabled = SetupLightCloudTransmittanceParameters(GraphBuilder, Scene, View, LightSceneInfo, Out.CloudShadow) ? 1 : 0;
|
|
Out.LightAttenuationTexture = ShadowMaskTexture ? ShadowMaskTexture : WhiteDummy;
|
|
Out.LightAttenuationTextureSampler = TStaticSamplerState<SF_Point, AM_Wrap, AM_Wrap, AM_Wrap>::GetRHI();
|
|
Out.View = View.ViewUniformBuffer;
|
|
Out.DeferredLight = CreateDeferredLightUniformBuffer(GraphBuilder, View, *LightSceneInfo);
|
|
// PS - Hair (default value)
|
|
Out.ScreenShadowMaskSubPixelTexture = WhiteDummy;
|
|
Out.HairTransmittanceBuffer = BufferDummySRV;
|
|
Out.HairTransmittanceBufferMaxCount = 0;
|
|
Out.HairShadowMaskValid = false;
|
|
Out.ShadowChannelMask = FVector4f(1, 1, 1, 1);
|
|
// PS - One pass projection
|
|
Out.VirtualShadowMap = VirtualShadowMapUniformBuffer;
|
|
Out.VirtualShadowMapId = VirtualShadowMapId;
|
|
Out.ShadowMaskBits = ShadowMaskBits ? ShadowMaskBits : GSystemTextures.GetZeroUIntDummy(GraphBuilder);
|
|
|
|
// If the light is not batched, it could be due to shadow, so we still specify light function atlas sampling.
|
|
Out.LightFunctionAtlas = LightFunctionAtlas::BindGlobalParameters(GraphBuilder, View);
|
|
|
|
// PS - Render Targets
|
|
Out.RenderTargets[0] = FRenderTargetBinding(SceneColorTexture, ERenderTargetLoadAction::ELoad);
|
|
if (Substrate::IsOpaqueRoughRefractionEnabled(View.GetShaderPlatform()) && Substrate::UsesSubstrateMaterialBuffer(View.GetShaderPlatform()))
|
|
{
|
|
Out.RenderTargets[1] = FRenderTargetBinding(Scene->SubstrateSceneData.SeparatedOpaqueRoughRefractionSceneColor, ERenderTargetLoadAction::ELoad);
|
|
Out.RenderTargets[2] = FRenderTargetBinding(Scene->SubstrateSceneData.SeparatedSubSurfaceSceneColor, ERenderTargetLoadAction::ELoad);
|
|
}
|
|
if (SceneDepthTexture)
|
|
{
|
|
Out.RenderTargets.DepthStencil = FDepthStencilBinding(SceneDepthTexture, ERenderTargetLoadAction::ELoad, ERenderTargetLoadAction::ELoad, FExclusiveDepthStencil::DepthRead_StencilWrite);
|
|
}
|
|
|
|
return Out;
|
|
}
|
|
|
|
uint32 SetupLightGraphicsPSOState(
|
|
bool bDirectional,
|
|
bool bCameraInsideLightGeometry,
|
|
bool bReverseCulling,
|
|
ESubstrateTileType SubstrateTileMaterialType,
|
|
FGraphicsPipelineStateInitializer& GraphicsPSOInit,
|
|
EShaderPlatform ShaderPlatform)
|
|
{
|
|
GraphicsPSOInit.PrimitiveType = PT_TriangleList;
|
|
if (Substrate::IsOpaqueRoughRefractionEnabled(ShaderPlatform))
|
|
{
|
|
GraphicsPSOInit.BlendState = TStaticBlendState<
|
|
CW_RGBA, BO_Add, BF_One, BF_One, BO_Add, BF_One, BF_One,
|
|
CW_RGBA, BO_Add, BF_One, BF_One, BO_Add, BF_One, BF_One,
|
|
CW_RGBA, BO_Add, BF_One, BF_One, BO_Add, BF_One, BF_One>::GetRHI();
|
|
}
|
|
else
|
|
{
|
|
GraphicsPSOInit.BlendState = TStaticBlendState<CW_RGBA, BO_Add, BF_One, BF_One, BO_Add, BF_One, BF_One>::GetRHI();
|
|
}
|
|
|
|
uint32 StencilRef = 0;
|
|
if (bDirectional)
|
|
{
|
|
// Turn DBT back off
|
|
GraphicsPSOInit.bDepthBounds = false;
|
|
GraphicsPSOInit.RasterizerState = TStaticRasterizerState<FM_Solid, CM_None>::GetRHI();
|
|
GraphicsPSOInit.BoundShaderState.VertexDeclarationRHI = GFilterVertexDeclaration.VertexDeclarationRHI;
|
|
GraphicsPSOInit.DepthStencilState = TStaticDepthStencilState<false, CF_Always>::GetRHI();
|
|
}
|
|
else
|
|
{
|
|
// Use DBT to allow work culling on shadow lights
|
|
// Disable depth bound when hair rendering is enabled as this rejects partially covered pixel write (with opaque background)
|
|
GraphicsPSOInit.bDepthBounds = GSupportsDepthBoundsTest && GAllowDepthBoundsTest != 0;
|
|
|
|
StencilRef = SetBoundingGeometryRasterizerAndDepthState(GraphicsPSOInit, bReverseCulling, bCameraInsideLightGeometry, SubstrateTileMaterialType);
|
|
GraphicsPSOInit.BoundShaderState.VertexDeclarationRHI = GetVertexDeclarationFVector4();
|
|
}
|
|
|
|
return StencilRef;
|
|
}
|
|
|
|
// Used by RenderLights to render a light to the scene color buffer.
|
|
template<typename TShaderType, typename TParametersType>
|
|
static void InternalRenderLight(
|
|
FRDGBuilder& GraphBuilder,
|
|
const FScene* Scene,
|
|
const FViewInfo& View,
|
|
const FLightSceneInfo* LightSceneInfo,
|
|
TShaderType& PixelShader,
|
|
TParametersType* PassParameters,
|
|
ESubstrateTileType SubstrateTileMaterialType,
|
|
const TCHAR* ShaderName)
|
|
{
|
|
const FLightSceneProxy* RESTRICT LightProxy = LightSceneInfo->Proxy;
|
|
const bool bTransmission = LightProxy->Transmission();
|
|
const FSphere LightBounds = LightProxy->GetBoundingSphere();
|
|
const ELightComponentType LightType = (ELightComponentType)LightProxy->GetLightType();
|
|
|
|
GraphBuilder.AddPass(
|
|
RDG_EVENT_NAME("%s: %s", ShaderName, *LightProxy->GetOwnerNameOrLabel()),
|
|
PassParameters,
|
|
ERDGPassFlags::Raster,
|
|
[Scene, &View, PixelShader, LightSceneInfo, PassParameters, LightBounds, LightType, SubstrateTileMaterialType](FRDGAsyncTask, FRHICommandList& RHICmdList)
|
|
{
|
|
|
|
const bool bIsRadial = LightType != LightType_Directional;
|
|
const bool bEnableSubstrateTiledPass = SubstrateTileMaterialType != ESubstrateTileType::ECount;
|
|
const bool bEnableSubstrateStencilTest = SubstrateTileMaterialType != ESubstrateTileType::ECount && bIsRadial;
|
|
|
|
FGraphicsPipelineStateInitializer GraphicsPSOInit;
|
|
RHICmdList.ApplyCachedRenderTargets(GraphicsPSOInit);
|
|
|
|
const bool bCameraInsideLightGeometry = ((FVector)View.ViewMatrices.GetViewOrigin() - LightBounds.Center).SizeSquared() < FMath::Square(LightBounds.W * 1.05f + View.NearClippingDistance * 2.0f)
|
|
//const bool bCameraInsideLightGeometry = LightProxy->AffectsBounds( FSphere( View.ViewMatrices.GetViewOrigin(), View.NearClippingDistance * 2.0f ) )
|
|
// Always draw backfaces in ortho
|
|
//@todo - accurate ortho camera / light intersection
|
|
|| !View.IsPerspectiveProjection();
|
|
|
|
const uint32 StencilRef = SetupLightGraphicsPSOState(LightType == LightType_Directional, bCameraInsideLightGeometry, View.bReverseCulling, SubstrateTileMaterialType, GraphicsPSOInit, View.GetShaderPlatform());
|
|
|
|
// Set the device viewport for the view.
|
|
RHICmdList.SetViewport(View.ViewRect.Min.X, View.ViewRect.Min.Y, 0.0f, View.ViewRect.Max.X, View.ViewRect.Max.Y, 1.0f);
|
|
|
|
if (LightType == LightType_Directional)
|
|
{
|
|
FDeferredLightVS::FPermutationDomain PermutationVectorVS;
|
|
PermutationVectorVS.Set<FDeferredLightVS::FRadialLight>(false);
|
|
TShaderMapRef<FDeferredLightVS> VertexShader(View.ShaderMap, PermutationVectorVS);
|
|
|
|
Substrate::FSubstrateTilePassVS::FPermutationDomain VSPermutationVector;
|
|
VSPermutationVector.Set< Substrate::FSubstrateTilePassVS::FEnableDebug >(false);
|
|
VSPermutationVector.Set< Substrate::FSubstrateTilePassVS::FEnableTexCoordScreenVector >(true);
|
|
TShaderMapRef<Substrate::FSubstrateTilePassVS> TileVertexShader(View.ShaderMap, VSPermutationVector);
|
|
|
|
GraphicsPSOInit.BoundShaderState.VertexShaderRHI = bEnableSubstrateTiledPass ? TileVertexShader.GetVertexShader() : VertexShader.GetVertexShader();
|
|
GraphicsPSOInit.BoundShaderState.PixelShaderRHI = PixelShader.GetPixelShader();
|
|
|
|
#if PSO_PRECACHING_VALIDATE
|
|
if (PSOCollectorStats::IsFullPrecachingValidationEnabled())
|
|
{
|
|
static const int32 GlobalPSOCollectorIndex = FGlobalPSOCollectorManager::GetIndex(DeferredLightGlobalPSOCollectorName);
|
|
PSOCollectorStats::CheckGlobalGraphicsPipelineStateInCache(GraphicsPSOInit, GlobalPSOCollectorIndex);
|
|
}
|
|
#endif // PSO_PRECACHING_VALIDATE
|
|
|
|
Substrate::FSubstrateTilePassVS::FParameters VSParameters;
|
|
if (Substrate::IsSubstrateEnabled())
|
|
{
|
|
VSParameters = Substrate::SetTileParameters(View, SubstrateTileMaterialType, GraphicsPSOInit.PrimitiveType);
|
|
}
|
|
|
|
SetGraphicsPipelineState(RHICmdList, GraphicsPSOInit, StencilRef);
|
|
SetShaderParameters(RHICmdList, PixelShader, PixelShader.GetPixelShader(), PassParameters->PS);
|
|
|
|
if (SubstrateTileMaterialType != ECount)
|
|
{
|
|
check(Substrate::IsSubstrateEnabled());
|
|
SetShaderParameters(RHICmdList, TileVertexShader, TileVertexShader.GetVertexShader(), VSParameters);
|
|
RHICmdList.DrawPrimitiveIndirect(VSParameters.TileIndirectBuffer->GetIndirectRHICallBuffer(), Substrate::TileTypeDrawIndirectArgOffset(SubstrateTileMaterialType));
|
|
}
|
|
else
|
|
{
|
|
FDeferredLightVS::FParameters VSParameters2 = FDeferredLightVS::GetParameters(View);
|
|
SetShaderParameters(RHICmdList, VertexShader, VertexShader.GetVertexShader(), VSParameters2);
|
|
|
|
// Apply the directional light as a full screen quad
|
|
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);
|
|
}
|
|
}
|
|
else // Radial light (LightType_Point, LightType_Spot, LightType_Rect)
|
|
{
|
|
FDeferredLightVS::FPermutationDomain PermutationVectorVS;
|
|
PermutationVectorVS.Set<FDeferredLightVS::FRadialLight>(true);
|
|
TShaderMapRef<FDeferredLightVS> VertexShader(View.ShaderMap, PermutationVectorVS);
|
|
|
|
GraphicsPSOInit.BoundShaderState.VertexShaderRHI = VertexShader.GetVertexShader();
|
|
GraphicsPSOInit.BoundShaderState.PixelShaderRHI = PixelShader.GetPixelShader();
|
|
|
|
#if PSO_PRECACHING_VALIDATE
|
|
if (PSOCollectorStats::IsFullPrecachingValidationEnabled())
|
|
{
|
|
static const int32 GlobalPSOCollectorIndex = FGlobalPSOCollectorManager::GetIndex(DeferredLightGlobalPSOCollectorName);
|
|
PSOCollectorStats::CheckGlobalGraphicsPipelineStateInCache(GraphicsPSOInit, GlobalPSOCollectorIndex);
|
|
}
|
|
#endif // PSO_PRECACHING_VALIDATE
|
|
|
|
SetGraphicsPipelineState(RHICmdList, GraphicsPSOInit, StencilRef);
|
|
SetShaderParameters(RHICmdList, PixelShader, PixelShader.GetPixelShader(), PassParameters->PS);
|
|
|
|
FDeferredLightVS::FParameters VSParameters2 = FDeferredLightVS::GetParameters(View, LightSceneInfo);
|
|
SetShaderParameters(RHICmdList, VertexShader, VertexShader.GetVertexShader(), VSParameters2);
|
|
|
|
// Use DBT to allow work culling on shadow lights
|
|
if (GraphicsPSOInit.bDepthBounds)
|
|
{
|
|
// Can use the depth bounds test to skip work for pixels which won't be touched by the light (i.e outside the depth range)
|
|
float NearDepth = 1.f;
|
|
float FarDepth = 0.f;
|
|
CalculateLightNearFarDepthFromBounds(View,LightBounds,NearDepth,FarDepth);
|
|
if (NearDepth <= FarDepth)
|
|
{
|
|
NearDepth = 1.0f;
|
|
FarDepth = 0.0f;
|
|
}
|
|
|
|
// UE uses reversed depth, so far < near
|
|
RHICmdList.SetDepthBounds(FarDepth, NearDepth);
|
|
}
|
|
|
|
if( LightType == LightType_Point || LightType == LightType_Rect )
|
|
{
|
|
// Apply the point or spot light with some approximate bounding geometry,
|
|
// So we can get speedups from depth testing and not processing pixels outside of the light's influence.
|
|
StencilingGeometry::DrawSphere(RHICmdList);
|
|
}
|
|
else if (LightType == LightType_Spot)
|
|
{
|
|
StencilingGeometry::DrawCone(RHICmdList);
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
|
|
/** Shader parameters for Standard Deferred Light Overlap Debug pass. */
|
|
BEGIN_SHADER_PARAMETER_STRUCT(FRenderLightParameters, )
|
|
// PS/VS parameter structs
|
|
SHADER_PARAMETER_STRUCT_INCLUDE(FDeferredLightPS::FParameters, PS)
|
|
SHADER_PARAMETER_STRUCT_INCLUDE(FDeferredLightVS::FParameters, VS)
|
|
// Substrate tiles
|
|
SHADER_PARAMETER_STRUCT_INCLUDE(FSubstrateTileParameter, SubstrateTileSimple)
|
|
SHADER_PARAMETER_STRUCT_INCLUDE(FSubstrateTileParameter, SubstrateTileSingle)
|
|
SHADER_PARAMETER_STRUCT_INCLUDE(FSubstrateTileParameter, SubstrateTileComplex)
|
|
SHADER_PARAMETER_STRUCT_INCLUDE(FSubstrateTileParameter, SubstrateTileSpectialComplex)
|
|
END_SHADER_PARAMETER_STRUCT()
|
|
|
|
/** Shader parameters for Standard Deferred Light pass. */
|
|
BEGIN_SHADER_PARAMETER_STRUCT(FRenderLightOverlapParameters, )
|
|
// PS/VS parameter structs
|
|
SHADER_PARAMETER_STRUCT_INCLUDE(FDeferredLightOverlapPS::FParameters, PS)
|
|
SHADER_PARAMETER_STRUCT_INCLUDE(FDeferredLightVS::FParameters, VS)
|
|
END_SHADER_PARAMETER_STRUCT()
|
|
|
|
static void RenderLight(
|
|
FRDGBuilder& GraphBuilder,
|
|
const FScene* Scene,
|
|
const FViewInfo& View,
|
|
const FMinimalSceneTextures& SceneTextures,
|
|
const FLightSceneInfo* LightSceneInfo,
|
|
FRDGTextureRef ScreenShadowMaskTexture,
|
|
FRDGTextureRef LightingChannelsTexture,
|
|
bool bRenderOverlap,
|
|
bool bCloudShadow,
|
|
const bool bCanLightUsesAtlasForUnbatchedLight,
|
|
TRDGUniformBufferRef<FVirtualShadowMapUniformParameters> VirtualShadowMapUniformBuffer,
|
|
FRDGTextureRef ShadowMaskBits,
|
|
int32 VirtualShadowMapId)
|
|
{
|
|
// Ensure the light is valid for this view
|
|
if (!LightSceneInfo->ShouldRenderLight(View))
|
|
{
|
|
return;
|
|
}
|
|
|
|
SCOPE_CYCLE_COUNTER(STAT_DirectLightRenderingTime);
|
|
INC_DWORD_STAT(STAT_NumLightsUsingStandardDeferred);
|
|
|
|
const FLightSceneProxy* RESTRICT LightProxy = LightSceneInfo->Proxy;
|
|
const bool bUseIESTexture = View.Family->EngineShowFlags.TexturedLightProfiles && (LightSceneInfo->Proxy->GetIESTextureResource() != 0);
|
|
const bool bTransmission = LightProxy->Transmission();
|
|
const FSphere LightBounds = LightProxy->GetBoundingSphere();
|
|
const ELightComponentType LightType = (ELightComponentType)LightProxy->GetLightType();
|
|
const bool bIsRadial = LightType != LightType_Directional;
|
|
const bool bSupportAnisotropyPermutation = ShouldRenderAnisotropyPass(View) && !Substrate::IsSubstrateEnabled(); // Substrate managed anisotropy differently than legacy path. No need for special permutation.
|
|
const bool bUseVirtualShadowMapMask = VirtualShadowMapId != INDEX_NONE && ShadowMaskBits;
|
|
const bool bNeedComplexTransmittanceSupport = View.HairCardsMeshElements.Num() && IsHairStrandsSupported(EHairStrandsShaderType::All, View.GetShaderPlatform());
|
|
|
|
check(!bUseVirtualShadowMapMask || bIsRadial); // VSM mask only stores local lights
|
|
|
|
// Debug Overlap shader
|
|
if (bRenderOverlap)
|
|
{
|
|
FRenderLightOverlapParameters* PassParameters = GraphBuilder.AllocParameters<FRenderLightOverlapParameters>();
|
|
// PS - General parameters
|
|
PassParameters->PS.bHasValidChannel = LightSceneInfo->Proxy->GetPreviewShadowMapChannel() == INDEX_NONE ? 0.0f : 1.0f;
|
|
PassParameters->PS.View = View.ViewUniformBuffer;
|
|
PassParameters->PS.DeferredLight = CreateDeferredLightUniformBuffer(GraphBuilder, View, *LightSceneInfo);
|
|
PassParameters->PS.SceneTextures = SceneTextures.UniformBuffer;
|
|
PassParameters->PS.RenderTargets[0] = FRenderTargetBinding(SceneTextures.Color.Target, ERenderTargetLoadAction::ELoad);
|
|
if (SceneTextures.Depth.Target)
|
|
{
|
|
PassParameters->PS.RenderTargets.DepthStencil = FDepthStencilBinding(SceneTextures.Depth.Target, ERenderTargetLoadAction::ELoad, ERenderTargetLoadAction::ELoad, FExclusiveDepthStencil::DepthRead_StencilWrite);
|
|
}
|
|
// VS - General parameters
|
|
if (bIsRadial)
|
|
{
|
|
PassParameters->VS = FDeferredLightVS::GetParameters(View, LightSceneInfo, false);
|
|
}
|
|
else
|
|
{
|
|
PassParameters->VS = FDeferredLightVS::GetParameters(View, false);
|
|
}
|
|
|
|
FDeferredLightOverlapPS::FPermutationDomain PermutationVector;
|
|
PermutationVector.Set<FDeferredLightOverlapPS::FRadialAttenuation>(bIsRadial);
|
|
TShaderMapRef<FDeferredLightOverlapPS> PixelShader(View.ShaderMap, PermutationVector);
|
|
InternalRenderLight(GraphBuilder, Scene, View, LightSceneInfo, PixelShader, PassParameters, ESubstrateTileType::ECount, TEXT("Light::StandardDeferred(Overlap)"));
|
|
}
|
|
// Lighting shader
|
|
else
|
|
{
|
|
FRenderLightParameters* PassParameters = GraphBuilder.AllocParameters<FRenderLightParameters>();
|
|
// PS - Generatl parameters
|
|
PassParameters->PS = GetDeferredLightPSParameters(
|
|
GraphBuilder, Scene, View, LightSceneInfo, SceneTextures.Color.Target, SceneTextures.Depth.Target, SceneTextures.UniformBuffer, View.HairStrandsViewData.UniformBuffer,
|
|
ScreenShadowMaskTexture, LightingChannelsTexture, bCloudShadow, VirtualShadowMapUniformBuffer, ShadowMaskBits, VirtualShadowMapId);
|
|
// VS - General parameters
|
|
if (bIsRadial)
|
|
{
|
|
PassParameters->VS = FDeferredLightVS::GetParameters(View, LightSceneInfo, false);
|
|
}
|
|
else // Directional
|
|
{
|
|
PassParameters->VS = FDeferredLightVS::GetParameters(View, false);
|
|
}
|
|
// VS - Substrate tile parameters
|
|
if (Substrate::IsSubstrateEnabled())
|
|
{
|
|
// Note: we register all tile types here in order to have all resources tracked properly and being able
|
|
// to create a single pass parameters struct instead of created one for each tile types
|
|
PassParameters->SubstrateTileSimple = Substrate::SetTileParameters(GraphBuilder, View, ESubstrateTileType::ESingle);
|
|
PassParameters->SubstrateTileSingle = Substrate::SetTileParameters(GraphBuilder, View, ESubstrateTileType::ESimple);
|
|
PassParameters->SubstrateTileComplex = Substrate::SetTileParameters(GraphBuilder, View, ESubstrateTileType::EComplex);
|
|
PassParameters->SubstrateTileSpectialComplex = Substrate::SetTileParameters(GraphBuilder, View, ESubstrateTileType::EComplexSpecial);
|
|
}
|
|
#if 0
|
|
PassParameters->PS.OrthoGridUniformBuffer = HeterogeneousVolumes::GetOrthoVoxelGridUniformBuffer(GraphBuilder, View.ViewState);
|
|
PassParameters->PS.FrustumGridUniformBuffer = HeterogeneousVolumes::GetFrustumVoxelGridUniformBuffer(GraphBuilder, View.ViewState);
|
|
#endif
|
|
PassParameters->PS.AVSM = HeterogeneousVolumes::GetAdaptiveVolumetricShadowMapUniformBuffer(GraphBuilder, View.ViewState, LightSceneInfo);
|
|
|
|
FDeferredLightPS::FPermutationDomain PermutationVector;
|
|
PermutationVector.Set< FDeferredLightPS::FTransmissionDim >(bTransmission);
|
|
PermutationVector.Set< FDeferredLightPS::FHairLighting>(0);
|
|
PermutationVector.Set< FDeferredLightPS::FLightingChannelsDim >(View.bUsesLightingChannels);
|
|
PermutationVector.Set< FDeferredLightPS::FVisualizeCullingDim >(View.Family->EngineShowFlags.VisualizeLightCulling);
|
|
PermutationVector.Set< FDeferredLightPS::FVirtualShadowMapMask >(bUseVirtualShadowMapMask);
|
|
PermutationVector.Set< FDeferredLightPS::FSubstrateTileType >(0);
|
|
PermutationVector.Set< FDeferredLightPS::FHairComplexTransmittance >(bNeedComplexTransmittanceSupport);
|
|
PermutationVector.Set< FDeferredLightPS::FLightFunctionAtlasDim >(
|
|
LightFunctionAtlas::IsEnabled(View, ELightFunctionAtlasSystem::DeferredLighting) && LightSceneInfo->Proxy->HasValidLightFunctionAtlasSlot() &&
|
|
LightSceneInfo->Proxy->GetLightFunctionMaterial() != nullptr && !View.Family->EngineShowFlags.VisualizeLightCulling && bCanLightUsesAtlasForUnbatchedLight);
|
|
|
|
if (bIsRadial)
|
|
{
|
|
PermutationVector.Set< FDeferredLightPS::FSourceShapeDim >(LightProxy->IsRectLight() ? ELightSourceShape::Rect : ELightSourceShape::Capsule);
|
|
PermutationVector.Set< FDeferredLightPS::FSourceTextureDim >(LightProxy->IsRectLight() && LightProxy->HasSourceTexture());
|
|
PermutationVector.Set< FDeferredLightPS::FIESProfileDim >(bUseIESTexture);
|
|
PermutationVector.Set< FDeferredLightPS::FAnistropicMaterials >(bSupportAnisotropyPermutation && !LightSceneInfo->Proxy->IsRectLight());
|
|
PermutationVector.Set < FDeferredLightPS::FAtmosphereTransmittance >(false);
|
|
PermutationVector.Set< FDeferredLightPS::FCloudTransmittance >(false);
|
|
}
|
|
else // Directional
|
|
{
|
|
PermutationVector.Set< FDeferredLightPS::FSourceShapeDim >(ELightSourceShape::Directional);
|
|
PermutationVector.Set< FDeferredLightPS::FSourceTextureDim >(false);
|
|
PermutationVector.Set< FDeferredLightPS::FIESProfileDim >(false);
|
|
PermutationVector.Set< FDeferredLightPS::FAnistropicMaterials >(bSupportAnisotropyPermutation);
|
|
// Only directional lights are rendered in this path, so we only need to check if it is use to light the atmosphere
|
|
PermutationVector.Set< FDeferredLightPS::FAtmosphereTransmittance >(IsLightAtmospherePerPixelTransmittanceEnabled(Scene, View, LightSceneInfo));
|
|
PermutationVector.Set< FDeferredLightPS::FCloudTransmittance >(PassParameters->PS.CloudShadowEnabled > 0);
|
|
}
|
|
PermutationVector = FDeferredLightPS::RemapPermutation(PermutationVector);
|
|
|
|
// Substrate tile rendering:
|
|
// * if the light is directional, then dispatch a set of rect tiles
|
|
// * if the light is radial/local, then dispatch a light geometry with stencil test. The stencil buffer has been prefilled with the tile result (simple/complex)
|
|
// so that the geometry get correctly stencil culled on complex/simple part of the screen
|
|
if (Substrate::IsSubstrateEnabled())
|
|
{
|
|
// Complex Special tiles
|
|
if (Substrate::GetSubstrateUsesComplexSpecialPath(View))
|
|
{
|
|
const ESubstrateTileType TileType = ESubstrateTileType::EComplexSpecial;
|
|
PermutationVector.Set<FDeferredLightPS::FSubstrateTileType>(TileType);
|
|
TShaderMapRef< FDeferredLightPS > PixelShader(View.ShaderMap, PermutationVector);
|
|
InternalRenderLight(GraphBuilder, Scene, View, LightSceneInfo, PixelShader, PassParameters, TileType, TEXT("Light::StandardDeferred(ComplexSpecial)"));
|
|
}
|
|
// Complex tiles
|
|
{
|
|
const ESubstrateTileType TileType = ESubstrateTileType::EComplex;
|
|
PermutationVector.Set<FDeferredLightPS::FSubstrateTileType>(TileType);
|
|
TShaderMapRef< FDeferredLightPS > PixelShader(View.ShaderMap, PermutationVector);
|
|
InternalRenderLight(GraphBuilder, Scene, View, LightSceneInfo, PixelShader, PassParameters, TileType, TEXT("Light::StandardDeferred(Complex)"));
|
|
}
|
|
// Single tiles
|
|
{
|
|
const ESubstrateTileType TileType = ESubstrateTileType::ESingle;
|
|
PermutationVector.Set<FDeferredLightPS::FSubstrateTileType>(TileType);
|
|
TShaderMapRef< FDeferredLightPS > PixelShader(View.ShaderMap, PermutationVector);
|
|
InternalRenderLight(GraphBuilder, Scene, View, LightSceneInfo, PixelShader, PassParameters, TileType, TEXT("Light::StandardDeferred(Single)"));
|
|
}
|
|
// Simple tiles
|
|
{
|
|
const ESubstrateTileType TileType = ESubstrateTileType::ESimple;
|
|
PermutationVector.Set<FDeferredLightPS::FSubstrateTileType>(TileType);
|
|
TShaderMapRef< FDeferredLightPS > PixelShader(View.ShaderMap, PermutationVector);
|
|
InternalRenderLight(GraphBuilder, Scene, View, LightSceneInfo, PixelShader, PassParameters, TileType, TEXT("Light::StandardDeferred(Simple)"));
|
|
}
|
|
}
|
|
else
|
|
{
|
|
PermutationVector.Set< FDeferredLightPS::FSubstrateTileType>(0);
|
|
TShaderMapRef< FDeferredLightPS > PixelShader(View.ShaderMap, PermutationVector);
|
|
InternalRenderLight(GraphBuilder, Scene, View, LightSceneInfo, PixelShader, PassParameters, ESubstrateTileType::ECount, TEXT("Light::StandardDeferred"));
|
|
}
|
|
}
|
|
}
|
|
|
|
/** Shader parameters for Standard Deferred Light for HairStrands pass. */
|
|
BEGIN_SHADER_PARAMETER_STRUCT(FRenderLightForHairParameters, )
|
|
SHADER_PARAMETER_STRUCT_INCLUDE(FDeferredLightHairVS::FParameters, VS)
|
|
SHADER_PARAMETER_STRUCT_INCLUDE(FDeferredLightPS::FParameters, PS)
|
|
END_SHADER_PARAMETER_STRUCT()
|
|
|
|
void SetupLightForHairGraphicsPSOState(FGraphicsPipelineStateInitializer& GraphicsPSOInit)
|
|
{
|
|
GraphicsPSOInit.BlendState = TStaticBlendState<CW_RGBA, BO_Add, BF_One, BF_One, BO_Max, BF_One, BF_One>::GetRHI();
|
|
GraphicsPSOInit.PrimitiveType = PT_TriangleList;
|
|
GraphicsPSOInit.BoundShaderState.VertexDeclarationRHI = GetVertexDeclarationFVector4();
|
|
GraphicsPSOInit.bDepthBounds = false;
|
|
GraphicsPSOInit.RasterizerState = TStaticRasterizerState<FM_Solid, CM_None>::GetRHI();
|
|
GraphicsPSOInit.DepthStencilState = TStaticDepthStencilState<false, CF_Always>::GetRHI();
|
|
}
|
|
|
|
void FDeferredShadingSceneRenderer::RenderLightForHair(
|
|
FRDGBuilder& GraphBuilder,
|
|
FViewInfo& View,
|
|
const FMinimalSceneTextures& SceneTextures,
|
|
const FLightSceneInfo* LightSceneInfo,
|
|
FRDGTextureRef HairShadowMaskTexture,
|
|
FRDGTextureRef LightingChannelsTexture,
|
|
const FHairStrandsTransmittanceMaskData& InTransmittanceMaskData,
|
|
const bool bForwardRendering,
|
|
const bool bCanLightUsesAtlasForUnbatchedLight,
|
|
TRDGUniformBufferRef<FVirtualShadowMapUniformParameters> VirtualShadowMapUniformBuffer,
|
|
FRDGTextureRef ShadowMaskBits,
|
|
int32 VirtualShadowMapId)
|
|
{
|
|
// Ensure the light is valid for this view
|
|
const bool bHairRenderingEnabled = HairStrands::HasViewHairStrandsData(View);
|
|
if (!bHairRenderingEnabled || !LightSceneInfo->ShouldRenderLight(View) || View.HairStrandsViewData.VisibilityData.SampleLightingTexture == nullptr || View.Family->EngineShowFlags.VisualizeLightCulling)
|
|
{
|
|
return;
|
|
}
|
|
|
|
// Sanity check
|
|
check(InTransmittanceMaskData.TransmittanceMask);
|
|
|
|
SCOPE_CYCLE_COUNTER(STAT_DirectLightRenderingTime);
|
|
INC_DWORD_STAT(STAT_NumLightsUsingStandardDeferred);
|
|
RDG_EVENT_SCOPE(GraphBuilder, "StandardDeferredLighting_Hair");
|
|
RDG_GPU_MASK_SCOPE(GraphBuilder, View.GPUMask);
|
|
|
|
const bool bIsDirectional = LightSceneInfo->Proxy->GetLightType() == LightType_Directional;
|
|
const bool bCloudShadow = bIsDirectional;
|
|
const bool bUseVirtualShadowMapMask = VirtualShadowMapId != INDEX_NONE && ShadowMaskBits;
|
|
|
|
FRenderLightForHairParameters* PassParameters = GraphBuilder.AllocParameters<FRenderLightForHairParameters>();
|
|
// VS - General parameters
|
|
PassParameters->VS.HairStrands = HairStrands::BindHairStrandsViewUniformParameters(View);
|
|
// PS - General parameters
|
|
PassParameters->PS = GetDeferredLightPSParameters(
|
|
GraphBuilder,
|
|
Scene,
|
|
View,
|
|
LightSceneInfo,
|
|
SceneTextures.Color.Target,
|
|
SceneTextures.Depth.Target,
|
|
SceneTextures.UniformBuffer,
|
|
HairStrands::BindHairStrandsViewUniformParameters(View),
|
|
HairShadowMaskTexture,
|
|
LightingChannelsTexture,
|
|
bCloudShadow,
|
|
VirtualShadowMapUniformBuffer,
|
|
ShadowMaskBits,
|
|
VirtualShadowMapId);
|
|
|
|
// PS - Hair parameters
|
|
const FIntPoint SampleLightingViewportResolution = View.HairStrandsViewData.VisibilityData.SampleLightingViewportResolution;
|
|
PassParameters->PS.HairTransmittanceBuffer = GraphBuilder.CreateSRV(InTransmittanceMaskData.TransmittanceMask, FHairStrandsTransmittanceMaskData::Format);
|
|
PassParameters->PS.HairTransmittanceBufferMaxCount = InTransmittanceMaskData.TransmittanceMask ? InTransmittanceMaskData.TransmittanceMask->Desc.NumElements : 0;
|
|
PassParameters->PS.ShadowChannelMask = FVector4f(1, 1, 1, 1);
|
|
PassParameters->PS.LightSceneId = LightSceneInfo->Id;
|
|
if (HairShadowMaskTexture)
|
|
{
|
|
PassParameters->PS.ScreenShadowMaskSubPixelTexture = HairShadowMaskTexture;
|
|
PassParameters->PS.HairShadowMaskValid = true;
|
|
}
|
|
if (bForwardRendering)
|
|
{
|
|
PassParameters->PS.ShadowChannelMask = FVector4f(0, 0, 0, 0);
|
|
PassParameters->PS.ShadowChannelMask[FMath::Clamp(LightSceneInfo->GetDynamicShadowMapChannel(), 0, 3)] = 1.f;
|
|
}
|
|
PassParameters->PS.RenderTargets[0] = FRenderTargetBinding(View.HairStrandsViewData.VisibilityData.SampleLightingTexture, ERenderTargetLoadAction::ELoad);
|
|
PassParameters->PS.RenderTargets[1] = FRenderTargetBinding();
|
|
PassParameters->PS.RenderTargets[2] = FRenderTargetBinding();
|
|
PassParameters->PS.RenderTargets.DepthStencil = FDepthStencilBinding(nullptr, ERenderTargetLoadAction::ENoAction, ERenderTargetLoadAction::ENoAction, FExclusiveDepthStencil::DepthNop_StencilNop);
|
|
|
|
FDeferredLightPS::FPermutationDomain PermutationVector;
|
|
PermutationVector.Set< FDeferredLightPS::FLightingChannelsDim >(View.bUsesLightingChannels);
|
|
PermutationVector.Set< FDeferredLightPS::FVisualizeCullingDim >(false);
|
|
PermutationVector.Set< FDeferredLightPS::FTransmissionDim >(false);
|
|
PermutationVector.Set< FDeferredLightPS::FHairLighting>(1);
|
|
PermutationVector.Set< FDeferredLightPS::FHairComplexTransmittance>(true);
|
|
PermutationVector.Set< FDeferredLightPS::FVirtualShadowMapMask >(bUseVirtualShadowMapMask);
|
|
PermutationVector.Set< FDeferredLightPS::FLightFunctionAtlasDim >(
|
|
LightFunctionAtlas::IsEnabled(View, ELightFunctionAtlasSystem::DeferredLighting) && LightSceneInfo->Proxy->HasValidLightFunctionAtlasSlot() &&
|
|
LightSceneInfo->Proxy->GetLightFunctionMaterial() != nullptr && !View.Family->EngineShowFlags.VisualizeLightCulling && bCanLightUsesAtlasForUnbatchedLight);
|
|
if (bIsDirectional)
|
|
{
|
|
PermutationVector.Set< FDeferredLightPS::FSourceShapeDim >(ELightSourceShape::Directional);
|
|
PermutationVector.Set< FDeferredLightPS::FSourceTextureDim >(false);
|
|
PermutationVector.Set< FDeferredLightPS::FIESProfileDim >(false);
|
|
PermutationVector.Set< FDeferredLightPS::FAtmosphereTransmittance >(IsLightAtmospherePerPixelTransmittanceEnabled(Scene, View, LightSceneInfo));
|
|
PermutationVector.Set< FDeferredLightPS::FCloudTransmittance >(PassParameters->PS.CloudShadowEnabled > 0.f);
|
|
}
|
|
else
|
|
{
|
|
const bool bUseIESTexture = View.Family->EngineShowFlags.TexturedLightProfiles && LightSceneInfo->Proxy->GetIESTextureResource() != 0;
|
|
PermutationVector.Set< FDeferredLightPS::FSourceShapeDim >(LightSceneInfo->Proxy->IsRectLight() ? ELightSourceShape::Rect : ELightSourceShape::Capsule);
|
|
PermutationVector.Set< FDeferredLightPS::FSourceTextureDim >(LightSceneInfo->Proxy->IsRectLight() && LightSceneInfo->Proxy->HasSourceTexture());
|
|
PermutationVector.Set< FDeferredLightPS::FIESProfileDim >(bUseIESTexture);
|
|
PermutationVector.Set< FDeferredLightPS::FAtmosphereTransmittance >(false);
|
|
PermutationVector.Set< FDeferredLightPS::FCloudTransmittance >(false);
|
|
}
|
|
|
|
TShaderMapRef<FDeferredLightHairVS> VertexShader(View.ShaderMap);
|
|
TShaderMapRef<FDeferredLightPS> PixelShader(View.ShaderMap, PermutationVector);
|
|
|
|
GraphBuilder.AddPass(
|
|
{},
|
|
PassParameters,
|
|
ERDGPassFlags::Raster,
|
|
[this, VertexShader, PixelShader, PassParameters, SampleLightingViewportResolution](FRDGAsyncTask, FRHICommandList& RHICmdList)
|
|
{
|
|
RHICmdList.SetViewport(0, 0, 0.0f, SampleLightingViewportResolution.X, SampleLightingViewportResolution.Y, 1.0f);
|
|
|
|
FGraphicsPipelineStateInitializer GraphicsPSOInit;
|
|
RHICmdList.ApplyCachedRenderTargets(GraphicsPSOInit);
|
|
|
|
SetupLightForHairGraphicsPSOState(GraphicsPSOInit);
|
|
|
|
GraphicsPSOInit.BoundShaderState.VertexShaderRHI = VertexShader.GetVertexShader();
|
|
GraphicsPSOInit.BoundShaderState.PixelShaderRHI = PixelShader.GetPixelShader();
|
|
|
|
#if PSO_PRECACHING_VALIDATE
|
|
if (PSOCollectorStats::IsFullPrecachingValidationEnabled())
|
|
{
|
|
static const int32 GlobalPSOCollectorIndex = FGlobalPSOCollectorManager::GetIndex(DeferredLightGlobalPSOCollectorName);
|
|
PSOCollectorStats::CheckGlobalGraphicsPipelineStateInCache(GraphicsPSOInit, GlobalPSOCollectorIndex);
|
|
}
|
|
#endif // PSO_PRECACHING_VALIDATE
|
|
|
|
SetGraphicsPipelineState(RHICmdList, GraphicsPSOInit, 0);
|
|
|
|
SetShaderParameters(RHICmdList, VertexShader, VertexShader.GetVertexShader(), PassParameters->VS);
|
|
SetShaderParameters(RHICmdList, PixelShader, PixelShader.GetPixelShader(), PassParameters->PS);
|
|
|
|
RHICmdList.SetStreamSource(0, nullptr, 0);
|
|
RHICmdList.DrawPrimitive(0, 1, 1);
|
|
});
|
|
}
|
|
|
|
// Forward lighting version for hair
|
|
void FDeferredShadingSceneRenderer::RenderLightsForHair(
|
|
FRDGBuilder& GraphBuilder,
|
|
const FMinimalSceneTextures& SceneTextures,
|
|
const FSortedLightSetSceneInfo& SortedLightSet,
|
|
FRDGTextureRef ScreenShadowMaskSubPixelTexture,
|
|
FRDGTextureRef LightingChannelsTexture)
|
|
{
|
|
const TArray<FSortedLightSceneInfo, SceneRenderingAllocator>& SortedLights = SortedLightSet.SortedLights;
|
|
const int32 UnbatchedLightStart = SortedLightSet.UnbatchedLightStart;
|
|
const int32 SimpleLightsEnd = SortedLightSet.SimpleLightsEnd;
|
|
|
|
if (ViewFamily.EngineShowFlags.DirectLighting)
|
|
{
|
|
RDG_EVENT_SCOPE(GraphBuilder, "DirectLighting");
|
|
|
|
for (int32 ViewIndex = 0, ViewCount = Views.Num(); ViewIndex < ViewCount; ++ViewIndex)
|
|
{
|
|
FViewInfo& View = Views[ViewIndex];
|
|
|
|
if (!HairStrands::HasViewHairStrandsData(View))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
FHairStrandsTransmittanceMaskData DummyTransmittanceMaskData = CreateDummyHairStrandsTransmittanceMaskData(GraphBuilder, View.ShaderMap);
|
|
for (int32 LightIndex = UnbatchedLightStart; LightIndex < SortedLights.Num(); LightIndex++)
|
|
{
|
|
const FSortedLightSceneInfo& SortedLightInfo = SortedLights[LightIndex];
|
|
const FLightSceneInfo& LightSceneInfo = *SortedLightInfo.LightSceneInfo;
|
|
if (LightSceneInfo.Proxy)
|
|
{
|
|
const bool bDrawHairShadow = SortedLightInfo.SortKey.Fields.bShadowed;
|
|
FHairStrandsTransmittanceMaskData TransmittanceMaskData = DummyTransmittanceMaskData;
|
|
if (bDrawHairShadow)
|
|
{
|
|
TransmittanceMaskData = RenderHairStrandsTransmittanceMask(GraphBuilder, View, ViewIndex, &LightSceneInfo, true, ScreenShadowMaskSubPixelTexture);
|
|
}
|
|
|
|
RenderLightForHair(
|
|
GraphBuilder,
|
|
View,
|
|
SceneTextures,
|
|
&LightSceneInfo,
|
|
ScreenShadowMaskSubPixelTexture,
|
|
LightingChannelsTexture,
|
|
TransmittanceMaskData,
|
|
true /*bForwardRendering*/,
|
|
SortedLightInfo.bIsCompatibleWithLightFunctionAtlas);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
BEGIN_SHADER_PARAMETER_STRUCT(FSimpleLightsStandardDeferredParameters, )
|
|
SHADER_PARAMETER_STRUCT_INCLUDE(FDeferredLightPS::FParameters, PS)
|
|
SHADER_PARAMETER_STRUCT_INCLUDE(FDeferredLightVS::FParameters, VS)
|
|
END_SHADER_PARAMETER_STRUCT()
|
|
|
|
static FSimpleLightsStandardDeferredParameters GetRenderLightSimpleParameters(
|
|
FRDGBuilder& GraphBuilder,
|
|
const FScene* Scene,
|
|
const FViewInfo& View,
|
|
const FMinimalSceneTextures& SceneTextures,
|
|
const FSimpleLightEntry& SimpleLight,
|
|
const FVector& SimpleLightPosition)
|
|
{
|
|
FSimpleLightsStandardDeferredParameters Out;
|
|
|
|
FRDGTextureRef WhiteDummy = GSystemTextures.GetWhiteDummy(GraphBuilder);
|
|
FRDGBufferRef BufferDummy = GSystemTextures.GetDefaultBuffer(GraphBuilder, 4, 0u);
|
|
FRDGBufferSRVRef BufferDummySRV = GraphBuilder.CreateSRV(BufferDummy, PF_R32_UINT);
|
|
|
|
// PS - General parameters
|
|
Out.PS.SceneTextures = SceneTextures.UniformBuffer;
|
|
Out.PS.HairStrands = View.HairStrandsViewData.UniformBuffer;
|
|
Out.PS.Substrate = Substrate::BindSubstrateGlobalUniformParameters(View);
|
|
Out.PS.LightingChannelsTexture = WhiteDummy;
|
|
Out.PS.LightingChannelsSampler = TStaticSamplerState<SF_Point, AM_Clamp, AM_Clamp, AM_Clamp>::GetRHI();
|
|
Out.PS.CloudShadowAO = GetCloudShadowAOParameters(GraphBuilder, View, nullptr);
|
|
Out.PS.CloudShadowEnabled = 0;
|
|
SetupLightCloudTransmittanceParameters(GraphBuilder, nullptr, View, nullptr, Out.PS.CloudShadow);
|
|
Out.PS.LightAttenuationTexture = WhiteDummy;
|
|
Out.PS.LightAttenuationTextureSampler = TStaticSamplerState<SF_Point, AM_Wrap, AM_Wrap, AM_Wrap>::GetRHI();
|
|
Out.PS.View = View.ViewUniformBuffer;
|
|
Out.PS.DeferredLight = CreateDeferredLightUniformBuffer(GraphBuilder, View, SimpleLight, SimpleLightPosition);
|
|
// PS - Hair (default)
|
|
Out.PS.ScreenShadowMaskSubPixelTexture = WhiteDummy;
|
|
Out.PS.HairTransmittanceBuffer = BufferDummySRV;
|
|
Out.PS.HairTransmittanceBufferMaxCount = 0;
|
|
Out.PS.HairShadowMaskValid = false;
|
|
Out.PS.ShadowChannelMask = FVector4f(1, 1, 1, 1);
|
|
// PS - RT/Depth
|
|
Out.PS.RenderTargets[0] = FRenderTargetBinding(SceneTextures.Color.Target, ERenderTargetLoadAction::ELoad);
|
|
if (SceneTextures.Depth.Target)
|
|
{
|
|
Out.PS.RenderTargets.DepthStencil = FDepthStencilBinding(SceneTextures.Depth.Target, ERenderTargetLoadAction::ELoad, ERenderTargetLoadAction::ELoad, FExclusiveDepthStencil::DepthRead_StencilWrite);
|
|
}
|
|
Out.PS.AVSM = HeterogeneousVolumes::CreateEmptyAdaptiveVolumetricShadowMapUniformBuffer(GraphBuilder);
|
|
|
|
// VS - General parameters (dummy geometry, as the geometry is setup within the pass light loop)
|
|
FSphere SphereLight;
|
|
SphereLight.Center = SimpleLightPosition; // Should we account for LWC Position+Tile here?
|
|
SphereLight.W = SimpleLight.Radius;
|
|
Out.VS = FDeferredLightVS::GetParameters(View, SphereLight, false);
|
|
|
|
return Out;
|
|
}
|
|
|
|
static void InternalRenderSimpleLightsStandardDeferred(
|
|
FRDGBuilder& GraphBuilder,
|
|
const FScene* Scene,
|
|
const FViewInfo& View,
|
|
const uint32 ViewIndex,
|
|
const uint32 NumViews,
|
|
const FMinimalSceneTextures& SceneTextures,
|
|
const FSimpleLightArray& SimpleLights,
|
|
ESubstrateTileType TileType)
|
|
{
|
|
FSimpleLightsStandardDeferredParameters* PassParameters = GraphBuilder.AllocParameters<FSimpleLightsStandardDeferredParameters>();
|
|
*PassParameters = GetRenderLightSimpleParameters(
|
|
GraphBuilder,
|
|
Scene,
|
|
View,
|
|
SceneTextures,
|
|
SimpleLights.InstanceData[0], // Use a dummy light to create the PassParameter buffer. The light data will be
|
|
FVector(0, 0, 0)); // update dynamically with the pass light loop for efficiency purpose
|
|
|
|
const bool bNeedComplexTransmittanceSupport = View.HairCardsMeshElements.Num() && IsHairStrandsSupported(EHairStrandsShaderType::All, View.GetShaderPlatform());
|
|
|
|
FDeferredLightPS::FPermutationDomain PermutationVector;
|
|
PermutationVector.Set< FDeferredLightPS::FSourceShapeDim >(ELightSourceShape::Capsule);
|
|
PermutationVector.Set< FDeferredLightPS::FIESProfileDim >(false);
|
|
PermutationVector.Set< FDeferredLightPS::FLightFunctionAtlasDim>(false);
|
|
PermutationVector.Set< FDeferredLightPS::FVisualizeCullingDim >(View.Family->EngineShowFlags.VisualizeLightCulling);
|
|
PermutationVector.Set< FDeferredLightPS::FLightingChannelsDim >(false);
|
|
PermutationVector.Set< FDeferredLightPS::FAnistropicMaterials >(false);
|
|
PermutationVector.Set< FDeferredLightPS::FTransmissionDim >(false);
|
|
PermutationVector.Set< FDeferredLightPS::FHairLighting>(0);
|
|
PermutationVector.Set< FDeferredLightPS::FHairComplexTransmittance>(bNeedComplexTransmittanceSupport);
|
|
PermutationVector.Set< FDeferredLightPS::FAtmosphereTransmittance >(false);
|
|
PermutationVector.Set< FDeferredLightPS::FCloudTransmittance >(false);
|
|
PermutationVector.Set< FDeferredLightPS::FSubstrateTileType>(TileType != ESubstrateTileType::ECount ? TileType : 0);
|
|
TShaderMapRef<FDeferredLightPS> PixelShader(View.ShaderMap, PermutationVector);
|
|
|
|
FDeferredLightVS::FPermutationDomain PermutationVectorVS;
|
|
PermutationVectorVS.Set<FDeferredLightVS::FRadialLight>(true);
|
|
TShaderMapRef<FDeferredLightVS> VertexShader(View.ShaderMap, PermutationVectorVS);
|
|
|
|
GraphBuilder.AddPass(
|
|
RDG_EVENT_NAME("Light::DeferredSimpleLights(Substrate:%s,Tile:%s)", Substrate::IsSubstrateEnabled() ? TEXT("True") : TEXT("False"), Substrate::IsSubstrateEnabled() ? ToString(TileType) : TEXT("None")),
|
|
PassParameters,
|
|
ERDGPassFlags::Raster,
|
|
[&View, &SimpleLights, ViewIndex, NumViews, PassParameters, PixelShader, VertexShader, TileType](FRDGAsyncTask, FRHICommandList& RHICmdList)
|
|
{
|
|
FGraphicsPipelineStateInitializer GraphicsPSOInit;
|
|
RHICmdList.ApplyCachedRenderTargets(GraphicsPSOInit);
|
|
|
|
// Use additive blending for color
|
|
GraphicsPSOInit.BlendState = TStaticBlendState<CW_RGBA, BO_Add, BF_One, BF_One, BO_Add, BF_One, BF_One>::GetRHI();
|
|
GraphicsPSOInit.PrimitiveType = PT_TriangleList;
|
|
|
|
const int32 GlobalPSOCollectorIndex = FGlobalPSOCollectorManager::GetIndex(DeferredLightGlobalPSOCollectorName);
|
|
|
|
for (int32 LightIndex = 0; LightIndex < SimpleLights.InstanceData.Num(); LightIndex++)
|
|
{
|
|
const FSimpleLightEntry& SimpleLight = SimpleLights.InstanceData[LightIndex];
|
|
|
|
const FSimpleLightPerViewEntry& SimpleLightPerViewData = SimpleLights.GetViewDependentData(LightIndex, ViewIndex, NumViews);
|
|
const FSphere LightBounds(SimpleLightPerViewData.Position, SimpleLight.Radius);
|
|
|
|
|
|
// Set the device viewport for the view.
|
|
RHICmdList.SetViewport(View.ViewRect.Min.X, View.ViewRect.Min.Y, 0.0f, View.ViewRect.Max.X, View.ViewRect.Max.Y, 1.0f);
|
|
|
|
const bool bCameraInsideLightGeometry = ((FVector)View.ViewMatrices.GetViewOrigin() - LightBounds.Center).SizeSquared() < FMath::Square(LightBounds.W * 1.05f + View.NearClippingDistance * 2.0f)
|
|
// Always draw backfaces in ortho
|
|
//@todo - accurate ortho camera / light intersection
|
|
|| !View.IsPerspectiveProjection();
|
|
|
|
const uint32 StencilRef = SetBoundingGeometryRasterizerAndDepthState(GraphicsPSOInit, View.bReverseCulling, bCameraInsideLightGeometry, TileType);
|
|
GraphicsPSOInit.BoundShaderState.VertexDeclarationRHI = GetVertexDeclarationFVector4();
|
|
GraphicsPSOInit.BoundShaderState.VertexShaderRHI = VertexShader.GetVertexShader();
|
|
GraphicsPSOInit.BoundShaderState.PixelShaderRHI = PixelShader.GetPixelShader();
|
|
|
|
#if PSO_PRECACHING_VALIDATE
|
|
if (PSOCollectorStats::IsFullPrecachingValidationEnabled())
|
|
{
|
|
PSOCollectorStats::CheckGlobalGraphicsPipelineStateInCache(GraphicsPSOInit, GlobalPSOCollectorIndex);
|
|
}
|
|
#endif // PSO_PRECACHING_VALIDATE
|
|
|
|
SetGraphicsPipelineState(RHICmdList, GraphicsPSOInit, StencilRef);
|
|
|
|
// Update the light parameters with a custom uniform buffer
|
|
FDeferredLightUniformStruct DeferredLightUniformsValue = GetSimpleDeferredLightParameters(View, SimpleLight, SimpleLightPerViewData);
|
|
|
|
SetShaderParametersMixed(RHICmdList, PixelShader, PixelShader.GetPixelShader(), PassParameters->PS, DeferredLightUniformsValue);
|
|
|
|
// Update vertex shader parameters with custom parameters/uniform buffer
|
|
FDeferredLightVS::FParameters ParametersVS = FDeferredLightVS::GetParameters(View, LightBounds);
|
|
SetShaderParameters(RHICmdList, VertexShader, VertexShader.GetVertexShader(), ParametersVS /*PassParameters->VS*/);
|
|
|
|
// Apply the point or spot light with some approximately bounding geometry,
|
|
// So we can get speedups from depth testing and not processing pixels outside of the light's influence.
|
|
StencilingGeometry::DrawSphere(RHICmdList);
|
|
}
|
|
});
|
|
}
|
|
|
|
void FDeferredShadingSceneRenderer::RenderSimpleLightsStandardDeferred(
|
|
FRDGBuilder& GraphBuilder,
|
|
const FMinimalSceneTextures& SceneTextures,
|
|
const FSimpleLightArray& SimpleLights)
|
|
{
|
|
if (SimpleLights.InstanceData.Num() == 0)
|
|
{
|
|
return;
|
|
}
|
|
|
|
SCOPE_CYCLE_COUNTER(STAT_DirectLightRenderingTime);
|
|
INC_DWORD_STAT_BY(STAT_NumLightsUsingStandardDeferred, SimpleLights.InstanceData.Num());
|
|
|
|
for (int32 ViewIndex = 0, NumViews = Views.Num(); ViewIndex < NumViews; ViewIndex++)
|
|
{
|
|
const FViewInfo& View = Views[ViewIndex];
|
|
|
|
if (Substrate::IsSubstrateEnabled())
|
|
{
|
|
if (Substrate::GetSubstrateUsesComplexSpecialPath(View))
|
|
{
|
|
InternalRenderSimpleLightsStandardDeferred(GraphBuilder, Scene, View, ViewIndex, NumViews, SceneTextures, SimpleLights, ESubstrateTileType::EComplexSpecial);
|
|
}
|
|
InternalRenderSimpleLightsStandardDeferred(GraphBuilder, Scene, View, ViewIndex, NumViews, SceneTextures, SimpleLights, ESubstrateTileType::EComplex);
|
|
InternalRenderSimpleLightsStandardDeferred(GraphBuilder, Scene, View, ViewIndex, NumViews, SceneTextures, SimpleLights, ESubstrateTileType::ESingle);
|
|
InternalRenderSimpleLightsStandardDeferred(GraphBuilder, Scene, View, ViewIndex, NumViews, SceneTextures, SimpleLights, ESubstrateTileType::ESimple);
|
|
}
|
|
else
|
|
{
|
|
InternalRenderSimpleLightsStandardDeferred(GraphBuilder, Scene, View, ViewIndex, NumViews, SceneTextures, SimpleLights, ESubstrateTileType::ECount);
|
|
}
|
|
}
|
|
}
|
|
|
|
class FCopyStencilToLightingChannelsPS : public FGlobalShader
|
|
{
|
|
public:
|
|
DECLARE_GLOBAL_SHADER(FCopyStencilToLightingChannelsPS);
|
|
SHADER_USE_PARAMETER_STRUCT(FCopyStencilToLightingChannelsPS, FGlobalShader);
|
|
|
|
class FNaniteCompositeDim : SHADER_PERMUTATION_BOOL("NANITE_COMPOSITE");
|
|
using FPermutationDomain = TShaderPermutationDomain<FNaniteCompositeDim>;
|
|
|
|
BEGIN_SHADER_PARAMETER_STRUCT(FParameters, )
|
|
SHADER_PARAMETER_STRUCT_INCLUDE(FViewShaderParameters, View)
|
|
SHADER_PARAMETER_RDG_TEXTURE_SRV(Texture2D<float>, SceneStencilTexture)
|
|
SHADER_PARAMETER_RDG_TEXTURE(Texture2D<uint>, NaniteShadingMask)
|
|
RENDER_TARGET_BINDING_SLOTS()
|
|
END_SHADER_PARAMETER_STRUCT()
|
|
|
|
static bool ShouldCompilePermutation(const FGlobalShaderPermutationParameters& Parameters)
|
|
{
|
|
FPermutationDomain PermutationVector(Parameters.PermutationId);
|
|
|
|
if (!DoesPlatformSupportNanite(Parameters.Platform) && PermutationVector.Get<FNaniteCompositeDim>() != 0)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
return IsFeatureLevelSupported(Parameters.Platform, ERHIFeatureLevel::SM5);
|
|
}
|
|
|
|
static void ModifyCompilationEnvironment(const FGlobalShaderPermutationParameters& Parameters, FShaderCompilerEnvironment& OutEnvironment)
|
|
{
|
|
FGlobalShader::ModifyCompilationEnvironment(Parameters, OutEnvironment);
|
|
OutEnvironment.SetDefine(TEXT("STENCIL_LIGHTING_CHANNELS_SHIFT"), STENCIL_LIGHTING_CHANNELS_BIT_ID);
|
|
OutEnvironment.SetRenderTargetOutputFormat(0, PF_R16_UINT);
|
|
}
|
|
};
|
|
|
|
IMPLEMENT_GLOBAL_SHADER(FCopyStencilToLightingChannelsPS, "/Engine/Private/DownsampleDepthPixelShader.usf", "CopyStencilToLightingChannelsPS", SF_Pixel);
|
|
|
|
FRDGTextureRef FDeferredShadingSceneRenderer::CopyStencilToLightingChannelTexture(
|
|
FRDGBuilder& GraphBuilder,
|
|
FRDGTextureSRVRef SceneStencilTexture,
|
|
const TArrayView<FRDGTextureRef> NaniteShadingMasks
|
|
)
|
|
{
|
|
bool bNeedToCopyStencilToTexture = false;
|
|
|
|
for (int32 ViewIndex = 0, ViewCount = Views.Num(); ViewIndex < ViewCount; ++ViewIndex)
|
|
{
|
|
if (Views[ViewIndex].bUsesLightingChannels
|
|
|| (GetViewPipelineState(Views[ViewIndex]).DiffuseIndirectMethod == EDiffuseIndirectMethod::Lumen && Lumen::IsUsingDistanceFieldRepresentationBit(Views[ViewIndex])))
|
|
{
|
|
bNeedToCopyStencilToTexture = true;
|
|
}
|
|
}
|
|
|
|
FRDGTextureRef LightingChannelsTexture = nullptr;
|
|
|
|
if (bNeedToCopyStencilToTexture)
|
|
{
|
|
RDG_EVENT_SCOPE(GraphBuilder, "CopyStencilToLightingChannels");
|
|
|
|
{
|
|
check(SceneStencilTexture && SceneStencilTexture->Desc.Texture);
|
|
const FIntPoint TextureExtent = SceneStencilTexture->Desc.Texture->Desc.Extent;
|
|
const FRDGTextureDesc Desc = FRDGTextureDesc::Create2D(TextureExtent, PF_R8_UINT, FClearValueBinding::None, TexCreate_RenderTargetable | TexCreate_ShaderResource);
|
|
LightingChannelsTexture = GraphBuilder.CreateTexture(Desc, TEXT("LightingChannels"));
|
|
}
|
|
|
|
const ERenderTargetLoadAction LoadAction = ERenderTargetLoadAction::ENoAction;
|
|
|
|
const bool bNaniteComposite = NaniteShadingMasks.Num() == Views.Num();
|
|
|
|
for (int32 ViewIndex = 0, ViewCount = Views.Num(); ViewIndex < ViewCount; ++ViewIndex)
|
|
{
|
|
const FViewInfo& View = Views[ViewIndex];
|
|
RDG_EVENT_SCOPE_CONDITIONAL(GraphBuilder, Views.Num() > 1, "View%d", ViewIndex);
|
|
RDG_GPU_MASK_SCOPE(GraphBuilder, View.GPUMask);
|
|
|
|
auto* PassParameters = GraphBuilder.AllocParameters<FCopyStencilToLightingChannelsPS::FParameters>();
|
|
PassParameters->RenderTargets[0] = FRenderTargetBinding(LightingChannelsTexture, View.DecayLoadAction(LoadAction));
|
|
PassParameters->SceneStencilTexture = SceneStencilTexture;
|
|
PassParameters->NaniteShadingMask = bNaniteComposite ? NaniteShadingMasks[ViewIndex] : nullptr;
|
|
PassParameters->View = View.GetShaderParameters();
|
|
|
|
const FScreenPassTextureViewport Viewport(LightingChannelsTexture, View.ViewRect);
|
|
|
|
FCopyStencilToLightingChannelsPS::FPermutationDomain PermutationVector;
|
|
PermutationVector.Set<FCopyStencilToLightingChannelsPS::FNaniteCompositeDim>(PassParameters->NaniteShadingMask != nullptr);
|
|
TShaderMapRef<FCopyStencilToLightingChannelsPS> PixelShader(View.ShaderMap, PermutationVector);
|
|
|
|
AddDrawScreenPass(GraphBuilder, {}, View, Viewport, Viewport, PixelShader, PassParameters);
|
|
}
|
|
}
|
|
|
|
return LightingChannelsTexture;
|
|
}
|
|
|
|
void DeferredLightGlobalPSOCollector(const FSceneTexturesConfig& SceneTexturesConfig, int32 GlobalPSOCollectorIndex, TArray<FPSOPrecacheData>& PSOInitializers)
|
|
{
|
|
EShaderPlatform ShaderPlatform = SceneTexturesConfig.ShaderPlatform;
|
|
FGlobalShaderMap* GlobalShaderMap = GetGlobalShaderMap(ShaderPlatform);
|
|
|
|
auto AddPSOInitializer = [&](bool bIsHairShader, bool bCameraInsideLightGeometry, bool bReverseCulling, bool bIsDirectional, ESubstrateTileType SubstrateTileMaterialType, FRHIPixelShader* PixelShaderRHI)
|
|
{
|
|
FGraphicsPipelineStateInitializer GraphicsPSOInit;
|
|
|
|
FGraphicsPipelineRenderTargetsInfo RenderTargetsInfo;
|
|
RenderTargetsInfo.NumSamples = 1;
|
|
if (bIsHairShader)
|
|
{
|
|
SetupLightForHairGraphicsPSOState(GraphicsPSOInit);
|
|
|
|
// TODO: find out the render target info for hair first
|
|
return;
|
|
}
|
|
else
|
|
{
|
|
SetupLightGraphicsPSOState(bIsDirectional, bCameraInsideLightGeometry, bReverseCulling, SubstrateTileMaterialType, GraphicsPSOInit, ShaderPlatform);
|
|
|
|
if (bIsDirectional)
|
|
{
|
|
FDeferredLightVS::FPermutationDomain PermutationVectorVS;
|
|
PermutationVectorVS.Set<FDeferredLightVS::FRadialLight>(false);
|
|
TShaderMapRef<FDeferredLightVS> VertexShader(GlobalShaderMap, PermutationVectorVS);
|
|
|
|
Substrate::FSubstrateTilePassVS::FPermutationDomain VSPermutationVector;
|
|
VSPermutationVector.Set< Substrate::FSubstrateTilePassVS::FEnableDebug >(false);
|
|
VSPermutationVector.Set< Substrate::FSubstrateTilePassVS::FEnableTexCoordScreenVector >(true);
|
|
TShaderMapRef<Substrate::FSubstrateTilePassVS> TileVertexShader(GlobalShaderMap, VSPermutationVector);
|
|
|
|
const bool bEnableSubstrateTiledPass = SubstrateTileMaterialType != ESubstrateTileType::ECount;
|
|
GraphicsPSOInit.BoundShaderState.VertexShaderRHI = bEnableSubstrateTiledPass ? TileVertexShader.GetVertexShader() : VertexShader.GetVertexShader();
|
|
GraphicsPSOInit.BoundShaderState.PixelShaderRHI = PixelShaderRHI;
|
|
}
|
|
else // Radial light (LightType_Point, LightType_Spot, LightType_Rect)
|
|
{
|
|
FDeferredLightVS::FPermutationDomain PermutationVectorVS;
|
|
PermutationVectorVS.Set<FDeferredLightVS::FRadialLight>(true);
|
|
TShaderMapRef<FDeferredLightVS> VertexShader(GlobalShaderMap, PermutationVectorVS);
|
|
|
|
GraphicsPSOInit.BoundShaderState.VertexShaderRHI = VertexShader.GetVertexShader();
|
|
GraphicsPSOInit.BoundShaderState.PixelShaderRHI = PixelShaderRHI;
|
|
}
|
|
|
|
AddRenderTargetInfo(SceneTexturesConfig.ColorFormat, SceneTexturesConfig.ColorCreateFlags, RenderTargetsInfo);
|
|
if (Substrate::IsOpaqueRoughRefractionEnabled(ShaderPlatform) && Substrate::UsesSubstrateMaterialBuffer(ShaderPlatform))
|
|
{
|
|
// TODO: find out the render target info for subtrate here
|
|
//Out.RenderTargets[1] = FRenderTargetBinding(Scene->SubstrateSceneData.SeparatedOpaqueRoughRefractionSceneColor, ERenderTargetLoadAction::ELoad);
|
|
//Out.RenderTargets[2] = FRenderTargetBinding(Scene->SubstrateSceneData.SeparatedSubSurfaceSceneColor, ERenderTargetLoadAction::ELoad);
|
|
return;
|
|
}
|
|
ETextureCreateFlags DepthStencilCreateFlags = SceneTexturesConfig.DepthCreateFlags;
|
|
SetupDepthStencilInfo(PF_DepthStencil, DepthStencilCreateFlags, ERenderTargetLoadAction::ELoad,
|
|
ERenderTargetLoadAction::ELoad, FExclusiveDepthStencil::DepthRead_StencilWrite, RenderTargetsInfo);
|
|
}
|
|
|
|
GraphicsPSOInit.StatePrecachePSOHash = RHIComputeStatePrecachePSOHash(GraphicsPSOInit);
|
|
ApplyTargetsInfo(GraphicsPSOInit, RenderTargetsInfo);
|
|
|
|
FPSOPrecacheData PSOPrecacheData;
|
|
PSOPrecacheData.bRequired = true;
|
|
PSOPrecacheData.Type = FPSOPrecacheData::EType::Graphics;
|
|
PSOPrecacheData.GraphicsPSOInitializer = GraphicsPSOInit;
|
|
#if PSO_PRECACHING_VALIDATE
|
|
PSOPrecacheData.PSOCollectorIndex = GlobalPSOCollectorIndex;
|
|
PSOPrecacheData.VertexFactoryType = nullptr;
|
|
#endif // PSO_PRECACHING_VALIDATE
|
|
|
|
PSOInitializers.Add(MoveTemp(PSOPrecacheData));
|
|
};
|
|
|
|
// Create variations for given render & depth stencil compare states - influence raster state culling for non directional only
|
|
const bool bCameraInsideLightGeometry = true;
|
|
const bool bReverseCulling = false;
|
|
|
|
// Precache PSOs are never required
|
|
const bool bRequired = false;
|
|
|
|
EShaderPermutationFlags PermutationFlags = EShaderPermutationFlags::None;
|
|
|
|
FShaderType* ShaderType = FShaderType::GetShaderTypeByName(FDeferredLightPS::GetStaticType().GetName());
|
|
FGlobalShaderType* GlobalShaderType = ShaderType->GetGlobalShaderType();
|
|
for (int32 PermutationId = 0; PermutationId < GlobalShaderType->GetPermutationCount(); PermutationId++)
|
|
{
|
|
if (GlobalShaderType->ShouldCompilePermutation(ShaderPlatform, PermutationId, PermutationFlags) &&
|
|
GlobalShaderType->ShouldPrecachePermutation(ShaderPlatform, PermutationId, PermutationFlags) == EShaderPermutationPrecacheRequest::Precached)
|
|
{
|
|
TShaderRef<FShader> GlobalShader = GlobalShaderMap->GetShader(GlobalShaderType, PermutationId);
|
|
|
|
FRHIPixelShader* RHIPixelShader = static_cast<FRHIPixelShader*>(GlobalShader.GetRHIShaderBase(SF_Pixel, bRequired));
|
|
if (RHIPixelShader)
|
|
{
|
|
FDeferredLightPS::FPermutationDomain PermutationVector(PermutationId);
|
|
|
|
// Extract useful information from the permutation vector
|
|
bool bIsDirectional = (PermutationVector.Get< FDeferredLightPS::FSourceShapeDim >() == ELightSourceShape::Directional);
|
|
ESubstrateTileType SubstrateTileMaterialType = Substrate::IsSubstrateEnabled() ? (ESubstrateTileType) PermutationVector.Get< FDeferredLightPS::FSubstrateTileType >() : ESubstrateTileType::ECount;
|
|
bool bIsHairShader = PermutationVector.Get<FDeferredLightPS::FHairLighting>() > 0;
|
|
|
|
AddPSOInitializer(bIsHairShader, bCameraInsideLightGeometry, bReverseCulling, bIsDirectional, SubstrateTileMaterialType, RHIPixelShader);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
FRegisterGlobalPSOCollectorFunction RegisterDeferredLightGlobalPSOCollector(&DeferredLightGlobalPSOCollector, DeferredLightGlobalPSOCollectorName);
|