Files
UnrealEngine/Engine/Shaders/Private/VirtualShadowMaps/VirtualShadowMapProjection.usf
2025-05-18 13:04:45 +08:00

896 lines
28 KiB
HLSL

// Copyright Epic Games, Inc. All Rights Reserved.
#include "../Common.ush"
#include "../SceneTexturesCommon.ush"
#include "../Substrate/Substrate.ush"
#include "../DeferredShadingCommon.ush"
#include "../DynamicLightingCommon.ush"
#include "../MortonCode.ush"
#include "../LightShaderParameters.ush"
#include "../LightGridCommon.ush"
#include "../HairStrands/HairStrandsVisibilityCommon.ush"
#include "../Visualization.ush"
#include "../BlueNoise.ush"
#include "VirtualShadowMapPageAccessCommon.ush"
#include "VirtualShadowMapProjectionCommon.ush"
#include "VirtualShadowMapProjectionFilter.ush"
#include "VirtualShadowMapProjectionDirectional.ush"
#include "VirtualShadowMapProjectionSpot.ush"
#include "VirtualShadowMapTransmissionCommon.ush"
#include "VirtualShadowMapScreenRayTrace.ush"
#include "VirtualShadowMapLightGrid.ush"
#include "VirtualShadowMapStats.ush"
#include "/Engine/Shared/VirtualShadowMapDefinitions.h"
#if HAS_HAIR_STRANDS
#define VOXEL_TRAVERSAL_TYPE VOXEL_TRAVERSAL_LINEAR_MIPMAP
#define VOXEL_TRAVERSAL_DEBUG 0
#include "../HairStrands/HairStrandsVoxelPageCommon.ush"
#include "../HairStrands/HairStrandsVoxelPageTraversal.ush"
#if USE_BLUE_NOISE
DEFINE_HAIR_BLUE_NOISE_JITTER_FUNCTION
#endif
#endif
#ifndef DIRECTIONAL_LIGHT
#define DIRECTIONAL_LIGHT 0
#endif
#ifndef VISUALIZE_OUTPUT
#define VISUALIZE_OUTPUT 0
#endif
#define SCREEN_RAY_SAMPLES 4
struct FProjectionShadingInfo
{
bool bIsValid;
bool bIsHair;
bool bIsSubsurface;
bool bDisableBackFaceCulling;
bool bIsFirstPerson;
float3 WorldNormal;
};
FProjectionShadingInfo GetProjectionShadingInfo(uint2 PixelPos)
{
FProjectionShadingInfo Out;
Out.bDisableBackFaceCulling = false;
#if SUBTRATE_GBUFFER_FORMAT==1
FSubstrateAddressing SubstrateAddressing = GetSubstratePixelDataByteOffset(PixelPos, uint2(View.BufferSizeAndInvSize.xy), Substrate.MaxBytesPerPixel);
FSubstratePixelHeader SubstratePixelHeader = UnpackSubstrateHeaderIn(Substrate.MaterialTextureArray, SubstrateAddressing, Substrate.TopLayerTexture);
const FSubstrateTopLayerData TopLayerData = SubstrateUnpackTopLayerData(Substrate.TopLayerTexture.Load(uint3(PixelPos, 0)));
const FSubstrateSubsurfaceHeader SSSHeader = SubstrateLoadSubsurfaceHeader(Substrate.MaterialTextureArray, Substrate.FirstSliceStoringSubstrateSSSData, PixelPos);
const bool bIsValid = SubstrateSubSurfaceHeaderGetIsValid(SSSHeader);
const bool bIsProfile = SubstrateSubSurfaceHeaderGetIsProfile(SSSHeader);
const uint SSSType = SSSHEADER_TYPE(SSSHeader);
const bool bHasSSS = SubstratePixelHeader.HasSubsurface();
const uint BSDFType = SubstratePixelHeader.SubstrateGetBSDFType();
Out.bIsValid = SubstratePixelHeader.IsSubstrateMaterial();
Out.bIsHair = BSDFType == SUBSTRATE_BSDF_TYPE_HAIR;
Out.WorldNormal = TopLayerData.WorldNormal;
Out.bIsSubsurface = SSSType != SSS_TYPE_NONE; // legacy IsSubsurfaceModel is true for subsurface profile, eyes, etc.
// VSM traces using the top layer normal when NoL>0 only. So if normals for slabs below do not match, light leaking will occur.
// For instance: a flat translucent top layer above a normal mapped bottom layer.
// That is why, for materials with multiple local bases, we must not do backface culling.
if (SubstratePixelHeader.GetLocalBasesCount() > 1)
{
Out.bDisableBackFaceCulling = true;
}
Out.bIsFirstPerson = SubstratePixelHeader.IsFirstPerson();
#else
const FGBufferData GBufferData = GetGBufferDataUint(PixelPos, true);
Out.bIsValid = GBufferData.ShadingModelID != SHADINGMODELID_UNLIT;
Out.bIsHair = GBufferData.ShadingModelID == SHADINGMODELID_HAIR;
Out.bIsSubsurface = IsSubsurfaceModel(GBufferData.ShadingModelID);
Out.bDisableBackFaceCulling = GBufferData.ShadingModelID == SHADINGMODELID_CLEAR_COAT;
Out.WorldNormal = GBufferData.WorldNormal;
Out.bIsFirstPerson = IsFirstPerson(GBufferData);
#endif
Out.bDisableBackFaceCulling = Out.bDisableBackFaceCulling || Out.bIsSubsurface || Out.bIsHair;
return Out;
}
int GetSMRTRayCount()
{
#if DIRECTIONAL_LIGHT
return VirtualShadowMap.SMRTRayCountDirectional;
#else
return VirtualShadowMap.SMRTRayCountLocal;
#endif
}
SCHEDULER_MIN_PRESSURE
MAX_OCCUPANCY
// NOTE: Not currently used by occasionally useful for testing
float3 GetEstimatedGeoWorldNormal(float3 TranslatedWorldPosition, float3 ShadingNormal)
{
// Figure out slope, we do world space since that is the space where we might override using the shading normal...
float3 TranslatedWorldPositionDDX = ddx_fine(TranslatedWorldPosition);
float3 TranslatedWorldPositionDDY = ddy_fine(TranslatedWorldPosition);
float3 EstimatedGeoWorldNormal = cross(TranslatedWorldPositionDDX, TranslatedWorldPositionDDY);
// Handle NaNs; will cause it to revert to shading normal below
float LengthSq = dot(EstimatedGeoWorldNormal, EstimatedGeoWorldNormal);
EstimatedGeoWorldNormal = LengthSq > 1e-8f ? normalize(EstimatedGeoWorldNormal) : float3(0, 0, 0);
#if 1
// NOTE: Gbuffer normal is not the surface normal for hair; hair lighting takes a different path but possibly
// necessary to do some sort of special casing here (disable normal offset and biasing entirely?).
// If the estimated geo normal is too far out we assume it's broken (derivative includes other surfaces or background) and fall back to the shading normal
if (dot(ShadingNormal, EstimatedGeoWorldNormal) < 0.25f)
{
EstimatedGeoWorldNormal = ShadingNormal;
}
#endif
return EstimatedGeoWorldNormal;
}
float4 ComputeRandom4(uint2 PixelPosition)
{
const uint InSeed = View.StateFrameIndexMod8;
const uint2 Seed0 = Rand3DPCG16(int3(PixelPosition, InSeed)).xy;
const uint2 Seed1 = Rand3DPCG16(int3(PixelPosition + 17, InSeed)).xy;
return float4(
Hammersley16(InSeed, 8, Seed0),
Hammersley16(InSeed, 8, Seed1));
}
int4 ProjectionRect;
float SubsurfaceMinSourceRadius;
// One pass projection
#if ONE_PASS_PROJECTION
RWTexture2D< uint4 > OutShadowMaskBits;
#else
// Single light per pass
// Light parameters loaded via GetRootLightShaderParameters();
int LightUniformVirtualShadowMapId;
RWTexture2D< float2 > OutShadowFactor;
#endif
// Visualization output
StructuredBuffer< FPhysicalPageMetaData > PhysicalPageMetaData;
RWTexture2D< float4 > OutVisualize;
int VisualizeModeId;
int bVisualizeCachedPagesOnly;
int VisualizeVirtualShadowMapId;
#if FIRST_PERSON_SHADOW
int FirstPersonVirtualShadowMapId;
#endif
// Type of input data consume by the page allocation (i.e., data read from the source buffer: Gbuffer, HairStrands data, ...)
#define INPUT_TYPE_GBUFFER 0
#define INPUT_TYPE_HAIRSTRANDS 1
uint InputType;
uint bCullBackfacingPixels;
FVirtualShadowMapSampleResult ProjectLight(
int VirtualShadowMapId,
FLightShaderParameters Light,
FProjectionShadingInfo ShadingInfo,
uint2 PixelPos,
float SceneDepth,
float ScreenRayLengthWorld,
float3 TranslatedWorldPosition,
const float Noise,
const FSubsurfaceOpacityMFP SubsurfaceOpacityMFP)
{
const bool bIsHairInput = InputType == INPUT_TYPE_HAIRSTRANDS;
bool bBackfaceCull = (bCullBackfacingPixels > 0) && !bIsHairInput && !ShadingInfo.bDisableBackFaceCulling;
#if VISUALIZE
// Modes more related to the shadow depth rendering don't want to see the projection culling
bBackfaceCull = bBackfaceCull &&
VisualizeModeId != VIRTUAL_SHADOW_MAP_VISUALIZE_NANITE_OVERDRAW;
#endif
#if DIRECTIONAL_LIGHT
float3 L = Light.Direction;
bool bInLightRegion = true;
if (bBackfaceCull && IsBackfaceToDirectionalLight(ShadingInfo.WorldNormal, Light.Direction, Light.SourceRadius))
{
bInLightRegion = false;
}
#else // Local light
float3 ToLight = Light.TranslatedWorldPosition - TranslatedWorldPosition;
float InvDist = rsqrt(dot(ToLight, ToLight));
float3 L = ToLight * InvDist;
bool bInLightRadius = InvDist >= Light.InvRadius;
bool bInLightCone = SpotAttenuationMask(L, -Light.Direction, Light.SpotAngles) > 0.0f;
bool bInLightRegion = bInLightRadius && bInLightCone;
const bool bIsRectLight = Light.RectLightBarnLength > -2.0f;
if (bIsRectLight)
{
const bool bIsBehindRectLight = dot(ToLight, Light.Direction) < 0;
if (bIsBehindRectLight)
{
bInLightRegion = false;
}
}
if (bInLightRegion && bBackfaceCull)
{
if (IsBackfaceToLocalLight(ToLight, ShadingInfo.WorldNormal, Light.SourceRadius))
{
bInLightRegion = false;
}
}
#endif // DIRECTIONAL_LIGHT
FVirtualShadowMapSampleResult Result = InitVirtualShadowMapSampleResult();
const bool bValidPixel = bIsHairInput || ShadingInfo.bIsValid;
BRANCH
if (bInLightRegion && bValidPixel)
{
const float3 WorldNormal = (bIsHairInput || ShadingInfo.bIsHair) ? L : ShadingInfo.WorldNormal;
if (!ShadingInfo.bIsSubsurface)
{
TranslatedWorldPosition += WorldNormal * VirtualShadowMapGetNormalBiasLength(TranslatedWorldPosition);
}
// Do not run contact shadow when computing the hair shadow mask (i.e. shadow mask applied on hair, has the scene
// depth buffer contains fully opaque depth, which create false positive intersection resulting in wrong self shadowing)
float SMRTRayOffset = ScreenRayLengthWorld;
if (!bIsHairInput && !ShadingInfo.bIsSubsurface)
{
if (ScreenRayLengthWorld > 0.0f)
{
// Trace a screen space ray to try and get "away" from the receiver surface before
// we trace the SMRT ray to avoid mismatches/incorrect self-shadowing.
SMRTRayOffset = VirtualShadowMapScreenRayCast(
TranslatedWorldPosition,
L,
ScreenRayLengthWorld,
Noise);
}
}
#if DIRECTIONAL_LIGHT
if (GetSMRTRayCount() > 0)
{
Result = TraceDirectional(
VirtualShadowMapId,
Light,
PixelPos,
SceneDepth,
TranslatedWorldPosition,
SMRTRayOffset,
Noise,
WorldNormal);
}
else
{
Result = SampleVirtualShadowMapDirectional(
VirtualShadowMapId,
TranslatedWorldPosition,
SMRTRayOffset,
WorldNormal,
SceneDepth);
}
#if FIRST_PERSON_SHADOW
// If there is an extra FP shadow map, it represents the shadow cast from the 3rd person mesh of the player onto the world only, thus it should only be sampled if
// 1. the pixel is not marked as FP (e.g., the weapon)
//
// 2. the pixel is not already in shadow (100%)
bool bFullyShadowed = Result.bValid && Result.ShadowFactor == 0.0f;
if (FirstPersonVirtualShadowMapId >= 0 && !ShadingInfo.bIsFirstPerson && !bFullyShadowed)
{
FVirtualShadowMapSampleResult FPResult = InitVirtualShadowMapSampleResult();
if (GetSMRTRayCount() > 0)
{
FPResult = TraceDirectional(
FirstPersonVirtualShadowMapId,
Light,
PixelPos,
SceneDepth,
TranslatedWorldPosition,
SMRTRayOffset,
Noise,
WorldNormal);
}
else
{
FPResult = SampleVirtualShadowMapDirectional(
FirstPersonVirtualShadowMapId,
TranslatedWorldPosition,
SMRTRayOffset,
WorldNormal,
SceneDepth);
}
if (FPResult.bValid)
{
if (!Result.bValid)
{
Result = FPResult;
}
else
{
// Merge the result, picking the most-shadowed value & nearest occluder distance
Result.ShadowFactor = min(Result.ShadowFactor, FPResult.ShadowFactor);
Result.OccluderDistance = min(Result.OccluderDistance, FPResult.OccluderDistance);
}
}
}
#endif // FIRST_PERSON_SHADOW
#else // !DIRECTIONAL_LIGHT
if (GetSMRTRayCount() > 0)
{
Result = TraceLocalLight(
VirtualShadowMapId,
Light,
PixelPos,
SceneDepth,
TranslatedWorldPosition,
SMRTRayOffset,
Noise,
WorldNormal);
}
else
{
Result = SampleVirtualShadowMapLocal(
VirtualShadowMapId,
TranslatedWorldPosition,
SMRTRayOffset,
WorldNormal,
true, // OptimalBias
SceneDepth
);
}
#endif // DIRECTIONAL_LIGHT
// Explanation:
// - If bDataIsOpacity = 1: Data stores the surface's SSS opacity. We compute a transmitted color when the surface is not fully opaque (opacity == 1).
// - If bDataIsOpacity = 0: Data stores the surface's MFP. We compute a transmitted color when the surface is not fully opaque (MFP == 0)
if ((SubsurfaceOpacityMFP.bDataIsOpacity && SubsurfaceOpacityMFP.Data < 1.0f) || (!SubsurfaceOpacityMFP.bDataIsOpacity && SubsurfaceOpacityMFP.Data > 0.0f))
{
// We use a single term for both the shadow and "transmission shadow" in the VSM path to provide a
// higher quality filtered result on back faces and avoid artifacts at the point where normals flip.
// This is especially important for foliage as normals are often "warped" to provide softer shading
// and do not represent the underlying geometry accurately, so having a continuous shadow function
// is important.
Result.ShadowFactor = GetShadowFactorSubsurface(Result.ShadowFactor, Result.OccluderDistance, SubsurfaceOpacityMFP);
}
//Result.GeneralDebug = GreenToRedTurbo(1.0f - (SMRTRayOffset / ScreenRayLengthWorld));
//Result.GeneralDebug = SpotAttenuation(-L, Light.Direction, Light.SpotAngles).xxx;
//Result.GeneralDebug = (Result.OccluderDistance / 20.0f).xxx;
// Hair strands voxel traversal to apply hair shadow on top of opaque geometry
#if HAS_HAIR_STRANDS
if (!bIsHairInput && Result.ShadowFactor > 0)
{
float AccShadowFactor = 0;
// TODO: Maybe do this min() in the uniform buffer parameter itself?
uint HairRayCount = min(VirtualShadowMap.SMRTHairRayCount, uint(GetSMRTRayCount()));
uint RayIndex = 0;
for (; RayIndex < HairRayCount; RayIndex++ )
{
float3 RayStart = 0;
float3 RayEnd = 0;
#if USE_BLUE_NOISE
const float4 Random = GetHairBlueNoiseJitter(PixelPos, RayIndex, HairRayCount, View.StateFrameIndexMod8);
#else
const float4 Random = ComputeRandom4(PixelPos);
#endif
#if DIRECTIONAL_LIGHT
const bool bCastHairRay = GenerateRayDirectional(
Light,
PixelPos,
TranslatedWorldPosition,
VirtualShadowMap.SMRTRayLengthScale * 100,
RayIndex,
HairRayCount,
RayStart,
RayEnd);
#else
const bool bCastHairRay = GenerateRayLocalLight(
Light,
PixelPos,
TranslatedWorldPosition,
WorldNormal,
RayIndex,
HairRayCount,
RayStart,
RayEnd);
#endif
if (bCastHairRay)
{
// Jitter start position to mask/compensate coarse voxel resolution
float3 NormalizedDepthBias = 0;
{
const float PositionBiasScale = 0.5f;
NormalizedDepthBias = (VirtualVoxel.DepthBiasScale_Shadow * L + PositionBiasScale * (Random.xyz * 2 - 1));
}
const float DistanceThreshold = 100000.0f;
const float CoverageThreshold = 0.995f; // When Coverage is high, we do not trace shadow on opaque since hair/fur is covering the background.
FVirtualVoxelCommonDesc CommonDesc;
CommonDesc.PageCountResolution = VirtualVoxel.PageCountResolution;
CommonDesc.PageTextureResolution = VirtualVoxel.PageTextureResolution;
CommonDesc.PageResolution = VirtualVoxel.PageResolution;
CommonDesc.PageResolutionLog2 = VirtualVoxel.PageResolutionLog2;
FHairTraversalSettings TraversalSettings = InitHairTraversalSettings();
TraversalSettings.DensityScale = VirtualVoxel.DensityScale_Shadow;
TraversalSettings.CountThreshold = 1;
TraversalSettings.DistanceThreshold = DistanceThreshold;
TraversalSettings.bDebugEnabled = false;
TraversalSettings.SteppingScale = VirtualVoxel.SteppingScale_Shadow;
TraversalSettings.Random = Random.xyz;
TraversalSettings.PixelRadius = ConvertGivenDepthRadiusForProjectionType(VirtualVoxel.HairCoveragePixelRadiusAtDepth1, SceneDepth);
TraversalSettings.bUseOpaqueVisibility = false;
TraversalSettings.bCastShadow = true;
float TraversalHairCount = 0;
const uint VoxelDescCount = VirtualVoxel.NodeDescCount;
for (uint VoxelDescIt=0; TraversalHairCount < 1 && VoxelDescIt<VoxelDescCount; ++VoxelDescIt)
{
const FVirtualVoxelNodeDesc NodeDesc = UnpackVoxelNode(VirtualVoxel.NodeDescBuffer[VoxelDescIt], VirtualVoxel.PageResolution);
FHairTraversalResult HairResult = InitHairTraversalResult();
HairResult = ComputeHairCountVirtualVoxel(
RayStart + NodeDesc.VoxelWorldSize * NormalizedDepthBias,
RayEnd,
CommonDesc,
NodeDesc,
VirtualVoxel.PageIndexBuffer,
VirtualVoxel.PageTexture,
TraversalSettings);
TraversalHairCount += HairResult.HairCount;
}
const float IterationShadowFactor = saturate(1 - TraversalHairCount);
AccShadowFactor += IterationShadowFactor;
#define USE_ADAPTATIVE 1
#if COMPUTESHADER && USE_ADAPTATIVE
if (VirtualShadowMap.SMRTAdaptiveRayCount > 0)
{
// TODO: Adapt this heuristic based on SMRTAdaptiveRayCount as well?
if( RayIndex == 0 )
{
bool bMiss = IterationShadowFactor >= 1.f;
// All lanes missed
bool bAllLanesMiss = WaveActiveAllTrue( bMiss );
if( bAllLanesMiss )
{
break;
}
}
else if( RayIndex >= VirtualShadowMap.SMRTAdaptiveRayCount )
{
bool bHit = IterationShadowFactor == 0;
// After N iterations and all have hit, assume umbra
bool bAllLanesHit = WaveActiveAllTrue( bHit );
if( bAllLanesHit )
{
break;
}
}
}
#endif
}
else
{
AccShadowFactor += 1.f;
}
}
AccShadowFactor /= float(max(1, min(RayIndex, HairRayCount)));
Result.ShadowFactor = min(Result.ShadowFactor, AccShadowFactor);
}
#endif
}
return Result;
}
float3 GetVirtualPageColor(uint2 VirtualTexelAddress)
{
uint2 vPage = VirtualTexelAddress.xy >> VSM_LOG2_PAGE_SIZE;
return IntToColor(vPage.x + (vPage.y << 10U));
}
float Grayscale(float3 SourceClr)
{
return 0.299 * SourceClr.r + 0.587 * SourceClr.g + 0.114 * SourceClr.b;
}
void OutputVisualize(
int VirtualShadowMapId,
uint2 PixelPos,
float3 TranslatedWorldPosition, // Unmodified, raw position before any biasing
FVirtualShadowMapSampleResult Result)
{
bool bOneLightVisible = VisualizeVirtualShadowMapId >= 0;
if (bOneLightVisible && VisualizeVirtualShadowMapId != VirtualShadowMapId)
{
return;
}
// NOTE: Important to *not* write output if it isn't a recognized mode, as a different
// pass may write that output instead.
float4 Output = float4( 1, 0, 1, 0 );
if ( Result.bValid )
{
const float3 vPageColor = GetVirtualPageColor(Result.VirtualTexelAddress);
uint2 pPage = Result.PhysicalTexelAddress.xy >> VSM_LOG2_PAGE_SIZE;
bool bValidPageAddress = all( pPage < uint2(VirtualShadowMap.PhysicalPoolSizePages) );
FPhysicalPageMetaData pPageMeta = (FPhysicalPageMetaData)0;
if (bValidPageAddress)
{
pPageMeta = PhysicalPageMetaData[VSMPhysicalPageAddressToIndex(pPage)];
}
float4 Accumulator = OutVisualize[PixelPos];
if ( VisualizeModeId == VIRTUAL_SHADOW_MAP_VISUALIZE_SHADOW_FACTOR )
{
float4 Lit = float4(1,1,0,1);
float4 Shadow = float4(0,0,0.5,1);
Output = lerp(Shadow, Lit, Result.ShadowFactor);
if (!bOneLightVisible)
{
// todo: figure out which is the least confusing blend mode
Output = Accumulator + Output*0.25;
Output.b = min(Output.b, 0.12); // a bit of blue helps retain contrast in shadowed areas
}
}
else if ( VisualizeModeId == VIRTUAL_SHADOW_MAP_VISUALIZE_CLIPMAP_OR_MIP )
{
// todo: consider separating out clipmap
// todo: consider changing color mapping so near/far is easier to tell apart
float3 Color = IntToColor( Result.ClipmapOrMipLevel );
Output = float4(0.8f*Color + 0.2f*Result.ShadowFactor.xxx, 1.0);
if (!bOneLightVisible)
{
Output = Accumulator + Output*0.25;
Output.a = 1.0;
}
}
else if ( VisualizeModeId == VIRTUAL_SHADOW_MAP_VISUALIZE_VIRTUAL_PAGE && bValidPageAddress )
{
float3 MipColor = IntToColor( Result.ClipmapOrMipLevel );
Output = float4(0.4f*vPageColor + 0.4f*MipColor + 0.2f*Result.ShadowFactor.xxx, 1.0);
if (!bOneLightVisible)
{
Output = Accumulator + Output*0.25;
Output.a = 1.0;
}
}
else if ( VisualizeModeId == VIRTUAL_SHADOW_MAP_VISUALIZE_SMRT_RAY_COUNT )
{
Output.xy = Accumulator.xy + float2(Result.RayCount, GetSMRTRayCount());
Output.zw = float2(0, 1.0);
}
else if ( VisualizeModeId == VIRTUAL_SHADOW_MAP_VISUALIZE_CACHED_PAGE && bValidPageAddress)
{
bool bUncachedDynamic = (pPageMeta.Flags & VSM_FLAG_DYNAMIC_UNCACHED) != 0;
bool bUncachedStatic = (pPageMeta.Flags & VSM_FLAG_STATIC_UNCACHED) != 0;
bool bForceCached = (pPageMeta.Flags & VSM_EXTENDED_FLAG_FORCE_CACHED) != 0;
float CacheAlpha = bOneLightVisible ? 0.9 : 0.25;
float VPageAlpha = 0;
float ShadowAlpha = 0.1;
float4 CacheColor = float4(0, 0, 0, 0);
// Red = both uncached, blue = only static cached
if (bUncachedDynamic)
{
if (!bVisualizeCachedPagesOnly)
{
CacheColor = bUncachedStatic ? float4(1, 0, 0, CacheAlpha) : float4(0, 0, 1, CacheAlpha);
}
}
else if (bForceCached)
{
if (!bVisualizeCachedPagesOnly)
{
// TODO: Maybe only enable this debug color when "advanced" visualizations are enabled?
// Could be confusing to content developers.
CacheColor = float4(0.75, 1, 0, CacheAlpha);
}
}
else if (bOneLightVisible || bVisualizeCachedPagesOnly)
{
CacheColor = float4(0, 1, 0, CacheAlpha); // Green if cached
VPageAlpha = 0.1;
}
if (CacheColor.a > 0)
{
Output.rgb = CacheColor.a*CacheColor.rgb + VPageAlpha*vPageColor + ShadowAlpha*Result.ShadowFactor.xxx;
Output = float4(Output.rgb + Accumulator.rgb, 0.9f);
}
else
{
Output = float4(0, 0, 0, 0);
}
}
else if (VisualizeModeId == VIRTUAL_SHADOW_MAP_VISUALIZE_DIRTY_PAGE)
{
float3 PageColor = vPageColor * 0.4f;
float3 DirtyColor = PageColor;
if (!bOneLightVisible)
{
PageColor = Grayscale(PageColor);
}
if (bValidPageAddress)
{
bool bDirty = (pPageMeta.Flags & VSM_EXTENDED_FLAG_DIRTY) != 0U;
if (bDirty)
{
DirtyColor.x = 1.0f;
PageColor = lerp(PageColor, DirtyColor, 0.8f);
}
}
Output = float4(PageColor, 1.0);
if (!bOneLightVisible)
{
Output = Accumulator + Output*0.25;
Output.a = 1.0;
}
}
else if (VisualizeModeId == VIRTUAL_SHADOW_MAP_VISUALIZE_GPU_INVALIDATED_PAGE)
{
float3 CacheColor = float3(0, 0, 0);
if (bValidPageAddress)
{
bool bInvalidatedDynamic = (pPageMeta.Flags & VSM_EXTENDED_FLAG_INVALIDATE_DYNAMIC) != 0;
bool bInvalidatedStatic = (pPageMeta.Flags & VSM_EXTENDED_FLAG_INVALIDATE_STATIC) != 0;
if (bInvalidatedStatic)
{
CacheColor.x = 1.0f;
}
if (bInvalidatedDynamic)
{
CacheColor.y = 1.0f;
}
}
if (bOneLightVisible)
{
Output = float4(0.55f*CacheColor + 0.25f*vPageColor + 0.2f*Result.ShadowFactor.xxx, 1.0);
}
else
{
Output = float4(0.55f*CacheColor + 0.25f * Grayscale(vPageColor) + 0.2f*Result.ShadowFactor.xxx, 1.0);
Output = Accumulator + Output*0.25;
Output.a = 1.0;
}
}
else if ( VisualizeModeId == VIRTUAL_SHADOW_MAP_VISUALIZE_MERGED_PAGE)
{
bool bMerged = ((pPageMeta.Flags & VSM_EXTENDED_FLAG_DIRTY) != 0U) && (pPageMeta.Flags & VSM_EXTENDED_FLAG_VIEW_UNCACHED) == 0U;
if (bOneLightVisible)
{
float3 Color = float3(0, 1, 0);
if (bMerged)
{
Color = float3(1, 0, 0);
}
Output = float4(0.55f * Color + 0.25f * vPageColor + 0.2f * Result.ShadowFactor.xxx, 1.0);
}
else
{
float3 Color = float3(0, 0, 0);
if (bMerged)
{
Color = float3(1, 0, 0);
}
Output = float4(0.55f * Color + 0.25f * Grayscale(vPageColor) + 0.2f * Result.ShadowFactor.xxx, 1.0);
Output = Accumulator + Output*0.25;
Output.a = 1.0;
}
}
else if ( VisualizeModeId == VIRTUAL_SHADOW_MAP_VISUALIZE_GENERAL_DEBUG )
{
Output = float4(0.8f*Result.GeneralDebug + 0.2f*Result.ShadowFactor.xxx, 1.0);
if (!bOneLightVisible)
{
Output = Accumulator + Output*0.25;
Output.a = 1.0;
}
}
else if ( VisualizeModeId == VIRTUAL_SHADOW_MAP_VISUALIZE_NANITE_OVERDRAW )
{
// This is just to get the raw physical address
FVirtualShadowMapSampleResult RawResult = SampleVirtualShadowMapTranslatedWorld(
VirtualShadowMapId, TranslatedWorldPosition);
const float OverdrawCount = float(VirtualShadowMap.PhysicalPagePool.Load(uint4(RawResult.PhysicalTexelAddress.xy, 2, 0))); // See FVisBufferPixel::WriteOverdraw
Output = float4(OverdrawCount, 0, 0, 1.0);
if (!bOneLightVisible)
{
Output.r += Accumulator.r;
}
}
/* TODO: Enable this and add to menu
else if ( VisualizeModeId == VIRTUAL_SHADOW_MAP_VISUALIZE_OCCLUDER_DISTANCE )
{
Output = (Result.OccluderDistance / 1000.0f).xxx;
}
*/
}
if (Output.a > 0)
{
OutVisualize[PixelPos] = Output;
}
}
#if USE_TILE_LIST
Buffer<uint> TileListData;
#endif
[numthreads(WORK_TILE_SIZE, WORK_TILE_SIZE, 1)]
void VirtualShadowMapProjection(
uint3 GroupId : SV_GroupID,
uint GroupIndex : SV_GroupIndex,
uint3 DispatchThreadId : SV_DispatchThreadID )
{
#if USE_TILE_LIST
const uint2 TileCoord = UnpackTileCoord12bits(TileListData.Load(GroupId.x)); // Need to match SingleLayerWater tile encoding
#else
const uint2 TileCoord = GroupId.xy;
#endif // USE_TILE_LIST
// Morton order within a group so page access/atomics are more coherent and wave-swizzled gradients are possible.
uint2 LocalPixelPos = WORK_TILE_SIZE * TileCoord + MortonDecode(GroupIndex);
uint2 PixelPos = LocalPixelPos + uint2( ProjectionRect.xy );
if ( any( PixelPos >= uint2( ProjectionRect.zw ) ) )
{
return;
}
float DeviceZ = SceneTexturesStruct.SceneDepthTexture.Load( int3( PixelPos, 0 ) ).r;
const bool bIsHairInput = InputType == INPUT_TYPE_HAIRSTRANDS;
#if HAS_HAIR_STRANDS
if (bIsHairInput)
{
DeviceZ = HairStrands.HairOnlyDepthTexture.Load(int3(PixelPos, 0)).x;
if (DeviceZ == 0)
{
return;
}
}
#endif
const float SceneDepth = ConvertFromDeviceZ( DeviceZ );
const float4 SvPosition = float4( float2( PixelPos ) + 0.5, DeviceZ, 1.0f );
const float3 TranslatedWorldPosition = SvPositionToTranslatedWorld( SvPosition );
const float ScreenRayLengthWorld = GetScreenRayLengthMultiplierForProjectionType(VirtualShadowMap.ScreenRayLength * SceneDepth).y;
const float Noise = BlueNoiseScalar(SvPosition.xy, View.StateFrameIndex);
const FProjectionShadingInfo ShadingInfo = GetProjectionShadingInfo(PixelPos);
FSubsurfaceOpacityMFP SubsurfaceOpacityMFP = GetInitialisedSubsurfaceOpacityMFP();
if (!bIsHairInput)
{
SubsurfaceOpacityMFP = GetSubsurfaceOpacityFromGbuffer(PixelPos);
}
#if ONE_PASS_PROJECTION
const FCulledLightsGridHeader LightGridHeader = VirtualShadowMapGetLightsGridHeader(LocalPixelPos, SceneDepth);
// We can only handle so many lights in our output encoding right now, so no purpose in computing more
uint LightCount = min(GetPackedShadowMaskMaxLightCount(), LightGridHeader.NumLights);
bool bLightCountOverflow = GetPackedShadowMaskMaxLightCount() < LightGridHeader.NumLights;
StatsBufferInterlockedEnableFlags(VSM_STAT_OVERFLOW_FLAGS, VSM_STAT_OVERFLOW_FLAG_OPP_MAX_LIGHTS, bLightCountOverflow, true);
uint4 ShadowMask = InitializePackedShadowMask();
LOOP
for (uint Index = 0; Index < LightCount; ++Index)
{
const FLocalLightData LightData = VirtualShadowMapGetLocalLightData(LightGridHeader, Index);
int VirtualShadowMapId = LightData.Internal.VirtualShadowMapId;
if (VirtualShadowMapId != INDEX_NONE)
{
FLightShaderParameters Light = ConvertFromLocal( LightData );
FVirtualShadowMapSampleResult Result = ProjectLight(
VirtualShadowMapId,
Light,
ShadingInfo,
PixelPos,
SceneDepth,
ScreenRayLengthWorld,
TranslatedWorldPosition,
Noise,
SubsurfaceOpacityMFP);
FilterVirtualShadowMapSampleResult(PixelPos, Result);
PackShadowMask(ShadowMask, Result.ShadowFactor, Index);
#if VISUALIZE_OUTPUT
OutputVisualize( VirtualShadowMapId, PixelPos - View.ViewRectMin.xy, TranslatedWorldPosition, Result );
#endif // VISUALIZE_OUTPUT
}
}
OutShadowMaskBits[PixelPos] = ShadowMask;
#else // !ONE_PASS_PROJECTION
{
int VirtualShadowMapId = LightUniformVirtualShadowMapId;
FLightShaderParameters Light = GetRootLightShaderParameters();
#if DIRECTIONAL_LIGHT
// Increases light source radius for subsurface material as the opacity reduces to emulates light diffusion.
// TODO: Does something similar make sense for local lights?
// Substrate: this approximation has no concrete foundation so it is currently skipped for Substrate using PerPixel SSS, e.g. when bDataIsOpacity==false and data is MFP.
if (SubsurfaceOpacityMFP.bDataIsOpacity && SubsurfaceOpacityMFP.Data < 1.0f)
{
const float SubsurfaceOpacity = SubsurfaceOpacityMFP.Data;
Light.SourceRadius = max(Light.SourceRadius, (1.0f - SubsurfaceOpacity) * SubsurfaceMinSourceRadius);
}
#endif
FVirtualShadowMapSampleResult Result = ProjectLight(
VirtualShadowMapId,
Light,
ShadingInfo,
PixelPos,
SceneDepth,
ScreenRayLengthWorld,
TranslatedWorldPosition,
Noise,
SubsurfaceOpacityMFP);
OutShadowFactor[ PixelPos ] = Result.ShadowFactor.xx;
#if VISUALIZE_OUTPUT
OutputVisualize( VirtualShadowMapId, PixelPos - View.ViewRectMin.xy, TranslatedWorldPosition, Result );
#endif // VISUALIZE_OUTPUT
}
#endif // ONE_PASS_PROJECTION
}