2020-07-06 18:58:26 -04:00
// Copyright Epic Games, Inc. All Rights Reserved.
/*=============================================================================
VirtualShadowMapClipmap . cpp
= = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = */
# include "VirtualShadowMapClipmap.h"
# include "CoreMinimal.h"
# include "HAL/IConsoleManager.h"
# include "RendererModule.h"
# include "VirtualShadowMapArray.h"
# include "VirtualShadowMapCacheManager.h"
2021-04-13 16:54:28 -04:00
static TAutoConsoleVariable < float > CVarVirtualShadowMapResolutionLodBiasDirectional (
TEXT ( " r.Shadow.Virtual.ResolutionLodBiasDirectional " ) ,
2021-01-04 20:05:41 -04:00
- 0.5f ,
2021-04-13 16:54:28 -04:00
TEXT ( " Bias applied to LOD calculations for directional lights. -1.0 doubles resolution, 1.0 halves it and so on. " ) ,
2021-12-07 16:24:58 -05:00
ECVF_Scalability | ECVF_RenderThreadSafe
2020-07-06 18:58:26 -04:00
) ;
2021-01-27 01:49:27 -04:00
static TAutoConsoleVariable < int32 > CVarVirtualShadowMapClipmapFirstLevel (
2021-02-18 13:44:36 -04:00
TEXT ( " r.Shadow.Virtual.Clipmap.FirstLevel " ) ,
2021-04-13 16:54:28 -04:00
6 ,
2020-07-06 18:58:26 -04:00
TEXT ( " First level of the virtual clipmap. Lower values allow higher resolution shadows closer to the camera. " ) ,
2021-12-07 16:24:58 -05:00
ECVF_Scalability | ECVF_RenderThreadSafe
2020-07-06 18:58:26 -04:00
) ;
2021-03-08 23:14:54 -04:00
static TAutoConsoleVariable < int32 > CVarVirtualShadowMapClipmapLastLevel (
TEXT ( " r.Shadow.Virtual.Clipmap.LastLevel " ) ,
2021-04-20 08:44:23 -04:00
22 ,
2021-03-08 23:14:54 -04:00
TEXT ( " Last level of the virtual climap. Indirectly determines radius the clipmap can cover. " ) ,
2021-12-07 16:24:58 -05:00
ECVF_Scalability | ECVF_RenderThreadSafe
2021-03-08 23:14:54 -04:00
) ;
TAutoConsoleVariable < int32 > CVarVirtualShadowMapClipmapFirstCoarseLevel (
TEXT ( " r.Shadow.Virtual.Clipmap.FirstCoarseLevel " ) ,
15 ,
TEXT ( " First level of the clipmap to mark coarse pages for. Lower values allow higher resolution coarse pages near the camera but increase total page counts. " ) ,
2021-12-07 16:24:58 -05:00
ECVF_Scalability | ECVF_RenderThreadSafe
2021-03-08 23:14:54 -04:00
) ;
TAutoConsoleVariable < int32 > CVarVirtualShadowMapClipmapLastCoarseLevel (
TEXT ( " r.Shadow.Virtual.Clipmap.LastCoarseLevel " ) ,
18 ,
TEXT ( " Last level of the clipmap to mark coarse pages for. Higher values provide dense clipmap data for a longer radius but increase total page counts. " ) ,
2021-12-07 16:24:58 -05:00
ECVF_Scalability | ECVF_RenderThreadSafe
2021-01-27 01:49:27 -04:00
) ;
2020-07-06 18:58:26 -04:00
2022-01-06 14:20:46 -05:00
TAutoConsoleVariable < float > CVarVirtualShadowMapClipmapZRangeScale (
TEXT ( " r.Shadow.Virtual.Clipmap.ZRangeScale " ) ,
1000.0f ,
TEXT ( " Scale of the clipmap level depth range relative to the radius. Should generally be at least 10 or it will result in excessive cache invalidations. " ) ,
ECVF_RenderThreadSafe
) ;
2020-07-06 18:58:26 -04:00
// "Virtual" clipmap level to clipmap radius
2021-02-18 21:09:05 -04:00
// NOTE: This is the radius of around the clipmap origin that this level must cover
// The actual clipmap dimensions will be larger due to snapping and other accomodations
2020-07-06 18:58:26 -04:00
static float GetLevelRadius ( int32 Level )
{
2021-01-04 20:05:41 -04:00
// NOTE: Virtual clipmap indices can be negative (although not commonly)
// Clipmap level rounds *down*, so radius needs to cover out to 2^(Level+1), where it flips
2020-07-06 18:58:26 -04:00
return FMath : : Pow ( 2.0f , static_cast < float > ( Level + 1 ) ) ;
}
FVirtualShadowMapClipmap : : FVirtualShadowMapClipmap (
FVirtualShadowMapArray & VirtualShadowMapArray ,
const FLightSceneInfo & InLightSceneInfo ,
2020-09-08 13:37:21 -04:00
const FMatrix & WorldToLightRotationMatrix ,
2020-07-06 18:58:26 -04:00
const FViewMatrices & CameraViewMatrices ,
2021-02-02 15:25:19 -04:00
FIntPoint CameraViewRectSize ,
const FViewInfo * InDependentView )
: LightSceneInfo ( InLightSceneInfo ) ,
DependentView ( InDependentView )
2020-07-06 18:58:26 -04:00
{
2020-09-08 13:37:21 -04:00
check ( WorldToLightRotationMatrix . GetOrigin ( ) = = FVector ( 0 , 0 , 0 ) ) ; // Should not contain translation or scaling
2022-05-24 13:17:13 -04:00
FVirtualShadowMapArrayCacheManager * VirtualShadowMapArrayCacheManager = VirtualShadowMapArray . CacheManager ;
2021-05-27 17:36:27 -04:00
const bool bCacheValid = VirtualShadowMapArrayCacheManager & & VirtualShadowMapArrayCacheManager - > IsValid ( ) ;
2020-07-06 18:58:26 -04:00
const FMatrix FaceMatrix (
FPlane ( 0 , 0 , 1 , 0 ) ,
FPlane ( 0 , 1 , 0 , 0 ) ,
FPlane ( - 1 , 0 , 0 , 0 ) ,
FPlane ( 0 , 0 , 0 , 1 ) ) ;
2021-06-15 12:47:24 -04:00
WorldToLightViewRotationMatrix = WorldToLightRotationMatrix * FaceMatrix ;
2020-09-08 13:37:21 -04:00
// Pure rotation matrix
2021-06-15 12:47:24 -04:00
FMatrix ViewToWorldRotationMatrix = WorldToLightViewRotationMatrix . GetTransposed ( ) ;
2020-07-06 18:58:26 -04:00
// NOTE: Rotational (roll) invariance of the directional light depends on square pixels so we just base everything on the camera X scales/resolution
2021-02-12 19:00:35 -04:00
// NOTE: 0.5 because we double the size of the clipmap region below to handle snapping
float LodScale = 0.5f / CameraViewMatrices . GetProjectionScale ( ) . X ;
2021-01-04 20:05:41 -04:00
LodScale * = float ( FVirtualShadowMap : : VirtualMaxResolutionXY ) / float ( CameraViewRectSize . X ) ;
2021-03-08 23:14:54 -04:00
// For now we adjust resolution by just biasing the page we look up in. This is wasteful in terms of page table vs.
// just resizing the virtual shadow maps for each clipmap, but convenient for now. This means we need to additionally bias
// which levels are present.
2021-04-13 16:54:28 -04:00
ResolutionLodBias = CVarVirtualShadowMapResolutionLodBiasDirectional . GetValueOnRenderThread ( ) + FMath : : Log2 ( LodScale ) ;
2021-01-04 20:05:41 -04:00
// Clamp negative absolute resolution biases as they would exceed the maximum resolution/ranges allocated
2020-07-06 18:58:26 -04:00
ResolutionLodBias = FMath : : Max ( 0.0f , ResolutionLodBias ) ;
2021-01-27 01:49:27 -04:00
FirstLevel = CVarVirtualShadowMapClipmapFirstLevel . GetValueOnRenderThread ( ) ;
2021-03-08 23:14:54 -04:00
int32 LastLevel = CVarVirtualShadowMapClipmapLastLevel . GetValueOnRenderThread ( ) ;
2020-07-06 18:58:26 -04:00
LastLevel = FMath : : Max ( FirstLevel , LastLevel ) ;
int32 LevelCount = LastLevel - FirstLevel + 1 ;
// Per-clipmap projection data
LevelData . Empty ( ) ;
LevelData . AddDefaulted ( LevelCount ) ;
2020-08-27 14:20:18 -04:00
WorldOrigin = CameraViewMatrices . GetViewOrigin ( ) ;
2020-07-06 18:58:26 -04:00
2022-01-18 08:51:35 -05:00
if ( bCacheValid )
{
PerLightCacheEntry = VirtualShadowMapArrayCacheManager - > FindCreateLightCacheEntry ( LightSceneInfo . Id ) ;
}
2020-07-06 18:58:26 -04:00
for ( int32 Index = 0 ; Index < LevelCount ; + + Index )
{
FLevelData & Level = LevelData [ Index ] ;
2021-05-27 17:36:27 -04:00
const int32 AbsoluteLevel = Index + FirstLevel ; // Absolute (virtual) level index
2020-07-06 18:58:26 -04:00
// TODO: Allocate these as a chunk if we continue to use one per clipmap level
Level . VirtualShadowMap = VirtualShadowMapArray . Allocate ( ) ;
ensure ( Index = = 0 | | ( Level . VirtualShadowMap - > ID = = ( LevelData [ Index - 1 ] . VirtualShadowMap - > ID + 1 ) ) ) ;
2021-05-27 17:36:27 -04:00
const float RawLevelRadius = GetLevelRadius ( AbsoluteLevel ) ;
2020-07-06 18:58:26 -04:00
2022-02-11 14:57:27 -05:00
double HalfLevelDim = 2.0 * RawLevelRadius ;
double SnapSize = RawLevelRadius ;
2021-02-12 19:00:35 -04:00
2021-06-15 12:47:24 -04:00
FVector ViewCenter = WorldToLightViewRotationMatrix . TransformPosition ( WorldOrigin ) ;
2021-02-12 19:00:35 -04:00
FIntPoint CenterSnapUnits (
FMath : : RoundToInt ( ViewCenter . X / SnapSize ) ,
FMath : : RoundToInt ( ViewCenter . Y / SnapSize ) ) ;
ViewCenter . X = CenterSnapUnits . X * SnapSize ;
ViewCenter . Y = CenterSnapUnits . Y * SnapSize ;
2020-07-06 18:58:26 -04:00
2021-02-18 21:09:05 -04:00
FIntPoint CornerOffset ;
CornerOffset . X = - CenterSnapUnits . X + 2 ;
CornerOffset . Y = CenterSnapUnits . Y + 2 ;
2020-09-08 13:37:21 -04:00
const FVector SnappedWorldCenter = ViewToWorldRotationMatrix . TransformPosition ( ViewCenter ) ;
2020-12-16 17:57:13 -04:00
2020-07-06 18:58:26 -04:00
Level . WorldCenter = SnappedWorldCenter ;
2021-02-18 21:09:05 -04:00
Level . CornerOffset = CornerOffset ;
2020-07-06 18:58:26 -04:00
2021-05-27 17:36:27 -04:00
// Check if we have a cache entry for this level
// If we do and it covers our required depth range, we can use cached pages. Otherwise we need to invalidate.
TSharedPtr < FVirtualShadowMapCacheEntry > CacheEntry = nullptr ;
2022-01-18 08:51:35 -05:00
if ( PerLightCacheEntry . IsValid ( ) )
2020-07-06 18:58:26 -04:00
{
// NOTE: We use the absolute (virtual) level index so that the caching is robust against changes to the chosen level range
2022-01-18 08:51:35 -05:00
CacheEntry = PerLightCacheEntry - > FindCreateShadowMapEntry ( AbsoluteLevel ) ;
2020-07-06 18:58:26 -04:00
}
2021-05-27 17:36:27 -04:00
// We expand the depth range of the clipmap level to allow for camera movement without having to invalidate cached shadow data
// (See VirtualShadowMapCacheManager::UpdateClipmap for invalidation logic.)
2021-12-07 16:24:58 -05:00
// This also better accomodates SMRT where we want to avoid stepping outside of the Z bounds of a given clipmap
// NOTE: It's tempting to use a single global Z range for the entire clipmap (which avoids some SMRT overhead too)
// but this can cause precision issues with cached pages very near the camera.
2022-02-11 14:57:27 -05:00
const double ViewRadiusZScale = CVarVirtualShadowMapClipmapZRangeScale . GetValueOnRenderThread ( ) ;
2021-05-27 17:36:27 -04:00
2022-02-11 14:57:27 -05:00
double ViewRadiusZ = RawLevelRadius * ViewRadiusZScale ;
double ViewCenterDeltaZ = 0.0f ;
2021-05-27 17:36:27 -04:00
if ( CacheEntry )
{
// We snap to half the size of the VSM at each level
check ( ( FVirtualShadowMap : : Level0DimPagesXY & 1 ) = = 0 ) ;
FIntPoint PageOffset ( CornerOffset * ( FVirtualShadowMap : : Level0DimPagesXY > > 2 ) ) ;
CacheEntry - > UpdateClipmap ( Level . VirtualShadowMap - > ID ,
WorldToLightRotationMatrix ,
PageOffset ,
RawLevelRadius ,
ViewCenter . Z ,
ViewRadiusZ ) ;
Level . VirtualShadowMap - > VirtualShadowMapCacheEntry = CacheEntry ;
// Update min/max Z based on the cached page (if present and valid)
// We need to ensure we use a consistent depth range as the camera moves for each level
ViewCenterDeltaZ = ViewCenter . Z - CacheEntry - > Clipmap . ViewCenterZ ;
ViewRadiusZ = CacheEntry - > Clipmap . ViewRadiusZ ;
}
2022-02-11 14:57:27 -05:00
// NOTE: These values are all in regular ranges after being offset
const double ZScale = 0.5 / ViewRadiusZ ;
const double ZOffset = ViewRadiusZ + ViewCenterDeltaZ ;
2021-05-27 17:36:27 -04:00
Level . ViewToClip = FReversedZOrthoMatrix ( HalfLevelDim , HalfLevelDim , ZScale , ZOffset ) ;
2020-07-06 18:58:26 -04:00
}
2021-06-15 12:47:24 -04:00
ComputeBoundingVolumes ( CameraViewMatrices ) ;
}
void FVirtualShadowMapClipmap : : ComputeBoundingVolumes ( const FViewMatrices & CameraViewMatrices )
{
// We don't really do much CPU culling with clipmaps. After various testing the fact that we are culling
// a single frustum that goes out and basically the entire map, and we have to extrude towards (and away!) from
// the light, and dilate to cover full pages at every clipmap level (to avoid culling something that will go
// into a page that then gets cached with incomplete geometry), in many situations there is effectively no
// culling that happens. For instance, as soon as the camera looks vaguely towards or away from the light direction,
// the extruded frustum effectively covers the whole world.
const FVector CameraOrigin = CameraViewMatrices . GetViewOrigin ( ) ;
const FVector CameraDirection = CameraViewMatrices . GetViewMatrix ( ) . GetColumn ( 2 ) ;
// Thus we don't spend a lot of time trying to optimize for the easy cases and instead just pick an extremely
// conservative frustum.
ViewFrustumBounds = FConvexVolume ( ) ;
BoundingSphere = FSphere ( CameraOrigin , GetMaxRadius ( ) ) ;
2020-07-06 18:58:26 -04:00
}
2021-03-08 23:14:54 -04:00
float FVirtualShadowMapClipmap : : GetMaxRadius ( ) const
{
return GetLevelRadius ( GetClipmapLevel ( GetLevelCount ( ) - 1 ) ) ;
}
2020-07-06 18:58:26 -04:00
FViewMatrices FVirtualShadowMapClipmap : : GetViewMatrices ( int32 ClipmapIndex ) const
{
check ( ClipmapIndex > = 0 & & ClipmapIndex < LevelData . Num ( ) ) ;
const FLevelData & Level = LevelData [ ClipmapIndex ] ;
FViewMatrices : : FMinimalInitializer Initializer ;
// NOTE: Be careful here! There's special logic in FViewMatrices around ViewOrigin for ortho projections we need to bypass...
// There's also the fact that some of this data is going to be "wrong", due to the "overridden" matrix thing that shadows do
Initializer . ViewOrigin = Level . WorldCenter ;
2021-06-15 12:47:24 -04:00
Initializer . ViewRotationMatrix = WorldToLightViewRotationMatrix ;
2020-07-06 18:58:26 -04:00
Initializer . ProjectionMatrix = Level . ViewToClip ;
// TODO: This is probably unused in the shadows/nanite path, but coupling here is not ideal
Initializer . ConstrainedViewRect = FIntRect ( 0 , 0 , FVirtualShadowMap : : VirtualMaxResolutionXY , FVirtualShadowMap : : VirtualMaxResolutionXY ) ;
return FViewMatrices ( Initializer ) ;
}
2020-08-19 12:54:43 -04:00
FVirtualShadowMapProjectionShaderData FVirtualShadowMapClipmap : : GetProjectionShaderData ( int32 ClipmapIndex ) const
2020-07-06 18:58:26 -04:00
{
check ( ClipmapIndex > = 0 & & ClipmapIndex < LevelData . Num ( ) ) ;
const FLevelData & Level = LevelData [ ClipmapIndex ] ;
2020-12-16 17:57:13 -04:00
2022-02-11 14:57:27 -05:00
const FLargeWorldRenderPosition PreViewTranslation ( GetPreViewTranslation ( ClipmapIndex ) ) ;
// WorldOrigin should be near the Level.WorldCenter, so we share the LWC tile offset
// NOTE: We need to negate so that it's not opposite though
const FVector TileOffset = PreViewTranslation . GetTileOffset ( ) ;
const FVector3f NegativeClipmapWorldOriginOffset ( - WorldOrigin - TileOffset ) ;
2020-12-16 17:57:13 -04:00
// NOTE: Some shader logic (projection, etc) assumes some of these parameters are constant across all levels in a clipmap
2020-07-06 18:58:26 -04:00
FVirtualShadowMapProjectionShaderData Data ;
2022-02-11 14:57:27 -05:00
Data . TranslatedWorldToShadowViewMatrix = FMatrix44f ( WorldToLightViewRotationMatrix ) ;
2022-01-27 07:20:20 -05:00
Data . ShadowViewToClipMatrix = FMatrix44f ( Level . ViewToClip ) ;
Data . TranslatedWorldToShadowUVMatrix = FMatrix44f ( CalcTranslatedWorldToShadowUVMatrix ( WorldToLightViewRotationMatrix , Level . ViewToClip ) ) ;
Data . TranslatedWorldToShadowUVNormalMatrix = FMatrix44f ( CalcTranslatedWorldToShadowUVNormalMatrix ( WorldToLightViewRotationMatrix , Level . ViewToClip ) ) ;
2022-02-11 14:57:27 -05:00
Data . PreViewTranslationLWCTile = PreViewTranslation . GetTile ( ) ;
Data . PreViewTranslationLWCOffset = PreViewTranslation . GetOffset ( ) ;
2020-08-27 14:20:18 -04:00
Data . LightType = ELightComponentType : : LightType_Directional ;
2022-02-11 14:57:27 -05:00
Data . NegativeClipmapWorldOriginLWCOffset = NegativeClipmapWorldOriginOffset ;
2021-02-12 19:00:35 -04:00
Data . ClipmapIndex = ClipmapIndex ;
2020-07-06 18:58:26 -04:00
Data . ClipmapLevel = FirstLevel + ClipmapIndex ;
Data . ClipmapLevelCount = LevelData . Num ( ) ;
Data . ClipmapResolutionLodBias = ResolutionLodBias ;
2021-02-18 21:09:05 -04:00
Data . ClipmapCornerOffset = Level . CornerOffset ;
2021-09-01 13:13:01 -04:00
Data . LightSourceRadius = GetLightSceneInfo ( ) . Proxy - > GetSourceRadius ( ) ;
2020-07-06 18:58:26 -04:00
return Data ;
}
2021-03-08 23:14:54 -04:00
uint32 FVirtualShadowMapClipmap : : GetCoarsePageClipmapIndexMask ( )
{
uint32 BitMask = 0 ;
const int FirstLevel = CVarVirtualShadowMapClipmapFirstLevel . GetValueOnRenderThread ( ) ;
const int LastLevel = FMath : : Max ( FirstLevel , CVarVirtualShadowMapClipmapLastLevel . GetValueOnRenderThread ( ) ) ;
int FirstCoarseIndex = CVarVirtualShadowMapClipmapFirstCoarseLevel . GetValueOnRenderThread ( ) - FirstLevel ;
int LastCoarseIndex = CVarVirtualShadowMapClipmapLastCoarseLevel . GetValueOnRenderThread ( ) - FirstLevel ;
ensureMsgf ( ( LastLevel - FirstLevel ) < 32 , TEXT ( " Too many clipmap levels for coarse page bitmask. " ) ) ;
FirstCoarseIndex = FMath : : Max ( 0 , FirstCoarseIndex ) ;
if ( LastCoarseIndex > = FirstCoarseIndex )
{
uint32 BitCount = static_cast < uint32 > ( LastCoarseIndex - FirstCoarseIndex + 1 ) ;
uint32 BitRange = ( 1 < < BitCount ) - 1 ;
BitMask = BitMask | ( BitRange < < FirstCoarseIndex ) ;
}
// Always mark coarse pages in the last level for clouds/skyatmosphere
BitMask = BitMask | ( 1 < < ( LastLevel - FirstLevel ) ) ;
return BitMask ;
}
2022-01-18 08:51:35 -05:00
2022-02-09 15:11:16 -05:00
static inline void LazyInitAndSetBitArray ( TBitArray < > & BitArray , int32 Index , bool Value , int32 MaxNum )
{
if ( BitArray . IsEmpty ( ) )
{
BitArray . Init ( false , MaxNum ) ;
}
BitArray [ Index ] = Value ;
}
void FVirtualShadowMapClipmap : : OnPrimitiveRendered ( const FPrimitiveSceneInfo * PrimitiveSceneInfo )
{
2022-01-18 08:51:35 -05:00
if ( PerLightCacheEntry . IsValid ( ) )
{
2022-02-09 15:11:16 -05:00
FPersistentPrimitiveIndex PersistentPrimitiveId = PrimitiveSceneInfo - > GetPersistentIndex ( ) ;
check ( PersistentPrimitiveId . Index > = 0 ) ;
2022-01-19 07:04:51 -05:00
check ( PersistentPrimitiveId . Index < PerLightCacheEntry - > RenderedPrimitives . Num ( ) ) ;
2022-02-09 15:11:16 -05:00
// Check previous-frame state to detect transition from hidden->visible
2022-01-18 08:51:35 -05:00
if ( ! PerLightCacheEntry - > RenderedPrimitives [ PersistentPrimitiveId . Index ] )
{
2022-02-09 15:11:16 -05:00
LazyInitAndSetBitArray ( RevealedPrimitivesMask , PersistentPrimitiveId . Index , true , PerLightCacheEntry - > RenderedPrimitives . Num ( ) ) ;
2022-01-18 08:51:35 -05:00
}
2022-02-09 15:11:16 -05:00
// update current frame-state.
LazyInitAndSetBitArray ( RenderedPrimitives , PersistentPrimitiveId . Index , true , PerLightCacheEntry - > RenderedPrimitives . Num ( ) ) ;
// update cached state (this is checked & cleared whenever a primitive is invalidating the VSM).
PerLightCacheEntry - > OnPrimitiveRendered ( PrimitiveSceneInfo ) ;
2022-01-18 08:51:35 -05:00
}
}
2022-02-09 15:11:16 -05:00
void FVirtualShadowMapClipmap : : UpdateCachedFrameData ( )
{
if ( PerLightCacheEntry . IsValid ( ) )
{
PerLightCacheEntry - > RenderedPrimitives = MoveTemp ( RenderedPrimitives ) ;
}
}