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

2567 lines
113 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
/*=============================================================================
ShadowRendering.cpp: Shadow rendering implementation
=============================================================================*/
#include "ShadowRendering.h"
#include "PrimitiveViewRelevance.h"
#include "DepthRendering.h"
#include "SceneRendering.h"
#include "DeferredShadingRenderer.h"
#include "ScenePrivate.h"
#include "PipelineStateCache.h"
#include "ClearQuad.h"
#include "HairStrands/HairStrandsRendering.h"
#include "VirtualShadowMaps/VirtualShadowMapProjection.h"
#include "Shadows/ShadowSceneRenderer.h"
#include "Shadows/ScreenSpaceShadows.h"
#include "Shadows/FirstPersonSelfShadow.h"
#include "RenderCore.h"
#include "TranslucentLighting.h"
#include "MobileBasePassRendering.h"
#include "RHIResourceUtils.h"
using namespace LightFunctionAtlas;
using namespace ShadowRendering;
///////////////////////////////////////////////////////////////////////////////////////////////////
// Directional light
static TAutoConsoleVariable<float> CVarCSMShadowDepthBias(
TEXT("r.Shadow.CSMDepthBias"),
10.0f,
TEXT("Constant depth bias used by CSM"),
ECVF_RenderThreadSafe);
static TAutoConsoleVariable<float> CVarCSMShadowSlopeScaleDepthBias(
TEXT("r.Shadow.CSMSlopeScaleDepthBias"),
3.0f,
TEXT("Slope scale depth bias used by CSM"),
ECVF_RenderThreadSafe);
static TAutoConsoleVariable<float> CVarPerObjectDirectionalShadowDepthBias(
TEXT("r.Shadow.PerObjectDirectionalDepthBias"),
10.0f,
TEXT("Constant depth bias used by per-object shadows from directional lights\n")
TEXT("Lower values give better shadow contact, but increase self-shadowing artifacts"),
ECVF_RenderThreadSafe);
static TAutoConsoleVariable<float> CVarPerObjectDirectionalShadowSlopeScaleDepthBias(
TEXT("r.Shadow.PerObjectDirectionalSlopeDepthBias"),
3.0f,
TEXT("Slope scale depth bias used by per-object shadows from directional lights\n")
TEXT("Lower values give better shadow contact, but increase self-shadowing artifacts"),
ECVF_RenderThreadSafe);
static TAutoConsoleVariable<float> CVarCSMSplitPenumbraScale(
TEXT("r.Shadow.CSMSplitPenumbraScale"),
0.5f,
TEXT("Scale applied to the penumbra size of Cascaded Shadow Map splits, useful for minimizing the transition between splits"),
ECVF_RenderThreadSafe);
static TAutoConsoleVariable<int32> CVarCSMDepthBoundsTest(
TEXT("r.Shadow.CSMDepthBoundsTest"),
1,
TEXT("Whether to use depth bounds tests rather than stencil tests for the CSM bounds"),
ECVF_RenderThreadSafe);
static TAutoConsoleVariable<float> CVarShadowTransitionScale(
TEXT("r.Shadow.TransitionScale"),
60.0f,
TEXT("This controls the 'fade in' region between a caster and where its shadow shows up. Larger values make a smaller region which will have more self shadowing artifacts"),
ECVF_RenderThreadSafe);
static TAutoConsoleVariable<float> CVarCSMShadowReceiverBias(
TEXT("r.Shadow.CSMReceiverBias"),
0.9f,
TEXT("Receiver bias used by CSM. Value between 0 and 1."),
ECVF_RenderThreadSafe);
///////////////////////////////////////////////////////////////////////////////////////////////////
// Point light
static TAutoConsoleVariable<float> CVarPointLightShadowDepthBias(
TEXT("r.Shadow.PointLightDepthBias"),
0.02f,
TEXT("Depth bias that is applied in the depth pass for shadows from point lights. (0.03 avoids peter paning but has some shadow acne)"),
ECVF_RenderThreadSafe);
static TAutoConsoleVariable<float> CVarPointLightShadowSlopeScaleDepthBias(
TEXT("r.Shadow.PointLightSlopeScaleDepthBias"),
3.0f,
TEXT("Slope scale depth bias that is applied in the depth pass for shadows from point lights"),
ECVF_RenderThreadSafe);
///////////////////////////////////////////////////////////////////////////////////////////////////
// Rect light
static TAutoConsoleVariable<float> CVarRectLightShadowDepthBias(
TEXT("r.Shadow.RectLightDepthBias"),
0.025f,
TEXT("Depth bias that is applied in the depth pass for shadows from rect lights. (0.03 avoids peter paning but has some shadow acne)"),
ECVF_RenderThreadSafe);
static TAutoConsoleVariable<float> CVarRectLightShadowSlopeScaleDepthBias(
TEXT("r.Shadow.RectLightSlopeScaleDepthBias"),
2.5f,
TEXT("Slope scale depth bias that is applied in the depth pass for shadows from rect lights"),
ECVF_RenderThreadSafe);
static TAutoConsoleVariable<float> CVarRectLightShadowReceiverBias(
TEXT("r.Shadow.RectLightReceiverBias"),
0.3f,
TEXT("Receiver bias used by rect light. Value between 0 and 1."),
ECVF_RenderThreadSafe);
///////////////////////////////////////////////////////////////////////////////////////////////////
// Spot light
static TAutoConsoleVariable<float> CVarSpotLightShadowDepthBias(
TEXT("r.Shadow.SpotLightDepthBias"),
3.0f,
TEXT("Depth bias that is applied in the depth pass for whole-scene projected shadows from spot lights"),
ECVF_RenderThreadSafe);
static TAutoConsoleVariable<float> CVarSpotLightShadowSlopeScaleDepthBias(
TEXT("r.Shadow.SpotLightSlopeDepthBias"),
3.0f,
TEXT("Slope scale depth bias that is applied in the depth pass for whole-scene projected shadows from spot lights"),
ECVF_RenderThreadSafe);
static TAutoConsoleVariable<float> CVarPerObjectSpotLightShadowDepthBias(
TEXT("r.Shadow.PerObjectSpotLightDepthBias"),
3.0f,
TEXT("Depth bias that is applied in the depth pass for per-object projected shadows from spot lights"),
ECVF_RenderThreadSafe);
static TAutoConsoleVariable<float> CVarPerObjectSpotLightShadowSlopeScaleDepthBias(
TEXT("r.Shadow.PerObjectSpotLightSlopeDepthBias"),
3.0f,
TEXT("Slope scale depth bias that is applied in the depth pass for per-object projected shadows from spot lights"),
ECVF_RenderThreadSafe);
static TAutoConsoleVariable<float> CVarSpotLightShadowTransitionScale(
TEXT("r.Shadow.SpotLightTransitionScale"),
60.0f,
TEXT("Transition scale for spotlights"),
ECVF_RenderThreadSafe);
static TAutoConsoleVariable<float> CVarSpotLightShadowReceiverBias(
TEXT("r.Shadow.SpotLightReceiverBias"),
0.5f,
TEXT("Receiver bias used by spotlights. Value between 0 and 1."),
ECVF_RenderThreadSafe);
///////////////////////////////////////////////////////////////////////////////////////////////////
// General
static TAutoConsoleVariable<int32> CVarEnableModulatedSelfShadow(
TEXT("r.Shadow.EnableModulatedSelfShadow"),
0,
TEXT("Allows modulated shadows to affect the shadow caster. (mobile only)"),
ECVF_RenderThreadSafe);
static int GStencilOptimization = 1;
static FAutoConsoleVariableRef CVarStencilOptimization(
TEXT("r.Shadow.StencilOptimization"),
GStencilOptimization,
TEXT("Removes stencil clears between shadow projections by zeroing the stencil during testing"),
ECVF_RenderThreadSafe
);
static int GShadowStencilCulling = 1;
static FAutoConsoleVariableRef CVarGShadowStencilCulling(
TEXT("r.Shadow.StencilCulling"),
GShadowStencilCulling,
TEXT("Whether to use stencil light culling during shadow projection (default) or only depth."),
ECVF_RenderThreadSafe
);
static TAutoConsoleVariable<int32> CVarFilterMethod(
TEXT("r.Shadow.FilterMethod"),
0,
TEXT("Chooses the shadow filtering method.\n")
TEXT(" 0: Uniform PCF (default)\n")
TEXT(" 1: PCSS (experimental)\n"),
ECVF_RenderThreadSafe);
static TAutoConsoleVariable<int32> CVarMaxSoftKernelSize(
TEXT("r.Shadow.MaxSoftKernelSize"),
40,
TEXT("Mazimum size of the softening kernels in pixels."),
ECVF_RenderThreadSafe);
static TAutoConsoleVariable<float> CVarShadowMaxSlopeScaleDepthBias(
TEXT("r.Shadow.ShadowMaxSlopeScaleDepthBias"),
1.0f,
TEXT("Max Slope depth bias used for shadows for all lights\n")
TEXT("Higher values give better self-shadowing, but increase self-shadowing artifacts"),
ECVF_RenderThreadSafe);
DECLARE_GPU_DRAWCALL_STAT_NAMED(DistanceFieldShadows, TEXT("DistanceField Shadows"));
FForwardScreenSpaceShadowMaskTextureMobileOutputs GScreenSpaceShadowMaskTextureMobileOutputs;
const FVector4f GDummyWholeSceneDirectionalShadowStencilVertexBufferData[] =
{
// Far Plane
FVector4f( 1, 1, 1 /* StencilFar */),
FVector4f(-1, 1, 1),
FVector4f( 1, -1, 1),
FVector4f( 1, -1, 1),
FVector4f(-1, 1, 1),
FVector4f(-1, -1, 1),
// Near Plane
FVector4f(-1, 1, -1 /* StencilNear */),
FVector4f( 1, 1, -1),
FVector4f(-1, -1, -1),
FVector4f(-1, -1, -1),
FVector4f( 1, 1, -1),
FVector4f( 1, -1, -1),
};
/**
* A dummy vertex buffer to bind when rendering whole scene shadows. This
* prevents some D3D debug warnings about zero-element input layouts, but is not
* strictly required. It is, however, required for Metal.
*/
class FDummyWholeSceneDirectionalShadowStencilVertexBuffer : public FVertexBuffer
{
public:
virtual void InitRHI(FRHICommandListBase& RHICmdList) override
{
VertexBufferRHI = UE::RHIResourceUtils::CreateVertexBufferFromArray(RHICmdList, TEXT("FDummyWholeSceneDirectionalShadowStencilVertexBuffer"), EBufferUsageFlags::Static, MakeConstArrayView(GDummyWholeSceneDirectionalShadowStencilVertexBufferData));
}
};
TGlobalResource<FDummyWholeSceneDirectionalShadowStencilVertexBuffer> GDummyWholeSceneDirectionalShadowStencilVertexBuffer;
///////////////////////////////////////////////////////////////////////////////////////////////////
// Hair
static TAutoConsoleVariable<int32> CVarHairStrandsCullPerObjectShadowCaster(
TEXT("r.HairStrands.Shadow.CullPerObjectShadowCaster"),
1,
TEXT("Enable CPU culling of object casting per-object shadow (stationnary object)"),
ECVF_RenderThreadSafe);
DEFINE_GPU_STAT(ShadowProjection);
// 0:off, 1:low, 2:med, 3:high, 4:very high, 5:max
uint32 GetShadowQuality()
{
static const auto ICVarQuality = IConsoleManager::Get().FindTConsoleVariableDataInt(TEXT("r.ShadowQuality"));
int Ret = ICVarQuality->GetValueOnAnyThread();
#if !(UE_BUILD_SHIPPING || UE_BUILD_TEST)
static const auto ICVarLimit = IConsoleManager::Get().FindTConsoleVariableDataInt(TEXT("r.LimitRenderingFeatures"));
if(ICVarLimit)
{
int32 Limit = ICVarLimit->GetValueOnAnyThread();
if(Limit > 2)
{
Ret = 0;
}
}
#endif
return FMath::Clamp(Ret, 0, 5);
}
void GetOnePassPointShadowProjectionParameters(FRDGBuilder& GraphBuilder, const FProjectedShadowInfo* ShadowInfo, FOnePassPointShadowProjection& OutParameters)
{
const FRDGSystemTextures& SystemTextures = FRDGSystemTextures::Get(GraphBuilder);
//@todo DynamicGI: remove duplication with FOnePassPointShadowProjectionShaderParameters
FRDGTexture* ShadowDepthTextureValue = ShadowInfo
? GraphBuilder.RegisterExternalTexture(ShadowInfo->RenderTargets.DepthTarget)
: SystemTextures.BlackDepthCube;
OutParameters.ShadowDepthCubeTexture = ShadowDepthTextureValue;
OutParameters.ShadowDepthCubeTexture2 = ShadowDepthTextureValue;
// Use a comparison sampler to do hardware PCF
OutParameters.ShadowDepthCubeTextureSampler = TStaticSamplerState<SF_Bilinear, AM_Clamp, AM_Clamp, AM_Clamp, 0, 0, 0, SCF_Less>::GetRHI();
if (ShadowInfo)
{
for (int32 i = 0; i < ShadowInfo->OnePassShadowViewProjectionMatrices.Num(); i++)
{
OutParameters.ShadowViewProjectionMatrices[i] = FMatrix44f(ShadowInfo->OnePassShadowViewProjectionMatrices[i]); // LWC_TODO: Precision loss
}
OutParameters.InvShadowmapResolution = 1.0f / ShadowInfo->ResolutionX;
}
else
{
FPlatformMemory::Memzero(&OutParameters.ShadowViewProjectionMatrices[0], sizeof(OutParameters.ShadowViewProjectionMatrices));
OutParameters.InvShadowmapResolution = 0;
}
}
/*-----------------------------------------------------------------------------
FShadowVolumeBoundProjectionVS
-----------------------------------------------------------------------------*/
void FShadowVolumeBoundProjectionVS::SetParameters(
FRHIBatchedShaderParameters& BatchedParameters,
const FSceneView& View,
const FProjectedShadowInfo* ShadowInfo,
EShadowProjectionVertexShaderFlags Flags)
{
if(ShadowInfo->IsWholeScenePointLightShadow())
{
// Handle stenciling sphere for point light.
StencilingGeometryParameters.Set(BatchedParameters, View, ShadowInfo->LightSceneInfo);
}
else
{
StencilingGeometryParameters.Set(BatchedParameters, FVector4f(0,0,0,1));
}
if ((Flags & EShadowProjectionVertexShaderFlags::DrawingFrustum) != EShadowProjectionVertexShaderFlags::None)
{
const FVector PreShadowToPreView(View.ViewMatrices.GetPreViewTranslation() - ShadowInfo->PreShadowTranslation);
SetShaderValue(BatchedParameters, InvReceiverInnerMatrix, ShadowInfo->InvReceiverInnerMatrix);
SetShaderValue(BatchedParameters, PreShadowToPreViewTranslation, FVector4f((FVector3f)PreShadowToPreView, 0));
}
else
{
SetShaderValue(BatchedParameters, InvReceiverInnerMatrix, FMatrix44f::Identity);
SetShaderValue(BatchedParameters, PreShadowToPreViewTranslation, FVector4f(0, 0, 0, 0));
}
}
IMPLEMENT_TYPE_LAYOUT(FShadowProjectionPixelShaderInterface);
IMPLEMENT_SHADER_TYPE(,FShadowProjectionNoTransformVS,TEXT("/Engine/Private/ShadowProjectionVertexShader.usf"),TEXT("ShadowProjectionNoTransformVS"),SF_Vertex);
IMPLEMENT_SHADER_TYPE(,FShadowVolumeBoundProjectionVS,TEXT("/Engine/Private/ShadowProjectionVertexShader.usf"),TEXT("ShadowVolumeBoundProjectionVS"),SF_Vertex);
template<uint32 T>
TModulatedShadowProjection<T>::TModulatedShadowProjection(const ShaderMetaType::CompiledShaderInitializerType& Initializer) :
TShadowProjectionPS<T, false, true>(Initializer)
{
ModulatedShadowColorParameter.Bind(Initializer.ParameterMap, TEXT("ModulatedShadowColor"));
MobileBasePassUniformBuffer.Bind(Initializer.ParameterMap, FMobileBasePassUniformParameters::FTypeInfo::GetStructMetadata()->GetShaderVariableName());
}
/**
* Implementations for TShadowProjectionPS.
*/
#if !UE_BUILD_DOCS
#define IMPLEMENT_SHADOW_PROJECTION_PIXEL_SHADER(Quality,UseFadePlane,UseTransmission, SupportSubPixel) \
typedef TShadowProjectionPS<Quality, UseFadePlane, false, UseTransmission, SupportSubPixel> FShadowProjectionPS##Quality##UseFadePlane##UseTransmission##SupportSubPixel; \
IMPLEMENT_SHADER_TYPE(template<>,FShadowProjectionPS##Quality##UseFadePlane##UseTransmission##SupportSubPixel,TEXT("/Engine/Private/ShadowProjectionPixelShader.usf"),TEXT("Main"),SF_Pixel);
// Projection shaders without the distance fade, with different quality levels.
IMPLEMENT_SHADOW_PROJECTION_PIXEL_SHADER(1,false,false,false);
IMPLEMENT_SHADOW_PROJECTION_PIXEL_SHADER(2,false,false,false);
IMPLEMENT_SHADOW_PROJECTION_PIXEL_SHADER(3,false,false,false);
IMPLEMENT_SHADOW_PROJECTION_PIXEL_SHADER(4,false,false,false);
IMPLEMENT_SHADOW_PROJECTION_PIXEL_SHADER(5,false,false,false);
IMPLEMENT_SHADOW_PROJECTION_PIXEL_SHADER(1,false,true,false);
IMPLEMENT_SHADOW_PROJECTION_PIXEL_SHADER(2,false,true,false);
IMPLEMENT_SHADOW_PROJECTION_PIXEL_SHADER(3,false,true,false);
IMPLEMENT_SHADOW_PROJECTION_PIXEL_SHADER(4,false,true,false);
IMPLEMENT_SHADOW_PROJECTION_PIXEL_SHADER(5,false,true,false);
// Projection shaders with the distance fade, with different quality levels.
IMPLEMENT_SHADOW_PROJECTION_PIXEL_SHADER(1,true,false,false);
IMPLEMENT_SHADOW_PROJECTION_PIXEL_SHADER(2,true,false,false);
IMPLEMENT_SHADOW_PROJECTION_PIXEL_SHADER(3,true,false,false);
IMPLEMENT_SHADOW_PROJECTION_PIXEL_SHADER(4,true,false,false);
IMPLEMENT_SHADOW_PROJECTION_PIXEL_SHADER(5,true,false,false);
IMPLEMENT_SHADOW_PROJECTION_PIXEL_SHADER(1,true,true,false);
IMPLEMENT_SHADOW_PROJECTION_PIXEL_SHADER(2,true,true,false);
IMPLEMENT_SHADOW_PROJECTION_PIXEL_SHADER(3,true,true,false);
IMPLEMENT_SHADOW_PROJECTION_PIXEL_SHADER(4,true,true,false);
IMPLEMENT_SHADOW_PROJECTION_PIXEL_SHADER(5,true,true,false);
// Projection shaders without the distance fade, without transmission, with Sub-PixelSupport with different quality levels
IMPLEMENT_SHADOW_PROJECTION_PIXEL_SHADER(1, false, false, true);
IMPLEMENT_SHADOW_PROJECTION_PIXEL_SHADER(2, false, false, true);
IMPLEMENT_SHADOW_PROJECTION_PIXEL_SHADER(3, false, false, true);
IMPLEMENT_SHADOW_PROJECTION_PIXEL_SHADER(4, false, false, true);
IMPLEMENT_SHADOW_PROJECTION_PIXEL_SHADER(5, false, false, true);
#undef IMPLEMENT_SHADOW_PROJECTION_PIXEL_SHADER
#define IMPLEMENT_MODULATED_SHADOW_PROJECTION_PIXEL_SHADER(Quality) \
template class TModulatedShadowProjection<Quality>; \
using FShadowModulatedProjectionPS##Quality = TShadowProjectionPS<Quality, false, true>; \
IMPLEMENT_TEMPLATE_TYPE_LAYOUT(template<>, FShadowModulatedProjectionPS##Quality); \
IMPLEMENT_SHADER_TYPE(template<>, TModulatedShadowProjection<Quality>, TEXT("/Engine/Private/ShadowProjectionPixelShader.usf"), TEXT("Main"), SF_Pixel);
// Implement a pixel shader for rendering modulated shadow projections.
IMPLEMENT_MODULATED_SHADOW_PROJECTION_PIXEL_SHADER(1);
IMPLEMENT_MODULATED_SHADOW_PROJECTION_PIXEL_SHADER(2);
IMPLEMENT_MODULATED_SHADOW_PROJECTION_PIXEL_SHADER(3);
IMPLEMENT_MODULATED_SHADOW_PROJECTION_PIXEL_SHADER(4);
IMPLEMENT_MODULATED_SHADOW_PROJECTION_PIXEL_SHADER(5);
#undef IMPLEMENT_MODULATED_SHADOW_PROJECTION_PIXEL_SHADER
#endif
// with different quality levels
IMPLEMENT_SHADER_TYPE(template<>,TShadowProjectionFromTranslucencyPS<1>,TEXT("/Engine/Private/ShadowProjectionPixelShader.usf"),TEXT("Main"),SF_Pixel);
IMPLEMENT_SHADER_TYPE(template<>,TShadowProjectionFromTranslucencyPS<2>,TEXT("/Engine/Private/ShadowProjectionPixelShader.usf"),TEXT("Main"),SF_Pixel);
IMPLEMENT_SHADER_TYPE(template<>,TShadowProjectionFromTranslucencyPS<3>,TEXT("/Engine/Private/ShadowProjectionPixelShader.usf"),TEXT("Main"),SF_Pixel);
IMPLEMENT_SHADER_TYPE(template<>,TShadowProjectionFromTranslucencyPS<4>,TEXT("/Engine/Private/ShadowProjectionPixelShader.usf"),TEXT("Main"),SF_Pixel);
IMPLEMENT_SHADER_TYPE(template<>,TShadowProjectionFromTranslucencyPS<5>,TEXT("/Engine/Private/ShadowProjectionPixelShader.usf"),TEXT("Main"),SF_Pixel);
// Implement a pixel shader for rendering one pass point light shadows with different quality levels
#define IMPLEMENT_ONEPASS_POINT_SHADOW_PROJECTION_PIXEL_SHADER(Quality,UseTransmission,UseSubPixel) \
typedef TOnePassPointShadowProjectionPS<Quality, UseTransmission, UseSubPixel> FOnePassPointShadowProjectionPS##Quality##UseTransmission##UseSubPixel; \
IMPLEMENT_SHADER_TYPE(template<>,FOnePassPointShadowProjectionPS##Quality##UseTransmission##UseSubPixel,TEXT("/Engine/Private/ShadowProjectionPixelShader.usf"),TEXT("MainOnePassPointLightPS"),SF_Pixel);
IMPLEMENT_ONEPASS_POINT_SHADOW_PROJECTION_PIXEL_SHADER(1, false, true);
IMPLEMENT_ONEPASS_POINT_SHADOW_PROJECTION_PIXEL_SHADER(2, false, true);
IMPLEMENT_ONEPASS_POINT_SHADOW_PROJECTION_PIXEL_SHADER(3, false, true);
IMPLEMENT_ONEPASS_POINT_SHADOW_PROJECTION_PIXEL_SHADER(4, false, true);
IMPLEMENT_ONEPASS_POINT_SHADOW_PROJECTION_PIXEL_SHADER(5, false, true);
IMPLEMENT_ONEPASS_POINT_SHADOW_PROJECTION_PIXEL_SHADER(1, false, false);
IMPLEMENT_ONEPASS_POINT_SHADOW_PROJECTION_PIXEL_SHADER(2, false, false);
IMPLEMENT_ONEPASS_POINT_SHADOW_PROJECTION_PIXEL_SHADER(3, false, false);
IMPLEMENT_ONEPASS_POINT_SHADOW_PROJECTION_PIXEL_SHADER(4, false, false);
IMPLEMENT_ONEPASS_POINT_SHADOW_PROJECTION_PIXEL_SHADER(5, false, false);
IMPLEMENT_ONEPASS_POINT_SHADOW_PROJECTION_PIXEL_SHADER(1, true, false);
IMPLEMENT_ONEPASS_POINT_SHADOW_PROJECTION_PIXEL_SHADER(2, true, false);
IMPLEMENT_ONEPASS_POINT_SHADOW_PROJECTION_PIXEL_SHADER(3, true, false);
IMPLEMENT_ONEPASS_POINT_SHADOW_PROJECTION_PIXEL_SHADER(4, true, false);
IMPLEMENT_ONEPASS_POINT_SHADOW_PROJECTION_PIXEL_SHADER(5, true, false);
// Implements a pixel shader for directional light PCSS.
#define IMPLEMENT_SHADOW_PROJECTION_PIXEL_SHADER(Quality,UseFadePlane) \
typedef TDirectionalPercentageCloserShadowProjectionPS<Quality, UseFadePlane> TDirectionalPercentageCloserShadowProjectionPS##Quality##UseFadePlane; \
IMPLEMENT_SHADER_TYPE(template<>,TDirectionalPercentageCloserShadowProjectionPS##Quality##UseFadePlane,TEXT("/Engine/Private/ShadowProjectionPixelShader.usf"),TEXT("Main"),SF_Pixel);
IMPLEMENT_SHADOW_PROJECTION_PIXEL_SHADER(5,false);
IMPLEMENT_SHADOW_PROJECTION_PIXEL_SHADER(5,true);
#undef IMPLEMENT_SHADOW_PROJECTION_PIXEL_SHADER
// Implements a pixel shader for spot light PCSS.
#define IMPLEMENT_SHADOW_PROJECTION_PIXEL_SHADER(Quality,UseFadePlane) \
typedef TSpotPercentageCloserShadowProjectionPS<Quality, UseFadePlane> TSpotPercentageCloserShadowProjectionPS##Quality##UseFadePlane; \
IMPLEMENT_SHADER_TYPE(template<>,TSpotPercentageCloserShadowProjectionPS##Quality##UseFadePlane,TEXT("/Engine/Private/ShadowProjectionPixelShader.usf"),TEXT("Main"),SF_Pixel);
IMPLEMENT_SHADOW_PROJECTION_PIXEL_SHADER(5, false);
IMPLEMENT_SHADOW_PROJECTION_PIXEL_SHADER(5, true);
#undef IMPLEMENT_SHADOW_PROJECTION_PIXEL_SHADER
template<typename VertexShaderType, typename PixelShaderType>
static void BindShaderShaders(FRHICommandList& RHICmdList, FGraphicsPipelineStateInitializer& GraphicsPSOInit,
int32 ViewIndex, const FViewInfo& View, const FProjectedShadowInfo* ShadowInfo, uint32 StencilRef, FRHIUniformBuffer* HairStrandsUniformBuffer = nullptr)
{
TShaderRef<VertexShaderType> VertexShader = View.ShaderMap->GetShader<VertexShaderType>();
TShaderRef<PixelShaderType> PixelShader = View.ShaderMap->GetShader<PixelShaderType>();
GraphicsPSOInit.BoundShaderState.VertexShaderRHI = VertexShader.GetVertexShader();
GraphicsPSOInit.BoundShaderState.PixelShaderRHI = PixelShader.GetPixelShader();
SetGraphicsPipelineState(RHICmdList, GraphicsPSOInit, StencilRef);
SetShaderParametersLegacyVS(RHICmdList, VertexShader, View, ShadowInfo, EShadowProjectionVertexShaderFlags::DrawingFrustum);
FRHIBatchedShaderParameters& BatchedParameters = RHICmdList.GetScratchShaderParameters();
const bool bUseLightFunctionAtlas = LightFunctionAtlas::IsEnabled(View, ELightFunctionAtlasSystem::DeferredLighting);
PixelShader->SetParameters(BatchedParameters, ViewIndex, View, ShadowInfo, bUseLightFunctionAtlas);
if (Substrate::IsSubstrateEnabled())
{
TRDGUniformBufferRef<FSubstrateGlobalUniformParameters> SubstrateUniformBuffer = Substrate::BindSubstrateGlobalUniformParameters(View);
PixelShader->FGlobalShader::template SetParameters<FSubstrateGlobalUniformParameters>(BatchedParameters, SubstrateUniformBuffer->GetRHIRef());
}
if (HairStrandsUniformBuffer)
{
PixelShader->FGlobalShader::template SetParameters<FHairStrandsViewUniformParameters>(BatchedParameters, HairStrandsUniformBuffer);
}
RHICmdList.SetBatchedShaderParameters(PixelShader.GetPixelShader(), BatchedParameters);
}
static void BindShadowProjectionShaders(int32 Quality, FRHICommandList& RHICmdList, FGraphicsPipelineStateInitializer GraphicsPSOInit, int32 ViewIndex, const FViewInfo& View,
const FProjectedShadowInfo* ShadowInfo, uint32 StencilRef, bool bMobileModulatedProjections, FRHIUniformBuffer* HairStrandsUniformBuffer)
{
const bool bSubPixelShadow = HairStrandsUniformBuffer != nullptr;
if (bSubPixelShadow)
{
check(!bMobileModulatedProjections);
if (ShadowInfo->IsWholeSceneDirectionalShadow())
{
switch (Quality)
{
case 1: BindShaderShaders<FShadowProjectionNoTransformVS, TShadowProjectionPS<1, false, false, false, true> >(RHICmdList, GraphicsPSOInit, ViewIndex, View, ShadowInfo, StencilRef, HairStrandsUniformBuffer); break;
case 2: BindShaderShaders<FShadowProjectionNoTransformVS, TShadowProjectionPS<2, false, false, false, true> >(RHICmdList, GraphicsPSOInit, ViewIndex, View, ShadowInfo, StencilRef, HairStrandsUniformBuffer); break;
case 3: BindShaderShaders<FShadowProjectionNoTransformVS, TShadowProjectionPS<3, false, false, false, true> >(RHICmdList, GraphicsPSOInit, ViewIndex, View, ShadowInfo, StencilRef, HairStrandsUniformBuffer); break;
case 4: BindShaderShaders<FShadowProjectionNoTransformVS, TShadowProjectionPS<4, false, false, false, true> >(RHICmdList, GraphicsPSOInit, ViewIndex, View, ShadowInfo, StencilRef, HairStrandsUniformBuffer); break;
case 5: BindShaderShaders<FShadowProjectionNoTransformVS, TShadowProjectionPS<5, false, false, false, true> >(RHICmdList, GraphicsPSOInit, ViewIndex, View, ShadowInfo, StencilRef, HairStrandsUniformBuffer); break;
default:
check(0);
}
}
else
{
switch (Quality)
{
case 1: BindShaderShaders<FShadowVolumeBoundProjectionVS, TShadowProjectionPS<1, false, false, false, true> >(RHICmdList, GraphicsPSOInit, ViewIndex, View, ShadowInfo, StencilRef, HairStrandsUniformBuffer); break;
case 2: BindShaderShaders<FShadowVolumeBoundProjectionVS, TShadowProjectionPS<2, false, false, false, true> >(RHICmdList, GraphicsPSOInit, ViewIndex, View, ShadowInfo, StencilRef, HairStrandsUniformBuffer); break;
case 3: BindShaderShaders<FShadowVolumeBoundProjectionVS, TShadowProjectionPS<3, false, false, false, true> >(RHICmdList, GraphicsPSOInit, ViewIndex, View, ShadowInfo, StencilRef, HairStrandsUniformBuffer); break;
case 4: BindShaderShaders<FShadowVolumeBoundProjectionVS, TShadowProjectionPS<4, false, false, false, true> >(RHICmdList, GraphicsPSOInit, ViewIndex, View, ShadowInfo, StencilRef, HairStrandsUniformBuffer); break;
case 5: BindShaderShaders<FShadowVolumeBoundProjectionVS, TShadowProjectionPS<5, false, false, false, true> >(RHICmdList, GraphicsPSOInit, ViewIndex, View, ShadowInfo, StencilRef, HairStrandsUniformBuffer); break;
default:
check(0);
}
}
return;
}
if (ShadowInfo->bTranslucentShadow)
{
switch (Quality)
{
case 1: BindShaderShaders<FShadowVolumeBoundProjectionVS, TShadowProjectionFromTranslucencyPS<1> >(RHICmdList, GraphicsPSOInit, ViewIndex, View, ShadowInfo, StencilRef); break;
case 2: BindShaderShaders<FShadowVolumeBoundProjectionVS, TShadowProjectionFromTranslucencyPS<2> >(RHICmdList, GraphicsPSOInit, ViewIndex, View, ShadowInfo, StencilRef); break;
case 3: BindShaderShaders<FShadowVolumeBoundProjectionVS, TShadowProjectionFromTranslucencyPS<3> >(RHICmdList, GraphicsPSOInit, ViewIndex, View, ShadowInfo, StencilRef); break;
case 4: BindShaderShaders<FShadowVolumeBoundProjectionVS, TShadowProjectionFromTranslucencyPS<4> >(RHICmdList, GraphicsPSOInit, ViewIndex, View, ShadowInfo, StencilRef); break;
case 5: BindShaderShaders<FShadowVolumeBoundProjectionVS, TShadowProjectionFromTranslucencyPS<5> >(RHICmdList, GraphicsPSOInit, ViewIndex, View, ShadowInfo, StencilRef); break;
default:
check(0);
}
}
else if (ShadowInfo->IsWholeSceneDirectionalShadow())
{
if (CVarFilterMethod.GetValueOnRenderThread() == 1)
{
if (ShadowInfo->CascadeSettings.FadePlaneLength > 0)
BindShaderShaders<FShadowProjectionNoTransformVS, TDirectionalPercentageCloserShadowProjectionPS<5, true> >(RHICmdList, GraphicsPSOInit, ViewIndex, View, ShadowInfo, StencilRef);
else
BindShaderShaders<FShadowProjectionNoTransformVS, TDirectionalPercentageCloserShadowProjectionPS<5, false> >(RHICmdList, GraphicsPSOInit, ViewIndex, View, ShadowInfo, StencilRef);
}
else if (ShadowInfo->CascadeSettings.FadePlaneLength > 0)
{
if (ShadowInfo->bTransmission)
{
switch (Quality)
{
case 1: BindShaderShaders<FShadowProjectionNoTransformVS, TShadowProjectionPS<1, true, false, true> >(RHICmdList, GraphicsPSOInit, ViewIndex, View, ShadowInfo, StencilRef); break;
case 2: BindShaderShaders<FShadowProjectionNoTransformVS, TShadowProjectionPS<2, true, false, true> >(RHICmdList, GraphicsPSOInit, ViewIndex, View, ShadowInfo, StencilRef); break;
case 3: BindShaderShaders<FShadowProjectionNoTransformVS, TShadowProjectionPS<3, true, false, true> >(RHICmdList, GraphicsPSOInit, ViewIndex, View, ShadowInfo, StencilRef); break;
case 4: BindShaderShaders<FShadowProjectionNoTransformVS, TShadowProjectionPS<4, true, false, true> >(RHICmdList, GraphicsPSOInit, ViewIndex, View, ShadowInfo, StencilRef); break;
case 5: BindShaderShaders<FShadowProjectionNoTransformVS, TShadowProjectionPS<5, true, false, true> >(RHICmdList, GraphicsPSOInit, ViewIndex, View, ShadowInfo, StencilRef); break;
default:
check(0);
}
}
else
{
switch (Quality)
{
case 1: BindShaderShaders<FShadowProjectionNoTransformVS, TShadowProjectionPS<1, true> >(RHICmdList, GraphicsPSOInit, ViewIndex, View, ShadowInfo, StencilRef); break;
case 2: BindShaderShaders<FShadowProjectionNoTransformVS, TShadowProjectionPS<2, true> >(RHICmdList, GraphicsPSOInit, ViewIndex, View, ShadowInfo, StencilRef); break;
case 3: BindShaderShaders<FShadowProjectionNoTransformVS, TShadowProjectionPS<3, true> >(RHICmdList, GraphicsPSOInit, ViewIndex, View, ShadowInfo, StencilRef); break;
case 4: BindShaderShaders<FShadowProjectionNoTransformVS, TShadowProjectionPS<4, true> >(RHICmdList, GraphicsPSOInit, ViewIndex, View, ShadowInfo, StencilRef); break;
case 5: BindShaderShaders<FShadowProjectionNoTransformVS, TShadowProjectionPS<5, true> >(RHICmdList, GraphicsPSOInit, ViewIndex, View, ShadowInfo, StencilRef); break;
default:
check(0);
}
}
}
else
{
if (ShadowInfo->bTransmission)
{
switch (Quality)
{
case 1: BindShaderShaders<FShadowProjectionNoTransformVS, TShadowProjectionPS<1, false, false, true> >(RHICmdList, GraphicsPSOInit, ViewIndex, View, ShadowInfo, StencilRef); break;
case 2: BindShaderShaders<FShadowProjectionNoTransformVS, TShadowProjectionPS<2, false, false, true> >(RHICmdList, GraphicsPSOInit, ViewIndex, View, ShadowInfo, StencilRef); break;
case 3: BindShaderShaders<FShadowProjectionNoTransformVS, TShadowProjectionPS<3, false, false, true> >(RHICmdList, GraphicsPSOInit, ViewIndex, View, ShadowInfo, StencilRef); break;
case 4: BindShaderShaders<FShadowProjectionNoTransformVS, TShadowProjectionPS<4, false, false, true> >(RHICmdList, GraphicsPSOInit, ViewIndex, View, ShadowInfo, StencilRef); break;
case 5: BindShaderShaders<FShadowProjectionNoTransformVS, TShadowProjectionPS<5, false, false, true> >(RHICmdList, GraphicsPSOInit, ViewIndex, View, ShadowInfo, StencilRef); break;
default:
check(0);
}
}
else
{
switch (Quality)
{
case 1: BindShaderShaders<FShadowProjectionNoTransformVS, TShadowProjectionPS<1, false> >(RHICmdList, GraphicsPSOInit, ViewIndex, View, ShadowInfo, StencilRef); break;
case 2: BindShaderShaders<FShadowProjectionNoTransformVS, TShadowProjectionPS<2, false> >(RHICmdList, GraphicsPSOInit, ViewIndex, View, ShadowInfo, StencilRef); break;
case 3: BindShaderShaders<FShadowProjectionNoTransformVS, TShadowProjectionPS<3, false> >(RHICmdList, GraphicsPSOInit, ViewIndex, View, ShadowInfo, StencilRef); break;
case 4: BindShaderShaders<FShadowProjectionNoTransformVS, TShadowProjectionPS<4, false> >(RHICmdList, GraphicsPSOInit, ViewIndex, View, ShadowInfo, StencilRef); break;
case 5: BindShaderShaders<FShadowProjectionNoTransformVS, TShadowProjectionPS<5, false> >(RHICmdList, GraphicsPSOInit, ViewIndex, View, ShadowInfo, StencilRef); break;
default:
check(0);
}
}
}
}
else
{
if(bMobileModulatedProjections)
{
switch (Quality)
{
case 1: BindShaderShaders<FShadowVolumeBoundProjectionVS, TModulatedShadowProjection<1> >(RHICmdList, GraphicsPSOInit, ViewIndex, View, ShadowInfo, StencilRef); break;
case 2: BindShaderShaders<FShadowVolumeBoundProjectionVS, TModulatedShadowProjection<2> >(RHICmdList, GraphicsPSOInit, ViewIndex, View, ShadowInfo, StencilRef); break;
case 3: BindShaderShaders<FShadowVolumeBoundProjectionVS, TModulatedShadowProjection<3> >(RHICmdList, GraphicsPSOInit, ViewIndex, View, ShadowInfo, StencilRef); break;
case 4: BindShaderShaders<FShadowVolumeBoundProjectionVS, TModulatedShadowProjection<4> >(RHICmdList, GraphicsPSOInit, ViewIndex, View, ShadowInfo, StencilRef); break;
case 5: BindShaderShaders<FShadowVolumeBoundProjectionVS, TModulatedShadowProjection<5> >(RHICmdList, GraphicsPSOInit, ViewIndex, View, ShadowInfo, StencilRef); break;
default:
check(0);
}
}
else if (ShadowInfo->bTransmission)
{
switch (Quality)
{
case 1: BindShaderShaders<FShadowVolumeBoundProjectionVS, TShadowProjectionPS<1, false, false, true> >(RHICmdList, GraphicsPSOInit, ViewIndex, View, ShadowInfo, StencilRef); break;
case 2: BindShaderShaders<FShadowVolumeBoundProjectionVS, TShadowProjectionPS<2, false, false, true> >(RHICmdList, GraphicsPSOInit, ViewIndex, View, ShadowInfo, StencilRef); break;
case 3: BindShaderShaders<FShadowVolumeBoundProjectionVS, TShadowProjectionPS<3, false, false, true> >(RHICmdList, GraphicsPSOInit, ViewIndex, View, ShadowInfo, StencilRef); break;
case 4: BindShaderShaders<FShadowVolumeBoundProjectionVS, TShadowProjectionPS<4, false, false, true> >(RHICmdList, GraphicsPSOInit, ViewIndex, View, ShadowInfo, StencilRef); break;
case 5: BindShaderShaders<FShadowVolumeBoundProjectionVS, TShadowProjectionPS<5, false, false, true> >(RHICmdList, GraphicsPSOInit, ViewIndex, View, ShadowInfo, StencilRef); break;
default:
check(0);
}
}
else
{
if (CVarFilterMethod.GetValueOnRenderThread() == 1 && ShadowInfo->GetLightSceneInfo().Proxy->GetLightType() == LightType_Spot)
{
BindShaderShaders<FShadowVolumeBoundProjectionVS, TSpotPercentageCloserShadowProjectionPS<5, false> >(RHICmdList, GraphicsPSOInit, ViewIndex, View, ShadowInfo, StencilRef);
}
else
{
switch (Quality)
{
case 1: BindShaderShaders<FShadowVolumeBoundProjectionVS, TShadowProjectionPS<1, false> >(RHICmdList, GraphicsPSOInit, ViewIndex, View, ShadowInfo, StencilRef); break;
case 2: BindShaderShaders<FShadowVolumeBoundProjectionVS, TShadowProjectionPS<2, false> >(RHICmdList, GraphicsPSOInit, ViewIndex, View, ShadowInfo, StencilRef); break;
case 3: BindShaderShaders<FShadowVolumeBoundProjectionVS, TShadowProjectionPS<3, false> >(RHICmdList, GraphicsPSOInit, ViewIndex, View, ShadowInfo, StencilRef); break;
case 4: BindShaderShaders<FShadowVolumeBoundProjectionVS, TShadowProjectionPS<4, false> >(RHICmdList, GraphicsPSOInit, ViewIndex, View, ShadowInfo, StencilRef); break;
case 5: BindShaderShaders<FShadowVolumeBoundProjectionVS, TShadowProjectionPS<5, false> >(RHICmdList, GraphicsPSOInit, ViewIndex, View, ShadowInfo, StencilRef); break;
default:
check(0);
}
}
}
}
check(GraphicsPSOInit.BoundShaderState.VertexShaderRHI);
check(GraphicsPSOInit.BoundShaderState.PixelShaderRHI);
}
bool FProjectedShadowInfo::HasShadowStencilCulling(FStaticShaderPlatform ShaderPlatform)
{
if (IsMobilePlatform(ShaderPlatform) && IsMobileDeferredShadingEnabled(ShaderPlatform))
{
// ShadowStencilMask now clashes with a mobile deferred ShadingModels mask
return false;
}
return GShadowStencilCulling != 0;
}
FRHIBlendState* FProjectedShadowInfo::GetBlendStateForProjection(
int32 ShadowMapChannel,
bool bIsWholeSceneDirectionalShadow,
bool bUseFadePlane,
bool bProjectingForForwardShading,
bool bMobileModulatedProjections)
{
// With forward shading we are packing shadowing for all 4 possible stationary lights affecting each pixel into channels of the same texture, based on assigned shadowmap channels.
// With deferred shading we have 4 channels for each light.
// * CSM and per-object shadows are kept in separate channels to allow fading CSM out to precomputed shadowing while keeping per-object shadows past the fade distance.
// * Subsurface shadowing requires an extra channel for each
FRHIBlendState* BlendState = nullptr;
if (bProjectingForForwardShading)
{
if (bUseFadePlane)
{
if (ShadowMapChannel == 0)
{
// alpha is used to fade between cascades
BlendState = TStaticBlendState<CW_RED, BO_Add, BF_SourceAlpha, BF_InverseSourceAlpha>::GetRHI();
}
else if (ShadowMapChannel == 1)
{
BlendState = TStaticBlendState<CW_GREEN, BO_Add, BF_SourceAlpha, BF_InverseSourceAlpha>::GetRHI();
}
else if (ShadowMapChannel == 2)
{
BlendState = TStaticBlendState<CW_BLUE, BO_Add, BF_SourceAlpha, BF_InverseSourceAlpha>::GetRHI();
}
else if (ShadowMapChannel == 3)
{
BlendState = TStaticBlendState<CW_ALPHA, BO_Add, BF_SourceAlpha, BF_InverseSourceAlpha>::GetRHI();
}
}
else
{
if (ShadowMapChannel == 0)
{
BlendState = TStaticBlendState<CW_RED, BO_Min, BF_One, BF_One, BO_Min, BF_One, BF_One>::GetRHI();
}
else if (ShadowMapChannel == 1)
{
BlendState = TStaticBlendState<CW_GREEN, BO_Min, BF_One, BF_One, BO_Min, BF_One, BF_One>::GetRHI();
}
else if (ShadowMapChannel == 2)
{
BlendState = TStaticBlendState<CW_BLUE, BO_Min, BF_One, BF_One, BO_Min, BF_One, BF_One>::GetRHI();
}
else if (ShadowMapChannel == 3)
{
BlendState = TStaticBlendState<CW_ALPHA, BO_Min, BF_One, BF_One, BO_Min, BF_One, BF_One>::GetRHI();
}
}
checkf(BlendState, TEXT("Only shadows whose stationary lights have a valid ShadowMapChannel can be projected with forward shading"));
}
else
{
// Light Attenuation channel assignment:
// R: WholeSceneShadows, non SSS
// G: WholeSceneShadows, SSS
// B: non WholeSceneShadows, non SSS
// A: non WholeSceneShadows, SSS
//
// SSS: SubsurfaceScattering materials
// non SSS: shadow for opaque materials
// WholeSceneShadows: directional light CSM
// non WholeSceneShadows: spotlight, per object shadows, translucency lighting, omni-directional lights
if (bIsWholeSceneDirectionalShadow)
{
// Note: blend logic has to match ordering in FCompareFProjectedShadowInfoBySplitIndex. For example the fade plane blend mode requires that shadow to be rendered first.
// use R and G in Light Attenuation
if (bUseFadePlane)
{
// alpha is used to fade between cascades, we don't don't need to do BO_Min as we leave B and A untouched which has translucency shadow
BlendState = TStaticBlendState<CW_RG, BO_Add, BF_SourceAlpha, BF_InverseSourceAlpha>::GetRHI();
}
else
{
// first cascade rendered doesn't require fading (CO_Min is needed to combine multiple shadow passes)
// RTDF shadows: CO_Min is needed to combine with far shadows which overlap the same depth range
BlendState = TStaticBlendState<CW_RG, BO_Min, BF_One, BF_One>::GetRHI();
}
}
else
{
if (bMobileModulatedProjections)
{
// Color modulate shadows, ignore alpha.
BlendState = TStaticBlendState<CW_RGB, BO_Add, BF_Zero, BF_SourceColor, BO_Add, BF_Zero, BF_One, CW_NONE>::GetRHI();
}
else
{
// use B and A in Light Attenuation
// CO_Min is needed to combine multiple shadow passes
BlendState = TStaticBlendState<CW_BA, BO_Min, BF_One, BF_One, BO_Min, BF_One, BF_One>::GetRHI();
}
}
}
return BlendState;
}
FRHIBlendState* FProjectedShadowInfo::GetBlendStateForProjection(bool bProjectingForForwardShading, bool bMobileModulatedProjections) const
{
return GetBlendStateForProjection(
GetLightSceneInfo().GetDynamicShadowMapChannel(),
IsWholeSceneDirectionalShadow(),
CascadeSettings.FadePlaneLength > 0 && !bRayTracedDistanceField,
bProjectingForForwardShading,
bMobileModulatedProjections);
}
class FFrustumVertexBuffer : public FVertexBuffer
{
public:
virtual void InitRHI(FRHICommandListBase& RHICmdList) override
{
const FRHIBufferCreateDesc CreateDesc =
FRHIBufferCreateDesc::CreateVertex<FVector4f>(TEXT("FProjectedShadowInfoStencilFrustum"), 8)
.AddUsage(EBufferUsageFlags::Static)
.SetInitActionInitializer()
.DetermineInitialState();
TRHIBufferInitializer<FVector4f> OutFrustumVertices = RHICmdList.CreateBufferInitializer(CreateDesc);
for(uint32 vZ = 0;vZ < 2;vZ++)
{
for(uint32 vY = 0;vY < 2;vY++)
{
for(uint32 vX = 0;vX < 2;vX++)
{
OutFrustumVertices[GetCubeVertexIndex(vX,vY,vZ)] = FVector4f(
(vX ? -1.0f : 1.0f),
(vY ? -1.0f : 1.0f),
(vZ ? 1.0f : 0.0f),
1.0f);
}
}
}
VertexBufferRHI = OutFrustumVertices.Finalize();
}
};
TGlobalResource<FFrustumVertexBuffer> GFrustumVertexBuffer;
void FProjectedShadowInfo::SetupFrustumForProjection(const FViewInfo* View, TArray<FVector4f, TInlineAllocator<8>>& OutFrustumVertices, bool& bOutCameraInsideShadowFrustum, FPlane* OutPlanes) const
{
bOutCameraInsideShadowFrustum = true;
// Calculate whether the camera is inside the shadow frustum, or the near plane is potentially intersecting the frustum.
if (!IsWholeSceneDirectionalShadow())
{
OutFrustumVertices.AddUninitialized(8);
// The shadow transforms and view transforms are relative to different origins, so the world coordinates need to be translated.
const FVector3f PreShadowToPreViewTranslation(View->ViewMatrices.GetPreViewTranslation() - PreShadowTranslation);
// fill out the frustum vertices (this is only needed in the non-whole scene case)
for(uint32 vZ = 0;vZ < 2;vZ++)
{
for(uint32 vY = 0;vY < 2;vY++)
{
for(uint32 vX = 0;vX < 2;vX++)
{
const FVector4f UnprojectedVertex = InvReceiverInnerMatrix.TransformFVector4(
FVector4f(
(vX ? -1.0f : 1.0f),
(vY ? -1.0f : 1.0f),
(vZ ? 1.0f : 0.0f),
1.0f
)
);
const FVector3f ProjectedVertex = UnprojectedVertex / UnprojectedVertex.W + PreShadowToPreViewTranslation;
OutFrustumVertices[GetCubeVertexIndex(vX,vY,vZ)] = FVector4f(ProjectedVertex, 0);
}
}
}
const FVector ShadowViewOrigin = View->ViewMatrices.GetViewOrigin();
const FVector ShadowPreViewTranslation = View->ViewMatrices.GetPreViewTranslation();
const FVector FrontTopRight = (FVector4)OutFrustumVertices[GetCubeVertexIndex(0,0,1)] - ShadowPreViewTranslation;
const FVector FrontTopLeft = (FVector4)OutFrustumVertices[GetCubeVertexIndex(1,0,1)] - ShadowPreViewTranslation;
const FVector FrontBottomLeft = (FVector4)OutFrustumVertices[GetCubeVertexIndex(1,1,1)] - ShadowPreViewTranslation;
const FVector FrontBottomRight = (FVector4)OutFrustumVertices[GetCubeVertexIndex(0,1,1)] - ShadowPreViewTranslation;
const FVector BackTopRight = (FVector4)OutFrustumVertices[GetCubeVertexIndex(0,0,0)] - ShadowPreViewTranslation;
const FVector BackTopLeft = (FVector4)OutFrustumVertices[GetCubeVertexIndex(1,0,0)] - ShadowPreViewTranslation;
const FVector BackBottomLeft = (FVector4)OutFrustumVertices[GetCubeVertexIndex(1,1,0)] - ShadowPreViewTranslation;
const FVector BackBottomRight = (FVector4)OutFrustumVertices[GetCubeVertexIndex(0,1,0)] - ShadowPreViewTranslation;
const FPlane Front(FrontTopRight, FrontTopLeft, FrontBottomLeft);
const float FrontDistance = Front.PlaneDot(ShadowViewOrigin);
const FPlane Right(BackBottomRight, BackTopRight, FrontTopRight);
const float RightDistance = Right.PlaneDot(ShadowViewOrigin);
const FPlane Back(BackTopLeft, BackTopRight, BackBottomRight);
const float BackDistance = Back.PlaneDot(ShadowViewOrigin);
const FPlane Left(FrontTopLeft, BackTopLeft, BackBottomLeft);
const float LeftDistance = Left.PlaneDot(ShadowViewOrigin);
const FPlane Top(BackTopRight, BackTopLeft, FrontTopLeft);
const float TopDistance = Top.PlaneDot(ShadowViewOrigin);
const FPlane Bottom(BackBottomLeft, BackBottomRight, FrontBottomLeft);
const float BottomDistance = Bottom.PlaneDot(ShadowViewOrigin);
OutPlanes[0] = Front;
OutPlanes[1] = Right;
OutPlanes[2] = Back;
OutPlanes[3] = Left;
OutPlanes[4] = Top;
OutPlanes[5] = Bottom;
// Use a distance threshold to treat the case where the near plane is intersecting the frustum as the camera being inside
// The near plane handling is not exact since it just needs to be conservative about saying the camera is outside the frustum
const float DistanceThreshold = -View->NearClippingDistance * 3.0f;
bOutCameraInsideShadowFrustum =
FrontDistance > DistanceThreshold &&
RightDistance > DistanceThreshold &&
BackDistance > DistanceThreshold &&
LeftDistance > DistanceThreshold &&
TopDistance > DistanceThreshold &&
BottomDistance > DistanceThreshold;
}
}
class FWholeSceneDirectionalShadowStencilVS : public FGlobalShader
{
DECLARE_GLOBAL_SHADER(FWholeSceneDirectionalShadowStencilVS);
SHADER_USE_PARAMETER_STRUCT(FWholeSceneDirectionalShadowStencilVS, FGlobalShader)
BEGIN_SHADER_PARAMETER_STRUCT(FParameters, )
SHADER_PARAMETER(FVector4f, ClipZValues)
END_SHADER_PARAMETER_STRUCT()
};
IMPLEMENT_GLOBAL_SHADER(FWholeSceneDirectionalShadowStencilVS, "/Engine/Private/ShadowProjectionVertexShader.usf", "WholeSceneDirectionalShadowStencilVS", SF_Vertex);
void FProjectedShadowInfo::SetupProjectionStencilMask(
FRHICommandList& RHICmdList,
const FViewInfo* View,
int32 ViewIndex,
const FSceneRenderer* SceneRender,
const TArray<FVector4f, TInlineAllocator<8>>& FrustumVertices,
bool bMobileModulatedProjections,
bool bCameraInsideShadowFrustum,
const FInstanceCullingDrawParams& InstanceCullingDrawParams) const
{
FMeshPassProcessorRenderState DrawRenderState;
// Depth test wo/ writes, no color writing.
DrawRenderState.SetDepthStencilState(TStaticDepthStencilState<false, CF_DepthNearOrEqual>::GetRHI());
DrawRenderState.SetBlendState(TStaticBlendState<CW_NONE, BO_Add, BF_Zero, BF_SourceColor, BO_Add, BF_Zero, BF_One, CW_NONE>::GetRHI());
// If this is a preshadow, mask the projection by the receiver primitives.
if (bPreShadow || bSelfShadowOnly)
{
SCOPED_DRAW_EVENTF(RHICmdList, EventMaskSubjects, TEXT("Stencil Mask Subjects"));
// NOTE: If instanced stereo is enabled, we need to render each view of the stereo pair using the instanced stereo transform to avoid bias issues.
// This means doing 2x renders, but letting the scissor rect kill the undersired half. Drawing the full mask once is easy, but since the outer
// loop is over each view, the stencil mask is not retained when the right view comes around.
// TODO: Support instanced stereo properly in the projection stenciling pass.
const bool bIsInstancedStereoBypassed = View->bIsInstancedStereoEnabled && !View->bIsMobileMultiViewEnabled&& IStereoRendering::IsStereoEyeView(*View);
if (bIsInstancedStereoBypassed && ProjectionStencilingPasses.IsValidIndex(View->PrimaryViewIndex))
{
ensure(ProjectionStencilingPasses[View->PrimaryViewIndex]->GetInstanceCullingMode() == EInstanceCullingMode::Stereo);
const FViewInfo* PrimaryView = View->GetPrimaryView();
const FViewInfo* InstancedView = View->GetInstancedView();
if (View->bIsMultiViewportEnabled)
{
float LeftMinX = PrimaryView->ViewRect.Min.X;
float LeftMaxX = PrimaryView->ViewRect.Max.X;
float LeftMinY = PrimaryView->ViewRect.Min.Y;
float LeftMaxY = PrimaryView->ViewRect.Max.Y;
float RightMinX = InstancedView->ViewRect.Min.X;
float RightMaxX = InstancedView->ViewRect.Max.X;
float RightMinY = InstancedView->ViewRect.Min.Y;
float RightMaxY = InstancedView->ViewRect.Max.Y;
// multi-viewport - collapse the other view in pair to be 0-width 0-height, effectively disabling it
if (IStereoRendering::IsAPrimaryView(*View))
{
RightMaxX = RightMinX;
RightMaxY = RightMinY;
}
else
{
LeftMinX = LeftMaxX; // not a typo, just to have viewports adjacent to each other still
LeftMaxY = LeftMinY;
}
RHICmdList.SetStereoViewport(LeftMinX, RightMinX, LeftMinY, RightMinY, /*MinZ*/ 0.0f,
LeftMaxX, RightMaxX, LeftMaxY, RightMaxY, /*MaxZ*/ 1.0f);
}
else
{
// clip planes
RHICmdList.SetViewport(PrimaryView->ViewRect.Min.X, PrimaryView->ViewRect.Min.Y, 0.0f, InstancedView->ViewRect.Max.X, InstancedView->ViewRect.Max.Y, 1.0f);
RHICmdList.SetScissorRect(true, View->ViewRect.Min.X, View->ViewRect.Min.Y, View->ViewRect.Max.X, View->ViewRect.Max.Y);
}
// Submit the first (and only pass - we share that at least) as the pass is set up for stereo.
ProjectionStencilingPasses[View->PrimaryViewIndex]->SubmitDraw(RHICmdList, InstanceCullingDrawParams);
RHICmdList.SetScissorRect(false, 0, 0, 0, 0);
RHICmdList.SetViewport(View->ViewRect.Min.X, View->ViewRect.Min.Y, 0.0f, View->ViewRect.Max.X, View->ViewRect.Max.Y, 1.0f);
}
else if (ViewIndex < ProjectionStencilingPasses.Num())
{
check(ProjectionStencilingPasses[ViewIndex] != nullptr);
ProjectionStencilingPasses[ViewIndex]->SubmitDraw(RHICmdList, InstanceCullingDrawParams);
}
}
else if (IsWholeSceneDirectionalShadow())
{
// Increment stencil on front-facing zfail, decrement on back-facing zfail.
DrawRenderState.SetDepthStencilState(
TStaticDepthStencilState<
false, CF_DepthNearOrEqual,
true, CF_Always, SO_Keep, SO_Increment, SO_Keep,
true, CF_Always, SO_Keep, SO_Decrement, SO_Keep,
ShadowStencilMask, ShadowStencilMask
>::GetRHI());
FGraphicsPipelineStateInitializer GraphicsPSOInit;
RHICmdList.ApplyCachedRenderTargets(GraphicsPSOInit);
GraphicsPSOInit.PrimitiveType = PT_TriangleList;
DrawRenderState.ApplyToPSO(GraphicsPSOInit);
GraphicsPSOInit.RasterizerState = TStaticRasterizerState<FM_Solid, CM_None>::GetRHI();
checkSlow(CascadeSettings.ShadowSplitIndex >= 0);
checkSlow(bDirectionalLight);
// Draw 2 fullscreen planes, front facing one at the near subfrustum plane, and back facing one at the far.
const FVector4 Near = View->ViewMatrices.GetProjectionMatrix().TransformFVector4(FVector4(0, 0, CascadeSettings.SplitNear));
const FVector4 Far = View->ViewMatrices.GetProjectionMatrix().TransformFVector4(FVector4(0, 0, CascadeSettings.SplitFar));
const FVector4::FReal StencilNear = (Near.Z / Near.W);
const FVector4::FReal StencilFar = (Far.Z / Far.W);
TShaderMapRef<FWholeSceneDirectionalShadowStencilVS> VertexShader(View->ShaderMap);
GraphicsPSOInit.BoundShaderState.VertexDeclarationRHI = GetVertexDeclarationFVector4();
GraphicsPSOInit.BoundShaderState.VertexShaderRHI = VertexShader.GetVertexShader();
SetGraphicsPipelineState(RHICmdList, GraphicsPSOInit, 0);
FWholeSceneDirectionalShadowStencilVS::FParameters Parameters;
Parameters.ClipZValues = FVector4f((float)StencilFar, (float)StencilNear, 0, 0); // LWC_TODO: precision loss
SetShaderParameters(RHICmdList, VertexShader, VertexShader.GetVertexShader(), Parameters);
RHICmdList.SetStreamSource(0, GDummyWholeSceneDirectionalShadowStencilVertexBuffer.VertexBufferRHI, 0);
RHICmdList.DrawPrimitive(0, (CascadeSettings.ShadowSplitIndex > 0) ? 2 : 1, 1);
}
// Not a preshadow, mask the projection to any pixels inside the frustum.
else
{
if (bCameraInsideShadowFrustum)
{
// Use zfail stenciling when the camera is inside the frustum or the near plane is potentially clipping,
// Because zfail handles these cases while zpass does not.
// zfail stenciling is somewhat slower than zpass because on modern GPUs HiZ will be disabled when setting up stencil.
// Increment stencil on front-facing zfail, decrement on back-facing zfail.
DrawRenderState.SetDepthStencilState(
TStaticDepthStencilState<
false, CF_DepthNearOrEqual,
true, CF_Always, SO_Keep, SO_Increment, SO_Keep,
true, CF_Always, SO_Keep, SO_Decrement, SO_Keep,
ShadowStencilMask, ShadowStencilMask
>::GetRHI());
}
else
{
// Increment stencil on front-facing zpass, decrement on back-facing zpass.
// HiZ will be enabled on modern GPUs which will save a little GPU time.
DrawRenderState.SetDepthStencilState(
TStaticDepthStencilState<
false, CF_DepthNearOrEqual,
true, CF_Always, SO_Keep, SO_Keep, SO_Increment,
true, CF_Always, SO_Keep, SO_Keep, SO_Decrement,
ShadowStencilMask, ShadowStencilMask
>::GetRHI());
}
FGraphicsPipelineStateInitializer GraphicsPSOInit;
RHICmdList.ApplyCachedRenderTargets(GraphicsPSOInit);
GraphicsPSOInit.PrimitiveType = PT_TriangleList;
DrawRenderState.ApplyToPSO(GraphicsPSOInit);
GraphicsPSOInit.RasterizerState = TStaticRasterizerState<FM_Solid, CM_None>::GetRHI();
// Find the projection shaders.
TShaderMapRef<FShadowVolumeBoundProjectionVS> VertexShader(View->ShaderMap);
GraphicsPSOInit.BoundShaderState.VertexDeclarationRHI = GetVertexDeclarationFVector4();
GraphicsPSOInit.BoundShaderState.VertexShaderRHI = VertexShader.GetVertexShader();
GraphicsPSOInit.PrimitiveType = PT_TriangleList;
SetGraphicsPipelineState(RHICmdList, GraphicsPSOInit, 0);
// Set the projection vertex shader parameters
SetShaderParametersLegacyVS(RHICmdList, VertexShader, *View, this, EShadowProjectionVertexShaderFlags::DrawingFrustum);
RHICmdList.SetStreamSource(0, GFrustumVertexBuffer.VertexBufferRHI, 0);
// Shadow projection stenciling is special-cased to run per-view for instanced stereo views.
// TODO: Support instanced stereo properly in the projection stenciling pass.
const bool bIsInstancedStereoBypassed = View->bIsInstancedStereoEnabled && !View->bIsMobileMultiViewEnabled&& IStereoRendering::IsStereoEyeView(*View);
const uint32 NumberOfInstances = bIsInstancedStereoBypassed ? 1 : View->GetStereoPassInstanceFactor();
// Draw the frustum using the stencil buffer to mask just the pixels which are inside the shadow frustum.
RHICmdList.DrawIndexedPrimitive(GCubeIndexBuffer.IndexBufferRHI, 0, 0, 8, 0, 12, NumberOfInstances);
// if rendering modulated shadows mask out subject mesh elements to prevent self shadowing.
if (bMobileModulatedProjections && !CVarEnableModulatedSelfShadow.GetValueOnRenderThread())
{
if (ViewIndex < ProjectionStencilingPasses.Num())
{
ProjectionStencilingPasses[ViewIndex]->SubmitDraw(RHICmdList, InstanceCullingDrawParams);
}
}
}
}
// Mark hair-strands pixels encompassed into the rasterized volumes.
// * Mark 1: for hair pixels encompassed into the volume
// * Mark 2: for pixels touching the volume
// This affects only the hair-only depth stencil texture.
void FProjectedShadowInfo::SetupProjectionStencilMaskForHair(FRHICommandList& RHICmdList, const FViewInfo* View) const
{
check(!IsWholeSceneDirectionalShadow());
FGraphicsPipelineStateInitializer GraphicsPSOInit;
RHICmdList.ApplyCachedRenderTargets(GraphicsPSOInit);
TShaderRef<FShadowVolumeBoundProjectionVS> VertexShader = View->ShaderMap->GetShader<FShadowVolumeBoundProjectionVS>();
GraphicsPSOInit.RasterizerState = TStaticRasterizerState<FM_Solid, CM_None>::GetRHI();
GraphicsPSOInit.bDepthBounds = false;
GraphicsPSOInit.DepthStencilState =
TStaticDepthStencilState<
false, CF_GreaterEqual,
true, CF_Always, SO_Keep, SO_Keep, SO_Increment,
true, CF_Always, SO_Keep, SO_Keep, SO_Increment,
0xff, 0xff>::GetRHI();
GraphicsPSOInit.BlendState = TStaticBlendState<CW_RED, BO_Add, BF_SourceAlpha, BF_InverseSourceAlpha>::GetRHI();
GraphicsPSOInit.PrimitiveType = PT_TriangleList;
GraphicsPSOInit.BoundShaderState.VertexDeclarationRHI = GetVertexDeclarationFVector4();
GraphicsPSOInit.BoundShaderState.VertexShaderRHI = VertexShader.GetVertexShader();
GraphicsPSOInit.BoundShaderState.PixelShaderRHI = nullptr;
SetGraphicsPipelineState(RHICmdList, GraphicsPSOInit, 0);
SetShaderParametersLegacyVS(RHICmdList, VertexShader, *View, this, EShadowProjectionVertexShaderFlags::DrawingFrustum);
RHICmdList.SetStreamSource(0, GFrustumVertexBuffer.VertexBufferRHI, 0);
RHICmdList.DrawIndexedPrimitive(GCubeIndexBuffer.IndexBufferRHI, 0, 0, 8, 0, 12, 1);
}
BEGIN_SHADER_PARAMETER_STRUCT(FShadowProjectionPassParameters, )
SHADER_PARAMETER_STRUCT_INCLUDE(FViewShaderParameters, View)
SHADER_PARAMETER_RDG_UNIFORM_BUFFER(FHairStrandsViewUniformParameters, HairStrands)
SHADER_PARAMETER_RDG_UNIFORM_BUFFER(FSubstrateGlobalUniformParameters, Substrate)
SHADER_PARAMETER_STRUCT_INCLUDE(FSceneTextureShaderParameters, SceneTextures)
SHADER_PARAMETER_STRUCT_INCLUDE(FInstanceCullingDrawParams, InstanceCullingDrawParams)
RDG_TEXTURE_ACCESS(ShadowTexture0, ERHIAccess::SRVGraphics)
RDG_TEXTURE_ACCESS(ShadowTexture1, ERHIAccess::SRVGraphics)
RENDER_TARGET_BINDING_SLOTS()
END_SHADER_PARAMETER_STRUCT()
void FProjectedShadowInfo::RenderProjection(
FRDGBuilder& GraphBuilder,
const FShadowProjectionPassParameters& CommonPassParameters,
int32 ViewIndex,
const FViewInfo* View,
const FLightSceneProxy* LightSceneProxy,
const FSceneRenderer* SceneRender,
bool bProjectingForForwardShading,
bool bSubPixelShadow) const
{
// Find the shadow's view relevance.
const FVisibleLightViewInfo& VisibleLightViewInfo = View->VisibleLightInfos[LightSceneInfo->Id];
{
FPrimitiveViewRelevance ViewRelevance = VisibleLightViewInfo.ProjectedShadowViewRelevanceMap[ShadowId];
// Don't render shadows for subjects which aren't view relevant.
if (ViewRelevance.bShadowRelevance == false)
{
return;
}
}
bool bCameraInsideShadowFrustum;
TArray<FVector4f, TInlineAllocator<8>> FrustumVertices;
FPlane OutPlanes[6];
SetupFrustumForProjection(View, FrustumVertices, bCameraInsideShadowFrustum, OutPlanes);
const bool bDepthBoundsTestEnabled = IsWholeSceneDirectionalShadow() && GSupportsDepthBoundsTest && CVarCSMDepthBoundsTest.GetValueOnRenderThread() != 0;// && !bSubPixelSupport;
if (bSubPixelShadow)
{
// Do not apply pre-shadow on opaque geometry during sub-pixel pass as we only care about opaque geometry 'casting' shadow (not receiving shadow)
// However, applied pre-shadow onto hair primitive (which are the only one able to cast deep shadow)
if (bPreShadow)
{
const bool bIsValid = ReceiverPrimitives.Num() > 0 && ReceiverPrimitives[0]->Proxy->CastsDeepShadow();
if (!bIsValid)
{
return;
}
}
const bool bValidPlanes = FrustumVertices.Num() > 0;
if (bValidPlanes && CVarHairStrandsCullPerObjectShadowCaster.GetValueOnRenderThread() > 0)
{
// Skip volume which does not intersect hair clusters
bool bIntersect = bValidPlanes;
for (const FHairStrandsMacroGroupData& Data : View->HairStrandsViewData.MacroGroupDatas)
{
const FSphere BoundSphere = Data.Bounds.GetSphere();
// Return the signed distance to the plane. The planes are pointing inward
const float D0 = -OutPlanes[0].PlaneDot(BoundSphere.Center);
const float D1 = -OutPlanes[1].PlaneDot(BoundSphere.Center);
const float D2 = -OutPlanes[2].PlaneDot(BoundSphere.Center);
const float D3 = -OutPlanes[3].PlaneDot(BoundSphere.Center);
const float D4 = -OutPlanes[4].PlaneDot(BoundSphere.Center);
const float D5 = -OutPlanes[5].PlaneDot(BoundSphere.Center);
const bool bOutside =
D0 - BoundSphere.W > 0 ||
D1 - BoundSphere.W > 0 ||
D2 - BoundSphere.W > 0 ||
D3 - BoundSphere.W > 0 ||
D4 - BoundSphere.W > 0 ||
D5 - BoundSphere.W > 0;
bIntersect = !bOutside;
if (bIntersect)
{
break;
}
}
// The light frustum does not intersect the hair cluster, and thus doesn't have any interacction with it, and the shadow mask computation is not needed in this case
if (!bIntersect)
{
return;
}
}
}
FString EventName;
#if WANTS_DRAW_MESH_EVENTS
if (GetEmitDrawEvents())
{
GetShadowTypeNameForDrawEvent(EventName);
}
#endif
// Interpret null render targets as skipping the render pass; this is currently used by mobile.
const ERDGPassFlags PassFlags = CommonPassParameters.RenderTargets[0].GetTexture() == nullptr ? ERDGPassFlags::SkipRenderPass : ERDGPassFlags::None;
auto* PassParameters = GraphBuilder.AllocParameters<FShadowProjectionPassParameters>();
*PassParameters = CommonPassParameters;
PassParameters->View = View->GetShaderParameters();
PassParameters->RenderTargets.MultiViewCount = (View->bIsMobileMultiViewEnabled) ? 2 : (View->Aspects.IsMobileMultiViewEnabled() ? 1 : 0);
if (RenderTargets.DepthTarget)
{
PassParameters->ShadowTexture0 = GraphBuilder.RegisterExternalTexture(RenderTargets.DepthTarget);
}
else
{
PassParameters->ShadowTexture0 = GraphBuilder.RegisterExternalTexture(RenderTargets.ColorTargets[0]);
PassParameters->ShadowTexture1 = GraphBuilder.RegisterExternalTexture(RenderTargets.ColorTargets[1]);
}
const bool bIsInstancedStereoBypassed = View->bIsInstancedStereoEnabled && !View->bIsMobileMultiViewEnabled&& IStereoRendering::IsStereoEyeView(*View);
if (ViewIndex < ProjectionStencilingPasses.Num() && ProjectionStencilingPasses[ViewIndex] != nullptr)
{
// GPUCULL_TODO: get rid of const cast
FSimpleMeshDrawCommandPass& ProjectionStencilingPass = *const_cast<FSimpleMeshDrawCommandPass*>(ProjectionStencilingPasses[ViewIndex]);
ProjectionStencilingPass.BuildRenderingCommands(GraphBuilder, *View, *SceneRender->Scene, PassParameters->InstanceCullingDrawParams);
}
else if (bIsInstancedStereoBypassed && (bPreShadow || bSelfShadowOnly))
{
// NOTE: This here is a hack that must match up to the use inside SetupProjectionStencilMask, where we use the Stereo setup but draw each eye independently
// by scissoring the undersired half (while setting the full viewport to get the scaling to match the stereo pathfor base/pre-pass 1:1).
ensure(View->StereoPass == EStereoscopicPass::eSSP_SECONDARY);
// GPUCULL_TODO: get rid of const cast
FSimpleMeshDrawCommandPass& ProjectionStencilingPass = *const_cast<FSimpleMeshDrawCommandPass*>(ProjectionStencilingPasses[0]);
ensure(ProjectionStencilingPass.GetInstanceCullingMode() == EInstanceCullingMode::Stereo);
ProjectionStencilingPass.BuildRenderingCommands(GraphBuilder, *View->GetPrimaryView(), *SceneRender->Scene, PassParameters->InstanceCullingDrawParams);
}
const FInstanceCullingDrawParams& InstanceCullingDrawParams = PassParameters->InstanceCullingDrawParams;
GraphBuilder.AddPass(
RDG_EVENT_NAME("%s", *EventName),
PassParameters,
ERDGPassFlags::Raster | PassFlags,
[this, SceneRender, View, ViewIndex, LightSceneProxy, bProjectingForForwardShading, &InstanceCullingDrawParams, bSubPixelShadow, PassParameters](FRDGAsyncTask, FRHICommandList& RHICmdList)
{
RenderProjectionInternal(RHICmdList, ViewIndex, View, LightSceneProxy, SceneRender, bProjectingForForwardShading, false, InstanceCullingDrawParams, bSubPixelShadow && PassParameters->HairStrands ? PassParameters->HairStrands.GetUniformBuffer()->GetRHI() : nullptr);
});
}
void FProjectedShadowInfo::RenderProjectionInternal(
FRHICommandList& RHICmdList,
int32 ViewIndex,
const FViewInfo* View,
const FLightSceneProxy* LightSceneProxy,
const FSceneRenderer* SceneRender,
bool bProjectingForForwardShading,
bool bMobileModulatedProjections,
const FInstanceCullingDrawParams& InstanceCullingDrawParams,
FRHIUniformBuffer* HairStrandsUniformBuffer) const
{
RHICmdList.SetViewport(View->ViewRect.Min.X, View->ViewRect.Min.Y, 0.0f, View->ViewRect.Max.X, View->ViewRect.Max.Y, 1.0f);
LightSceneProxy->SetScissorRect(RHICmdList, *View, View->ViewRect);
FScopeCycleCounter Scope(bWholeSceneShadow ? GET_STATID(STAT_RenderWholeSceneShadowProjectionsTime) : GET_STATID(STAT_RenderPerObjectShadowProjectionsTime));
FGraphicsPipelineStateInitializer GraphicsPSOInit;
RHICmdList.ApplyCachedRenderTargets(GraphicsPSOInit);
FPlane OutFrustmPlanes[6];
bool bCameraInsideShadowFrustum;
TArray<FVector4f, TInlineAllocator<8>> FrustumVertices;
SetupFrustumForProjection(View, FrustumVertices, bCameraInsideShadowFrustum, OutFrustmPlanes);
const bool bSubPixelSupport = HairStrandsUniformBuffer != nullptr;// HairStrands::HasViewHairStrandsData(*View);
const bool bStencilTestEnabled = !bSubPixelSupport && HasShadowStencilCulling(View->GetShaderPlatform());
const bool bDepthBoundsTestEnabled = IsWholeSceneDirectionalShadow() && GSupportsDepthBoundsTest && CVarCSMDepthBoundsTest.GetValueOnRenderThread() != 0;// && !bSubPixelSupport;
const uint32 StencilRef = bSubPixelSupport && !IsWholeSceneDirectionalShadow() && !bCameraInsideShadowFrustum ? 1u : 0u;
if (!bDepthBoundsTestEnabled && bStencilTestEnabled)
{
SetupProjectionStencilMask(RHICmdList, View, ViewIndex, SceneRender, FrustumVertices, bMobileModulatedProjections, bCameraInsideShadowFrustum, InstanceCullingDrawParams);
}
// Mark stencil so that only hair pixel within volume bound will be affected by the pre-shadow mask
if (bSubPixelSupport && !bDepthBoundsTestEnabled)
{
DrawClearQuad(RHICmdList, false, FLinearColor::Transparent, false, 0, true, 0);
if (!IsWholeSceneDirectionalShadow())
{
SetupProjectionStencilMaskForHair(RHICmdList, View);
}
}
// solid rasterization w/ back-face culling.
GraphicsPSOInit.RasterizerState = (View->bReverseCulling || IsWholeSceneDirectionalShadow()) ? TStaticRasterizerState<FM_Solid, CM_CCW>::GetRHI() : TStaticRasterizerState<FM_Solid, CM_CW>::GetRHI();
GraphicsPSOInit.bDepthBounds = bDepthBoundsTestEnabled;
if (bDepthBoundsTestEnabled)
{
// no depth test or writes
GraphicsPSOInit.DepthStencilState = TStaticDepthStencilState<false, CF_Always>::GetRHI();
}
else if (bStencilTestEnabled)
{
// By pass depth/stencil test for rendering pre-shadow during sub-pixel shadow, as the hair geometry is not re-rendered
const bool bBypass = bSubPixelSupport && bPreShadow;
if (bBypass)
{
GraphicsPSOInit.DepthStencilState =
TStaticDepthStencilState<
false, CF_Always,
false, CF_Always, SO_Zero, SO_Zero, SO_Zero,
false, CF_Always, SO_Zero, SO_Zero, SO_Zero,
0xff, 0xff
>::GetRHI();
}
else if (GStencilOptimization)
{
// No depth test or writes, zero the stencil
// Note: this will disable hi-stencil on many GPUs, but still seems
// to be faster. However, early stencil still works
GraphicsPSOInit.DepthStencilState =
TStaticDepthStencilState<
false, CF_Always,
true, CF_NotEqual, SO_Zero, SO_Zero, SO_Zero,
false, CF_Always, SO_Zero, SO_Zero, SO_Zero,
ShadowStencilMask, ShadowStencilMask
>::GetRHI();
}
else
{
// no depth test or writes, Test stencil for non-zero.
GraphicsPSOInit.DepthStencilState =
TStaticDepthStencilState<
false, CF_Always,
true, CF_NotEqual, SO_Keep, SO_Keep, SO_Keep,
false, CF_Always, SO_Keep, SO_Keep, SO_Keep,
ShadowStencilMask, ShadowStencilMask
>::GetRHI();
}
}
else
{
if (bSubPixelSupport && !bDepthBoundsTestEnabled)
{
GraphicsPSOInit.DepthStencilState = TStaticDepthStencilState<false, CF_DepthFartherOrEqual, true, CF_Equal, SO_Keep, SO_Keep, SO_Keep, true, CF_Equal, SO_Keep, SO_Keep, SO_Keep, 0xFF, 0xFF>::GetRHI();
}
else
{
GraphicsPSOInit.DepthStencilState = TStaticDepthStencilState<false, CF_DepthFartherOrEqual>::GetRHI();
}
}
GraphicsPSOInit.BlendState = GetBlendStateForProjection(bProjectingForForwardShading, bMobileModulatedProjections);
GraphicsPSOInit.PrimitiveType = IsWholeSceneDirectionalShadow() ? PT_TriangleStrip : PT_TriangleList;
{
uint32 LocalQuality = GetShadowQuality();
if (LocalQuality > 1)
{
if (IsWholeSceneDirectionalShadow() && CascadeSettings.ShadowSplitIndex > 0)
{
// adjust kernel size so that the penumbra size of distant splits will better match up with the closer ones
const float SizeScale = CascadeSettings.ShadowSplitIndex / FMath::Max(0.001f, CVarCSMSplitPenumbraScale.GetValueOnRenderThread());
}
else if (LocalQuality > 2 && !bWholeSceneShadow)
{
static auto CVarPreShadowResolutionFactor = IConsoleManager::Get().FindTConsoleVariableDataFloat(TEXT("r.Shadow.PreShadowResolutionFactor"));
const int32 TargetResolution = bPreShadow ? FMath::TruncToInt(512 * CVarPreShadowResolutionFactor->GetValueOnRenderThread()) : 512;
int32 Reduce = 0;
{
int32 Res = ResolutionX;
while (Res < TargetResolution)
{
Res *= 2;
++Reduce;
}
}
// Never drop to quality 1 due to low resolution, aliasing is too bad
LocalQuality = FMath::Clamp((int32)LocalQuality - Reduce, 3, 5);
}
}
GraphicsPSOInit.BoundShaderState.VertexDeclarationRHI = GetVertexDeclarationFVector4();
BindShadowProjectionShaders(LocalQuality, RHICmdList, GraphicsPSOInit, ViewIndex, *View, this, StencilRef, bMobileModulatedProjections, HairStrandsUniformBuffer);
if (bDepthBoundsTestEnabled)
{
SetDepthBoundsTest(RHICmdList, CascadeSettings.SplitNear, CascadeSettings.SplitFar, View->ViewMatrices.GetProjectionMatrix());
}
}
uint32 NumberOfInstances = View->GetStereoPassInstanceFactor();
if (IsWholeSceneDirectionalShadow())
{
RHICmdList.SetStreamSource(0, GClearVertexBuffer.VertexBufferRHI, 0);
RHICmdList.DrawPrimitive(0, 2, NumberOfInstances);
}
else
{
RHICmdList.SetStreamSource(0, GFrustumVertexBuffer.VertexBufferRHI, 0);
// Draw the frustum using the projection shader..
RHICmdList.DrawIndexedPrimitive(GCubeIndexBuffer.IndexBufferRHI, 0, 0, 8, 0, 12, NumberOfInstances);
}
if (!bDepthBoundsTestEnabled && bStencilTestEnabled)
{
// Clear the stencil buffer to 0.
if (!GStencilOptimization)
{
DrawClearQuad(RHICmdList, false, FLinearColor::Transparent, false, 0, true, 0);
}
}
RHICmdList.SetScissorRect(false, 0, 0, 0, 0);
}
void FProjectedShadowInfo::RenderMobileModulatedShadowProjection(
FRHICommandList& RHICmdList,
int32 ViewIndex,
const FViewInfo* View,
const FLightSceneProxy* LightSceneProxy,
const FSceneRenderer* SceneRender) const
{
// Find the shadow's view relevance.
const FVisibleLightViewInfo& VisibleLightViewInfo = View->VisibleLightInfos[LightSceneInfo->Id];
{
FPrimitiveViewRelevance ViewRelevance = VisibleLightViewInfo.ProjectedShadowViewRelevanceMap[ShadowId];
// Don't render shadows for subjects which aren't view relevant.
if (ViewRelevance.bShadowRelevance == false)
{
return;
}
}
const bool bProjectingForForwardShading = false;
const bool bMobileModulatedProjections = true;
// GPUCULL_TODO, mobile modulated shadow is inside the mobile render pass, probably couldn't work with the GPUCull.
FInstanceCullingDrawParams InstanceCullingDrawParams;
RenderProjectionInternal(RHICmdList, ViewIndex, View, LightSceneProxy, SceneRender, bProjectingForForwardShading, bMobileModulatedProjections, InstanceCullingDrawParams, nullptr);
}
template <uint32 Quality, bool bUseTransmission, bool bUseSubPixel>
static void SetPointLightShaderTempl(FRHICommandList& RHICmdList, FGraphicsPipelineStateInitializer& GraphicsPSOInit, int32 ViewIndex, const FViewInfo& View, const FProjectedShadowInfo* ShadowInfo, FRHIUniformBuffer* HairStrandsUniformBuffer = nullptr)
{
TShaderMapRef<FShadowVolumeBoundProjectionVS> VertexShader(View.ShaderMap);
TShaderMapRef<TOnePassPointShadowProjectionPS<Quality,bUseTransmission,bUseSubPixel> > PixelShader(View.ShaderMap);
GraphicsPSOInit.BoundShaderState.VertexDeclarationRHI = GetVertexDeclarationFVector4();
GraphicsPSOInit.BoundShaderState.VertexShaderRHI = VertexShader.GetVertexShader();
GraphicsPSOInit.BoundShaderState.PixelShaderRHI = PixelShader.GetPixelShader();
SetGraphicsPipelineState(RHICmdList, GraphicsPSOInit, 0);
SetShaderParametersLegacyVS(RHICmdList, VertexShader, View, ShadowInfo, EShadowProjectionVertexShaderFlags::None);
SetShaderParametersLegacyPS(RHICmdList, PixelShader, ViewIndex, View, ShadowInfo, HairStrandsUniformBuffer);
}
void FProjectedShadowInfo::RenderOnePassPointLightProjection(
FRDGBuilder& GraphBuilder,
const FShadowProjectionPassParameters& CommonPassParameters,
int32 ViewIndex,
const FViewInfo& View,
const FLightSceneProxy* LightSceneProxy,
bool bProjectingForForwardShading,
bool bSubPixelShadow) const
{
SCOPE_CYCLE_COUNTER(STAT_RenderWholeSceneShadowProjectionsTime);
checkSlow(bOnePassPointLightShadow);
const FSphere LightBounds = LightSceneInfo->Proxy->GetBoundingSphere();
const bool bUseTransmission = LightSceneInfo->Proxy->Transmission();
const bool bCameraInsideLightGeometry = ((FVector)View.ViewMatrices.GetViewOrigin() - LightBounds.Center).SizeSquared() < FMath::Square(LightBounds.W * 1.05f + View.NearClippingDistance * 2.0f);
// Interpret null render targets as skipping the render pass; this is currently used by mobile.
const ERDGPassFlags PassFlags = CommonPassParameters.RenderTargets[0].GetTexture() == nullptr ? ERDGPassFlags::SkipRenderPass : ERDGPassFlags::None;
auto* PassParameters = GraphBuilder.AllocParameters<FShadowProjectionPassParameters>();
*PassParameters = CommonPassParameters;
PassParameters->View = View.GetShaderParameters();
if (RenderTargets.DepthTarget)
{
PassParameters->ShadowTexture0 = GraphBuilder.RegisterExternalTexture(RenderTargets.DepthTarget);
}
else
{
PassParameters->ShadowTexture0 = GraphBuilder.RegisterExternalTexture(RenderTargets.ColorTargets[0]);
PassParameters->ShadowTexture1 = GraphBuilder.RegisterExternalTexture(RenderTargets.ColorTargets[1]);
}
GraphBuilder.AddPass(
RDG_EVENT_NAME("OnePassPointLightProjection"),
PassParameters,
ERDGPassFlags::Raster | PassFlags,
[this, &View, LightBounds, bProjectingForForwardShading, bCameraInsideLightGeometry, bUseTransmission, ViewIndex, LightSceneProxy, bSubPixelShadow, PassParameters](FRDGAsyncTask, FRHICommandList& RHICmdList)
{
RHICmdList.SetViewport(View.ViewRect.Min.X, View.ViewRect.Min.Y, 0.0f, View.ViewRect.Max.X, View.ViewRect.Max.Y, 1.0f);
LightSceneProxy->SetScissorRect(RHICmdList, View, View.ViewRect);
FGraphicsPipelineStateInitializer GraphicsPSOInit;
RHICmdList.ApplyCachedRenderTargets(GraphicsPSOInit);
GraphicsPSOInit.BlendState = GetBlendStateForProjection(bProjectingForForwardShading, false);
GraphicsPSOInit.PrimitiveType = PT_TriangleList;
if (bCameraInsideLightGeometry)
{
GraphicsPSOInit.DepthStencilState = TStaticDepthStencilState<false, CF_Always>::GetRHI();
// Render backfaces with depth tests disabled since the camera is inside (or close to inside) the light geometry
GraphicsPSOInit.RasterizerState = View.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.DepthStencilState = TStaticDepthStencilState<false, CF_DepthNearOrEqual>::GetRHI();
GraphicsPSOInit.RasterizerState = View.bReverseCulling ? TStaticRasterizerState<FM_Solid, CM_CCW>::GetRHI() : TStaticRasterizerState<FM_Solid, CM_CW>::GetRHI();
}
const uint32 LocalQuality = GetShadowQuality();
if (LocalQuality > 1)
{
// adjust kernel size so that the penumbra size of distant splits will better match up with the closer ones
//const float SizeScale = ShadowInfo->ResolutionX;
int32 Reduce = 0;
{
int32 Res = ResolutionX;
while (Res < 512)
{
Res *= 2;
++Reduce;
}
}
}
if (bSubPixelShadow)
{
// Do not apply pre-shadow on opaque geometry during sub-pixel pass as we only care about opaque geometry 'casting' shadow (not receiving shadow)
// However, applied pre-shadow onto hair primitive (which are the only one able to cast deep shadow)
if (bPreShadow)
{
const bool bIsValid = ReceiverPrimitives.Num() > 0 && ReceiverPrimitives[0]->Proxy->CastsDeepShadow();
if (!bIsValid)
{
return;
}
}
// Skip volume which does not intersect hair clusters
bool bIntersect = false;
if (CVarHairStrandsCullPerObjectShadowCaster.GetValueOnRenderThread() > 0)
{
for (const FHairStrandsMacroGroupData& Data : View.HairStrandsViewData.MacroGroupDatas)
{
const FSphere BoundSphere = Data.Bounds.GetSphere();
if (BoundSphere.Intersects(LightBounds))
{
bIntersect = true;
break;
}
}
// The light frustum does not intersect the hair cluster, and thus doesn't have any interacction with it, and the shadow mask computation is not needed in this case
if (!bIntersect)
{
return;
}
}
FRHIUniformBuffer* HairStrandsUniformBuffer = PassParameters->HairStrands.GetUniformBuffer()->GetRHI();
switch (LocalQuality)
{
case 1: SetPointLightShaderTempl<1, false, true>(RHICmdList, GraphicsPSOInit, ViewIndex, View, this, HairStrandsUniformBuffer); break;
case 2: SetPointLightShaderTempl<2, false, true>(RHICmdList, GraphicsPSOInit, ViewIndex, View, this, HairStrandsUniformBuffer); break;
case 3: SetPointLightShaderTempl<3, false, true>(RHICmdList, GraphicsPSOInit, ViewIndex, View, this, HairStrandsUniformBuffer); break;
case 4: SetPointLightShaderTempl<4, false, true>(RHICmdList, GraphicsPSOInit, ViewIndex, View, this, HairStrandsUniformBuffer); break;
case 5: SetPointLightShaderTempl<5, false, true>(RHICmdList, GraphicsPSOInit, ViewIndex, View, this, HairStrandsUniformBuffer); break;
default:
check(0);
}
}
else if (bUseTransmission)
{
switch (LocalQuality)
{
case 1: SetPointLightShaderTempl<1, true, false>(RHICmdList, GraphicsPSOInit, ViewIndex, View, this); break;
case 2: SetPointLightShaderTempl<2, true, false>(RHICmdList, GraphicsPSOInit, ViewIndex, View, this); break;
case 3: SetPointLightShaderTempl<3, true, false>(RHICmdList, GraphicsPSOInit, ViewIndex, View, this); break;
case 4: SetPointLightShaderTempl<4, true, false>(RHICmdList, GraphicsPSOInit, ViewIndex, View, this); break;
case 5: SetPointLightShaderTempl<5, true, false>(RHICmdList, GraphicsPSOInit, ViewIndex, View, this); break;
default:
check(0);
}
}
else
{
switch (LocalQuality)
{
case 1: SetPointLightShaderTempl<1, false, false>(RHICmdList, GraphicsPSOInit, ViewIndex, View, this); break;
case 2: SetPointLightShaderTempl<2, false, false>(RHICmdList, GraphicsPSOInit, ViewIndex, View, this); break;
case 3: SetPointLightShaderTempl<3, false, false>(RHICmdList, GraphicsPSOInit, ViewIndex, View, this); break;
case 4: SetPointLightShaderTempl<4, false, false>(RHICmdList, GraphicsPSOInit, ViewIndex, View, this); break;
case 5: SetPointLightShaderTempl<5, false, false>(RHICmdList, GraphicsPSOInit, ViewIndex, View, this); break;
default:
check(0);
}
}
// Project the point light shadow 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, View.GetStereoPassInstanceFactor());
RHICmdList.SetScissorRect(false, 0, 0, 0, 0);
});
}
void FProjectedShadowInfo::RenderFrustumWireframe(FPrimitiveDrawInterface* PDI) const
{
// Find the ID of an arbitrary subject primitive to use to color the shadow frustum.
int32 SubjectPrimitiveId = 0;
if(DynamicSubjectPrimitives.Num())
{
SubjectPrimitiveId = DynamicSubjectPrimitives[0]->GetIndex();
}
const FMatrix44f InvShadowTransform = (bWholeSceneShadow || bPreShadow) ? TranslatedWorldToClipInnerMatrix.InverseFast() : InvReceiverInnerMatrix;
FColor Color;
if (IsWholeSceneDirectionalShadow())
{
Color = FColor::White;
switch(CascadeSettings.ShadowSplitIndex)
{
case 0: Color = FColor::Red; break;
case 1: Color = FColor::Yellow; break;
case 2: Color = FColor::Green; break;
case 3: Color = FColor::Blue; break;
}
}
else
{
Color = FLinearColor::MakeFromHSV8(( ( SubjectPrimitiveId + LightSceneInfo->Id ) * 31 ) & 255, 0, 255).ToFColor(true);
}
// Render the wireframe for the frustum derived from ReceiverMatrix.
DrawFrustumWireframe(
PDI,
FMatrix(InvShadowTransform) * FTranslationMatrix(-PreShadowTranslation),
Color,
SDPG_World
);
}
FVector4f FProjectedShadowInfo::GetClipToShadowBufferUvScaleBias() const
{
const FIntPoint ShadowBufferResolution = GetShadowBufferResolution();
const float InvBufferResolutionX = 1.0f / (float)ShadowBufferResolution.X;
const float ShadowResolutionFractionX = 0.5f * (float)ResolutionX * InvBufferResolutionX;
const float InvBufferResolutionY = 1.0f / (float)ShadowBufferResolution.Y;
const float ShadowResolutionFractionY = 0.5f * (float)ResolutionY * InvBufferResolutionY;
return FVector4f(ShadowResolutionFractionX,
-ShadowResolutionFractionY,
(X + BorderSize) * InvBufferResolutionX + ShadowResolutionFractionX,
(Y + BorderSize) * InvBufferResolutionY + ShadowResolutionFractionY);
}
FMatrix FProjectedShadowInfo::GetScreenToShadowMatrix(const FSceneView& View, uint32 TileOffsetX, uint32 TileOffsetY, uint32 TileResolutionX, uint32 TileResolutionY) const
{
const FIntPoint ShadowBufferResolution = GetShadowBufferResolution();
const float InvBufferResolutionX = 1.0f / (float)ShadowBufferResolution.X;
const float ShadowResolutionFractionX = 0.5f * (float)TileResolutionX * InvBufferResolutionX;
const float InvBufferResolutionY = 1.0f / (float)ShadowBufferResolution.Y;
const float ShadowResolutionFractionY = 0.5f * (float)TileResolutionY * InvBufferResolutionY;
// Calculate the matrix to transform a screenspace position into shadow map space
FMatrix ScreenToShadow;
FMatrix ViewDependentTransform =
// Z of the position being transformed is actually view space Z,
// Transform it into post projection space by applying the projection matrix,
// Which is the required space before applying View.InvTranslatedViewProjectionMatrix
View.ViewMatrices.GetScreenToClipMatrix() *
// Transform the post projection space position into translated world space
// Translated world space is normal world space translated to the view's origin,
// Which prevents floating point imprecision far from the world origin.
View.ViewMatrices.GetInvTranslatedViewProjectionMatrix() *
FTranslationMatrix(-View.ViewMatrices.GetPreViewTranslation());
FMatrix ShadowMapDependentTransform =
// Translate to the origin of the shadow's translated world space
FTranslationMatrix(PreShadowTranslation) *
// Transform into the shadow's post projection space
// This has to be the same transform used to render the shadow depths
FMatrix(TranslatedWorldToClipInnerMatrix) *
// Scale and translate x and y to be texture coordinates into the ShadowInfo's rectangle in the shadow depth buffer
// Normalize z by MaxSubjectDepth, as was done when writing shadow depths
FMatrix(
FPlane(ShadowResolutionFractionX,0, 0, 0),
FPlane(0, -ShadowResolutionFractionY,0, 0),
FPlane(0, 0, InvMaxSubjectDepth, 0),
FPlane(
(TileOffsetX + BorderSize) * InvBufferResolutionX + ShadowResolutionFractionX,
(TileOffsetY + BorderSize) * InvBufferResolutionY + ShadowResolutionFractionY,
0,
1
)
);
// Checking the Aspects is a workaround for editor windows or scene captures where View.bIsMobileMultiViewEnabled
// is false but shaders have been compiled with MOBILE_MULTI_VIEW enabled.
if (View.bIsMobileMultiViewEnabled || View.Aspects.IsMobileMultiViewEnabled())
{
// In Multiview, we split ViewDependentTransform out into ViewUniformShaderParameters.MobileMultiviewShadowTransform
// So we can multiply it later in shader.
ScreenToShadow = ShadowMapDependentTransform;
}
else
{
ScreenToShadow = ViewDependentTransform * ShadowMapDependentTransform;
}
return ScreenToShadow;
}
FMatrix FProjectedShadowInfo::GetWorldToShadowMatrix(FVector4f& ShadowmapMinMax, const FIntPoint* ShadowBufferResolutionOverride) const
{
FIntPoint ShadowBufferResolution = ( ShadowBufferResolutionOverride ) ? *ShadowBufferResolutionOverride : GetShadowBufferResolution();
const float InvBufferResolutionX = 1.0f / (float)ShadowBufferResolution.X;
const float ShadowResolutionFractionX = 0.5f * (float)ResolutionX * InvBufferResolutionX;
const float InvBufferResolutionY = 1.0f / (float)ShadowBufferResolution.Y;
const float ShadowResolutionFractionY = 0.5f * (float)ResolutionY * InvBufferResolutionY;
const FMatrix WorldToShadowMatrix =
// Translate to the origin of the shadow's translated world space
FTranslationMatrix(PreShadowTranslation) *
// Transform into the shadow's post projection space
// This has to be the same transform used to render the shadow depths
FMatrix(TranslatedWorldToClipInnerMatrix) *
// Scale and translate x and y to be texture coordinates into the ShadowInfo's rectangle in the shadow depth buffer
// Normalize z by MaxSubjectDepth, as was done when writing shadow depths
FMatrix(
FPlane(ShadowResolutionFractionX,0, 0, 0),
FPlane(0, -ShadowResolutionFractionY,0, 0),
FPlane(0, 0, InvMaxSubjectDepth, 0),
FPlane(
(X + BorderSize) * InvBufferResolutionX + ShadowResolutionFractionX,
(Y + BorderSize) * InvBufferResolutionY + ShadowResolutionFractionY,
0,
1
)
);
ShadowmapMinMax = FVector4f(
(X + BorderSize) * InvBufferResolutionX,
(Y + BorderSize) * InvBufferResolutionY,
(X + BorderSize * 2 + ResolutionX) * InvBufferResolutionX,
(Y + BorderSize * 2 + ResolutionY) * InvBufferResolutionY);
return WorldToShadowMatrix;
}
void FProjectedShadowInfo::UpdateShaderDepthBias()
{
float DepthBias = 0;
float SlopeScaleDepthBias = 1;
if (IsWholeScenePointLightShadow())
{
const bool bIsRectLight = LightSceneInfo->Proxy->GetLightType() == LightType_Rect;
float DeptBiasConstant = 0;
float SlopeDepthBiasConstant = 0;
if (bIsRectLight)
{
DeptBiasConstant = CVarRectLightShadowDepthBias.GetValueOnRenderThread();
SlopeDepthBiasConstant = CVarRectLightShadowSlopeScaleDepthBias.GetValueOnRenderThread();
}
else
{
DeptBiasConstant = CVarPointLightShadowDepthBias.GetValueOnRenderThread();
SlopeDepthBiasConstant = CVarPointLightShadowSlopeScaleDepthBias.GetValueOnRenderThread();
}
DepthBias = DeptBiasConstant * 512.0f / FMath::Max(ResolutionX, ResolutionY);
// * 2.0f to be compatible with the system we had before ShadowBias
DepthBias *= 2.0f * LightSceneInfo->Proxy->GetUserShadowBias();
SlopeScaleDepthBias = SlopeDepthBiasConstant;
SlopeScaleDepthBias *= LightSceneInfo->Proxy->GetUserShadowSlopeBias();
}
else if (IsWholeSceneDirectionalShadow())
{
check(CascadeSettings.ShadowSplitIndex >= 0);
// the z range is adjusted to we need to adjust here as well
DepthBias = CVarCSMShadowDepthBias.GetValueOnRenderThread() / (MaxSubjectZ - MinSubjectZ);
const float WorldSpaceTexelScale = ShadowBounds.W / ResolutionX;
DepthBias = FMath::Lerp(DepthBias, DepthBias * WorldSpaceTexelScale, CascadeSettings.CascadeBiasDistribution);
DepthBias *= LightSceneInfo->Proxy->GetUserShadowBias();
SlopeScaleDepthBias = CVarCSMShadowSlopeScaleDepthBias.GetValueOnRenderThread();
SlopeScaleDepthBias *= LightSceneInfo->Proxy->GetUserShadowSlopeBias();
}
else if (bPreShadow)
{
// Preshadows don't need a depth bias since there is no self shadowing
DepthBias = 0;
SlopeScaleDepthBias = 0;
}
else
{
// per object shadows (the whole-scene are taken care of above)
if(bDirectionalLight)
{
// we use CSMShadowDepthBias cvar but this is per object shadows, maybe we want to use different settings
// the z range is adjusted to we need to adjust here as well
DepthBias = CVarPerObjectDirectionalShadowDepthBias.GetValueOnRenderThread() / (MaxSubjectZ - MinSubjectZ);
float WorldSpaceTexelScale = ShadowBounds.W / FMath::Max(ResolutionX, ResolutionY);
DepthBias *= WorldSpaceTexelScale;
DepthBias *= 0.5f; // avg GetUserShadowBias, in that case we don't want this adjustable
SlopeScaleDepthBias = CVarPerObjectDirectionalShadowSlopeScaleDepthBias.GetValueOnRenderThread();
SlopeScaleDepthBias *= LightSceneInfo->Proxy->GetUserShadowSlopeBias();
}
else // Only spot-lights left (both whole-scene and per-object), as point-lights dont have per-object shadowing
{
ensure(!bDirectionalLight);
ensure(!bOnePassPointLightShadow);
if (bPerObjectOpaqueShadow)
{
// spot lights (old code, might need to be improved)
const float LightTypeDepthBias = CVarPerObjectSpotLightShadowDepthBias.GetValueOnRenderThread();
DepthBias = LightTypeDepthBias * 512.0f / ((MaxSubjectZ - MinSubjectZ) * FMath::Max(ResolutionX, ResolutionY));
// * 2.0f to be compatible with the system we had before ShadowBias
DepthBias *= 2.0f * LightSceneInfo->Proxy->GetUserShadowBias();
SlopeScaleDepthBias = CVarPerObjectSpotLightShadowSlopeScaleDepthBias.GetValueOnRenderThread();
SlopeScaleDepthBias *= LightSceneInfo->Proxy->GetUserShadowSlopeBias();
}
else
{
// spot lights (old code, might need to be improved)
const float LightTypeDepthBias = CVarSpotLightShadowDepthBias.GetValueOnRenderThread();
DepthBias = LightTypeDepthBias * 512.0f / ((MaxSubjectZ - MinSubjectZ) * FMath::Max(ResolutionX, ResolutionY));
// * 2.0f to be compatible with the system we had before ShadowBias
DepthBias *= 2.0f * LightSceneInfo->Proxy->GetUserShadowBias();
SlopeScaleDepthBias = CVarSpotLightShadowSlopeScaleDepthBias.GetValueOnRenderThread();
SlopeScaleDepthBias *= LightSceneInfo->Proxy->GetUserShadowSlopeBias();
}
}
// Prevent a large depth bias due to low resolution from causing near plane clipping
DepthBias = FMath::Min(DepthBias, .1f);
}
ShaderDepthBias = FMath::Max(DepthBias, 0.0f);
ShaderSlopeDepthBias = FMath::Max(DepthBias * SlopeScaleDepthBias, 0.0f);
ShaderMaxSlopeDepthBias = CVarShadowMaxSlopeScaleDepthBias.GetValueOnRenderThread();
}
float FProjectedShadowInfo::ComputeTransitionSize() const
{
float TransitionSize = 1.0f;
if (IsWholeScenePointLightShadow())
{
// todo: optimize
TransitionSize = bDirectionalLight ? (1.0f / CVarShadowTransitionScale.GetValueOnRenderThread()) : (1.0f / CVarSpotLightShadowTransitionScale.GetValueOnRenderThread());
// * 2.0f to be compatible with the system we had before ShadowBias
TransitionSize *= 2.0f * LightSceneInfo->Proxy->GetUserShadowBias();
}
else if (IsWholeSceneDirectionalShadow())
{
check(CascadeSettings.ShadowSplitIndex >= 0);
// todo: remove GetShadowTransitionScale()
// make 1/ ShadowTransitionScale, SpotLightShadowTransitionScale
// the z range is adjusted to we need to adjust here as well
TransitionSize = CVarCSMShadowDepthBias.GetValueOnRenderThread() / (MaxSubjectZ - MinSubjectZ);
float WorldSpaceTexelScale = ShadowBounds.W / ResolutionX;
TransitionSize *= WorldSpaceTexelScale;
TransitionSize *= LightSceneInfo->Proxy->GetUserShadowBias();
}
else if (bPreShadow)
{
// Preshadows don't have self shadowing, so make sure the shadow starts as close to the caster as possible
TransitionSize = 0.0f;
}
else
{
// todo: optimize
TransitionSize = bDirectionalLight ? (1.0f / CVarShadowTransitionScale.GetValueOnRenderThread()) : (1.0f / CVarSpotLightShadowTransitionScale.GetValueOnRenderThread());
// * 2.0f to be compatible with the system we had before ShadowBias
TransitionSize *= 2.0f * LightSceneInfo->Proxy->GetUserShadowBias();
}
// Make sure that shadow soft transition size is greater than zero so 1/TransitionSize shader parameter won't be INF.
const float MinTransitionSize = 0.00001f;
return FMath::Max(TransitionSize, MinTransitionSize);
}
bool FProjectedShadowInfo::IsWholeSceneDirectionalShadow() const
{
return bWholeSceneShadow && CascadeSettings.ShadowSplitIndex >= 0 && bDirectionalLight;
}
bool FProjectedShadowInfo::IsWholeScenePointLightShadow() const
{
return bWholeSceneShadow && (LightSceneInfo->Proxy->GetLightType() == LightType_Point || LightSceneInfo->Proxy->GetLightType() == LightType_Rect);
}
float FProjectedShadowInfo::GetShaderReceiverDepthBias() const
{
float ShadowReceiverBias = 1;
{
switch (GetLightSceneInfo().Proxy->GetLightType())
{
case LightType_Directional : ShadowReceiverBias = CVarCSMShadowReceiverBias.GetValueOnRenderThread(); break;
case LightType_Rect : ShadowReceiverBias = CVarRectLightShadowReceiverBias.GetValueOnRenderThread(); break;
case LightType_Spot : ShadowReceiverBias = CVarSpotLightShadowReceiverBias.GetValueOnRenderThread(); break;
case LightType_Point : ShadowReceiverBias = GetShaderSlopeDepthBias(); break;
}
}
// Return the min lerp value for depth biasing
// 0 : max bias when NoL == 0
// 1 : no bias
return 1.0f - FMath::Clamp(ShadowReceiverBias, 0.0f, 1.0f);
}
/*-----------------------------------------------------------------------------
FDeferredShadingSceneRenderer
-----------------------------------------------------------------------------*/
/**
* Used by RenderLights to figure out if projected shadows need to be rendered to the attenuation buffer.
*
* @param LightSceneInfo Represents the current light
* @return true if anything needs to be rendered
*/
bool FSceneRenderer::CheckForProjectedShadows( const FLightSceneInfo* LightSceneInfo ) const
{
// First person self shadow is currently implemented as screen space shadow but serves as a standalone shadowing solution
// for first person primitives as they are not supported by any other shadowing method.
if (ShouldRenderFirstPersonSelfShadowForLight(*this, ViewFamily, Views, *LightSceneInfo))
{
return true;
}
// If light has ray-traced occlusion enabled, then it will project some shadows. No need
// for doing a lookup through shadow maps data
const FLightOcclusionType LightOcclusionType = GetLightOcclusionType(*LightSceneInfo->Proxy, ViewFamily);
if (LightOcclusionType == FLightOcclusionType::Raytraced)
return true;
// Find the projected shadows cast by this light.
const FVisibleLightInfo& VisibleLightInfo = VisibleLightInfos[LightSceneInfo->Id];
for( int32 ShadowIndex=0; ShadowIndex < VisibleLightInfo.AllProjectedShadows.Num(); ShadowIndex++ )
{
const FProjectedShadowInfo* ProjectedShadowInfo = VisibleLightInfo.AllProjectedShadows[ShadowIndex];
// Check that the shadow is visible in at least one view before rendering it.
bool bShadowIsVisible = false;
for(int32 ViewIndex = 0;ViewIndex < Views.Num();ViewIndex++)
{
const FViewInfo& View = Views[ViewIndex];
if (ProjectedShadowInfo->DependentView && ProjectedShadowInfo->DependentView != &View && ProjectedShadowInfo->DependentView != View.GetPrimaryView())
{
continue;
}
const FVisibleLightViewInfo& VisibleLightViewInfo = View.VisibleLightInfos[LightSceneInfo->Id];
bShadowIsVisible |= VisibleLightViewInfo.ProjectedShadowVisibilityMap[ShadowIndex];
}
if(bShadowIsVisible)
{
return true;
}
}
return false;
}
void FSceneRenderer::RenderShadowProjections(
FRDGBuilder& GraphBuilder,
FRDGTextureRef OutputTexture,
const FMinimalSceneTextures& SceneTextures,
const FLightSceneProxy* LightSceneProxy,
TArrayView<const FProjectedShadowInfo* const> Shadows,
bool bSubPixelShadow,
bool bProjectingForForwardShading)
{
CheckShadowDepthRenderCompleted();
for (int32 ViewIndex = 0; ViewIndex < Views.Num(); ViewIndex++)
{
const FViewInfo& View = Views[ViewIndex];
const bool bIsInstancedStereoBypassed = View.bIsInstancedStereoEnabled && !View.bIsMobileMultiViewEnabled && IStereoRendering::IsStereoEyeView(View);
if ((!bIsInstancedStereoBypassed && !View.ShouldRenderView()) || (bSubPixelShadow && !HairStrands::HasViewHairStrandsData(View)))
{
continue;
}
const FExclusiveDepthStencil ExclusiveDepthStencil = FExclusiveDepthStencil::DepthRead_StencilWrite;
View.BeginRenderView();
// Sanity check
if (bSubPixelShadow)
{
check(View.HairStrandsViewData.VisibilityData.HairOnlyDepthTexture);
}
FShadowProjectionPassParameters CommonPassParameters;
CommonPassParameters.SceneTextures = SceneTextures.GetSceneTextureShaderParameters(View.FeatureLevel);
CommonPassParameters.HairStrands = HairStrands::BindHairStrandsViewUniformParameters(View);
if (Substrate::IsSubstrateEnabled())
{
CommonPassParameters.Substrate = Substrate::BindSubstrateGlobalUniformParameters(View);
}
FRDGTextureRef DepthStencilTexture = nullptr;
// When using MSAA we need to bind the texture with the correct number of samples
if (!(OutputTexture->Desc.NumSamples > 1) && SceneTextures.Depth.IsSeparate() && HasBeenProduced(SceneTextures.Depth.Resolve))
{
DepthStencilTexture = SceneTextures.Depth.Resolve;
}
else
{
DepthStencilTexture = SceneTextures.Depth.Target;
}
CommonPassParameters.RenderTargets[0] = FRenderTargetBinding(OutputTexture, ERenderTargetLoadAction::ELoad);
CommonPassParameters.RenderTargets.DepthStencil =
bSubPixelShadow ?
FDepthStencilBinding(View.HairStrandsViewData.VisibilityData.HairOnlyDepthTexture, ERenderTargetLoadAction::ELoad, ERenderTargetLoadAction::ELoad, ExclusiveDepthStencil) :
FDepthStencilBinding(DepthStencilTexture, ERenderTargetLoadAction::ELoad, ERenderTargetLoadAction::ELoad, ExclusiveDepthStencil);
RDG_GPU_MASK_SCOPE(GraphBuilder, View.GPUMask);
RDG_EVENT_SCOPE_CONDITIONAL(GraphBuilder, Views.Num() > 1, "View%d", ViewIndex);
// Project the shadow depth buffers onto the scene.
for (const FProjectedShadowInfo* ProjectedShadowInfo : Shadows)
{
if (ProjectedShadowInfo->bAllocated)
{
// Only project the shadow if it's large enough in this particular view (split screen, etc... may have shadows that are large in one view but irrelevantly small in others)
if (ProjectedShadowInfo->FadeAlphas[ViewIndex] > 1.0f / 256.0f)
{
if (ProjectedShadowInfo->bOnePassPointLightShadow)
{
ProjectedShadowInfo->RenderOnePassPointLightProjection(GraphBuilder, CommonPassParameters, ViewIndex, View, LightSceneProxy, bProjectingForForwardShading, bSubPixelShadow);
}
else
{
ProjectedShadowInfo->RenderProjection(GraphBuilder, CommonPassParameters, ViewIndex, &View, LightSceneProxy, this, bProjectingForForwardShading, bSubPixelShadow);
}
}
}
}
}
}
void FSceneRenderer::RenderShadowProjections(
FRDGBuilder& GraphBuilder,
const FMinimalSceneTextures& SceneTextures,
FRDGTextureRef ScreenShadowMaskTexture,
FRDGTextureRef ScreenShadowMaskSubPixelTexture,
const FLightSceneInfo* LightSceneInfo,
bool bProjectingForForwardShading)
{
CheckShadowDepthRenderCompleted();
const FVisibleLightInfo& VisibleLightInfo = VisibleLightInfos[LightSceneInfo->Id];
const FLightSceneProxy* LightSceneProxy = LightSceneInfo->Proxy;
// Allocate arrays using the graph allocator so we can safely reference them in passes.
using FProjectedShadowInfoArray = TArray<FProjectedShadowInfo*, SceneRenderingAllocator>;
auto& DistanceFieldShadows = *GraphBuilder.AllocObject<FProjectedShadowInfoArray>();
auto& NormalShadows = *GraphBuilder.AllocObject<FProjectedShadowInfoArray>();
for (int32 ShadowIndex = 0; ShadowIndex < VisibleLightInfo.ShadowsToProject.Num(); ShadowIndex++)
{
FProjectedShadowInfo* ProjectedShadowInfo = VisibleLightInfo.ShadowsToProject[ShadowIndex];
if (ProjectedShadowInfo->bRayTracedDistanceField)
{
DistanceFieldShadows.Add(ProjectedShadowInfo);
}
else if (ProjectedShadowInfo->HasVirtualShadowMap())
{
// Skip - handled elsewhere
}
else
{
NormalShadows.Add(ProjectedShadowInfo);
}
}
if (NormalShadows.Num() > 0)
{
const auto RenderNormalShadows = [&](FRDGTextureRef OutputTexture, bool bSubPixel)
{
FString LightNameWithLevel;
GetLightNameForDrawEvent(LightSceneProxy, LightNameWithLevel);
RDG_EVENT_SCOPE(GraphBuilder, "%s", *LightNameWithLevel);
FSceneRenderer::RenderShadowProjections(
GraphBuilder,
OutputTexture,
SceneTextures,
LightSceneProxy,
NormalShadows,
bSubPixel,
bProjectingForForwardShading);
};
if (ScreenShadowMaskTexture)
{
RDG_EVENT_SCOPE(GraphBuilder, "Shadows");
RenderNormalShadows(ScreenShadowMaskTexture, false);
}
if (ScreenShadowMaskSubPixelTexture)
{
RDG_EVENT_SCOPE(GraphBuilder, "SubPixelShadows");
RenderNormalShadows(ScreenShadowMaskSubPixelTexture, true);
}
}
if (ScreenShadowMaskTexture && DistanceFieldShadows.Num() > 0)
{
RDG_EVENT_SCOPE(GraphBuilder, "DistanceFieldShadows");
RDG_GPU_STAT_SCOPE(GraphBuilder, DistanceFieldShadows);
// Distance field shadows need to be renderer last as they blend over far shadow cascades.
for (int32 ViewIndex = 0; ViewIndex < Views.Num(); ViewIndex++)
{
const FViewInfo& View = Views[ViewIndex];
RDG_GPU_MASK_SCOPE(GraphBuilder, View.GPUMask);
RDG_EVENT_SCOPE_CONDITIONAL(GraphBuilder, Views.Num() > 1, "View%d", ViewIndex);
FIntRect ScissorRect;
if (!LightSceneProxy->GetScissorRect(ScissorRect, View, View.ViewRect))
{
ScissorRect = View.ViewRect;
}
if (ScissorRect.Area() > 0)
{
for (int32 ShadowIndex = 0; ShadowIndex < DistanceFieldShadows.Num(); ShadowIndex++)
{
FProjectedShadowInfo* ProjectedShadowInfo = DistanceFieldShadows[ShadowIndex];
if (Views.Num() == 1 || !ProjectedShadowInfo->DependentView || ProjectedShadowInfo->DependentView == &View || ProjectedShadowInfo->DependentView == View.GetPrimaryView())
{
ProjectedShadowInfo->RenderRayTracedDistanceFieldProjection(
GraphBuilder,
SceneTextures,
ScreenShadowMaskTexture,
View,
ScissorRect,
bProjectingForForwardShading);
}
}
}
}
}
}
void FSceneRenderer::BeginAsyncDistanceFieldShadowProjections(FRDGBuilder& GraphBuilder, const FMinimalSceneTextures& SceneTextures, const FDynamicShadowsTaskData* TaskData) const
{
extern int32 GDFShadowAsyncCompute;
TConstArrayView<FProjectedShadowInfo*> ProjectedDistanceFieldShadows = GetProjectedDistanceFieldShadows(TaskData);
if (!!GDFShadowAsyncCompute && ViewFamily.EngineShowFlags.DynamicShadows && GetShadowQuality() > 0 && ProjectedDistanceFieldShadows.Num() > 0)
{
RDG_EVENT_SCOPE(GraphBuilder, "DistanceFieldShadows");
RDG_GPU_STAT_SCOPE(GraphBuilder, DistanceFieldShadows);
for (int32 ViewIndex = 0; ViewIndex < Views.Num(); ViewIndex++)
{
const FViewInfo& View = Views[ViewIndex];
RDG_GPU_MASK_SCOPE(GraphBuilder, View.GPUMask);
RDG_EVENT_SCOPE_CONDITIONAL(GraphBuilder, Views.Num() > 1, "View%d", ViewIndex);
for (int32 DFShadowIndex = 0; DFShadowIndex < ProjectedDistanceFieldShadows.Num(); ++DFShadowIndex)
{
FProjectedShadowInfo* ProjectedShadowInfo = ProjectedDistanceFieldShadows[DFShadowIndex];
FIntRect ScissorRect;
if (ProjectedShadowInfo->bDirectionalLight || !ProjectedShadowInfo->GetLightSceneInfo().Proxy->GetScissorRect(ScissorRect, View, View.ViewRect))
{
ScissorRect = View.ViewRect;
}
if (ScissorRect.Area() > 0 && (Views.Num() == 1 || ProjectedShadowInfo->DependentView == &View || ProjectedShadowInfo->DependentView == View.GetPrimaryView() || !ProjectedShadowInfo->DependentView))
{
// Kick off distance field shadow calculation in async compute
// Don't need store result reference because it is internally cached by FProjectedShadowInfo
ProjectedShadowInfo->RenderRayTracedDistanceFieldProjection(GraphBuilder, true, SceneTextures, View, ScissorRect);
}
}
}
}
}
void FDeferredShadingSceneRenderer::CollectLightForTranslucencyLightingVolumeInjection(
const FLightSceneInfo* LightSceneInfo,
bool bSupportShadowMaps,
FTranslucentLightInjectionCollector& Collector)
{
bool bInjectedShadowedLight = false;
if (bSupportShadowMaps)
{
const FVisibleLightInfo& VisibleLightInfo = VisibleLightInfos[LightSceneInfo->Id];
const TArray<FProjectedShadowInfo*, SceneRenderingAllocator>& ShadowMaps = VisibleLightInfo.ShadowsToProject;
for (int32 ShadowIndex = 0; ShadowIndex < ShadowMaps.Num(); ShadowIndex++)
{
const FProjectedShadowInfo* ProjectedShadowInfo = ShadowMaps[ShadowIndex];
if (ProjectedShadowInfo->bAllocated
&& ProjectedShadowInfo->bWholeSceneShadow
// Not supported on translucency yet
&& !ProjectedShadowInfo->bRayTracedDistanceField
// Don't inject shadowed lighting with whole scene shadows used for previewing a light with static shadows,
// Since that would cause a mismatch with the built lighting
// However, stationary directional lights allow whole scene shadows that blend with precomputed shadowing
&& (!LightSceneInfo->Proxy->HasStaticShadowing() || ProjectedShadowInfo->IsWholeSceneDirectionalShadow()))
{
for (int32 ViewIndex = 0; ViewIndex < Views.Num(); ++ViewIndex)
{
const FViewInfo& View = Views[ViewIndex];
// The translucency volume is shared between eyes when doing stereo so only need to inject once for primary
if (View.IsPrimarySceneView() && LightSceneInfo->ShouldRenderLight(View))
{
const FViewInfo* DependentView = ProjectedShadowInfo->DependentView;
if (DependentView == nullptr || DependentView == &View)
{
Collector.AddLightForInjection(View, ViewIndex, VisibleLightInfos, *LightSceneInfo, ProjectedShadowInfo);
bInjectedShadowedLight = true;
}
}
}
}
}
}
if (!bInjectedShadowedLight)
{
// Add unshadowed (or shadowed via VSM) lighting contribution
for (int32 ViewIndex = 0; ViewIndex < Views.Num(); ++ViewIndex)
{
FViewInfo& View = Views[ViewIndex];
if (View.IsPrimarySceneView() && LightSceneInfo->ShouldRenderLight(View))
{
Collector.AddLightForInjection(View, ViewIndex, VisibleLightInfos, *LightSceneInfo);
}
}
}
}
void FDeferredShadingSceneRenderer::RenderDeferredShadowProjections(
FRDGBuilder& GraphBuilder,
const FMinimalSceneTextures& SceneTextures,
const FLightSceneInfo* LightSceneInfo,
FRDGTextureRef ScreenShadowMaskTexture,
FRDGTextureRef ScreenShadowMaskSubPixelTexture)
{
CheckShadowDepthRenderCompleted();
SCOPED_NAMED_EVENT(FDeferredShadingSceneRenderer_RenderShadowProjections, FColor::Emerald);
SCOPE_CYCLE_COUNTER(STAT_ProjectedShadowDrawTime);
RDG_EVENT_SCOPE_STAT(GraphBuilder, ShadowProjection, "ShadowProjectionOnOpaque");
RDG_GPU_STAT_SCOPE(GraphBuilder, ShadowProjection);
const bool bProjectingForForwardShading = false;
const bool bIsGBufferInput = ScreenShadowMaskTexture != nullptr;
const bool bIsHairInput = ScreenShadowMaskSubPixelTexture != nullptr;
if (bIsGBufferInput)
{
RenderShadowProjections(GraphBuilder, SceneTextures, ScreenShadowMaskTexture, nullptr, LightSceneInfo, bProjectingForForwardShading);
GetSceneExtensionsRenderers().GetRenderer<FShadowSceneRenderer>().ApplyVirtualShadowMapProjectionForLight(GraphBuilder, SceneTextures, LightSceneInfo, EVirtualShadowMapProjectionInputType::GBuffer, ScreenShadowMaskTexture);
const FVisibleLightInfo& VisibleLightInfo = VisibleLightInfos[LightSceneInfo->Id];
RenderCapsuleDirectShadows(GraphBuilder, *LightSceneInfo, ScreenShadowMaskTexture, VisibleLightInfo.CapsuleShadowsToProject, bProjectingForForwardShading);
// Inject deep shadow mask for regular shadow map. When using virtual shadow map, it is directly handled in the shadow kernel.
if (HairStrands::HasViewHairStrandsData(Views))
{
bool bNeedHairShadowMaskPass = false;
const bool bVirtualShadowOnePass = VisibleLightInfo.VirtualShadowMapClipmaps.Num() > 0;
if (!bVirtualShadowOnePass && VisibleLightInfo.ShadowsToProject.Num() > 0)
{
bNeedHairShadowMaskPass = !VisibleLightInfo.ShadowsToProject[0]->HasVirtualShadowMap();
}
if (bNeedHairShadowMaskPass)
{
RenderHairStrandsShadowMask(GraphBuilder, Views, LightSceneInfo, VisibleLightInfos, false /*bForward*/, ScreenShadowMaskTexture);
}
}
}
else if (bIsHairInput)
{
RenderShadowProjections(GraphBuilder, SceneTextures, nullptr, ScreenShadowMaskSubPixelTexture, LightSceneInfo, bProjectingForForwardShading);
GetSceneExtensionsRenderers().GetRenderer<FShadowSceneRenderer>().ApplyVirtualShadowMapProjectionForLight(GraphBuilder, SceneTextures, LightSceneInfo, EVirtualShadowMapProjectionInputType::HairStrands, ScreenShadowMaskSubPixelTexture);
}
}
void FMobileSceneRenderer::RenderModulatedShadowProjections(FRHICommandList& RHICmdList, int32 ViewIndex, const FViewInfo& View)
{
if (!ViewFamily.EngineShowFlags.DynamicShadows || View.bIsPlanarReflection || bRequiresShadowProjections)
{
return;
}
CSV_SCOPED_TIMING_STAT_EXCLUSIVE(RenderShadowProjections);
SCOPED_NAMED_EVENT(FMobileSceneRenderer_RenderModulatedShadowProjections, FColor::Emerald);
SCOPE_CYCLE_COUNTER(STAT_ProjectedShadowDrawTime);
RHI_BREADCRUMB_EVENT_STAT(RHICmdList, ShadowProjection, "ShadowProjectionOnOpaque");
SCOPED_GPU_STAT(RHICmdList, ShadowProjection);
// render shadowmaps for relevant lights.
for (auto LightIt = Scene->Lights.CreateConstIterator(); LightIt; ++LightIt)
{
const FLightSceneInfoCompact& LightSceneInfoCompact = *LightIt;
const FLightSceneInfo* LightSceneInfo = LightSceneInfoCompact.LightSceneInfo;
const FLightSceneProxy* LightSceneProxy = LightSceneInfo->Proxy;
if(LightSceneInfo->ShouldRenderLightViewIndependent() && LightSceneProxy && LightSceneProxy->CastsModulatedShadows())
{
const FVisibleLightInfo& VisibleLightInfo = VisibleLightInfos[LightSceneInfo->Id];
if (VisibleLightInfo.ShadowsToProject.Num() > 0)
{
LightSceneProxy->SetScissorRect(RHICmdList, View, View.ViewRect);
TArrayView<const FProjectedShadowInfo* const> Shadows = VisibleLightInfo.ShadowsToProject;
// Project the shadow depth buffers onto the scene.
for (const FProjectedShadowInfo* ProjectedShadowInfo : Shadows)
{
if (ProjectedShadowInfo->bAllocated)
{
// Only project the shadow if it's large enough in this particular view (split screen, etc... may have shadows that are large in one view but irrelevantly small in others)
if (ProjectedShadowInfo->FadeAlphas[ViewIndex] > 1.0f / 256.0f
// Skip, if it is a whole scene directional shadow
&& !ProjectedShadowInfo->IsWholeSceneDirectionalShadow())
{
checkSlow(!ProjectedShadowInfo->bOnePassPointLightShadow);
ProjectedShadowInfo->RenderMobileModulatedShadowProjection(RHICmdList, ViewIndex, &View, LightSceneProxy, this);
}
}
}
RHICmdList.SetScissorRect(false, 0, 0, 0, 0);
}
}
}
}
void InitMobileShadowProjectionOutputs(FRHICommandListImmediate& RHICmdList, const FIntPoint& Extent, const bool bRequireMultiView)
{
const FIntPoint& BufferSize = Extent;
if (!GScreenSpaceShadowMaskTextureMobileOutputs.IsValid() || GScreenSpaceShadowMaskTextureMobileOutputs.ScreenSpaceShadowMaskTextureMobile->GetDesc().Extent != BufferSize)
{
GScreenSpaceShadowMaskTextureMobileOutputs.ScreenSpaceShadowMaskTextureMobile.SafeRelease();
FPooledRenderTargetDesc Desc = bRequireMultiView ? FPooledRenderTargetDesc::Create2DArrayDesc(BufferSize, PF_B8G8R8A8, FClearValueBinding::White, TexCreate_None, TexCreate_RenderTargetable | TexCreate_ShaderResource, false, 2, 1, false)
: FPooledRenderTargetDesc::Create2DDesc(BufferSize, PF_B8G8R8A8, FClearValueBinding::White, TexCreate_None, TexCreate_RenderTargetable | TexCreate_ShaderResource, false, 1, false);
GRenderTargetPool.FindFreeElement(RHICmdList, Desc, GScreenSpaceShadowMaskTextureMobileOutputs.ScreenSpaceShadowMaskTextureMobile, TEXT("ForwardScreenSpaceShadowMaskTextureTexture"));
}
}
void FMobileSceneRenderer::RenderMobileShadowProjections(
FRDGBuilder& GraphBuilder)
{
RDG_RHI_EVENT_SCOPE(GraphBuilder, RenderMobileShadowProjections);
FRDGTextureRef ScreenShadowMaskTexture = GraphBuilder.RegisterExternalTexture(GScreenSpaceShadowMaskTextureMobileOutputs.ScreenSpaceShadowMaskTextureMobile, TEXT("ScreenSpaceShadowMaskTextureMobile"));
FRDGTextureClearInfo TextureClearInfo;
if (GRHISupportsBindingTexArrayPerSlice)
{
TextureClearInfo.NumSlices = ScreenShadowMaskTexture->Desc.ArraySize;
}
AddClearRenderTargetPass(GraphBuilder, ScreenShadowMaskTexture, TextureClearInfo);
const FMinimalSceneTextures& SceneTextures = GetActiveSceneTextures();
for (auto LightIt = Scene->Lights.CreateConstIterator(); LightIt; ++LightIt)
{
const FLightSceneInfoCompact& LightSceneInfoCompact = *LightIt;
const FLightSceneInfo* LightSceneInfo = LightSceneInfoCompact.LightSceneInfo;
const FVisibleLightInfo& VisibleLightInfo = VisibleLightInfos[LightSceneInfo->Id];
const FLightSceneProxy* LightSceneProxy = LightSceneInfo->Proxy;
const bool bProjectingForForwardShading = true;
// Local light shadows don't render to shadow mask texture on mobile deferred
if (LightSceneProxy->GetLightType() == LightType_Directional || !IsMobileDeferredShadingEnabled(ShaderPlatform))
{
RenderShadowProjections(GraphBuilder, SceneTextures,
ScreenShadowMaskTexture,
nullptr,
LightSceneInfo,
bProjectingForForwardShading);
}
RenderCapsuleDirectShadows(GraphBuilder, *LightSceneInfo, ScreenShadowMaskTexture, VisibleLightInfo.CapsuleShadowsToProject, bProjectingForForwardShading);
if (LightSceneProxy->GetLightType() == LightType_Directional && LightSceneInfo->GetDynamicShadowMapChannel() != -1)
{
// Dynamic shadows are projected into channels of the light attenuation texture based on their assigned DynamicShadowMapChannel
// Only render screen space shadows if light is assigned to a valid DynamicShadowMapChannel
RenderScreenSpaceShadows(GraphBuilder, SceneTextures, Views, LightSceneInfo, bProjectingForForwardShading, ScreenShadowMaskTexture);
}
}
}
void ReleaseMobileShadowProjectionOutputs()
{
GScreenSpaceShadowMaskTextureMobileOutputs.Release();
}
IMPLEMENT_GLOBAL_SHADER_PARAMETER_STRUCT(FTranslucentSelfShadowUniformParameters, "TranslucentSelfShadow");
void SetupTranslucentSelfShadowUniformParameters(const FProjectedShadowInfo* ShadowInfo, FTranslucentSelfShadowUniformParameters& OutParameters)
{
if (ShadowInfo)
{
FVector4f ShadowmapMinMax;
FMatrix WorldToShadowMatrixValue = ShadowInfo->GetWorldToShadowMatrix(ShadowmapMinMax);
OutParameters.WorldToShadowMatrix = FMatrix44f(WorldToShadowMatrixValue); // LWC_TODO: Precision loss?
OutParameters.ShadowUVMinMax = ShadowmapMinMax;
const FLightSceneProxy* const LightProxy = ShadowInfo->GetLightSceneInfo().Proxy;
OutParameters.DirectionalLightDirection = FVector3f(LightProxy->GetDirection());
//@todo - support fading from both views
const float FadeAlpha = ShadowInfo->FadeAlphas[0];
FLinearColor LightColor;
if (LightProxy->IsUsedAsAtmosphereSunLight())
{
LightColor = LightProxy->GetSunIlluminanceAccountingForSkyAtmospherePerPixelTransmittance();
}
else
{
LightColor = LightProxy->GetColor();
}
// Incorporate the diffuse scale of 1 / PI into the light color
OutParameters.DirectionalLightColor = FVector4f(FVector3f(LightColor * FadeAlpha / PI), FadeAlpha);
OutParameters.Transmission0 = ShadowInfo->RenderTargets.ColorTargets[0]->GetRHI();
OutParameters.Transmission1 = ShadowInfo->RenderTargets.ColorTargets[1]->GetRHI();
OutParameters.Transmission0Sampler = TStaticSamplerState<SF_Bilinear, AM_Clamp, AM_Clamp, AM_Clamp>::GetRHI();
OutParameters.Transmission1Sampler = TStaticSamplerState<SF_Bilinear, AM_Clamp, AM_Clamp, AM_Clamp>::GetRHI();
}
else
{
OutParameters.Transmission0 = GBlackTexture->TextureRHI;
OutParameters.Transmission1 = GBlackTexture->TextureRHI;
OutParameters.Transmission0Sampler = GBlackTexture->SamplerStateRHI;
OutParameters.Transmission1Sampler = GBlackTexture->SamplerStateRHI;
OutParameters.DirectionalLightColor = FVector4f(0.0f, 0.0f, 0.0f, 0.0f);
}
}
void FEmptyTranslucentSelfShadowUniformBuffer::InitRHI(FRHICommandListBase& RHICmdList)
{
FTranslucentSelfShadowUniformParameters Parameters;
SetupTranslucentSelfShadowUniformParameters(nullptr, Parameters);
SetContentsNoUpdate(Parameters);
Super::InitRHI(RHICmdList);
}
FViewMatrices FProjectedShadowInfo::GetShadowDepthRenderingViewMatrices(int32 CubeFaceIndex, bool bUseForVSMCubeFaceWorkaround) const
{
FViewMatrices::FMinimalInitializer MatricesInitializer;
MatricesInitializer.ViewOrigin = -PreShadowTranslation;
MatricesInitializer.ConstrainedViewRect = GetOuterViewRect();
ensure(!bOnePassPointLightShadow || CubeFaceIndex >= 0 && CubeFaceIndex < 6);
if (bOnePassPointLightShadow && CubeFaceIndex >= 0 && CubeFaceIndex < 6)
{
if (bUseForVSMCubeFaceWorkaround)
{
MatricesInitializer.ViewRotationMatrix = OnePassShadowViewMatrices[CubeFaceIndex] * FScaleMatrix(FVector(1, 1, -1));
}
else
{
MatricesInitializer.ViewRotationMatrix = OnePassShadowViewMatrices[CubeFaceIndex];
}
MatricesInitializer.ProjectionMatrix = OnePassShadowFaceProjectionMatrix;
}
else
{
MatricesInitializer.ViewRotationMatrix = TranslatedWorldToView;
MatricesInitializer.ProjectionMatrix = ViewToClipOuter;
}
return FViewMatrices(MatricesInitializer);
}
/** */
TGlobalResource< FEmptyTranslucentSelfShadowUniformBuffer > GEmptyTranslucentSelfShadowUniformBuffer;