FSceneRenderer::InitDynamicShadows()
SetupInteractionShadows
void FSceneRenderer::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 FSceneRenderer::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 bOpaque = false;
bool bTranslucentRelevance = false;
bool bTranslucentShadowIsVisibleThisFrame = false;
int32 NumBufferedFrames = FOcclusionQueryHelpers::GetNumBufferedFrames(FeatureLevel);
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;
const FSceneViewState::FProjectedShadowKey OpaqueKey(PrimitiveSceneInfo->PrimitiveComponentId, LightSceneInfo->Proxy->GetLightComponent(), INDEX_NONE, false);
// Check if the shadow and preshadow are occluded.
const bool bOpaqueShadowIsOccluded =
!bCreateOpaqueObjectShadow ||
(
!View.bIgnoreExistingQueries && View.State &&
((FSceneViewState*)View.State)->IsShadowOccluded(RHICmdList, OpaqueKey, NumBufferedFrames)
);
const FSceneViewState::FProjectedShadowKey TranslucentKey(PrimitiveSceneInfo->PrimitiveComponentId, LightSceneInfo->Proxy->GetLightComponent(), INDEX_NONE, true);
const bool bTranslucentShadowIsOccluded =
!bCreateTranslucentObjectShadow ||
(
!View.bIgnoreExistingQueries && View.State &&
((FSceneViewState*)View.State)->IsShadowOccluded(RHICmdList, TranslucentKey, NumBufferedFrames)
);
// if subject doesn't render in the main pass, it's never considered visible
// (in this case, there will be no need to generate any preshadows for the subject)
if (PrimitiveSceneInfo->Proxy->ShouldRenderInMainPass())
{
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;
bOpaque |= ViewRelevance.bOpaque;
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);
#if ENABLE_NAN_DIAGNOSTIC
// allow for silent failure: only possible if NaN checking is enabled.
if (ShadowGroupPrimitives.Num() == 0)
{
return;
}
#endif
// Compute the composite bounds of this group of shadow primitives.
FBoxSphereBounds OriginalBounds = ShadowGroupPrimitives[0]->Proxy->GetBounds();
if (!ensureMsgf(OriginalBounds.ContainsNaN() == false, TEXT("OriginalBound contains NaN : %s"), *OriginalBounds.ToString()))
{
// fix up OriginalBounds. This is going to cause flickers
OriginalBounds = FBoxSphereBounds(FVector::ZeroVector, FVector(1.f), 1.f);
}
for (int32 ChildIndex = 1; ChildIndex < ShadowGroupPrimitives.Num(); ChildIndex++)
{
const FPrimitiveSceneInfo* ShadowChild = ShadowGroupPrimitives[ChildIndex];
if (ShadowChild->Proxy->CastsDynamicShadow())
{
FBoxSphereBounds ChildBound = ShadowChild->Proxy->GetBounds();
OriginalBounds = OriginalBounds + ChildBound;
if (!ensureMsgf(OriginalBounds.ContainsNaN() == false, TEXT("Child %s contains NaN : %s"), *ShadowChild->Proxy->GetOwnerName().ToString(), *ChildBound.ToString()))
{
// fix up OriginalBounds. This is going to cause flickers
OriginalBounds = FBoxSphereBounds(FVector::ZeroVector, FVector(1.f), 1.f);
}
}
}
FSceneRenderTargets& SceneContext = FSceneRenderTargets::Get(RHICmdList);
// Shadowing constants.
const uint32 MaxShadowResolutionSetting = GetCachedScalabilityCVars().MaxShadowResolution;
const FIntPoint ShadowBufferResolution = SceneContext.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 uint32 MinShadowResolution = FMath::Max<int32>(0, CVarMinShadowResolution.GetValueOnRenderThread());
const uint32 ShadowFadeResolution = FMath::Max<int32>(0, CVarShadowFadeResolution.GetValueOnRenderThread());
const uint32 MinPreShadowResolution = FMath::Max<int32>(0, CVarMinPreShadowResolution.GetValueOnRenderThread());
const uint32 PreShadowFadeResolution = FMath::Max<int32>(0, CVarPreShadowFadeResolution.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 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 FVector ShadowViewOrigin = View.ViewMatrices.GetViewOrigin();
float ShadowViewDistFromBounds = (OriginalBounds.Origin - ShadowViewOrigin).Size();
const float ScreenRadius = View.ShadowViewMatrices.GetScreenScale() *
OriginalBounds.SphereRadius /
FMath::Max(ShadowViewDistFromBounds, 1.0f);
// Early catch for invalid CalculateShadowFadeAlpha()
ensureMsgf(ScreenRadius >= 0.0f, TEXT("View.ShadowViewMatrices.ScreenScale %f, OriginalBounds.SphereRadius %f, ShadowViewDistFromBounds %f"), View.ShadowViewMatrices.GetScreenScale(), OriginalBounds.SphereRadius, ShadowViewDistFromBounds);
const float ScreenPercent = FMath::Max(
1.0f / 2.0f * View.ShadowViewMatrices.GetProjectionScale().X,
1.0f / 2.0f * View.ShadowViewMatrices.GetProjectionScale().Y
) *
OriginalBounds.SphereRadius /
FMath::Max(ShadowViewDistFromBounds, 1.0f);
MaxScreenPercent = FMath::Max(MaxScreenPercent, ScreenPercent);
// Determine the amount of shadow buffer resolution needed for this view.
const float UnclampedResolution = ScreenRadius * CVarShadowTexelsPerPixel.GetValueOnRenderThread();
// Calculate fading based on resolution
// Compute FadeAlpha before ShadowResolutionScale contribution (artists want to modify the softness of the shadow, not change the fade ranges)
const float ViewSpecificAlpha = CalculateShadowFadeAlpha(UnclampedResolution, ShadowFadeResolution, MinShadowResolution) * LightSceneInfo->Proxy->GetShadowAmount();
MaxResolutionFadeAlpha = FMath::Max(MaxResolutionFadeAlpha, ViewSpecificAlpha);
ResolutionFadeAlphas.Add(ViewSpecificAlpha);
const float ViewSpecificPreShadowAlpha = CalculateShadowFadeAlpha(UnclampedResolution * CVarPreShadowResolutionFactor.GetValueOnRenderThread(), PreShadowFadeResolution, MinPreShadowResolution) * LightSceneInfo->Proxy->GetShadowAmount();
MaxResolutionPreShadowFadeAlpha = FMath::Max(MaxResolutionPreShadowFadeAlpha, ViewSpecificPreShadowAlpha);
ResolutionPreShadowFadeAlphas.Add(ViewSpecificPreShadowAlpha);
const float ShadowResolutionScale = LightSceneInfo->Proxy->GetShadowResolutionScale();
float ClampedResolution = UnclampedResolution;
if (ShadowResolutionScale > 1.0f)
{
// Apply ShadowResolutionScale before the MaxShadowResolution clamp if raising the resolution
ClampedResolution *= ShadowResolutionScale;
}
ClampedResolution = FMath::Min<float>(ClampedResolution, MaxShadowResolution);
if (ShadowResolutionScale <= 1.0f)
{
// Apply ShadowResolutionScale after the MaxShadowResolution clamp if lowering the resolution
// Artists want to modify the softness of the shadow with ShadowResolutionScale
ClampedResolution *= ShadowResolutionScale;
}
MaxDesiredResolution = FMath::Max(
MaxDesiredResolution,
FMath::Max<uint32>(
ClampedResolution,
FMath::Min<int32>(MinShadowResolution, ShadowBufferResolution.X - SHADOW_BORDER * 2)
)
);
}
FBoxSphereBounds Bounds = OriginalBounds;
const bool bRenderPreShadow =
CVarAllowPreshadows.GetValueOnRenderThread()
&& LightSceneInfo->Proxy->HasStaticShadowing()
// 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())
// Disable preshadows from directional lights for primitives that use single sample shadowing, the shadow factor will be written into the precomputed shadow mask in the GBuffer instead
&& !(PrimitiveSceneInfo->Proxy->UseSingleSampleShadowFromStationaryLights() && LightSceneInfo->Proxy->GetLightType() == LightType_Directional)
&& Scene->GetFeatureLevel() >= ERHIFeatureLevel::SM5;
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 (bOpaque && 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,
SHADOW_BORDER,
MaxScreenPercent,
false)) // no translucent shadow
{
ProjectedShadowInfo->bPerObjectOpaqueShadow = true;
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, FeatureLevel, false);
}
}
else if (bShadowIsPotentiallyVisibleNextFrame)
{
VisibleLightInfo.OccludedPerObjectShadows.Add(ProjectedShadowInfo);
}
}
}
if (bTranslucentRelevance
&& Scene->GetFeatureLevel() >= ERHIFeatureLevel::SM5
&& 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 / SceneContext.GetTranslucentShadowDownsampleFactor(), 1, SceneContext.GetTranslucentShadowDepthTextureResolution().X - SHADOW_BORDER * 2),
FMath::Clamp<int32>(MaxShadowResolutionY / SceneContext.GetTranslucentShadowDownsampleFactor(), 1, SceneContext.GetTranslucentShadowDepthTextureResolution().Y - SHADOW_BORDER * 2),
SHADOW_BORDER,
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, FeatureLevel, false);
}
}
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
&& bOpaque)
{
// 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 = SceneContext.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(), WholeSceneShadow->DependentView->MaxShadowCascades);
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.GetViewOrigin() - 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);
bool bOk = true;
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;
bOk = ProjectedPreShadowInfo->SetupPerObjectProjection(
LightSceneInfo,
PrimitiveSceneInfo,
ShadowInitializer,
true, // preshadow
PreshadowSizeX,
FMath::TruncToInt(MaxShadowResolutionY * CVarPreShadowResolutionFactor.GetValueOnRenderThread()),
SHADOW_BORDER,
MaxScreenPercent,
false // not translucent shadow
);
}
if (bOk)
{
// 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 && ProjectedPreShadowInfo->CasterFrustum.PermutedPlanes.Num())
{
OutPreShadows.Add(ProjectedPreShadowInfo);
}
for (int32 ChildIndex = 0; ChildIndex < ShadowGroupPrimitives.Num(); ChildIndex++)
{
FPrimitiveSceneInfo* ShadowChild = ShadowGroupPrimitives[ChildIndex];
bool bChildIsVisibleInAnyView = false;
for (int32 ViewIndex = 0; ViewIndex < Views.Num(); ViewIndex++)
{
const FViewInfo& View = Views[ViewIndex];
if (View.PrimitiveVisibilityMap[ShadowChild->GetIndex()])
{
bChildIsVisibleInAnyView = true;
break;
}
}
if (bChildIsVisibleInAnyView)
{
ProjectedPreShadowInfo->AddReceiverPrimitive(ShadowChild);
}
}
}
}
}
}
}
void FProjectedShadowInfo::AddSubjectPrimitive(FPrimitiveSceneInfo* PrimitiveSceneInfo, TArray<FViewInfo>* ViewArray, ERHIFeatureLevel::Type FeatureLevel, bool bRecordShadowSubjectsForMobileShading)
{
// Ray traced shadows use the GPU managed distance field object buffers, no CPU culling should be used
check(!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
{
checkf(ViewArray,
TEXT("bWholeSceneShadow=%d, CascadeSettings.ShadowSplitIndex=%d, bDirectionalLight=%s"),
bWholeSceneShadow ? TEXT("true") : TEXT("false"),
CascadeSettings.ShadowSplitIndex,
bDirectionalLight ? TEXT("true") : TEXT("false"));
for (int32 ViewIndex = 0; ViewIndex < ViewArray->Num(); ViewIndex++)
{
Views.Add(&(*ViewArray)[ViewIndex]);
}
}
bool bOpaque = false;
bool bTranslucentRelevance = false;
bool bShadowRelevance = false;
uint32 ViewMask = 0;
int32 PrimitiveId = PrimitiveSceneInfo->GetIndex();
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.GetViewOrigin()).SizeSquared();
bool bIsDistanceCulled = CurrentView.IsDistanceCulled(
DistanceSquared,
Proxy->GetMinDrawDistance(),
Proxy->GetMaxDrawDistance(),
PrimitiveSceneInfo
);
if( bIsDistanceCulled )
{
continue;
}
}
// Respect HLOD visibility which can hide child LOD primitives
if (CurrentView.ViewState &&
CurrentView.ViewState->HLODVisibilityState.IsValidPrimitiveIndex(PrimitiveId) &&
CurrentView.ViewState->HLODVisibilityState.IsNodeForcedHidden(PrimitiveId))
{
continue;
}
if ((CurrentView.ShowOnlyPrimitives.IsSet() &&
!CurrentView.ShowOnlyPrimitives->Contains(PrimitiveSceneInfo->Proxy->GetPrimitiveComponentId())) ||
CurrentView.HiddenPrimitives.Contains(PrimitiveSceneInfo->Proxy->GetPrimitiveComponentId()))
{
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);
}
bOpaque |= ViewRelevance.bOpaque || ViewRelevance.bMasked;
bTranslucentRelevance |= ViewRelevance.HasTranslucency() && !ViewRelevance.bMasked;
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->UpdateComponentLastRenderTime(CurrentWorldTime, /*bUpdateLastRenderTimeOnScreen=*/false);
if (PrimitiveSceneInfo->NeedsUniformBufferUpdate())
{
for (int32 ViewIndex = 0; ViewIndex < Views.Num(); ++ViewIndex)
{
// Main view visible primitives are processed on parallel tasks, updating uniform buffer them here will cause a race condition.
check(!Views[ViewIndex]->PrimitiveVisibilityMap[PrimitiveSceneInfo->GetIndex()]);
}
PrimitiveSceneInfo->ConditionalUpdateUniformBuffer(FRHICommandListExecutor::GetImmediateCommandList());
}
if (PrimitiveSceneInfo->NeedsUpdateStaticMeshes())
{
// Need to defer to next InitViews, as main view visible primitives are processed on parallel tasks and calling
// CacheMeshDrawCommands may resize CachedDrawLists/CachedMeshDrawCommandStateBuckets causing a crash.
PrimitiveSceneInfo->BeginDeferredUpdateStaticMeshesWithoutVisibilityCheck();
}
}
if (bOpaque && 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.GetViewOrigin() ).SizeSquared();
if (bWholeSceneShadow)
{
const float LODScaleSquared = FMath::Square(CurrentView.LODDistanceFactor);
const bool bDrawShadowDepth = FMath::Square(Bounds.SphereRadius) > FMath::Square(GMinScreenRadiusForShadowCaster) * DistanceSquared * LODScaleSquared;
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)
{
bDrawingStaticMeshes |= ShouldDrawStaticMeshes(CurrentView, PrimitiveSceneInfo);
}
}
}
if (bDrawingStaticMeshes)
{
if (bRecordShadowSubjectsForMobileShading)
{
DependentView->VisibleLightInfos[GetLightSceneInfo().Id].MobileCSMSubjectPrimitives.AddSubjectPrimitive(PrimitiveSceneInfo, PrimitiveId);
}
}
else
{
// Add the primitive to the subject primitive list.
DynamicSubjectPrimitives.Add(PrimitiveSceneInfo);
if (bRecordShadowSubjectsForMobileShading)
{
DependentView->VisibleLightInfos[GetLightSceneInfo().Id].MobileCSMSubjectPrimitives.AddSubjectPrimitive(PrimitiveSceneInfo, PrimitiveId);
}
}
}
// Add translucent shadow casting primitives to SubjectTranslucentPrimitives
if (bTranslucentRelevance && bShadowRelevance)
{
SubjectTranslucentPrimitives.Add(PrimitiveSceneInfo);
}
}
}
bool FProjectedShadowInfo::SetupPerObjectProjection(
FLightSceneInfo* InLightSceneInfo,
const FPrimitiveSceneInfo* InParentSceneInfo,
const FPerObjectProjectedShadowInitializer& Initializer,
bool bInPreShadow,
uint32 InResolutionX,
uint32 MaxShadowResolutionY,
uint32 InBorderSize,
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;
BorderSize = InBorderSize;
MaxScreenPercent = InMaxScreenPercent;
bDirectionalLight = InLightSceneInfo->Proxy->GetLightType() == LightType_Directional;
const ERHIFeatureLevel::Type FeatureLevel = LightSceneInfo->Scene->GetFeatureLevel();
bCapsuleShadow = InParentSceneInfo->Proxy->CastsCapsuleDirectShadow() && !bInPreShadow && SupportsCapsuleDirectShadows(FeatureLevel, GShaderPlatformForFeatureLevel[FeatureLevel]);
bTranslucentShadow = bInTranslucentShadow;
bPreShadow = bInPreShadow;
bSelfShadowOnly = InParentSceneInfo->Proxy->CastsSelfShadowOnly();
bTransmission = InLightSceneInfo->Proxy->Transmission();
bHairStrandsDeepShadow = InLightSceneInfo->Proxy->CastsHairStrandsDeepShadow();
check(!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));
return false;
}
}
// 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;
if (bDirectionalLight)
{
// No room to fade out if the end of receiver range is inside the subject range, it will just clip.
if (MaxSubjectZ < MaxReceiverZ)
{
float ShadowSubjectRange = MaxSubjectZ - MinSubjectZ;
float FadeLength = FMath::Min(ShadowSubjectRange, MaxReceiverZ - MaxSubjectZ);
//Initializer.MaxDistanceToCastInLightW / 16.0f;
PerObjectShadowFadeStart = (MaxReceiverZ - MinSubjectZ - FadeLength) / ShadowSubjectRange;
InvPerObjectShadowFadeLength = ShadowSubjectRange / FMath::Max(0.000001f, FadeLength);
}
}
}
InvMaxSubjectDepth = 1.0f / MaxSubjectDepth;
MinPreSubjectZ = Initializer.MinLightW;
ResolutionY = FMath::Clamp<uint32>(FMath::TruncToInt(InResolutionX / AspectRatio), 1, MaxShadowResolutionY);
if (ResolutionX == 0 || ResolutionY == 0)
{
bRet = false;
}
else
{
// 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;
}