Files
UnrealEngineUWP/Engine/Source/Runtime/Renderer/Private/ShadowSetup.cpp
Rolando Caloca 059213b569 UE4 - Help track down a crash while rendering per object shadows (UE-14193)
[CL 2525002 by Rolando Caloca in Main branch]
2015-04-24 17:27:27 -04:00

2431 lines
102 KiB
C++

// Copyright 1998-2015 Epic Games, Inc. All Rights Reserved.
/*=============================================================================
ShadowSetup.cpp: Dynamic shadow setup implementation.
=============================================================================*/
#include "RendererPrivate.h"
#include "ScenePrivate.h"
#include "LightPropagationVolume.h"
static float GMinScreenRadiusForShadowCaster = 0.03f;
static FAutoConsoleVariableRef CVarMinScreenRadiusForShadowCaster(
TEXT("r.Shadow.RadiusThreshold"),
GMinScreenRadiusForShadowCaster,
TEXT("Cull shadow casters if they are too small, value is the minimal screen space bounding sphere radius\n")
TEXT("(default 0.03)"),
ECVF_Scalability | ECVF_RenderThreadSafe
);
static float GMinScreenRadiusForShadowCasterRSM = 0.06f;
static FAutoConsoleVariableRef CVarMinScreenRadiusForShadowCasterRSM(
TEXT("r.Shadow.RadiusThresholdRSM"),
GMinScreenRadiusForShadowCasterRSM,
TEXT("Cull shadow casters in the RSM if they are too small, values is the minimal screen space bounding sphere radius\n")
TEXT("(default 0.06)")
);
/** Can be used to visualize preshadow frustums when the shadowfrustums show flag is enabled. */
static TAutoConsoleVariable<int32> CVarDrawPreshadowFrustum(
TEXT("r.Shadow.DrawPreshadowFrustums"),
0,
TEXT("visualize preshadow frustums when the shadowfrustums show flag is enabled"),
ECVF_RenderThreadSafe
);
/** Whether to allow preshadows (static world casting on character), can be disabled for debugging. */
static TAutoConsoleVariable<int32> CVarAllowPreshadows(
TEXT("r.Shadow.Preshadows"),
1,
TEXT("Whether to allow preshadows (static world casting on character)"),
ECVF_RenderThreadSafe
);
/** Whether to allow per object shadows (character casting on world), can be disabled for debugging. */
static TAutoConsoleVariable<int32> CVarAllowPerObjectShadows(
TEXT("r.Shadow.PerObject"),
1,
TEXT("Whether to render per object shadows (character casting on world)\n")
TEXT("0: off\n")
TEXT("1: on (default)"),
ECVF_RenderThreadSafe
);
static TAutoConsoleVariable<float> CVarShadowFadeExponent(
TEXT("r.Shadow.FadeExponent"),
0.25f,
TEXT("Controls the rate at which shadows are faded out"),
ECVF_RenderThreadSafe);
/**
* Whether preshadows can be cached as an optimization.
* Disabling the caching through this setting is useful when debugging.
*/
static TAutoConsoleVariable<int32> CVarCachePreshadows(
TEXT("r.Shadow.CachePreshadow"),
1,
TEXT("Whether preshadows can be cached as an optimization"),
ECVF_RenderThreadSafe
);
bool ShouldUseCachePreshadows()
{
return CVarCachePreshadows.GetValueOnRenderThread() != 0;
}
/**
* This value specifies how much bounds will be expanded when rendering a cached preshadow (0.15 = 15% larger).
* Larger values result in more cache hits, but lower resolution and pull more objects into the depth pass.
*/
static TAutoConsoleVariable<float> CVarPreshadowExpandFraction(
TEXT("r.Shadow.PreshadowExpand"),
0.15f,
TEXT("How much bounds will be expanded when rendering a cached preshadow (0.15 = 15% larger)"),
ECVF_RenderThreadSafe
);
static TAutoConsoleVariable<float> CVarPreShadowResolutionFactor(
TEXT("r.Shadow.PreShadowResolutionFactor"),
0.5f,
TEXT("Mulitplier for preshadow resolution"),
ECVF_RenderThreadSafe);
static TAutoConsoleVariable<float> CVarShadowTexelsPerPixel(
TEXT("r.Shadow.TexelsPerPixel"),
1.27324f,
TEXT("The ratio of subject pixels to shadow texels for per-object shadows"),
ECVF_RenderThreadSafe);
static TAutoConsoleVariable<float> CVarShadowTexelsPerPixelSpotlight(
TEXT("r.Shadow.TexelsPerPixelSpotlight"),
1.27324f,
TEXT("The ratio of subject pixels to shadow texels for spotlights"),
ECVF_RenderThreadSafe);
static TAutoConsoleVariable<int32> CVarPreShadowFadeResolution(
TEXT("r.Shadow.PreShadowFadeResolution"),
16,
TEXT("Resolution in texels below which preshadows are faded out"),
ECVF_RenderThreadSafe);
static TAutoConsoleVariable<int32> CVarShadowFadeResolution(
TEXT("r.Shadow.FadeResolution"),
64,
TEXT("Resolution in texels below which shadows are faded out"),
ECVF_RenderThreadSafe);
static TAutoConsoleVariable<int32> CVarMinShadowResolution(
TEXT("r.Shadow.MinResolution"),
32,
TEXT("Minimum dimensions (in texels) allowed for rendering shadow subject depths"),
ECVF_RenderThreadSafe);
static TAutoConsoleVariable<int32> CVarMinPreShadowResolution(
TEXT("r.Shadow.MinPreShadowResolution"),
8,
TEXT("Minimum dimensions (in texels) allowed for rendering preshadow depths"),
ECVF_RenderThreadSafe);
static TAutoConsoleVariable<int32> CVarUseConservativeShadowBounds(
TEXT("r.Shadow.ConservativeBounds"),
0,
TEXT("Whether to use safe and conservative shadow frustum creation that wastes some shadowmap space"),
ECVF_RenderThreadSafe);
#if !(UE_BUILD_SHIPPING || UE_BUILD_TEST)
// read and written on the render thread
bool GDumpShadowSetup = false;
void DumpShadowDumpSetup()
{
ENQUEUE_UNIQUE_RENDER_COMMAND(
DumpShadowDumpSetup,
{
GDumpShadowSetup = true;
});
}
FAutoConsoleCommand CmdDumpShadowDumpSetup(
TEXT("r.DumpShadows"),
TEXT("Dump shadow setup (for developer only, only for non shiping build)"),
FConsoleCommandDelegate::CreateStatic(DumpShadowDumpSetup)
);
#endif // !(UE_BUILD_SHIPPING || UE_BUILD_TEST)
/**
* Helper function to determine fade alpha value for shadows based on resolution. In the below ASCII art (1) is
* the MinShadowResolution and (2) is the ShadowFadeResolution. Alpha will be 0 below the min resolution and 1
* above the fade resolution. In between it is going to be an exponential curve with the values between (1) and (2)
* being normalized in the 0..1 range.
*
*
* | /-------
* | /
* |/
* 1-----2-------
*
* @param MaxUnclampedResolution Requested resolution, unclamped so it can be below min
* @param ShadowFadeResolution Resolution at which fade begins
* @param MinShadowResolution Minimum resolution of shadow
*
* @return fade value between 0 and 1
*/
float CalculateShadowFadeAlpha(float MaxUnclampedResolution, int32 ShadowFadeResolution, int32 MinShadowResolution)
{
check(MaxUnclampedResolution >= 0);
check(ShadowFadeResolution >= 0);
check(MinShadowResolution >= 0);
float FadeAlpha = 0.0f;
// Shadow size is above fading resolution.
if (MaxUnclampedResolution > ShadowFadeResolution)
{
FadeAlpha = 1.0f;
}
// Shadow size is below fading resolution but above min resolution.
else if (MaxUnclampedResolution > MinShadowResolution)
{
const float InverseRange = 1.0f / (ShadowFadeResolution - MinShadowResolution);
const float FirstFadeValue = FMath::Pow(InverseRange, CVarShadowFadeExponent.GetValueOnRenderThread());
const float SizeRatio = (float)(MaxUnclampedResolution - MinShadowResolution) * InverseRange;
// Rescale the fade alpha to reduce the change between no fading and the first value, which reduces popping with small ShadowFadeExponent's
FadeAlpha = (FMath::Pow(SizeRatio, CVarShadowFadeExponent.GetValueOnRenderThread()) - FirstFadeValue) / (1.0f - FirstFadeValue);
}
return FadeAlpha;
}
typedef TArray<FVector,TInlineAllocator<8> > FBoundingBoxVertexArray;
/** Stores the indices for an edge of a bounding volume. */
struct FBoxEdge
{
uint16 FirstEdgeIndex;
uint16 SecondEdgeIndex;
FBoxEdge(uint16 InFirst, uint16 InSecond) :
FirstEdgeIndex(InFirst),
SecondEdgeIndex(InSecond)
{}
};
typedef TArray<FBoxEdge,TInlineAllocator<12> > FBoundingBoxEdgeArray;
/**
* Creates an array of vertices and edges for a bounding box.
* @param Box - The bounding box
* @param OutVertices - Upon return, the array will contain the vertices of the bounding box.
* @param OutEdges - Upon return, will contain indices of the edges of the bounding box.
*/
static void GetBoundingBoxVertices(const FBox& Box,FBoundingBoxVertexArray& OutVertices, FBoundingBoxEdgeArray& OutEdges)
{
OutVertices.Empty(8);
OutVertices.AddUninitialized(8);
for(int32 X = 0;X < 2;X++)
{
for(int32 Y = 0;Y < 2;Y++)
{
for(int32 Z = 0;Z < 2;Z++)
{
OutVertices[X * 4 + Y * 2 + Z] = FVector(
X ? Box.Min.X : Box.Max.X,
Y ? Box.Min.Y : Box.Max.Y,
Z ? Box.Min.Z : Box.Max.Z
);
}
}
}
OutEdges.Empty(12);
OutEdges.AddUninitialized(12);
for(uint16 X = 0;X < 2;X++)
{
uint16 BaseIndex = X * 4;
OutEdges[X * 4 + 0] = FBoxEdge(BaseIndex, BaseIndex + 1);
OutEdges[X * 4 + 1] = FBoxEdge(BaseIndex + 1, BaseIndex + 3);
OutEdges[X * 4 + 2] = FBoxEdge(BaseIndex + 3, BaseIndex + 2);
OutEdges[X * 4 + 3] = FBoxEdge(BaseIndex + 2, BaseIndex);
}
for(uint16 XEdge = 0;XEdge < 4;XEdge++)
{
OutEdges[8 + XEdge] = FBoxEdge(XEdge, XEdge + 4);
}
}
/**
* Computes the transform contains a set of bounding box vertices and minimizes the pre-transform volume inside the post-transform clip space.
* @param ZAxis - The Z axis of the transform.
* @param Points - The points that represent the bounding volume.
* @param Edges - The edges of the bounding volume.
* @param OutAspectRatio - Upon successful return, contains the aspect ratio of the AABB; the ratio of width:height.
* @param OutTransform - Upon successful return, contains the transform.
* @return true if it successfully found a non-zero area projection of the bounding points.
*/
static bool GetBestShadowTransform(const FVector& ZAxis,const FBoundingBoxVertexArray& Points, const FBoundingBoxEdgeArray& Edges, float& OutAspectRatio, FMatrix& OutTransform)
{
// Find the axis parallel to the edge between any two boundary points with the smallest projection of the bounds onto the axis.
FVector XAxis(0,0,0);
FVector YAxis(0,0,0);
FVector Translation(0,0,0);
float BestProjectedExtent = FLT_MAX;
bool bValidProjection = false;
// Cache unaliased pointers to point and edge data
const FVector* RESTRICT PointsPtr = Points.GetData();
const FBoxEdge* RESTRICT EdgesPtr = Edges.GetData();
const int32 NumPoints = Points.Num();
const int32 NumEdges = Edges.Num();
// We're always dealing with box geometry here, so we can hint the compiler
ASSUME( NumPoints == 8 );
ASSUME( NumEdges == 12 );
for(int32 EdgeIndex = 0;EdgeIndex < NumEdges; ++EdgeIndex)
{
const FVector Point = PointsPtr[EdgesPtr[EdgeIndex].FirstEdgeIndex];
const FVector OtherPoint = PointsPtr[EdgesPtr[EdgeIndex].SecondEdgeIndex];
const FVector PointDelta = OtherPoint - Point;
const FVector TrialXAxis = (PointDelta - ZAxis * (PointDelta | ZAxis)).GetSafeNormal();
const FVector TrialYAxis = (ZAxis ^ TrialXAxis).GetSafeNormal();
// Calculate the size of the projection of the bounds onto this axis and an axis orthogonal to it and the Z axis.
float MinProjectedX = FLT_MAX;
float MaxProjectedX = -FLT_MAX;
float MinProjectedY = FLT_MAX;
float MaxProjectedY = -FLT_MAX;
for(int32 ProjectedPointIndex = 0;ProjectedPointIndex < NumPoints; ++ProjectedPointIndex)
{
const float ProjectedX = PointsPtr[ProjectedPointIndex] | TrialXAxis;
MinProjectedX = FMath::Min(MinProjectedX,ProjectedX);
MaxProjectedX = FMath::Max(MaxProjectedX,ProjectedX);
const float ProjectedY = PointsPtr[ProjectedPointIndex] | TrialYAxis;
MinProjectedY = FMath::Min(MinProjectedY,ProjectedY);
MaxProjectedY = FMath::Max(MaxProjectedY,ProjectedY);
}
float ProjectedExtentX;
float ProjectedExtentY;
if (CVarUseConservativeShadowBounds.GetValueOnRenderThread() != 0)
{
ProjectedExtentX = 2 * FMath::Max(FMath::Abs(MaxProjectedX), FMath::Abs(MinProjectedX));
ProjectedExtentY = 2 * FMath::Max(FMath::Abs(MaxProjectedY), FMath::Abs(MinProjectedY));
}
else
{
ProjectedExtentX = MaxProjectedX - MinProjectedX;
ProjectedExtentY = MaxProjectedY - MinProjectedY;
}
const float ProjectedExtent = ProjectedExtentX * ProjectedExtentY;
if(ProjectedExtent < BestProjectedExtent - .05f
// Only allow projections with non-zero area
&& ProjectedExtent > DELTA)
{
bValidProjection = true;
BestProjectedExtent = ProjectedExtent;
XAxis = TrialXAxis * 2.0f / ProjectedExtentX;
YAxis = TrialYAxis * 2.0f / ProjectedExtentY;
// Translating in post-transform clip space can cause the corners of the world space bounds to be outside of the transform generated by this function
// This usually manifests in cinematics where the character's head is near the top of the bounds
if (CVarUseConservativeShadowBounds.GetValueOnRenderThread() == 0)
{
Translation.X = (MinProjectedX + MaxProjectedX) * 0.5f;
Translation.Y = (MinProjectedY + MaxProjectedY) * 0.5f;
}
if(ProjectedExtentY > ProjectedExtentX)
{
// Always make the X axis the largest one.
Exchange(XAxis,YAxis);
Exchange(Translation.X,Translation.Y);
XAxis *= -1.0f;
Translation.X *= -1.0f;
OutAspectRatio = ProjectedExtentY / ProjectedExtentX;
}
else
{
OutAspectRatio = ProjectedExtentX / ProjectedExtentY;
}
}
}
// Only create the shadow if the projected extent of the given points has a non-zero area.
if(bValidProjection && BestProjectedExtent > DELTA)
{
OutTransform = FBasisVectorMatrix(XAxis,YAxis,ZAxis,FVector(0,0,0)) * FTranslationMatrix(Translation);
return true;
}
else
{
return false;
}
}
FProjectedShadowInfo::FProjectedShadowInfo()
: DependentView(0)
, ShadowId(INDEX_NONE)
, PreShadowTranslation(0, 0, 0)
, ShadowBounds(0)
, X(0)
, Y(0)
, ResolutionX(0)
, ResolutionY(0)
, MaxScreenPercent(1.0f)
, bAllocated(false)
, bAllocatedInTranslucentLayout(false)
, bRendered(false)
, bAllocatedInPreshadowCache(false)
, bDepthsCached(false)
, bDirectionalLight(false)
, bWholeSceneShadow(false)
, bReflectiveShadowmap(false)
, bTranslucentShadow(false)
, bPreShadow(false)
, bSelfShadowOnly(false)
, LightSceneInfo(0)
, ParentSceneInfo(0)
, ShaderDepthBias(0.0f)
{
}
bool FProjectedShadowInfo::SetupPerObjectProjection(
FLightSceneInfo* InLightSceneInfo,
const FPrimitiveSceneInfo* InParentSceneInfo,
const FPerObjectProjectedShadowInitializer& Initializer,
bool bInPreShadow,
uint32 InResolutionX,
uint32 MaxShadowResolutionY,
float InMaxScreenPercent,
bool bInTranslucentShadow)
{
check(InParentSceneInfo);
LightSceneInfo = InLightSceneInfo;
LightSceneInfoCompact = InLightSceneInfo;
ParentSceneInfo = InParentSceneInfo;
PreShadowTranslation = Initializer.PreShadowTranslation;
ShadowBounds = FSphere(Initializer.SubjectBounds.Origin - Initializer.PreShadowTranslation, Initializer.SubjectBounds.SphereRadius);
ResolutionX = InResolutionX;
MaxScreenPercent = InMaxScreenPercent;
bDirectionalLight = InLightSceneInfo->Proxy->GetLightType() == LightType_Directional;
bTranslucentShadow = bInTranslucentShadow;
bPreShadow = bInPreShadow;
bSelfShadowOnly = InParentSceneInfo->Proxy->CastsSelfShadowOnly();
check(!CascadeSettings.bRayTracedDistanceField);
const FMatrix WorldToLightScaled = Initializer.WorldToLight * FScaleMatrix(Initializer.Scales);
// Create an array of the extreme vertices of the subject's bounds.
FBoundingBoxVertexArray BoundsPoints;
FBoundingBoxEdgeArray BoundsEdges;
GetBoundingBoxVertices(Initializer.SubjectBounds.GetBox(),BoundsPoints,BoundsEdges);
// Project the bounding box vertices.
FBoundingBoxVertexArray ProjectedBoundsPoints;
for (int32 PointIndex = 0; PointIndex < BoundsPoints.Num(); PointIndex++)
{
const FVector TransformedBoundsPoint = WorldToLightScaled.TransformPosition(BoundsPoints[PointIndex]);
const float TransformedBoundsPointW = Dot4(FVector4(0, 0, TransformedBoundsPoint | Initializer.FaceDirection,1), Initializer.WAxis);
if (TransformedBoundsPointW >= DELTA)
{
ProjectedBoundsPoints.Add(TransformedBoundsPoint / TransformedBoundsPointW);
}
else
{
ProjectedBoundsPoints.Add(FVector(FLT_MAX, FLT_MAX, FLT_MAX));
}
}
// Compute the transform from light-space to shadow-space.
FMatrix LightToShadow;
float AspectRatio;
// if this is a valid transform (can be false if the object is around the light)
bool bRet = false;
if (GetBestShadowTransform(Initializer.FaceDirection.GetSafeNormal(), ProjectedBoundsPoints, BoundsEdges, AspectRatio, LightToShadow))
{
bRet = true;
const FMatrix WorldToShadow = WorldToLightScaled * LightToShadow;
const FBox ShadowSubjectBounds = Initializer.SubjectBounds.GetBox().TransformBy(WorldToShadow);
MinSubjectZ = FMath::Max(Initializer.MinLightW, ShadowSubjectBounds.Min.Z);
float MaxReceiverZ = FMath::Min(MinSubjectZ + Initializer.MaxDistanceToCastInLightW, (float)HALF_WORLD_MAX);
// Max can end up smaller than min due to the clamp to HALF_WORLD_MAX above
MaxReceiverZ = FMath::Max(MaxReceiverZ, MinSubjectZ + 1);
MaxSubjectZ = FMath::Max(ShadowSubjectBounds.Max.Z, MinSubjectZ + 1);
const FMatrix SubjectMatrix = WorldToShadow * FShadowProjectionMatrix(MinSubjectZ, MaxSubjectZ, Initializer.WAxis);
const float MaxSubjectAndReceiverDepth = Initializer.SubjectBounds.GetBox().TransformBy(SubjectMatrix).Max.Z;
float MaxSubjectDepth;
if (bPreShadow)
{
const FMatrix PreSubjectMatrix = WorldToShadow * FShadowProjectionMatrix(Initializer.MinLightW, MaxSubjectZ, Initializer.WAxis);
// Preshadow frustum bounds go from the light to the furthest extent of the object in light space
SubjectAndReceiverMatrix = PreSubjectMatrix;
ReceiverMatrix = SubjectMatrix;
MaxSubjectDepth = bDirectionalLight ? MaxSubjectAndReceiverDepth : Initializer.SubjectBounds.GetBox().TransformBy(PreSubjectMatrix).Max.Z;
}
else
{
const FMatrix PostSubjectMatrix = WorldToShadow * FShadowProjectionMatrix(MinSubjectZ, MaxReceiverZ, Initializer.WAxis);
SubjectAndReceiverMatrix = SubjectMatrix;
ReceiverMatrix = PostSubjectMatrix;
MaxSubjectDepth = MaxSubjectAndReceiverDepth;
}
InvMaxSubjectDepth = 1.0f / MaxSubjectDepth;
MinPreSubjectZ = Initializer.MinLightW;
ResolutionY = FMath::Min<uint32>(FMath::TruncToInt(InResolutionX / AspectRatio), MaxShadowResolutionY);
// Store the view matrix
// Reorder the vectors to match the main view, since ShadowViewMatrix will be used to override the main view's view matrix during shadow depth rendering
ShadowViewMatrix = Initializer.WorldToLight *
FMatrix(
FPlane(0, 0, 1, 0),
FPlane(1, 0, 0, 0),
FPlane(0, 1, 0, 0),
FPlane(0, 0, 0, 1));
GetViewFrustumBounds(CasterFrustum,SubjectAndReceiverMatrix,true);
InvReceiverMatrix = ReceiverMatrix.InverseFast();
GetViewFrustumBounds(ReceiverFrustum,ReceiverMatrix,true);
UpdateShaderDepthBias();
}
return bRet;
}
void FProjectedShadowInfo::SetupWholeSceneProjection(
FLightSceneInfo* InLightSceneInfo,
FViewInfo* InDependentView,
const FWholeSceneProjectedShadowInitializer& Initializer,
uint32 InResolutionX,
uint32 InResolutionY,
bool bInReflectiveShadowMap)
{
LightSceneInfo = InLightSceneInfo;
LightSceneInfoCompact = InLightSceneInfo;
DependentView = InDependentView;
PreShadowTranslation = Initializer.PreShadowTranslation;
CascadeSettings = Initializer.CascadeSettings;
ResolutionX = InResolutionX;
ResolutionY = InResolutionY;
bDirectionalLight = InLightSceneInfo->Proxy->GetLightType() == LightType_Directional;
bWholeSceneShadow = true;
bReflectiveShadowmap = bInReflectiveShadowMap;
FVector XAxis, YAxis;
Initializer.FaceDirection.FindBestAxisVectors(XAxis,YAxis);
const FMatrix WorldToLightScaled = Initializer.WorldToLight * FScaleMatrix(Initializer.Scales);
const FMatrix WorldToFace = WorldToLightScaled * FBasisVectorMatrix(-XAxis,YAxis,Initializer.FaceDirection.GetSafeNormal(),FVector::ZeroVector);
MaxSubjectZ = WorldToFace.TransformPosition(Initializer.SubjectBounds.Origin).Z + Initializer.SubjectBounds.SphereRadius;
MinSubjectZ = FMath::Max(MaxSubjectZ - Initializer.SubjectBounds.SphereRadius * 2,Initializer.MinLightW);
if(bInReflectiveShadowMap)
{
check(!CascadeSettings.bOnePassPointLightShadow);
check(!CascadeSettings.ShadowSplitIndex);
// Quantise the RSM in shadow texel space
static bool bQuantize = true;
if ( bQuantize )
{
const FIntPoint ShadowBufferResolution = GSceneRenderTargets.GetReflectiveShadowMapTextureResolution();
uint32 ShadowDepthBufferSizeX = ShadowBufferResolution.X;
uint32 ShadowDepthBufferSizeY = ShadowBufferResolution.Y;
// Transform the shadow's position into shadowmap space
const FVector TransformedPosition = WorldToFace.TransformPosition(-PreShadowTranslation);
// Largest amount that the shadowmap will be downsampled to during sampling
// We need to take this into account when snapping to get a stable result
// This corresponds to the maximum kernel filter size used by subsurface shadows in ShadowProjectionPixelShader.usf
static int32 MaxDownsampleFactor = 4;
// Determine the distance necessary to snap the shadow's position to the nearest texel
const float SnapX = FMath::Fmod(TransformedPosition.X, 2.0f * MaxDownsampleFactor / ShadowDepthBufferSizeX);
const float SnapY = FMath::Fmod(TransformedPosition.Y, 2.0f * MaxDownsampleFactor / ShadowDepthBufferSizeY);
// Snap the shadow's position and transform it back into world space
// This snapping prevents sub-texel camera movements which removes view dependent aliasing from the final shadow result
// This only maintains stable shadows under camera translation and rotation
const FVector SnappedWorldPosition = WorldToFace.InverseFast().TransformPosition(TransformedPosition - FVector(SnapX, SnapY, 0.0f));
PreShadowTranslation = -SnappedWorldPosition;
}
ShadowBounds = FSphere(-PreShadowTranslation, Initializer.SubjectBounds.SphereRadius);
GetViewFrustumBounds(CasterFrustum, SubjectAndReceiverMatrix, true);
}
else
{
if(bDirectionalLight)
{
// Limit how small the depth range can be for smaller cascades
// This is needed for shadow modes like subsurface shadows which need depth information outside of the smaller cascade depth range
//@todo - expose this value to the ini
const float DepthRangeClamp = 5000;
MaxSubjectZ = FMath::Max(MaxSubjectZ, DepthRangeClamp);
MinSubjectZ = FMath::Min(MinSubjectZ, -DepthRangeClamp);
const FIntPoint ShadowBufferResolution = GSceneRenderTargets.GetShadowDepthTextureResolution();
const uint32 ShadowDepthBufferSizeX = ShadowBufferResolution.X - SHADOW_BORDER * 2;
const uint32 ShadowDepthBufferSizeY = ShadowBufferResolution.Y - SHADOW_BORDER * 2;
// Transform the shadow's position into shadowmap space
const FVector TransformedPosition = WorldToFace.TransformPosition(-PreShadowTranslation);
// Largest amount that the shadowmap will be downsampled to during sampling
// We need to take this into account when snapping to get a stable result
// This corresponds to the maximum kernel filter size used by subsurface shadows in ShadowProjectionPixelShader.usf
const int32 MaxDownsampleFactor = 4;
// Determine the distance necessary to snap the shadow's position to the nearest texel
const float SnapX = FMath::Fmod(TransformedPosition.X, 2.0f * MaxDownsampleFactor / ShadowDepthBufferSizeX);
const float SnapY = FMath::Fmod(TransformedPosition.Y, 2.0f * MaxDownsampleFactor / ShadowDepthBufferSizeY);
// Snap the shadow's position and transform it back into world space
// This snapping prevents sub-texel camera movements which removes view dependent aliasing from the final shadow result
// This only maintains stable shadows under camera translation and rotation
const FVector SnappedWorldPosition = WorldToFace.InverseFast().TransformPosition(TransformedPosition - FVector(SnapX, SnapY, 0.0f));
PreShadowTranslation = -SnappedWorldPosition;
}
if (CascadeSettings.ShadowSplitIndex >= 0 && bDirectionalLight)
{
checkSlow(InDependentView);
ShadowBounds = InLightSceneInfo->Proxy->GetShadowSplitBounds(
*InDependentView,
CascadeSettings.bRayTracedDistanceField ? INDEX_NONE : CascadeSettings.ShadowSplitIndex,
InLightSceneInfo->IsPrecomputedLightingValid(),
0);
}
else
{
ShadowBounds = FSphere(-Initializer.PreShadowTranslation, Initializer.SubjectBounds.SphereRadius);
}
// Any meshes between the light and the subject can cast shadows, also any meshes inside the subject region
const FMatrix CasterMatrix = WorldToFace * FShadowProjectionMatrix(Initializer.MinLightW, MaxSubjectZ, Initializer.WAxis);
GetViewFrustumBounds(CasterFrustum, CasterMatrix, true);
}
check(MaxSubjectZ > MinSubjectZ);
const float ClampedMaxLightW = FMath::Min(MinSubjectZ + Initializer.MaxDistanceToCastInLightW, (float)HALF_WORLD_MAX);
MinPreSubjectZ = Initializer.MinLightW;
SubjectAndReceiverMatrix = WorldToFace * FShadowProjectionMatrix(MinSubjectZ, MaxSubjectZ, Initializer.WAxis);
ReceiverMatrix = WorldToFace * FShadowProjectionMatrix(MinSubjectZ, ClampedMaxLightW, Initializer.WAxis);
float MaxSubjectDepth = SubjectAndReceiverMatrix.TransformPosition(
Initializer.SubjectBounds.Origin
+ WorldToLightScaled.InverseFast().TransformVector(Initializer.FaceDirection) * Initializer.SubjectBounds.SphereRadius
).Z;
if (CascadeSettings.bOnePassPointLightShadow)
{
MaxSubjectDepth = Initializer.SubjectBounds.SphereRadius;
}
InvMaxSubjectDepth = 1.0f / MaxSubjectDepth;
// Store the view matrix
// Reorder the vectors to match the main view, since ShadowViewMatrix will be used to override the main view's view matrix during shadow depth rendering
ShadowViewMatrix = Initializer.WorldToLight *
FMatrix(
FPlane(0, 0, 1, 0),
FPlane(1, 0, 0, 0),
FPlane(0, 1, 0, 0),
FPlane(0, 0, 0, 1));
InvReceiverMatrix = ReceiverMatrix.InverseFast();
GetViewFrustumBounds(ReceiverFrustum, ReceiverMatrix, true);
UpdateShaderDepthBias();
}
void FProjectedShadowInfo::AddSubjectPrimitive(FPrimitiveSceneInfo* PrimitiveSceneInfo, TArray<FViewInfo>* ViewArray)
{
QUICK_SCOPE_CYCLE_COUNTER(STAT_AddSubjectPrimitive);
// Ray traced shadows use the GPU managed distance field object buffers, no CPU culling should be used
check(!CascadeSettings.bRayTracedDistanceField);
if (!ReceiverPrimitives.Contains(PrimitiveSceneInfo)
// Far cascade only casts from primitives marked for it
&& (!CascadeSettings.bFarShadowCascade || PrimitiveSceneInfo->Proxy->CastsFarShadow()))
{
const FPrimitiveSceneProxy* Proxy = PrimitiveSceneInfo->Proxy;
TArray<FViewInfo*, TInlineAllocator<1> > Views;
const bool bWholeSceneDirectionalShadow = IsWholeSceneDirectionalShadow();
if (bWholeSceneDirectionalShadow)
{
Views.Add(DependentView);
}
else
{
check(ViewArray);
for (int32 ViewIndex = 0; ViewIndex < ViewArray->Num(); ViewIndex++)
{
Views.Add(&(*ViewArray)[ViewIndex]);
}
}
bool bOpaqueRelevance = false;
bool bTranslucentRelevance = false;
bool bShadowRelevance = false;
uint32 ViewMask = 0;
int32 PrimitiveId = PrimitiveSceneInfo->GetIndex();
const auto FeatureLevel = PrimitiveSceneInfo->Scene->GetFeatureLevel();
for (int32 ViewIndex = 0, Num = Views.Num(); ViewIndex < Num; ViewIndex++)
{
FViewInfo& CurrentView = *Views[ViewIndex];
FPrimitiveViewRelevance& ViewRelevance = CurrentView.PrimitiveViewRelevanceMap[PrimitiveId];
if (!ViewRelevance.bInitializedThisFrame)
{
if( CurrentView.IsPerspectiveProjection() )
{
// Compute the distance between the view and the primitive.
float DistanceSquared = (Proxy->GetBounds().Origin - CurrentView.ShadowViewMatrices.ViewOrigin).SizeSquared();
bool bIsDistanceCulled = CurrentView.IsDistanceCulled(
DistanceSquared,
Proxy->GetMinDrawDistance(),
Proxy->GetMaxDrawDistance(),
PrimitiveSceneInfo
);
if( bIsDistanceCulled )
{
continue;
}
}
// Compute the subject primitive's view relevance since it wasn't cached
// Update the main view's PrimitiveViewRelevanceMap
ViewRelevance = PrimitiveSceneInfo->Proxy->GetViewRelevance(&CurrentView);
ViewMask |= (1 << ViewIndex);
}
bOpaqueRelevance |= ViewRelevance.bOpaqueRelevance;
bTranslucentRelevance |= ViewRelevance.HasTranslucency();
bShadowRelevance |= ViewRelevance.bShadowRelevance;
}
if (bShadowRelevance)
{
// Update the primitive component's last render time. Allows the component to update when using bCastWhenHidden.
const float CurrentWorldTime = Views[0]->Family->CurrentWorldTime;
*(PrimitiveSceneInfo->ComponentLastRenderTime) = CurrentWorldTime;
}
if (bOpaqueRelevance && bShadowRelevance)
{
const FBoxSphereBounds& Bounds = Proxy->GetBounds();
bool bDrawingStaticMeshes = false;
if (PrimitiveSceneInfo->StaticMeshes.Num() > 0)
{
for (int32 ViewIndex = 0, ViewCount = Views.Num(); ViewIndex < ViewCount; ViewIndex++)
{
FViewInfo& CurrentView = *Views[ViewIndex];
const float DistanceSquared = ( Bounds.Origin - CurrentView.ShadowViewMatrices.ViewOrigin ).SizeSquared();
const bool bDrawShadowDepth = FMath::Square( Bounds.SphereRadius ) > FMath::Square( GMinScreenRadiusForShadowCaster ) * DistanceSquared;
if( !bDrawShadowDepth )
{
// cull object if it's too small to be considered as shadow caster
continue;
}
// Update visibility for meshes which weren't visible in the main views or were visible with static relevance
if (!CurrentView.PrimitiveVisibilityMap[PrimitiveId] || CurrentView.PrimitiveViewRelevanceMap[PrimitiveId].bStaticRelevance)
{
bool bUseExistingVisibility = false;
if(!bReflectiveShadowmap) // Don't use existing visibility for RSMs
{
for (int32 MeshIndex = 0; MeshIndex < PrimitiveSceneInfo->StaticMeshes.Num(); MeshIndex++)
{
const FStaticMesh& StaticMesh = PrimitiveSceneInfo->StaticMeshes[MeshIndex];
bool bMeshIsVisible = CurrentView.StaticMeshShadowDepthMap[StaticMesh.Id] && StaticMesh.CastShadow;
bUseExistingVisibility = bUseExistingVisibility || bMeshIsVisible;
if (bMeshIsVisible && bWholeSceneDirectionalShadow)
{
StaticMeshWholeSceneShadowDepthMap[StaticMesh.Id] = true;
StaticMeshWholeSceneShadowBatchVisibility[StaticMesh.Id] = CurrentView.StaticMeshBatchVisibility[StaticMesh.Id];
}
}
}
if (bUseExistingVisibility)
{
bDrawingStaticMeshes = true;
}
// Don't overwrite visibility set by the main views
// This is necessary to avoid popping when transitioning between LODs, because on the frame of the transition,
// The old LOD will continue to be drawn even though a different LOD would be chosen by distance.
else
{
FLODMask LODToRender;
int32 ForcedLODLevel = (CurrentView.Family->EngineShowFlags.LOD) ? GetCVarForceLOD() : 0;
// Add the primitive's static mesh elements to the draw lists.
if ( bReflectiveShadowmap)
{
int8 LODToRenderScan = -CHAR_MAX;
// Force the lowest detail LOD Level in reflective shadow maps.
for (int32 Index = 0; Index < PrimitiveSceneInfo->StaticMeshes.Num(); Index++)
{
LODToRenderScan = FMath::Max<int8>(PrimitiveSceneInfo->StaticMeshes[Index].LODIndex, LODToRenderScan);
}
if (LODToRenderScan != -CHAR_MAX)
{
LODToRender.SetLOD(LODToRenderScan);
}
}
else
{
FPrimitiveBounds PrimitiveBounds;
PrimitiveBounds.Origin = Bounds.Origin;
PrimitiveBounds.SphereRadius = Bounds.SphereRadius;
LODToRender = ComputeLODForMeshes(PrimitiveSceneInfo->StaticMeshes, CurrentView, PrimitiveBounds.Origin, PrimitiveBounds.SphereRadius, ForcedLODLevel);
}
for (int32 MeshIndex = 0; MeshIndex < PrimitiveSceneInfo->StaticMeshes.Num(); MeshIndex++)
{
const FStaticMesh& StaticMesh = PrimitiveSceneInfo->StaticMeshes[MeshIndex];
if (StaticMesh.CastShadow && LODToRender.ContainsLOD(StaticMesh.LODIndex))
{
if (bWholeSceneDirectionalShadow)
{
StaticMeshWholeSceneShadowDepthMap[StaticMesh.Id] = true;
StaticMeshWholeSceneShadowBatchVisibility[StaticMesh.Id] = StaticMesh.Elements.Num() == 1 ? 1 : StaticMesh.VertexFactory->GetStaticBatchElementVisibility(*DependentView, &StaticMesh);
}
else
{
CurrentView.StaticMeshShadowDepthMap[StaticMesh.Id] = true;
CurrentView.StaticMeshBatchVisibility[StaticMesh.Id] = StaticMesh.Elements.Num() == 1 ? 1 : StaticMesh.VertexFactory->GetStaticBatchElementVisibility(CurrentView, &StaticMesh);
}
bDrawingStaticMeshes = true;
}
}
}
}
}
}
if (bDrawingStaticMeshes)
{
if (!bWholeSceneDirectionalShadow)
{
// Add the primitive's static mesh elements to the draw lists.
for (int32 MeshIndex = 0; MeshIndex < PrimitiveSceneInfo->StaticMeshes.Num(); MeshIndex++)
{
FStaticMesh& StaticMesh = PrimitiveSceneInfo->StaticMeshes[MeshIndex];
if (StaticMesh.CastShadow)
{
const FMaterialRenderProxy* MaterialRenderProxy = StaticMesh.MaterialRenderProxy;
const FMaterial* Material = MaterialRenderProxy->GetMaterial(FeatureLevel);
const EBlendMode BlendMode = Material->GetBlendMode();
const EMaterialShadingModel ShadingModel = Material->GetShadingModel();
if(((!IsTranslucentBlendMode(BlendMode)) && ShadingModel != MSM_Unlit) || (bReflectiveShadowmap && Material->ShouldInjectEmissiveIntoLPV()))
{
const bool bTwoSided = Material->IsTwoSided() || PrimitiveSceneInfo->Proxy->CastsShadowAsTwoSided();
OverrideWithDefaultMaterialForShadowDepth(MaterialRenderProxy, Material, bReflectiveShadowmap, FeatureLevel);
SubjectMeshElements.Add(FShadowStaticMeshElement(MaterialRenderProxy, Material, &StaticMesh,bTwoSided));
}
}
}
}
}
else
{
// Add the primitive to the subject primitive list.
SubjectPrimitives.Add(PrimitiveSceneInfo);
}
}
// Add translucent shadow casting primitives to SubjectTranslucentPrimitives
if (bTranslucentRelevance && bShadowRelevance && bTranslucentShadow)
{
SubjectTranslucentPrimitives.Add(PrimitiveSceneInfo);
}
}
}
bool FProjectedShadowInfo::HasSubjectPrims() const
{
return SubjectPrimitives.Num() > 0 || SubjectMeshElements.Num() > 0;
}
void FProjectedShadowInfo::AddReceiverPrimitive(FPrimitiveSceneInfo* PrimitiveSceneInfo)
{
// Add the primitive to the receiver primitive list.
ReceiverPrimitives.Add(PrimitiveSceneInfo);
}
static TAutoConsoleVariable<int32> CVarDisableCullShadows(
TEXT("foliage.DisableCullShadows"),
0,
TEXT("First three bits are disable SubjectPrimitives, ReceiverPrimitives, SubjectTranslucentPrimitives"));
void FProjectedShadowInfo::GatherDynamicMeshElements(FSceneRenderer& Renderer, FVisibleLightInfo& VisibleLightInfo, TArray<const FSceneView*>& ReusedViewsArray)
{
if (SubjectPrimitives.Num() > 0 || ReceiverPrimitives.Num() > 0 || SubjectTranslucentPrimitives.Num() > 0)
{
// Choose an arbitrary view where this shadow's subject is relevant.
FViewInfo* FoundView = NULL;
for (int32 ViewIndex = 0;ViewIndex < Renderer.Views.Num();ViewIndex++)
{
FViewInfo* CheckView = &Renderer.Views[ViewIndex];
const FVisibleLightViewInfo& VisibleLightViewInfo = CheckView->VisibleLightInfos[LightSceneInfo->Id];
FPrimitiveViewRelevance ViewRel = VisibleLightViewInfo.ProjectedShadowViewRelevanceMap[ShadowId];
if (ViewRel.bShadowRelevance)
{
FoundView = CheckView;
break;
}
}
check(FoundView && IsInRenderingThread());
// Backup properties of the view that we will override
FMatrix OriginalViewMatrix = FoundView->ViewMatrices.ViewMatrix;
// Override the view matrix so that billboarding primitives will be aligned to the light
FoundView->ViewMatrices.ViewMatrix = ShadowViewMatrix;
// Prevent materials from getting overridden during shadow casting in viewmodes like lighting only
// Lighting only should only affect the material used with direct lighting, not the indirect lighting
FoundView->bForceShowMaterials = true;
ReusedViewsArray[0] = FoundView;
check(!FoundView->ViewMatrices.GetDynamicMeshElementsShadowCullFrustum);
int32 Disable = 0; //CVarDisableCullShadows.GetValueOnRenderThread();
FConvexVolume NoCull;
if (IsWholeSceneDirectionalShadow())
{
FoundView->ViewMatrices.PreShadowTranslation = FVector(0,0,0);
FoundView->ViewMatrices.GetDynamicMeshElementsShadowCullFrustum = (Disable & 1) ? &NoCull : &CascadeSettings.ShadowBoundsAccurate;
GatherDynamicMeshElementsArray(FoundView, Renderer, SubjectPrimitives, DynamicSubjectMeshElements, ReusedViewsArray);
FoundView->ViewMatrices.PreShadowTranslation = PreShadowTranslation;
}
else
{
FoundView->ViewMatrices.PreShadowTranslation = PreShadowTranslation;
FoundView->ViewMatrices.GetDynamicMeshElementsShadowCullFrustum = (Disable & 1) ? &NoCull : &CasterFrustum;
GatherDynamicMeshElementsArray(FoundView, Renderer, SubjectPrimitives, DynamicSubjectMeshElements, ReusedViewsArray);
}
FoundView->ViewMatrices.GetDynamicMeshElementsShadowCullFrustum = (Disable & 2) ? &NoCull : &ReceiverFrustum;
GatherDynamicMeshElementsArray(FoundView, Renderer, ReceiverPrimitives, DynamicReceiverMeshElements, ReusedViewsArray);
FoundView->ViewMatrices.GetDynamicMeshElementsShadowCullFrustum = (Disable & 4) ? &NoCull : &CasterFrustum;
GatherDynamicMeshElementsArray(FoundView, Renderer, SubjectTranslucentPrimitives, DynamicSubjectTranslucentMeshElements, ReusedViewsArray);
FoundView->ViewMatrices.GetDynamicMeshElementsShadowCullFrustum = nullptr;
FoundView->bForceShowMaterials = false;
FoundView->ViewMatrices.ViewMatrix = OriginalViewMatrix;
}
}
void FProjectedShadowInfo::GatherDynamicMeshElementsArray(
FViewInfo* FoundView,
FSceneRenderer& Renderer,
PrimitiveArrayType& PrimitiveArray,
TArray<FMeshBatchAndRelevance,SceneRenderingAllocator>& OutDynamicMeshElements,
TArray<const FSceneView*>& ReusedViewsArray)
{
// Simple elements not supported in shadow passes
FSimpleElementCollector DynamicSubjectSimpleElements;
Renderer.MeshCollector.ClearViewMeshArrays();
Renderer.MeshCollector.AddViewMeshArrays(FoundView, &OutDynamicMeshElements, &DynamicSubjectSimpleElements, Renderer.ViewFamily.GetFeatureLevel());
const uint32 PrimitiveCount = PrimitiveArray.Num();
for (uint32 PrimitiveIndex = 0; PrimitiveIndex < PrimitiveCount; ++PrimitiveIndex)
{
const FPrimitiveSceneInfo* PrimitiveSceneInfo = PrimitiveArray[PrimitiveIndex];
const FPrimitiveSceneProxy* PrimitiveSceneProxy = PrimitiveSceneInfo->Proxy;
// Lookup the primitive's cached view relevance
FPrimitiveViewRelevance ViewRelevance = FoundView->PrimitiveViewRelevanceMap[PrimitiveSceneInfo->GetIndex()];
if (!ViewRelevance.bInitializedThisFrame)
{
// Compute the subject primitive's view relevance since it wasn't cached
ViewRelevance = PrimitiveSceneInfo->Proxy->GetViewRelevance(FoundView);
}
// Only draw if the subject primitive is shadow relevant.
if (ViewRelevance.bShadowRelevance && ViewRelevance.bDynamicRelevance)
{
Renderer.MeshCollector.SetPrimitive(PrimitiveSceneInfo->Proxy, PrimitiveSceneInfo->DefaultDynamicHitProxyId);
PrimitiveSceneInfo->Proxy->GetDynamicMeshElements(ReusedViewsArray, Renderer.ViewFamily, 0x1, Renderer.MeshCollector);
}
}
}
/**
* @param View view to check visibility in
* @return true if this shadow info has any subject prims visible in the view
*/
bool FProjectedShadowInfo::SubjectsVisible(const FViewInfo& View) const
{
checkSlow(!IsWholeSceneDirectionalShadow());
for(int32 PrimitiveIndex = 0;PrimitiveIndex < SubjectPrimitives.Num();PrimitiveIndex++)
{
const FPrimitiveSceneInfo* SubjectPrimitiveSceneInfo = SubjectPrimitives[PrimitiveIndex];
if(View.PrimitiveVisibilityMap[SubjectPrimitiveSceneInfo->GetIndex()])
{
return true;
}
}
return false;
}
/**
* Clears arrays allocated with the scene rendering allocator.
* Cached preshadows are reused across frames so scene rendering allocations will be invalid.
*/
void FProjectedShadowInfo::ClearTransientArrays()
{
SubjectTranslucentPrimitives.Empty();
SubjectPrimitives.Empty();
ReceiverPrimitives.Empty();
SubjectMeshElements.Empty();
DynamicSubjectMeshElements.Empty();
DynamicReceiverMeshElements.Empty();
DynamicSubjectTranslucentMeshElements.Empty();
}
/** Returns a cached preshadow matching the input criteria if one exists. */
TRefCountPtr<FProjectedShadowInfo> FDeferredShadingSceneRenderer::GetCachedPreshadow(
const FLightPrimitiveInteraction* InParentInteraction,
const FProjectedShadowInitializer& Initializer,
const FBoxSphereBounds& Bounds,
uint32 InResolutionX)
{
if (ShouldUseCachePreshadows() && !Views[0].bIsSceneCapture)
{
const FPrimitiveSceneInfo* PrimitiveInfo = InParentInteraction->GetPrimitiveSceneInfo();
const FLightSceneInfo* LightInfo = InParentInteraction->GetLight();
const FSphere QueryBounds(Bounds.Origin, Bounds.SphereRadius);
for (int32 ShadowIndex = 0; ShadowIndex < Scene->CachedPreshadows.Num(); ShadowIndex++)
{
TRefCountPtr<FProjectedShadowInfo> CachedShadow = Scene->CachedPreshadows[ShadowIndex];
// Only reuse a cached preshadow if it was created for the same primitive and light
if (CachedShadow->GetParentSceneInfo() == PrimitiveInfo
&& &CachedShadow->GetLightSceneInfo() == LightInfo
// Only reuse if it contains the bounds being queried, with some tolerance
&& QueryBounds.IsInside(CachedShadow->ShadowBounds, CachedShadow->ShadowBounds.W * .04f)
// Only reuse if the resolution matches
&& CachedShadow->ResolutionX == InResolutionX
&& CachedShadow->bAllocated)
{
// Reset any allocations using the scene rendering allocator,
// Since those will point to freed memory now that we are using the shadow on a different frame than it was created on.
CachedShadow->ClearTransientArrays();
return CachedShadow;
}
}
}
// No matching cached preshadow was found
return NULL;
}
struct FComparePreshadows
{
FORCEINLINE bool operator()(const TRefCountPtr<FProjectedShadowInfo>& A, const TRefCountPtr<FProjectedShadowInfo>& B) const
{
if (B->ResolutionX * B->ResolutionY < A->ResolutionX * A->ResolutionY)
{
return true;
}
return false;
}
};
/** Removes stale shadows and attempts to add new preshadows to the cache. */
void FDeferredShadingSceneRenderer::UpdatePreshadowCache()
{
if (ShouldUseCachePreshadows() && !Views[0].bIsSceneCapture)
{
SCOPE_CYCLE_COUNTER(STAT_UpdatePreshadowCache);
if (Scene->PreshadowCacheLayout.GetSizeX() == 0)
{
// Initialize the texture layout if necessary
const FIntPoint PreshadowCacheBufferSize = GSceneRenderTargets.GetPreShadowCacheTextureResolution();
Scene->PreshadowCacheLayout = FTextureLayout(1, 1, PreshadowCacheBufferSize.X, PreshadowCacheBufferSize.Y, false, false);
}
// Iterate through the cached preshadows, removing those that are not going to be rendered this frame
for (int32 CachedShadowIndex = Scene->CachedPreshadows.Num() - 1; CachedShadowIndex >= 0; CachedShadowIndex--)
{
TRefCountPtr<FProjectedShadowInfo> CachedShadow = Scene->CachedPreshadows[CachedShadowIndex];
bool bShadowBeingRenderedThisFrame = false;
for (int32 LightIndex = 0; LightIndex < VisibleLightInfos.Num() && !bShadowBeingRenderedThisFrame; LightIndex++)
{
bShadowBeingRenderedThisFrame = VisibleLightInfos[LightIndex].ProjectedPreShadows.Find(CachedShadow) != INDEX_NONE;
}
if (!bShadowBeingRenderedThisFrame)
{
// Must succeed, since we added it to the layout earlier
verify(Scene->PreshadowCacheLayout.RemoveElement(
CachedShadow->X,
CachedShadow->Y,
CachedShadow->ResolutionX + SHADOW_BORDER * 2,
CachedShadow->ResolutionY + SHADOW_BORDER * 2));
Scene->CachedPreshadows.RemoveAt(CachedShadowIndex);
}
else if (GSceneRenderTargets.bPreshadowCacheNewlyAllocated)
{
CachedShadow->bDepthsCached = false;
}
}
GSceneRenderTargets.bPreshadowCacheNewlyAllocated = false;
TArray<TRefCountPtr<FProjectedShadowInfo>, SceneRenderingAllocator> UncachedPreShadows;
// Gather a list of preshadows that can be cached
for (int32 LightIndex = 0; LightIndex < VisibleLightInfos.Num(); LightIndex++)
{
for (int32 ShadowIndex = 0; ShadowIndex < VisibleLightInfos[LightIndex].ProjectedPreShadows.Num(); ShadowIndex++)
{
TRefCountPtr<FProjectedShadowInfo> CurrentShadow = VisibleLightInfos[LightIndex].ProjectedPreShadows[ShadowIndex];
checkSlow(CurrentShadow->bPreShadow);
if (!CurrentShadow->bAllocatedInPreshadowCache)
{
UncachedPreShadows.Add(CurrentShadow);
}
}
}
// Sort them from largest to smallest, based on the assumption that larger preshadows will have more objects in their depth only pass
UncachedPreShadows.Sort(FComparePreshadows());
for (int32 ShadowIndex = 0; ShadowIndex < UncachedPreShadows.Num(); ShadowIndex++)
{
TRefCountPtr<FProjectedShadowInfo> CurrentShadow = UncachedPreShadows[ShadowIndex];
// Try to find space for the preshadow in the texture layout
if (Scene->PreshadowCacheLayout.AddElement(
CurrentShadow->X,
CurrentShadow->Y,
CurrentShadow->ResolutionX + SHADOW_BORDER * 2,
CurrentShadow->ResolutionY + SHADOW_BORDER * 2))
{
// Mark the preshadow as existing in the cache
// It must now use the preshadow cache render target to render and read its depths instead of the usual shadow depth buffers
CurrentShadow->bAllocatedInPreshadowCache = true;
// Indicate that the shadow's X and Y have been initialized
CurrentShadow->bAllocated = true;
Scene->CachedPreshadows.Add(CurrentShadow);
}
}
}
}
bool FSceneRenderer::ShouldCreateObjectShadowForStationaryLight(const FLightSceneInfo* LightSceneInfo, const FPrimitiveSceneProxy* PrimitiveSceneProxy, bool bInteractionShadowMapped) const
{
const bool bCreateObjectShadowForStationaryLight =
LightSceneInfo->bCreatePerObjectShadowsForDynamicObjects
&& LightSceneInfo->IsPrecomputedLightingValid()
&& LightSceneInfo->Proxy->GetShadowMapChannel() != INDEX_NONE
// Create a per-object shadow if the object does not want static lighting and needs to integrate with the static shadowing of a stationary light
// Or if the object wants static lighting but does not have a built shadowmap (Eg has been moved in the editor)
&& (!PrimitiveSceneProxy->HasStaticLighting() || !bInteractionShadowMapped);
return bCreateObjectShadowForStationaryLight;
}
void FDeferredShadingSceneRenderer::SetupInteractionShadows(
FRHICommandListImmediate& RHICmdList,
FLightPrimitiveInteraction* Interaction,
FVisibleLightInfo& VisibleLightInfo,
bool bStaticSceneOnly,
const TArray<FProjectedShadowInfo*,SceneRenderingAllocator>& ViewDependentWholeSceneShadows,
TArray<FProjectedShadowInfo*,SceneRenderingAllocator>& PreShadows)
{
// too high on hit count to leave on
// SCOPE_CYCLE_COUNTER(STAT_SetupInteractionShadows);
FPrimitiveSceneInfo* PrimitiveSceneInfo = Interaction->GetPrimitiveSceneInfo();
FLightSceneProxy* LightProxy = Interaction->GetLight()->Proxy;
extern bool GUseTranslucencyShadowDepths;
bool bShadowHandledByParent = false;
if (PrimitiveSceneInfo->LightingAttachmentRoot.IsValid())
{
FAttachmentGroupSceneInfo& AttachmentGroup = Scene->AttachmentGroups.FindChecked(PrimitiveSceneInfo->LightingAttachmentRoot);
bShadowHandledByParent = AttachmentGroup.ParentSceneInfo && AttachmentGroup.ParentSceneInfo->Proxy->LightAttachmentsAsGroup();
}
// Shadowing for primitives with a shadow parent will be handled by that shadow parent
if (!bShadowHandledByParent)
{
const bool bCreateTranslucentObjectShadow = GUseTranslucencyShadowDepths && Interaction->HasTranslucentObjectShadow();
const bool bCreateInsetObjectShadow = Interaction->HasInsetObjectShadow();
const bool bCreateObjectShadowForStationaryLight = ShouldCreateObjectShadowForStationaryLight(Interaction->GetLight(), PrimitiveSceneInfo->Proxy, Interaction->IsShadowMapped());
if (Interaction->HasShadow()
// TODO: Handle inset shadows, especially when an object is only casting a self-shadow.
// Only render shadows from objects that use static lighting during a reflection capture, since the reflection capture doesn't update at runtime
&& (!bStaticSceneOnly || PrimitiveSceneInfo->Proxy->HasStaticLighting())
&& (bCreateTranslucentObjectShadow || bCreateInsetObjectShadow || bCreateObjectShadowForStationaryLight))
{
// Create projected shadow infos
CreatePerObjectProjectedShadow(RHICmdList, Interaction, bCreateTranslucentObjectShadow, bCreateInsetObjectShadow || bCreateObjectShadowForStationaryLight, ViewDependentWholeSceneShadows, PreShadows);
}
}
}
void FDeferredShadingSceneRenderer::CreatePerObjectProjectedShadow(
FRHICommandListImmediate& RHICmdList,
FLightPrimitiveInteraction* Interaction,
bool bCreateTranslucentObjectShadow,
bool bCreateOpaqueObjectShadow,
const TArray<FProjectedShadowInfo*,SceneRenderingAllocator>& ViewDependentWholeSceneShadows,
TArray<FProjectedShadowInfo*, SceneRenderingAllocator>& OutPreShadows)
{
check(bCreateOpaqueObjectShadow || bCreateTranslucentObjectShadow);
FPrimitiveSceneInfo* PrimitiveSceneInfo = Interaction->GetPrimitiveSceneInfo();
const int32 PrimitiveId = PrimitiveSceneInfo->GetIndex();
FLightSceneInfo* LightSceneInfo = Interaction->GetLight();
FVisibleLightInfo& VisibleLightInfo = VisibleLightInfos[LightSceneInfo->Id];
// Check if the shadow is visible in any of the views.
bool bShadowIsPotentiallyVisibleNextFrame = false;
bool bOpaqueShadowIsVisibleThisFrame = false;
bool bSubjectIsVisible = false;
bool bOpaqueRelevance = false;
bool bTranslucentRelevance = false;
bool bTranslucentShadowIsVisibleThisFrame = false;
for (int32 ViewIndex = 0; ViewIndex < Views.Num(); ViewIndex++)
{
const FViewInfo& View = Views[ViewIndex];
// Lookup the primitive's cached view relevance
FPrimitiveViewRelevance ViewRelevance = View.PrimitiveViewRelevanceMap[PrimitiveId];
if (!ViewRelevance.bInitializedThisFrame)
{
// Compute the subject primitive's view relevance since it wasn't cached
ViewRelevance = PrimitiveSceneInfo->Proxy->GetViewRelevance(&View);
}
// Check if the subject primitive is shadow relevant.
const bool bPrimitiveIsShadowRelevant = ViewRelevance.bShadowRelevance;
// Check if the shadow and preshadow are occluded.
const bool bOpaqueShadowIsOccluded =
!bCreateOpaqueObjectShadow ||
(
!View.bIgnoreExistingQueries && View.State &&
((FSceneViewState*)View.State)->IsShadowOccluded(RHICmdList, PrimitiveSceneInfo->PrimitiveComponentId, LightSceneInfo->Proxy->GetLightComponent(), INDEX_NONE, false)
);
const bool bTranslucentShadowIsOccluded =
!bCreateTranslucentObjectShadow ||
(
!View.bIgnoreExistingQueries && View.State &&
((FSceneViewState*)View.State)->IsShadowOccluded(RHICmdList, PrimitiveSceneInfo->PrimitiveComponentId, LightSceneInfo->Proxy->GetLightComponent(), INDEX_NONE, true)
);
const bool bSubjectIsVisibleInThisView = View.PrimitiveVisibilityMap[PrimitiveSceneInfo->GetIndex()];
bSubjectIsVisible |= bSubjectIsVisibleInThisView;
// The shadow is visible if it is view relevant and unoccluded.
bOpaqueShadowIsVisibleThisFrame |= (bPrimitiveIsShadowRelevant && !bOpaqueShadowIsOccluded);
bTranslucentShadowIsVisibleThisFrame |= (bPrimitiveIsShadowRelevant && !bTranslucentShadowIsOccluded);
bShadowIsPotentiallyVisibleNextFrame |= bPrimitiveIsShadowRelevant;
bOpaqueRelevance |= ViewRelevance.bOpaqueRelevance;
bTranslucentRelevance |= ViewRelevance.HasTranslucency();
}
if (!bOpaqueShadowIsVisibleThisFrame && !bTranslucentShadowIsVisibleThisFrame && !bShadowIsPotentiallyVisibleNextFrame)
{
// Don't setup the shadow info for shadows which don't need to be rendered or occlusion tested.
return;
}
TArray<FPrimitiveSceneInfo*, SceneRenderingAllocator> ShadowGroupPrimitives;
PrimitiveSceneInfo->GatherLightingAttachmentGroupPrimitives(ShadowGroupPrimitives);
// Compute the composite bounds of this group of shadow primitives.
FBoxSphereBounds OriginalBounds = ShadowGroupPrimitives[0]->Proxy->GetBounds();
for (int32 ChildIndex = 1; ChildIndex < ShadowGroupPrimitives.Num(); ChildIndex++)
{
const FPrimitiveSceneInfo* ShadowChild = ShadowGroupPrimitives[ChildIndex];
OriginalBounds = OriginalBounds + ShadowChild->Proxy->GetBounds();
}
// Shadowing constants.
const uint32 MinShadowResolution = CVarMinShadowResolution.GetValueOnRenderThread();
const uint32 MaxShadowResolutionSetting = GetCachedScalabilityCVars().MaxShadowResolution;
const FIntPoint ShadowBufferResolution = GSceneRenderTargets.GetShadowDepthTextureResolution();
const uint32 MaxShadowResolution = FMath::Min<int32>(MaxShadowResolutionSetting, ShadowBufferResolution.X) - SHADOW_BORDER * 2;
const uint32 MaxShadowResolutionY = FMath::Min<int32>(MaxShadowResolutionSetting, ShadowBufferResolution.Y) - SHADOW_BORDER * 2;
const int32 ShadowFadeResolution = CVarShadowFadeResolution.GetValueOnRenderThread();
// Compute the maximum resolution required for the shadow by any view. Also keep track of the unclamped resolution for fading.
uint32 MaxDesiredResolution = 0;
float MaxUnclampedResolution = 0;
float MaxScreenPercent = 0;
TArray<float, TInlineAllocator<2> > ResolutionFadeAlphas;
TArray<float, TInlineAllocator<2> > ResolutionPreShadowFadeAlphas;
float MaxResolutionFadeAlpha = 0;
float MaxResolutionPreShadowFadeAlpha = 0;
for (int32 ViewIndex = 0; ViewIndex < Views.Num(); ViewIndex++)
{
const FViewInfo& View = Views[ViewIndex];
// Determine the size of the subject's bounding sphere in this view.
const FVector4 ScreenPosition = View.WorldToScreen(OriginalBounds.Origin);
const float ScreenRadius = View.ShadowViewMatrices.ScreenScale *
OriginalBounds.SphereRadius /
FMath::Max(ScreenPosition.W,1.0f);
// Early catch for invalid CalculateShadowFadeAlpha()
checkf(ScreenRadius >= 0.0f, TEXT("View.ShadowViewMatrices.ScreenScale %f, OriginalBounds.SphereRadius %f, ScreenPosition.W %f"), View.ShadowViewMatrices.ScreenScale, OriginalBounds.SphereRadius, ScreenPosition.W);
const float ScreenPercent = FMath::Max(
1.0f / 2.0f * View.ShadowViewMatrices.ProjectionScale.X,
1.0f / 2.0f * View.ShadowViewMatrices.ProjectionScale.Y
) *
OriginalBounds.SphereRadius /
FMath::Max(ScreenPosition.W,1.0f);
MaxScreenPercent = FMath::Max(MaxScreenPercent, ScreenPercent);
// Determine the amount of shadow buffer resolution needed for this view.
const float UnclampedResolution = ScreenRadius * CVarShadowTexelsPerPixel.GetValueOnRenderThread();
MaxUnclampedResolution = FMath::Max( MaxUnclampedResolution, UnclampedResolution );
MaxDesiredResolution = FMath::Max(
MaxDesiredResolution,
FMath::Clamp<uint32>(
UnclampedResolution,
FMath::Min<int32>(MinShadowResolution,ShadowBufferResolution.X - SHADOW_BORDER * 2),
MaxShadowResolution
)
);
// Calculate fading based on resolution
const float ViewSpecificAlpha = CalculateShadowFadeAlpha( UnclampedResolution, ShadowFadeResolution, MinShadowResolution );
MaxResolutionFadeAlpha = FMath::Max(MaxResolutionFadeAlpha, ViewSpecificAlpha);
ResolutionFadeAlphas.Add(ViewSpecificAlpha);
const float ViewSpecificPreShadowAlpha = CalculateShadowFadeAlpha( UnclampedResolution * CVarPreShadowResolutionFactor.GetValueOnRenderThread(), CVarPreShadowFadeResolution.GetValueOnRenderThread(), CVarMinPreShadowResolution.GetValueOnRenderThread() );
MaxResolutionPreShadowFadeAlpha = FMath::Max(MaxResolutionPreShadowFadeAlpha, ViewSpecificPreShadowAlpha);
ResolutionPreShadowFadeAlphas.Add(ViewSpecificPreShadowAlpha);
}
FBoxSphereBounds Bounds = OriginalBounds;
const bool bRenderPreShadow =
CVarAllowPreshadows.GetValueOnRenderThread()
// Preshadow only affects the subject's pixels
&& bSubjectIsVisible
// Only objects with dynamic lighting should create a preshadow
// Unless we're in the editor and need to preview an object without built lighting
&& (!PrimitiveSceneInfo->Proxy->HasStaticLighting() || !Interaction->IsShadowMapped());
if (bRenderPreShadow && ShouldUseCachePreshadows())
{
float PreshadowExpandFraction = FMath::Max(CVarPreshadowExpandFraction.GetValueOnRenderThread(), 0.0f);
// If we're creating a preshadow, expand the bounds somewhat so that the preshadow will be cached more often as the shadow caster moves around.
//@todo - only expand the preshadow bounds for this, not the per object shadow.
Bounds.SphereRadius += (Bounds.BoxExtent * PreshadowExpandFraction).Size();
Bounds.BoxExtent *= PreshadowExpandFraction + 1.0f;
}
// Compute the projected shadow initializer for this primitive-light pair.
FPerObjectProjectedShadowInitializer ShadowInitializer;
if ((MaxResolutionFadeAlpha > 1.0f / 256.0f || (bRenderPreShadow && MaxResolutionPreShadowFadeAlpha > 1.0f / 256.0f))
&& LightSceneInfo->Proxy->GetPerObjectProjectedShadowInitializer(Bounds, ShadowInitializer))
{
const float MaxFadeAlpha = MaxResolutionFadeAlpha;
// Only create a shadow from this object if it hasn't completely faded away
if (CVarAllowPerObjectShadows.GetValueOnRenderThread() && MaxFadeAlpha > 1.0f / 256.0f)
{
// Round down to the nearest power of two so that resolution changes are always doubling or halving the resolution, which increases filtering stability
// Use the max resolution if the desired resolution is larger than that
const int32 SizeX = MaxDesiredResolution >= MaxShadowResolution ? MaxShadowResolution : (1 << (FMath::CeilLogTwo(MaxDesiredResolution) - 1));
if (bOpaqueRelevance && bCreateOpaqueObjectShadow && (bOpaqueShadowIsVisibleThisFrame || bShadowIsPotentiallyVisibleNextFrame))
{
// Create a projected shadow for this interaction's shadow.
FProjectedShadowInfo* ProjectedShadowInfo = new(FMemStack::Get(),1,16) FProjectedShadowInfo;
if(ProjectedShadowInfo->SetupPerObjectProjection(
LightSceneInfo,
PrimitiveSceneInfo,
ShadowInitializer,
false, // no preshadow
SizeX,
MaxShadowResolutionY,
MaxScreenPercent,
false)) // no translucent shadow
{
ProjectedShadowInfo->FadeAlphas = ResolutionFadeAlphas;
VisibleLightInfo.MemStackProjectedShadows.Add(ProjectedShadowInfo);
if (bOpaqueShadowIsVisibleThisFrame)
{
VisibleLightInfo.AllProjectedShadows.Add(ProjectedShadowInfo);
for (int32 ChildIndex = 0, ChildCount = ShadowGroupPrimitives.Num(); ChildIndex < ChildCount; ChildIndex++)
{
FPrimitiveSceneInfo* ShadowChild = ShadowGroupPrimitives[ChildIndex];
ProjectedShadowInfo->AddSubjectPrimitive(ShadowChild, &Views);
}
}
else if (bShadowIsPotentiallyVisibleNextFrame)
{
VisibleLightInfo.OccludedPerObjectShadows.Add(ProjectedShadowInfo);
}
}
}
if (bTranslucentRelevance
&& Scene->GetFeatureLevel() >= ERHIFeatureLevel::SM4
&& bCreateTranslucentObjectShadow
&& (bTranslucentShadowIsVisibleThisFrame || bShadowIsPotentiallyVisibleNextFrame))
{
// Create a projected shadow for this interaction's shadow.
FProjectedShadowInfo* ProjectedShadowInfo = new(FMemStack::Get(),1,16) FProjectedShadowInfo;
if(ProjectedShadowInfo->SetupPerObjectProjection(
LightSceneInfo,
PrimitiveSceneInfo,
ShadowInitializer,
false, // no preshadow
// Size was computed for the full res opaque shadow, convert to downsampled translucent shadow size with proper clamping
FMath::Clamp<int32>(SizeX / GSceneRenderTargets.GetTranslucentShadowDownsampleFactor(), 1, GSceneRenderTargets.GetTranslucentShadowDepthTextureResolution().X - SHADOW_BORDER * 2),
FMath::Clamp<int32>(MaxShadowResolutionY / GSceneRenderTargets.GetTranslucentShadowDownsampleFactor(), 1, GSceneRenderTargets.GetTranslucentShadowDepthTextureResolution().Y - SHADOW_BORDER * 2),
MaxScreenPercent,
true)) // translucent shadow
{
ProjectedShadowInfo->FadeAlphas = ResolutionFadeAlphas,
VisibleLightInfo.MemStackProjectedShadows.Add(ProjectedShadowInfo);
if (bTranslucentShadowIsVisibleThisFrame)
{
VisibleLightInfo.AllProjectedShadows.Add(ProjectedShadowInfo);
for (int32 ChildIndex = 0, ChildCount = ShadowGroupPrimitives.Num(); ChildIndex < ChildCount; ChildIndex++)
{
FPrimitiveSceneInfo* ShadowChild = ShadowGroupPrimitives[ChildIndex];
ProjectedShadowInfo->AddSubjectPrimitive(ShadowChild, &Views);
}
}
else if (bShadowIsPotentiallyVisibleNextFrame)
{
VisibleLightInfo.OccludedPerObjectShadows.Add(ProjectedShadowInfo);
}
}
}
}
const float MaxPreFadeAlpha = MaxResolutionPreShadowFadeAlpha;
// If the subject is visible in at least one view, create a preshadow for static primitives shadowing the subject.
if (MaxPreFadeAlpha > 1.0f / 256.0f
&& bRenderPreShadow
&& bOpaqueRelevance)
{
// Round down to the nearest power of two so that resolution changes are always doubling or halving the resolution, which increases filtering stability.
int32 PreshadowSizeX = 1 << (FMath::CeilLogTwo(FMath::TruncToInt(MaxDesiredResolution * CVarPreShadowResolutionFactor.GetValueOnRenderThread())) - 1);
const FIntPoint PreshadowCacheResolution = GSceneRenderTargets.GetPreShadowCacheTextureResolution();
checkSlow(PreshadowSizeX <= PreshadowCacheResolution.X);
bool bIsOutsideWholeSceneShadow = true;
for (int32 i = 0; i < ViewDependentWholeSceneShadows.Num(); i++)
{
const FProjectedShadowInfo* WholeSceneShadow = ViewDependentWholeSceneShadows[i];
const FVector2D DistanceFadeValues = WholeSceneShadow->GetLightSceneInfo().Proxy->GetDirectionalLightDistanceFadeParameters(Scene->GetFeatureLevel(), WholeSceneShadow->GetLightSceneInfo().IsPrecomputedLightingValid());
const float DistanceFromShadowCenterSquared = (WholeSceneShadow->ShadowBounds.Center - Bounds.Origin).SizeSquared();
//@todo - if view dependent whole scene shadows are ever supported in splitscreen,
// We can only disable the preshadow at this point if it is inside a whole scene shadow for all views
const float DistanceFromViewSquared = ((FVector)WholeSceneShadow->DependentView->ShadowViewMatrices.ViewOrigin - Bounds.Origin).SizeSquared();
// Mark the preshadow as inside the whole scene shadow if its bounding sphere is inside the near fade distance
if (DistanceFromShadowCenterSquared < FMath::Square(FMath::Max(WholeSceneShadow->ShadowBounds.W - Bounds.SphereRadius, 0.0f))
//@todo - why is this extra threshold required?
&& DistanceFromViewSquared < FMath::Square(FMath::Max(DistanceFadeValues.X - 200.0f - Bounds.SphereRadius, 0.0f)))
{
bIsOutsideWholeSceneShadow = false;
break;
}
}
// Only create opaque preshadows when part of the caster is outside the whole scene shadow.
if (bIsOutsideWholeSceneShadow)
{
// Try to reuse a preshadow from the cache
TRefCountPtr<FProjectedShadowInfo> ProjectedPreShadowInfo = GetCachedPreshadow(Interaction, ShadowInitializer, OriginalBounds, PreshadowSizeX);
if(!ProjectedPreShadowInfo)
{
// Create a new projected shadow for this interaction's preshadow
// Not using the scene rendering mem stack because this shadow info may need to persist for multiple frames if it gets cached
ProjectedPreShadowInfo = new FProjectedShadowInfo;
ProjectedPreShadowInfo->SetupPerObjectProjection(
LightSceneInfo,
PrimitiveSceneInfo,
ShadowInitializer,
true, // preshadow
PreshadowSizeX,
FMath::TruncToInt(MaxShadowResolutionY * CVarPreShadowResolutionFactor.GetValueOnRenderThread()),
MaxScreenPercent,
false // not translucent shadow
);
}
// Update fade alpha on the cached preshadow
ProjectedPreShadowInfo->FadeAlphas = ResolutionPreShadowFadeAlphas;
VisibleLightInfo.AllProjectedShadows.Add(ProjectedPreShadowInfo);
VisibleLightInfo.ProjectedPreShadows.Add(ProjectedPreShadowInfo);
// Only add to OutPreShadows if the preshadow doesn't already have depths cached,
// Since OutPreShadows is used to generate information only used when rendering the shadow depths.
if (!ProjectedPreShadowInfo->bDepthsCached)
{
OutPreShadows.Add(ProjectedPreShadowInfo);
}
for (int32 ChildIndex = 0; ChildIndex < ShadowGroupPrimitives.Num(); ChildIndex++)
{
FPrimitiveSceneInfo* ShadowChild = ShadowGroupPrimitives[ChildIndex];
ProjectedPreShadowInfo->AddReceiverPrimitive(ShadowChild);
}
}
}
}
}
/** Creates a projected shadow for all primitives affected by a light. If the light doesn't support whole-scene shadows, it returns false.
* @param LightSceneInfo - The light to create a shadow for.
* @return true if a whole scene shadow was created
*/
void FDeferredShadingSceneRenderer::CreateWholeSceneProjectedShadow(FLightSceneInfo* LightSceneInfo)
{
SCOPE_CYCLE_COUNTER(STAT_CreateWholeSceneProjectedShadow);
FVisibleLightInfo& VisibleLightInfo = VisibleLightInfos[LightSceneInfo->Id];
// Try to create a whole-scene projected shadow initializer for the light.
TArray<FWholeSceneProjectedShadowInitializer, TInlineAllocator<6> > ProjectedShadowInitializers;
if (LightSceneInfo->Proxy->GetWholeSceneProjectedShadowInitializer(ViewFamily, ProjectedShadowInitializers))
{
checkSlow(ProjectedShadowInitializers.Num() > 0);
// Shadow resolution constants.
const uint32 EffectiveDoubleShadowBorder = ProjectedShadowInitializers[0].CascadeSettings.bOnePassPointLightShadow ? 0 : SHADOW_BORDER * 2;
const int32 MinShadowResolution = CVarMinShadowResolution.GetValueOnRenderThread();
const int32 MaxShadowResolutionSetting = GetCachedScalabilityCVars().MaxShadowResolution;
const FIntPoint ShadowBufferResolution = GSceneRenderTargets.GetShadowDepthTextureResolution();
const uint32 MaxShadowResolution = FMath::Min(MaxShadowResolutionSetting, ShadowBufferResolution.X) - EffectiveDoubleShadowBorder;
const uint32 MaxShadowResolutionY = FMath::Min(MaxShadowResolutionSetting, ShadowBufferResolution.Y) - EffectiveDoubleShadowBorder;
const int32 ShadowFadeResolution = CVarShadowFadeResolution.GetValueOnRenderThread();
// Compute the maximum resolution required for the shadow by any view. Also keep track of the unclamped resolution for fading.
float MaxDesiredResolution = 0;
float MaxUnclampedResolution = 0;
TArray<float, TInlineAllocator<2> > FadeAlphas;
float MaxFadeAlpha = 0;
bool bStaticSceneOnly = false;
for(int32 ViewIndex = 0, ViewCount = Views.Num(); ViewIndex < ViewCount; ++ViewIndex)
{
const FViewInfo& View = Views[ViewIndex];
// Determine the size of the light's bounding sphere in this view.
const FVector4 ScreenPosition = View.WorldToScreen(LightSceneInfo->Proxy->GetOrigin());
const float ScreenRadius = View.ShadowViewMatrices.ScreenScale *
LightSceneInfo->Proxy->GetRadius() /
FMath::Max(ScreenPosition.W,1.0f);
// Determine the amount of shadow buffer resolution needed for this view.
const float UnclampedResolution = ScreenRadius * CVarShadowTexelsPerPixelSpotlight.GetValueOnRenderThread();
MaxUnclampedResolution = FMath::Max( MaxUnclampedResolution, UnclampedResolution );
MaxDesiredResolution = FMath::Max(
MaxDesiredResolution,
FMath::Clamp<float>(
UnclampedResolution,
FMath::Min<float>(MinShadowResolution,ShadowBufferResolution.X - EffectiveDoubleShadowBorder),
MaxShadowResolution
)
);
bStaticSceneOnly = bStaticSceneOnly || View.bStaticSceneOnly;
const float FadeAlpha = CalculateShadowFadeAlpha( MaxUnclampedResolution, ShadowFadeResolution, MinShadowResolution );
MaxFadeAlpha = FMath::Max(MaxFadeAlpha, FadeAlpha);
FadeAlphas.Add(FadeAlpha);
}
if (MaxFadeAlpha > 1.0f / 256.0f)
{
for (int32 ShadowIndex = 0, ShadowCount = ProjectedShadowInitializers.Num(); ShadowIndex < ShadowCount; ShadowIndex++)
{
const FWholeSceneProjectedShadowInitializer& ProjectedShadowInitializer = ProjectedShadowInitializers[ShadowIndex];
// Round down to the nearest power of two so that resolution changes are always doubling or halving the resolution, which increases filtering stability
// Use the max resolution if the desired resolution is larger than that
int32 SizeX = MaxDesiredResolution >= MaxShadowResolution ? MaxShadowResolution : (1 << (FMath::CeilLogTwo(MaxDesiredResolution) - 1));
const uint32 DesiredSizeY = FMath::TruncToInt(MaxDesiredResolution);
int32 SizeY = DesiredSizeY >= MaxShadowResolutionY ? MaxShadowResolutionY : (1 << (FMath::CeilLogTwo(DesiredSizeY) - 1));
if (ProjectedShadowInitializer.CascadeSettings.bOnePassPointLightShadow)
{
// Round to a resolution that is supported for one pass point light shadows
SizeX = SizeY = GSceneRenderTargets.GetCubeShadowDepthZResolution(GSceneRenderTargets.GetCubeShadowDepthZIndex(MaxDesiredResolution));
}
// Create the projected shadow info.
FProjectedShadowInfo* ProjectedShadowInfo = new(FMemStack::Get(),1,16) FProjectedShadowInfo;
ProjectedShadowInfo->SetupWholeSceneProjection(
LightSceneInfo,
NULL,
ProjectedShadowInitializer,
SizeX,
SizeY,
false // no RSM
);
ProjectedShadowInfo->FadeAlphas = FadeAlphas;
VisibleLightInfo.MemStackProjectedShadows.Add(ProjectedShadowInfo);
VisibleLightInfo.AllProjectedShadows.Add(ProjectedShadowInfo);
if (ProjectedShadowInitializer.CascadeSettings.bOnePassPointLightShadow)
{
const static FVector CubeDirections[6] =
{
FVector(-1, 0, 0),
FVector(1, 0, 0),
FVector(0, -1, 0),
FVector(0, 1, 0),
FVector(0, 0, -1),
FVector(0, 0, 1)
};
const static FVector UpVectors[6] =
{
FVector(0, 1, 0),
FVector(0, 1, 0),
FVector(0, 0, -1),
FVector(0, 0, 1),
FVector(0, 1, 0),
FVector(0, 1, 0)
};
const FLightSceneProxy& LightProxy = *(ProjectedShadowInfo->GetLightSceneInfo().Proxy);
const FMatrix FaceProjection = FPerspectiveMatrix(PI / 4.0f, 1, 1, 1, LightProxy.GetRadius());
const FVector LightPosition = LightProxy.GetPosition();
ProjectedShadowInfo->OnePassShadowViewProjectionMatrices.Empty(6);
ProjectedShadowInfo->OnePassShadowFrustums.Empty(6);
ProjectedShadowInfo->OnePassShadowFrustums.AddZeroed(6);
const FMatrix ScaleMatrix = FScaleMatrix(FVector(1, -1, 1));
for (int32 FaceIndex = 0; FaceIndex < 6; FaceIndex++)
{
// Create a view projection matrix for each cube face
const FMatrix ShadowViewProjectionMatrix = FLookAtMatrix(LightPosition, LightPosition + CubeDirections[FaceIndex], UpVectors[FaceIndex]) * ScaleMatrix * FaceProjection;
ProjectedShadowInfo->OnePassShadowViewProjectionMatrices.Add(ShadowViewProjectionMatrix);
// Create a convex volume out of the frustum so it can be used for object culling
GetViewFrustumBounds(ProjectedShadowInfo->OnePassShadowFrustums[FaceIndex], ShadowViewProjectionMatrix, false);
}
}
// Ray traced shadows use the GPU managed distance field object buffers, no CPU culling should be used
if (!ProjectedShadowInfo->CascadeSettings.bRayTracedDistanceField)
{
// Add all the shadow casting primitives affected by the light to the shadow's subject primitive list.
for(FLightPrimitiveInteraction* Interaction = LightSceneInfo->DynamicPrimitiveList;
Interaction;
Interaction = Interaction->GetNextPrimitive())
{
if (Interaction->HasShadow()
// If the primitive only wants to cast a self shadow don't include it in whole scene shadows.
&& !Interaction->CastsSelfShadowOnly()
&& (!bStaticSceneOnly || Interaction->GetPrimitiveSceneInfo()->Proxy->HasStaticLighting()))
{
ProjectedShadowInfo->AddSubjectPrimitive(Interaction->GetPrimitiveSceneInfo(), &Views);
}
}
}
}
}
}
}
void FSceneRenderer::InitProjectedShadowVisibility(FRHICommandListImmediate& RHICmdList)
{
SCOPE_CYCLE_COUNTER(STAT_InitProjectedShadowVisibility);
// Initialize the views' ProjectedShadowVisibilityMaps and remove shadows without subjects.
for(TSparseArray<FLightSceneInfoCompact>::TConstIterator LightIt(Scene->Lights);LightIt;++LightIt)
{
FVisibleLightInfo& VisibleLightInfo = VisibleLightInfos[LightIt.GetIndex()];
// Allocate the light's projected shadow visibility and view relevance maps for this view.
for(int32 ViewIndex = 0;ViewIndex < Views.Num();ViewIndex++)
{
FViewInfo& View = Views[ViewIndex];
FVisibleLightViewInfo& VisibleLightViewInfo = View.VisibleLightInfos[LightIt.GetIndex()];
VisibleLightViewInfo.ProjectedShadowVisibilityMap.Init(false,VisibleLightInfo.AllProjectedShadows.Num());
VisibleLightViewInfo.ProjectedShadowViewRelevanceMap.Empty(VisibleLightInfo.AllProjectedShadows.Num());
VisibleLightViewInfo.ProjectedShadowViewRelevanceMap.AddZeroed(VisibleLightInfo.AllProjectedShadows.Num());
}
for( int32 ShadowIndex=0; ShadowIndex<VisibleLightInfo.AllProjectedShadows.Num(); ShadowIndex++ )
{
FProjectedShadowInfo& ProjectedShadowInfo = *VisibleLightInfo.AllProjectedShadows[ShadowIndex];
// Assign the shadow its id.
ProjectedShadowInfo.ShadowId = ShadowIndex;
for(int32 ViewIndex = 0;ViewIndex < Views.Num();ViewIndex++)
{
FViewInfo& View = Views[ViewIndex];
if (ProjectedShadowInfo.DependentView && ProjectedShadowInfo.DependentView != &View)
{
// The view dependent projected shadow is valid for this view if it's the
// right eye and the projected shadow is being rendered for the left eye.
const bool bIsValidForView = View.StereoPass == eSSP_RIGHT_EYE
&& Views.IsValidIndex(ViewIndex - 1)
&& Views[ViewIndex - 1].StereoPass == eSSP_LEFT_EYE
&& ProjectedShadowInfo.FadeAlphas.IsValidIndex(ViewIndex)
&& ProjectedShadowInfo.FadeAlphas[ViewIndex] == 1.0f;
if (!bIsValidForView)
{
continue;
}
}
FVisibleLightViewInfo& VisibleLightViewInfo = View.VisibleLightInfos[LightIt.GetIndex()];
if(VisibleLightViewInfo.bInViewFrustum)
{
// Compute the subject primitive's view relevance. Note that the view won't necessarily have it cached,
// since the primitive might not be visible.
FPrimitiveViewRelevance ViewRelevance;
if(ProjectedShadowInfo.GetParentSceneInfo())
{
ViewRelevance = ProjectedShadowInfo.GetParentSceneInfo()->Proxy->GetViewRelevance(&View);
}
else
{
ViewRelevance.bDrawRelevance = ViewRelevance.bStaticRelevance = ViewRelevance.bDynamicRelevance = ViewRelevance.bShadowRelevance = true;
}
VisibleLightViewInfo.ProjectedShadowViewRelevanceMap[ShadowIndex] = ViewRelevance;
// Check if the subject primitive's shadow is view relevant.
const bool bPrimitiveIsShadowRelevant = ViewRelevance.bShadowRelevance;
// Check if the shadow and preshadow are occluded.
const bool bShadowIsOccluded =
!View.bIgnoreExistingQueries &&
View.State &&
((FSceneViewState*)View.State)->IsShadowOccluded(
RHICmdList,
ProjectedShadowInfo.GetParentSceneInfo() ?
ProjectedShadowInfo.GetParentSceneInfo()->PrimitiveComponentId :
FPrimitiveComponentId(),
ProjectedShadowInfo.GetLightSceneInfo().Proxy->GetLightComponent(),
ProjectedShadowInfo.CascadeSettings.ShadowSplitIndex,
ProjectedShadowInfo.bTranslucentShadow
);
// The shadow is visible if it is view relevant and unoccluded.
if(bPrimitiveIsShadowRelevant && !bShadowIsOccluded)
{
VisibleLightViewInfo.ProjectedShadowVisibilityMap[ShadowIndex] = true;
}
// Draw the shadow frustum.
if(bPrimitiveIsShadowRelevant && !bShadowIsOccluded && !ProjectedShadowInfo.bReflectiveShadowmap)
{
bool bDrawPreshadowFrustum = CVarDrawPreshadowFrustum.GetValueOnRenderThread() != 0;
if ((ViewFamily.EngineShowFlags.ShadowFrustums)
&& ((bDrawPreshadowFrustum && ProjectedShadowInfo.bPreShadow) || (!bDrawPreshadowFrustum && !ProjectedShadowInfo.bPreShadow)))
{
FViewElementPDI ShadowFrustumPDI(&Views[ViewIndex],NULL);
if(ProjectedShadowInfo.IsWholeSceneDirectionalShadow())
{
// Get split color
FColor Color = FColor::White;
switch(ProjectedShadowInfo.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;
}
const FMatrix ViewMatrix = View.ViewMatrices.ViewMatrix;
const FMatrix ProjectionMatrix = View.ViewMatrices.ProjMatrix;
const FVector4 ViewOrigin = View.ViewMatrices.ViewOrigin;
float AspectRatio = ProjectionMatrix.M[1][1] / ProjectionMatrix.M[0][0];
float ActualFOV = (ViewOrigin.W > 0.0f) ? FMath::Atan(1.0f / ProjectionMatrix.M[0][0]) : PI/4.0f;
float Near = ProjectedShadowInfo.CascadeSettings.SplitNear;
float Mid = ProjectedShadowInfo.CascadeSettings.FadePlaneOffset;
float Far = ProjectedShadowInfo.CascadeSettings.SplitFar;
// Camera Subfrustum
DrawFrustumWireframe(&ShadowFrustumPDI, (ViewMatrix * FPerspectiveMatrix(ActualFOV, AspectRatio, 1.0f, Near, Mid)).Inverse(), Color, 0);
DrawFrustumWireframe(&ShadowFrustumPDI, (ViewMatrix * FPerspectiveMatrix(ActualFOV, AspectRatio, 1.0f, Mid, Far)).Inverse(), FColor::White, 0);
// Subfrustum Sphere Bounds
DrawWireSphere(&ShadowFrustumPDI, FTransform(ProjectedShadowInfo.ShadowBounds.Center), Color, ProjectedShadowInfo.ShadowBounds.W, 40, 0);
// Shadow Map Projection Bounds
DrawFrustumWireframe(&ShadowFrustumPDI, ProjectedShadowInfo.SubjectAndReceiverMatrix.Inverse() * FTranslationMatrix(-ProjectedShadowInfo.PreShadowTranslation), Color, 0);
}
else
{
ProjectedShadowInfo.RenderFrustumWireframe(&ShadowFrustumPDI);
}
}
}
}
}
}
}
#if !(UE_BUILD_SHIPPING || UE_BUILD_TEST)
if(GDumpShadowSetup)
{
GDumpShadowSetup = false;
UE_LOG(LogRenderer, Display, TEXT("Dump Shadow Setup:"));
for(int32 ViewIndex = 0;ViewIndex < Views.Num();ViewIndex++)
{
FViewInfo& View = Views[ViewIndex];
UE_LOG(LogRenderer, Display, TEXT(" View %d/%d"), ViewIndex, Views.Num());
uint32 LightIndex = 0;
for(TSparseArray<FLightSceneInfoCompact>::TConstIterator LightIt(Scene->Lights); LightIt; ++LightIt, ++LightIndex)
{
FVisibleLightInfo& VisibleLightInfo = VisibleLightInfos[LightIt.GetIndex()];
FVisibleLightViewInfo& VisibleLightViewInfo = View.VisibleLightInfos[LightIt.GetIndex()];
UE_LOG(LogRenderer, Display, TEXT(" Light %d/%d:"), LightIndex, Scene->Lights.Num());
for( int32 ShadowIndex = 0, ShadowCount = VisibleLightInfo.AllProjectedShadows.Num(); ShadowIndex < ShadowCount; ShadowIndex++ )
{
FProjectedShadowInfo& ProjectedShadowInfo = *VisibleLightInfo.AllProjectedShadows[ShadowIndex];
if(VisibleLightViewInfo.bInViewFrustum)
{
UE_LOG(LogRenderer, Display, TEXT(" Shadow %d/%d: ShadowId=%d"), ShadowIndex, ShadowCount, ProjectedShadowInfo.ShadowId);
UE_LOG(LogRenderer, Display, TEXT(" WholeSceneDir=%d SplitIndex=%d near=%f far=%f"),
ProjectedShadowInfo.IsWholeSceneDirectionalShadow(),
ProjectedShadowInfo.CascadeSettings.ShadowSplitIndex,
ProjectedShadowInfo.CascadeSettings.SplitNear,
ProjectedShadowInfo.CascadeSettings.SplitFar);
UE_LOG(LogRenderer, Display, TEXT(" bDistField=%d bFarShadows=%d Bounds=%f,%f,%f,%f"),
ProjectedShadowInfo.CascadeSettings.bRayTracedDistanceField,
ProjectedShadowInfo.CascadeSettings.bFarShadowCascade,
ProjectedShadowInfo.ShadowBounds.Center.X,
ProjectedShadowInfo.ShadowBounds.Center.Y,
ProjectedShadowInfo.ShadowBounds.Center.Z,
ProjectedShadowInfo.ShadowBounds.W);
UE_LOG(LogRenderer, Display, TEXT(" SplitFadeRegion=%f .. %f FadePlaneOffset=%f FadePlaneLength=%f"),
ProjectedShadowInfo.CascadeSettings.SplitNearFadeRegion,
ProjectedShadowInfo.CascadeSettings.SplitFarFadeRegion,
ProjectedShadowInfo.CascadeSettings.FadePlaneOffset,
ProjectedShadowInfo.CascadeSettings.FadePlaneLength);
}
}
}
}
}
#endif // !(UE_BUILD_SHIPPING || UE_BUILD_TEST)
}
void FSceneRenderer::GatherShadowDynamicMeshElements()
{
TArray<const FSceneView*> ReusedViewsArray;
ReusedViewsArray.AddZeroed(1);
for (TSparseArray<FLightSceneInfoCompact>::TConstIterator LightIt(Scene->Lights); LightIt; ++LightIt)
{
FVisibleLightInfo& VisibleLightInfo = VisibleLightInfos[LightIt.GetIndex()];
for (int32 ShadowIndex = 0; ShadowIndex<VisibleLightInfo.AllProjectedShadows.Num(); ShadowIndex++)
{
FProjectedShadowInfo& ProjectedShadowInfo = *VisibleLightInfo.AllProjectedShadows[ShadowIndex];
ProjectedShadowInfo.GatherDynamicMeshElements(*this, VisibleLightInfo, ReusedViewsArray);
}
}
}
inline void FSceneRenderer::GatherShadowsForPrimitiveInner(
const FPrimitiveSceneInfoCompact& PrimitiveSceneInfoCompact,
const TArray<FProjectedShadowInfo*,SceneRenderingAllocator>& PreShadows,
const TArray<FProjectedShadowInfo*,SceneRenderingAllocator>& ViewDependentWholeSceneShadows,
bool bStaticSceneOnly)
{
if(PrimitiveSceneInfoCompact.bCastDynamicShadow)
{
FPrimitiveSceneInfo* RESTRICT PrimitiveSceneInfo = PrimitiveSceneInfoCompact.PrimitiveSceneInfo;
FPrimitiveSceneProxy* RESTRICT PrimitiveProxy = PrimitiveSceneInfoCompact.Proxy;
const FBoxSphereBounds& PrimitiveBounds = PrimitiveSceneInfoCompact.Bounds;
// Check if the primitive is a subject for any of the preshadows.
// Only allow preshadows from lightmapped primitives that cast both dynamic and static shadows.
if (PreShadows.Num() && PrimitiveProxy->CastsStaticShadow() && PrimitiveProxy->HasStaticLighting())
{
for( int32 ShadowIndex = 0, Num = PreShadows.Num(); ShadowIndex < PreShadows.Num(); ShadowIndex++ )
{
FProjectedShadowInfo* RESTRICT ProjectedShadowInfo = PreShadows[ShadowIndex];
// Check if this primitive is in the shadow's frustum.
bool bInFrustum = ProjectedShadowInfo->CasterFrustum.IntersectBox( PrimitiveBounds.Origin, ProjectedShadowInfo->PreShadowTranslation, PrimitiveBounds.BoxExtent );
if( bInFrustum && ProjectedShadowInfo->GetLightSceneInfoCompact().AffectsPrimitive(PrimitiveSceneInfoCompact) )
{
// Add this primitive to the shadow.
ProjectedShadowInfo->AddSubjectPrimitive(PrimitiveSceneInfo, &Views);
}
}
}
if(PrimitiveSceneInfoCompact.bCastDynamicShadow || PrimitiveSceneInfoCompact.bAffectDynamicIndirectLighting )
{
for(int32 ShadowIndex = 0, Num = ViewDependentWholeSceneShadows.Num();ShadowIndex < Num;ShadowIndex++)
{
FProjectedShadowInfo* RESTRICT ProjectedShadowInfo = ViewDependentWholeSceneShadows[ShadowIndex];
if ( ProjectedShadowInfo->bReflectiveShadowmap && !PrimitiveSceneInfoCompact.bAffectDynamicIndirectLighting )
{
continue;
}
if ( !ProjectedShadowInfo->bReflectiveShadowmap && !PrimitiveSceneInfoCompact.bCastDynamicShadow )
{
continue;
}
FLightSceneProxy* RESTRICT LightProxy = ProjectedShadowInfo->GetLightSceneInfo().Proxy;
const FVector LightDirection = LightProxy->GetDirection();
const FVector PrimitiveToShadowCenter = ProjectedShadowInfo->ShadowBounds.Center - PrimitiveBounds.Origin;
// Project the primitive's bounds origin onto the light vector
const float ProjectedDistanceFromShadowOriginAlongLightDir = PrimitiveToShadowCenter | LightDirection;
// Calculate the primitive's squared distance to the cylinder's axis
const float PrimitiveDistanceFromCylinderAxisSq = (-LightDirection * ProjectedDistanceFromShadowOriginAlongLightDir + PrimitiveToShadowCenter).SizeSquared();
// Include all primitives for movable lights, but only statically shadowed primitives from a light with static shadowing,
// Since lights with static shadowing still create per-object shadows for primitives without static shadowing.
if( (!LightProxy->HasStaticLighting() || !ProjectedShadowInfo->GetLightSceneInfo().IsPrecomputedLightingValid())
// Check if this primitive is in the shadow's cylinder
&& PrimitiveDistanceFromCylinderAxisSq < FMath::Square(ProjectedShadowInfo->ShadowBounds.W + PrimitiveBounds.SphereRadius)
// Check if the primitive is closer than the cylinder cap toward the light
// next line is commented as it breaks large world shadows, if this was meant to be an optimization we should think about a better solution
//// && ProjectedDistanceFromShadowOriginAlongLightDir - PrimitiveBounds.SphereRadius < -ProjectedShadowInfo->MinPreSubjectZ
// If the primitive is further along the cone axis than the shadow bounds origin,
// Check if the primitive is inside the spherical cap of the cascade's bounds
&& !(ProjectedDistanceFromShadowOriginAlongLightDir < 0
&& PrimitiveToShadowCenter.SizeSquared() > FMath::Square(ProjectedShadowInfo->ShadowBounds.W + PrimitiveBounds.SphereRadius)))
{
const bool bInFrustum = ProjectedShadowInfo->CascadeSettings.ShadowBoundsAccurate.IntersectBox( PrimitiveBounds.Origin, PrimitiveBounds.BoxExtent );
if( bInFrustum )
{
// Distance culling for RSMs
float MinScreenRadiusForShadowCaster = GMinScreenRadiusForShadowCaster;
if (ProjectedShadowInfo->bReflectiveShadowmap)
{
MinScreenRadiusForShadowCaster = GMinScreenRadiusForShadowCasterRSM;
}
bool bScreenSpaceSizeCulled = false;
check( ProjectedShadowInfo->DependentView );
if ( ProjectedShadowInfo->DependentView )
{
const float DistanceSquared = ( PrimitiveBounds.Origin - ProjectedShadowInfo->DependentView->ShadowViewMatrices.ViewOrigin ).SizeSquared();
bScreenSpaceSizeCulled = FMath::Square( PrimitiveBounds.SphereRadius ) < FMath::Square( MinScreenRadiusForShadowCaster ) * DistanceSquared;
}
if (ProjectedShadowInfo->GetLightSceneInfoCompact().AffectsPrimitive(PrimitiveSceneInfoCompact)
// Exclude primitives that will create their own per-object shadow, except when rendering RSMs
&& ( !PrimitiveProxy->CastsInsetShadow() || ProjectedShadowInfo->bReflectiveShadowmap )
// Exclude primitives that will create a per-object shadow from a stationary light
&& !ShouldCreateObjectShadowForStationaryLight(&ProjectedShadowInfo->GetLightSceneInfo(), PrimitiveSceneInfo->Proxy, true)
// Only render shadows from objects that use static lighting during a reflection capture, since the reflection capture doesn't update at runtime
&& (!bStaticSceneOnly || PrimitiveProxy->HasStaticLighting())
&& !bScreenSpaceSizeCulled )
{
// Add this primitive to the shadow.
ProjectedShadowInfo->AddSubjectPrimitive(PrimitiveSceneInfo, NULL);
}
}
}
}
}
}
}
void FSceneRenderer::GatherShadowPrimitives(
const TArray<FProjectedShadowInfo*,SceneRenderingAllocator>& PreShadows,
const TArray<FProjectedShadowInfo*,SceneRenderingAllocator>& ViewDependentWholeSceneShadows,
bool bStaticSceneOnly
)
{
SCOPE_CYCLE_COUNTER(STAT_GatherShadowPrimitivesTime);
if(PreShadows.Num() || ViewDependentWholeSceneShadows.Num())
{
for(int32 ShadowIndex = 0, Num = ViewDependentWholeSceneShadows.Num(); ShadowIndex < Num;ShadowIndex++)
{
FProjectedShadowInfo* ProjectedShadowInfo = ViewDependentWholeSceneShadows[ShadowIndex];
checkSlow(ProjectedShadowInfo->DependentView);
// Initialize the whole scene shadow's depth map with the shadow independent depth map from the view
ProjectedShadowInfo->StaticMeshWholeSceneShadowDepthMap.Init(false,Scene->StaticMeshes.GetMaxIndex());
ProjectedShadowInfo->StaticMeshWholeSceneShadowBatchVisibility.AddZeroed(Scene->StaticMeshes.GetMaxIndex());
}
// Find primitives that are in a shadow frustum in the octree.
for(FScenePrimitiveOctree::TConstIterator<SceneRenderingAllocator> PrimitiveOctreeIt(Scene->PrimitiveOctree);
PrimitiveOctreeIt.HasPendingNodes();
PrimitiveOctreeIt.Advance())
{
const FScenePrimitiveOctree::FNode& PrimitiveOctreeNode = PrimitiveOctreeIt.GetCurrentNode();
const FOctreeNodeContext& PrimitiveOctreeNodeContext = PrimitiveOctreeIt.GetCurrentContext();
{
QUICK_SCOPE_CYCLE_COUNTER(STAT_ShadowOctreeTraversal);
// Find children of this octree node that may contain relevant primitives.
FOREACH_OCTREE_CHILD_NODE(ChildRef)
{
if(PrimitiveOctreeNode.HasChild(ChildRef))
{
// Check that the child node is in the frustum for at least one shadow.
const FOctreeNodeContext ChildContext = PrimitiveOctreeNodeContext.GetChildContext(ChildRef);
bool bIsInFrustum = false;
// Check for subjects of preshadows.
if(!bIsInFrustum)
{
for(int32 ShadowIndex = 0, Num = PreShadows.Num(); ShadowIndex < Num; ShadowIndex++)
{
FProjectedShadowInfo* ProjectedShadowInfo = PreShadows[ShadowIndex];
// Check if this primitive is in the shadow's frustum.
if(ProjectedShadowInfo->CasterFrustum.IntersectBox(
ChildContext.Bounds.Center + ProjectedShadowInfo->PreShadowTranslation,
ChildContext.Bounds.Extent
))
{
bIsInFrustum = true;
break;
}
}
}
if (!bIsInFrustum)
{
for(int32 ShadowIndex = 0, Num = ViewDependentWholeSceneShadows.Num(); ShadowIndex < Num; ShadowIndex++)
{
FProjectedShadowInfo* ProjectedShadowInfo = ViewDependentWholeSceneShadows[ShadowIndex];
// Check if this primitive is in the shadow's frustum.
if(ProjectedShadowInfo->CasterFrustum.IntersectBox(
ChildContext.Bounds.Center + ProjectedShadowInfo->PreShadowTranslation,
ChildContext.Bounds.Extent
))
{
bIsInFrustum = true;
break;
}
}
}
if(bIsInFrustum)
{
// If the child node was in the frustum of at least one preshadow, push it on
// the iterator's pending node stack.
PrimitiveOctreeIt.PushChild(ChildRef);
}
}
}
}
// Check all the primitives in this octree node.
for(FScenePrimitiveOctree::ElementConstIt NodePrimitiveIt(PrimitiveOctreeNode.GetElementIt());NodePrimitiveIt;++NodePrimitiveIt)
{
// gather the shadows for this one primitive
GatherShadowsForPrimitiveInner(*NodePrimitiveIt, PreShadows, ViewDependentWholeSceneShadows, bStaticSceneOnly);
}
}
for(int32 ShadowIndex = 0, Num = PreShadows.Num(); ShadowIndex < Num; ShadowIndex++)
{
FProjectedShadowInfo* ProjectedShadowInfo = PreShadows[ShadowIndex];
//@todo - sort other shadow types' subject mesh elements?
// Probably needed for good performance with non-dominant whole scene shadows (spotlightmovable)
ProjectedShadowInfo->SortSubjectMeshElements();
}
}
}
void FSceneRenderer::AddViewDependentWholeSceneShadowsForView(
TArray<FProjectedShadowInfo*, SceneRenderingAllocator>& ShadowInfos,
TArray<FProjectedShadowInfo*, SceneRenderingAllocator>& ShadowInfosThatNeedCulling,
FVisibleLightInfo& VisibleLightInfo,
FLightSceneInfo& LightSceneInfo)
{
SCOPE_CYCLE_COUNTER(STAT_AddViewDependentWholeSceneShadowsForView);
// Allow each view to create a whole scene view dependent shadow
for (int32 ViewIndex = 0; ViewIndex < Views.Num(); ViewIndex++)
{
FViewInfo& View = Views[ViewIndex];
TArray<float, TInlineAllocator<2> > FadeAlphas;
FadeAlphas.Init(0.0f, Views.Num());
FadeAlphas[ViewIndex] = 1.0f;
if (View.StereoPass == eSSP_LEFT_EYE
&& Views.IsValidIndex(ViewIndex + 1)
&& Views[ViewIndex + 1].StereoPass == eSSP_RIGHT_EYE)
{
FadeAlphas[ViewIndex + 1] = 1.0f;
}
// If rendering in stereo mode we render shadow depths only for the left eye, but project for both eyes!
if (View.StereoPass != eSSP_RIGHT_EYE)
{
const bool bExtraDistanceFieldCascade = LightSceneInfo.Proxy->ShouldCreateRayTracedCascade(View.GetFeatureLevel(), LightSceneInfo.IsPrecomputedLightingValid());
const int32 ProjectionCount = LightSceneInfo.Proxy->GetNumViewDependentWholeSceneShadows(View, LightSceneInfo.IsPrecomputedLightingValid()) + (bExtraDistanceFieldCascade?1:0);
checkSlow(INDEX_NONE == -1);
// todo: this code can be simplified by computing all the distances in one place - avoiding some redundant work and complexity
for (int32 Index = 0; Index < ProjectionCount; Index++)
{
FWholeSceneProjectedShadowInitializer ProjectedShadowInitializer;
int32 LocalIndex = Index;
// Indexing like this puts the raytraced shadow cascade last (might not be needed)
if(bExtraDistanceFieldCascade && LocalIndex + 1 == ProjectionCount)
{
LocalIndex = INDEX_NONE;
}
if (LightSceneInfo.Proxy->GetViewDependentWholeSceneProjectedShadowInitializer(View, LocalIndex, LightSceneInfo.IsPrecomputedLightingValid(), ProjectedShadowInitializer))
{
const FIntPoint ShadowBufferResolution = GSceneRenderTargets.GetShadowDepthTextureResolution();
// Create the projected shadow info.
FProjectedShadowInfo* ProjectedShadowInfo = new(FMemStack::Get(), 1, 16) FProjectedShadowInfo;
ProjectedShadowInfo->SetupWholeSceneProjection(
&LightSceneInfo,
&View,
ProjectedShadowInitializer,
//@todo - remove the shadow border for whole scene shadows
ShadowBufferResolution.X - SHADOW_BORDER * 2,
ShadowBufferResolution.Y - SHADOW_BORDER * 2,
false // no RSM
);
ProjectedShadowInfo->FadeAlphas = FadeAlphas;
FVisibleLightInfo& LightViewInfo = VisibleLightInfos[LightSceneInfo.Id];
VisibleLightInfo.MemStackProjectedShadows.Add(ProjectedShadowInfo);
VisibleLightInfo.AllProjectedShadows.Add(ProjectedShadowInfo);
ShadowInfos.Add(ProjectedShadowInfo);
// Ray traced shadows use the GPU managed distance field object buffers, no CPU culling needed
if (!ProjectedShadowInfo->CascadeSettings.bRayTracedDistanceField)
{
ShadowInfosThatNeedCulling.Add(ProjectedShadowInfo);
}
}
}
FSceneViewState* ViewState = (FSceneViewState*)View.State;
if (ViewState)
{
FLightPropagationVolume* LightPropagationVolume = ViewState->GetLightPropagationVolume();
if (LightPropagationVolume && View.FinalPostProcessSettings.LPVIntensity > 0)
{
// Generate the RSM shadow info
FWholeSceneProjectedShadowInitializer ProjectedShadowInitializer;
FLightPropagationVolume& Lpv = *LightPropagationVolume;
if (LightSceneInfo.Proxy->GetViewDependentRsmWholeSceneProjectedShadowInitializer(View, Lpv.GetBoundingBox(), ProjectedShadowInitializer))
{
// moved out from the FProjectedShadowInfo constructor
ProjectedShadowInitializer.CascadeSettings.ShadowSplitIndex = 0;
const FIntPoint ShadowBufferResolution = GSceneRenderTargets.GetReflectiveShadowMapTextureResolution();
// Create the projected shadow info.
FProjectedShadowInfo* ProjectedShadowInfo = new(FMemStack::Get(), 1, 16) FProjectedShadowInfo;
ProjectedShadowInfo->SetupWholeSceneProjection(
&LightSceneInfo,
&View,
ProjectedShadowInitializer,
ShadowBufferResolution.X,
ShadowBufferResolution.Y,
true); // RSM
FVisibleLightInfo& LightViewInfo = VisibleLightInfos[LightSceneInfo.Id];
VisibleLightInfo.MemStackProjectedShadows.Add(ProjectedShadowInfo);
VisibleLightInfo.AllProjectedShadows.Add(ProjectedShadowInfo);
VisibleLightInfo.ReflectiveShadowMaps.Add(ProjectedShadowInfo);
ShadowInfos.Add(ProjectedShadowInfo); // or separate list?
// Ray traced shadows use the GPU managed distance field object buffers, no CPU culling needed
if (!ProjectedShadowInfo->CascadeSettings.bRayTracedDistanceField)
{
ShadowInfosThatNeedCulling.Add(ProjectedShadowInfo);
}
}
}
}
}
}
}
void FForwardShadingSceneRenderer::InitDynamicShadows(FRHICommandListImmediate& RHICmdList)
{
TArray<FProjectedShadowInfo*, SceneRenderingAllocator> ViewDependentWholeSceneShadows;
TArray<FProjectedShadowInfo*, SceneRenderingAllocator> ViewDependentWholeSceneShadowsThatNeedCulling;
TArray<FProjectedShadowInfo*, SceneRenderingAllocator> PreShadows;
{
SCOPE_CYCLE_COUNTER(STAT_InitDynamicShadowsTime);
for (TSparseArray<FLightSceneInfoCompact>::TConstIterator LightIt(Scene->Lights); LightIt; ++LightIt)
{
const FLightSceneInfoCompact& LightSceneInfoCompact = *LightIt;
FLightSceneInfo* LightSceneInfo = LightSceneInfoCompact.LightSceneInfo;
FVisibleLightInfo& VisibleLightInfo = VisibleLightInfos[LightSceneInfo->Id];
// Only consider lights that may have shadows.
if (LightSceneInfo->ShouldRenderViewIndependentWholeSceneShadows())
{
// see if the light is visible in any view
bool bIsVisibleInAnyView = false;
for (int32 ViewIndex = 0; ViewIndex < Views.Num(); ViewIndex++)
{
// View frustums are only checked when lights have visible primitives or have modulated shadows,
// so we don't need to check for that again here
bIsVisibleInAnyView = LightSceneInfo->ShouldRenderLight(Views[ViewIndex]);
if (bIsVisibleInAnyView)
{
break;
}
}
if (bIsVisibleInAnyView)
{
AddViewDependentWholeSceneShadowsForView(ViewDependentWholeSceneShadows, ViewDependentWholeSceneShadowsThatNeedCulling, VisibleLightInfo, *LightSceneInfo);
}
const int32 NumShadows = FMath::Min(VisibleLightInfo.AllProjectedShadows.Num(), MAX_FORWARD_SHADOWCASCADES);
if (NumShadows > 0)
{
//create the shadow depth texture and/or surface
const FIntPoint ShadowBufferResolution = GSceneRenderTargets.GetShadowDepthTextureResolution();
const int32 MaxWide = GMaxShadowDepthBufferSizeX / ShadowBufferResolution.X;
const int32 MaxHigh = GMaxShadowDepthBufferSizeY / ShadowBufferResolution.Y;
const int32 NumWide = FMath::Min(NumShadows, MaxWide);
const int32 NumHigh = FMath::Min(((NumShadows - 1) / MaxWide) + 1, MaxHigh);
const FIntPoint AtlasShadowBufferResolution(ShadowBufferResolution.X * NumWide, ShadowBufferResolution.Y * NumHigh);
GSceneRenderTargets.AllocateForwardShadingShadowDepthTarget(AtlasShadowBufferResolution);
// Allocate atlas shadow texture space to the shadows.
FTextureLayout ShadowLayout(1, 1, AtlasShadowBufferResolution.X, AtlasShadowBufferResolution.Y, false, false);
for (int32 ShadowIndex = 0; ShadowIndex < VisibleLightInfo.AllProjectedShadows.Num(); ShadowIndex++)
{
FProjectedShadowInfo* ProjectedShadowInfo = VisibleLightInfo.AllProjectedShadows[ShadowIndex];
if (!ProjectedShadowInfo->bRendered)
{
if (ShadowLayout.AddElement(
ProjectedShadowInfo->X,
ProjectedShadowInfo->Y,
ProjectedShadowInfo->ResolutionX + SHADOW_BORDER * 2,
ProjectedShadowInfo->ResolutionY + SHADOW_BORDER * 2)
)
{
ProjectedShadowInfo->bAllocated = true;
}
}
}
}
}
}
// Calculate visibility of the projected shadows.
InitProjectedShadowVisibility(RHICmdList);
}
// Gathers the list of primitives used to draw various shadow types
GatherShadowPrimitives(PreShadows, ViewDependentWholeSceneShadowsThatNeedCulling, false);
// Generate mesh element arrays from shadow primitive arrays
GatherShadowDynamicMeshElements();
}
void FDeferredShadingSceneRenderer::InitDynamicShadows(FRHICommandListImmediate& RHICmdList)
{
SCOPE_CYCLE_COUNTER(STAT_DynamicShadowSetupTime);
bool bStaticSceneOnly = false;
for (int32 ViewIndex = 0; ViewIndex < Views.Num(); ViewIndex++)
{
FViewInfo& View = Views[ViewIndex];
bStaticSceneOnly = bStaticSceneOnly || View.bStaticSceneOnly;
}
TArray<FProjectedShadowInfo*,SceneRenderingAllocator> PreShadows;
TArray<FProjectedShadowInfo*,SceneRenderingAllocator> ViewDependentWholeSceneShadows;
TArray<FProjectedShadowInfo*,SceneRenderingAllocator> ViewDependentWholeSceneShadowsThatNeedCulling;
{
SCOPE_CYCLE_COUNTER(STAT_InitDynamicShadowsTime);
for (TSparseArray<FLightSceneInfoCompact>::TConstIterator LightIt(Scene->Lights); LightIt; ++LightIt)
{
const FLightSceneInfoCompact& LightSceneInfoCompact = *LightIt;
FLightSceneInfo* LightSceneInfo = LightSceneInfoCompact.LightSceneInfo;
FScopeCycleCounter Context(LightSceneInfo->Proxy->GetStatId());
FVisibleLightInfo& VisibleLightInfo = VisibleLightInfos[LightSceneInfo->Id];
// Only consider lights that may have shadows.
if (LightSceneInfoCompact.bCastStaticShadow || LightSceneInfoCompact.bCastDynamicShadow)
{
// see if the light is visible in any view
bool bIsVisibleInAnyView = false;
for (int32 ViewIndex = 0; ViewIndex < Views.Num(); ViewIndex++)
{
// View frustums are only checked when lights have visible primitives or have modulated shadows,
// so we don't need to check for that again here
bIsVisibleInAnyView = LightSceneInfo->ShouldRenderLight(Views[ViewIndex]);
if (bIsVisibleInAnyView)
{
break;
}
}
if (bIsVisibleInAnyView)
{
static const auto AllowStaticLightingVar = IConsoleManager::Get().FindTConsoleVariableDataInt(TEXT("r.AllowStaticLighting"));
const bool bAllowStaticLighting = (!AllowStaticLightingVar || AllowStaticLightingVar->GetValueOnRenderThread() != 0);
// Only create whole scene shadows for lights that don't precompute shadowing (movable lights)
const bool bCreateShadowForMovableLight =
LightSceneInfoCompact.bCastDynamicShadow
&& (!LightSceneInfo->Proxy->HasStaticShadowing() || !bAllowStaticLighting);
// Also create a whole scene shadow for lights with precomputed shadows that are unbuilt
const bool bCreateShadowToPreviewStaticLight =
LightSceneInfo->Proxy->HasStaticShadowing()
&& LightSceneInfoCompact.bCastStaticShadow
&& !LightSceneInfo->IsPrecomputedLightingValid();
// Create a whole scene shadow for lights that want static shadowing but didn't get assigned to a valid shadowmap channel due to overlap
const bool bCreateShadowForOverflowStaticShadowing =
LightSceneInfo->Proxy->HasStaticShadowing()
&& !LightSceneInfo->Proxy->HasStaticLighting()
&& LightSceneInfoCompact.bCastStaticShadow
&& LightSceneInfo->IsPrecomputedLightingValid()
&& LightSceneInfo->Proxy->GetShadowMapChannel() == INDEX_NONE;
if (bCreateShadowForMovableLight || bCreateShadowToPreviewStaticLight || bCreateShadowForOverflowStaticShadowing)
{
// Try to create a whole scene projected shadow.
CreateWholeSceneProjectedShadow(LightSceneInfo);
}
// Allow movable and stationary lights to create CSM, or static lights that are unbuilt
if ((!LightSceneInfo->Proxy->HasStaticLighting() && LightSceneInfoCompact.bCastDynamicShadow) || bCreateShadowToPreviewStaticLight)
{
AddViewDependentWholeSceneShadowsForView(ViewDependentWholeSceneShadows, ViewDependentWholeSceneShadowsThatNeedCulling, VisibleLightInfo, *LightSceneInfo);
// Look for individual primitives with a dynamic shadow.
for (FLightPrimitiveInteraction* Interaction = LightSceneInfo->DynamicPrimitiveList;
Interaction;
Interaction = Interaction->GetNextPrimitive()
)
{
SetupInteractionShadows(RHICmdList, Interaction, VisibleLightInfo, bStaticSceneOnly, ViewDependentWholeSceneShadows, PreShadows);
}
}
}
}
}
// Calculate visibility of the projected shadows.
InitProjectedShadowVisibility(RHICmdList);
}
// Clear old preshadows and attempt to add new ones to the cache
UpdatePreshadowCache();
// Gathers the list of primitives used to draw various shadow types
GatherShadowPrimitives(PreShadows, ViewDependentWholeSceneShadowsThatNeedCulling, bStaticSceneOnly);
// Generate mesh element arrays from shadow primitive arrays
GatherShadowDynamicMeshElements();
}