Files
UnrealEngineUWP/Engine/Shaders/Private/VirtualShadowMaps/VirtualShadowMapProjection.usf
charles derousiers 225c57c163 Improve noise on hair transmittance and shadow.
This CL replace the jitter noise by a blue noise function which remove structural noise, and lead to more pleasing results.

#rb charles.derousiers

[CL 31834805 by charles derousiers in 5.4 branch]
2024-02-27 04:52:50 -05:00

732 lines
25 KiB
Plaintext

// Copyright Epic Games, Inc. All Rights Reserved.
#define SUPPORT_CONTACT_SHADOWS 1
#include "../Common.ush"
#include "../SceneTexturesCommon.ush"
#include "../Substrate/Substrate.ush"
#include "../DeferredShadingCommon.ush"
#include "../DeferredLightingCommon.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 "VirtualShadowMapLightGrid.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;
float3 WorldNormal;
};
FProjectionShadingInfo GetProjectionShadingInfo(uint2 PixelPos)
{
FProjectionShadingInfo Out;
#if SUBSTRATE_ENABLED
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_INVALID; // legacy IsSubsurfaceModel is true for subsurface profile, eyes, etc.
#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.WorldNormal = GBufferData.WorldNormal;
#endif
return Out;
}
SCHEDULER_MIN_PRESSURE
MAX_OCCUPANCY
// Screen space ray trace to attempt to skip over ambiguous regions near the receiver surface
// Returns length at which to start the virtual shadow map ray; usually this is where the screen ray ended or went behind a surface
float VirtualShadowMapScreenRayCast(
float3 RayOriginTranslatedWorld,
float3 RayDirection,
float RayLength,
float Dither)
{
float4 RayStartClip = mul(float4(RayOriginTranslatedWorld, 1), View.TranslatedWorldToClip);
float4 RayDirClip = mul(float4(RayDirection * RayLength, 0), View.TranslatedWorldToClip);
float4 RayEndClip = RayStartClip + RayDirClip;
float3 RayStartScreen = RayStartClip.xyz / RayStartClip.w;
float3 RayEndScreen = RayEndClip.xyz / RayEndClip.w;
float3 RayStepScreen = RayEndScreen - RayStartScreen;
float3 RayStartUVz = float3(RayStartScreen.xy * View.ScreenPositionScaleBias.xy + View.ScreenPositionScaleBias.wz, RayStartScreen.z);
float3 RayStepUVz = float3(RayStepScreen.xy * View.ScreenPositionScaleBias.xy, RayStepScreen.z);
float4 RayDepthClip = RayStartClip + mul(float4(0, 0, RayLength, 0), View.ViewToClip);
float3 RayDepthScreen = RayDepthClip.xyz / RayDepthClip.w;
const int Steps = SCREEN_RAY_SAMPLES;
float StepOffset = Dither - 0.5f;
const float Step = 1.0 / Steps;
float SampleTime = StepOffset * Step + Step;
const float StartDepth = SceneTexturesStruct.SceneDepthTexture.SampleLevel(SceneTexturesStruct_SceneDepthTextureSampler, RayStartUVz.xy, 0).r;
UNROLL
for (int i = 0; i < Steps; i++)
{
float3 SampleUVz = RayStartUVz + RayStepUVz * SampleTime;
float SampleDepth = SceneTexturesStruct.SceneDepthTexture.SampleLevel(SceneTexturesStruct_SceneDepthTextureSampler, SampleUVz.xy, 0).r;
// Avoid self-intersection with the start pixel (exact comparison due to point sampling depth buffer)
if (SampleDepth != StartDepth)
{
if (SampleUVz.z < SampleDepth)
{
// Behind geometry. Back up a bit along the ray and do the VSM sample from there.
return RayLength * max(0.0, SampleTime - 1.5f * Step);
}
}
SampleTime += Step;
}
// Got to the end of the ray without going behind or hitting anything
return RayLength;
}
// 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 ScreenRayLength;
float NormalBias;
float SubsurfaceMinSourceRadius;
int SMRTRayCount; // 0 = off
int SMRTSamplesPerRay;
float SMRTRayLengthScale; // Directional lights
float SMRTCotMaxRayAngleFromLight; // Spot/point lights
float SMRTTexelDitherScale; // Currently only directional lights
float SMRTExtrapolateSlope;
float SMRTMaxSlopeBias; // Currently only local lights
uint SMRTAdaptiveRayCount;
// One pass projection
RWTexture2D< uint4 > OutShadowMaskBits;
// Single light per pass
// Light parameters loaded via GetRootLightShaderParameters();
int LightUniformVirtualShadowMapId;
RWTexture2D< float2 > OutShadowFactor;
// Visualization output
StructuredBuffer< FPhysicalPageMetaData > PhysicalPageMetaData;
RWTexture2D< float4 > OutVisualize;
int VisualizeModeId;
int VisualizeVirtualShadowMapId;
float VisualizeNaniteOverdrawScale;
// 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;
const float DistanceToCamera = GetDistanceToCameraFromViewVector(TranslatedWorldPosition - View.TranslatedWorldCameraOrigin);
const float NormalBiasLength = max(0.02f, NormalBias * DistanceToCamera / GetCotanHalfFieldOfView().x);
#if DIRECTIONAL_LIGHT
float3 L = Light.Direction;
bool bInLightRegion = true;
#else
float3 ToLight = Light.TranslatedWorldPosition - TranslatedWorldPosition;
float d2 = dot( ToLight, ToLight );
float InvDist = rsqrt( d2 );
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;
}
}
#endif // DIRECTIONAL_LIGHT
FVirtualShadowMapSampleResult Result = InitVirtualShadowMapSampleResult();
const bool bValidPixel = bIsHairInput || ShadingInfo.bIsValid;
BRANCH
if (bInLightRegion && bValidPixel)
{
bool bBackfaceCull = (bCullBackfacingPixels > 0) && !bIsHairInput && !ShadingInfo.bIsSubsurface;
#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
float3 WorldNormal = (bIsHairInput || ShadingInfo.bIsHair) ? L : ShadingInfo.WorldNormal;
TranslatedWorldPosition += WorldNormal * NormalBiasLength;
// 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)
{
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 (SMRTRayCount > 0)
{
Result = TraceDirectional(
VirtualShadowMapId,
Light,
PixelPos,
SceneDepth,
TranslatedWorldPosition,
SMRTRayOffset,
SMRTRayCount,
SMRTSamplesPerRay,
SMRTRayLengthScale,
Noise,
bBackfaceCull,
WorldNormal,
SMRTTexelDitherScale,
SMRTExtrapolateSlope,
SMRTAdaptiveRayCount);
}
else
{
Result = SampleVirtualShadowMapDirectional(
VirtualShadowMapId,
TranslatedWorldPosition,
SMRTRayOffset,
WorldNormal);
}
#else // !DIRECTIONAL_LIGHT
if (SMRTRayCount > 0)
{
Result = TraceLocalLight(
VirtualShadowMapId,
Light,
PixelPos,
SceneDepth,
TranslatedWorldPosition,
SMRTRayOffset,
SMRTRayCount,
SMRTSamplesPerRay,
SMRTCotMaxRayAngleFromLight,
Noise,
bBackfaceCull,
WorldNormal,
SMRTTexelDitherScale,
SMRTExtrapolateSlope,
SMRTMaxSlopeBias,
SMRTAdaptiveRayCount);
}
else
{
Result = SampleVirtualShadowMapLocal(
VirtualShadowMapId,
TranslatedWorldPosition,
SMRTRayOffset,
WorldNormal);
}
#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)
{
float3 RayStart = 0;
float3 RayEnd = 0;
bool bCastHairRay = false;
float4 Random = 0;
if (Result.ShadowFactor > 0)
{
#if USE_BLUE_NOISE
Random = GetHairBlueNoiseJitter(PixelPos, 0 /*SampleIndex*/, 1 /*MaxSampleCount*/, View.StateFrameIndexMod8/*TimeIndex*/);
#else
Random = ComputeRandom4(PixelPos);
#endif
uint RayIndex = min(Random.w * SMRTRayCount, SMRTRayCount - 1);
#if DIRECTIONAL_LIGHT
bCastHairRay = GenerateRayDirectional(
Light,
PixelPos,
TranslatedWorldPosition,
SMRTRayLengthScale * 100,
RayIndex,
SMRTRayCount,
RayStart,
RayEnd);
#else
bCastHairRay = GenerateRayLocalLight(
Light,
PixelPos,
TranslatedWorldPosition,
WorldNormal,
SMRTCotMaxRayAngleFromLight,
RayIndex,
SMRTRayCount,
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;
const uint VoxelDescCount = VirtualVoxel.NodeDescCount;
for (uint VoxelDescIt=0; 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);
Result.ShadowFactor = min(Result.ShadowFactor, saturate(1 - HairResult.HairCount));
}
}
}
#endif
}
FilterVirtualShadowMapSampleResult(PixelPos, Result);
return Result;
}
float3 GetVirtualPageColor(uint2 VirtualTexelAddress)
{
uint2 vPage = VirtualTexelAddress.xy >> VSM_LOG2_PAGE_SIZE;
return IntToColor(vPage.x + (vPage.y << 10U));
}
void OutputVisualize(
uint2 PixelPos,
float3 TranslatedWorldPosition, // Unmodified, raw position before any biasing
FVirtualShadowMapSampleResult Result)
{
// NOTE: Important to *not* write output if it isn't a recognized mode, as a different
// pass may write that output instead.
float3 Output = float3( 1, 0, 1 );
bool bWriteOutput = false;
if ( Result.bValid )
{
bWriteOutput = true;
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)];
}
if ( VisualizeModeId == VIRTUAL_SHADOW_MAP_VISUALIZE_SHADOW_FACTOR )
{
Output = Result.ShadowFactor.xxx;
}
else if ( VisualizeModeId == VIRTUAL_SHADOW_MAP_VISUALIZE_CLIPMAP_OR_MIP )
{
float3 Color = IntToColor( Result.ClipmapOrMipLevel );
Output = 0.8f*Color + 0.2f*Result.ShadowFactor.xxx;
}
else if ( VisualizeModeId == VIRTUAL_SHADOW_MAP_VISUALIZE_VIRTUAL_PAGE && bValidPageAddress )
{
float3 MipColor = IntToColor( Result.ClipmapOrMipLevel );
Output = 0.4f*vPageColor + 0.4f*MipColor + 0.2f*Result.ShadowFactor.xxx;
}
else if ( VisualizeModeId == VIRTUAL_SHADOW_MAP_VISUALIZE_SMRT_RAY_COUNT )
{
Output = GreenToRedTurbo( float( Result.RayCount ) / float( SMRTRayCount ) );
}
else if ( VisualizeModeId == VIRTUAL_SHADOW_MAP_VISUALIZE_CACHED_PAGE && bValidPageAddress )
{
bool bUncachedDynamic = (pPageMeta.Flags & VSM_DYNAMIC_UNCACHED_FLAG) != 0;
bool bUncachedStatic = (pPageMeta.Flags & VSM_STATIC_UNCACHED_FLAG) != 0;
// Red = both uncached, blue = only static cached, green otherwise
float3 CacheColor = float3(0, 1, 0);
if (bUncachedDynamic)
{
CacheColor = bUncachedStatic ? float3(1, 0, 0) : float3(0, 0, 1);
}
Output = 0.55f*CacheColor + 0.25f*vPageColor + 0.2f*Result.ShadowFactor.xxx;
}
else if (VisualizeModeId == VIRTUAL_SHADOW_MAP_VISUALIZE_DIRTY_PAGE)
{
float3 PageColor = vPageColor * 0.4f;
float3 DirtyColor = PageColor;
if (bValidPageAddress)
{
bool bDirty = (pPageMeta.Flags & VSM_PHYSICAL_FLAG_DIRTY) != 0U;
if (bDirty)
{
DirtyColor.x = 1.0f;
}
PageColor = lerp(PageColor, DirtyColor, 0.8f);
}
Output = 0.8f * PageColor + 0.2f * Result.ShadowFactor.xxx;
}
else if (VisualizeModeId == VIRTUAL_SHADOW_MAP_VISUALIZE_GPU_INVALIDATED_PAGE)
{
float3 PageColor = vPageColor * 0.4f;
float3 DirtyColor = PageColor;
if (bValidPageAddress)
{
uint InvalidationFlags = pPageMeta.Flags >> VSM_PHYSICAL_PAGE_INVALIDATION_FLAGS_SHIFT;
bool bUncachedDynamic = (InvalidationFlags & VSM_DYNAMIC_UNCACHED_FLAG) != 0;
bool bUncachedStatic = (InvalidationFlags & VSM_STATIC_UNCACHED_FLAG) != 0;
if (bUncachedStatic)
{
DirtyColor.x = 1.0f;
}
if (bUncachedDynamic)
{
DirtyColor.y = 1.0f;
}
PageColor = lerp(PageColor, DirtyColor, 0.8f);
}
Output = 0.8f * PageColor + 0.2f * Result.ShadowFactor.xxx;
}
else if ( VisualizeModeId == VIRTUAL_SHADOW_MAP_VISUALIZE_MERGED_PAGE)
{
bool bMerged = ((pPageMeta.Flags & VSM_PHYSICAL_FLAG_DIRTY) != 0U) && (pPageMeta.Flags & VSM_PHYSICAL_FLAG_VIEW_UNCACHED) == 0U;
float3 Color = float3(0, 1, 0);
if (bMerged)
{
Color = float3(1, 0, 0);
}
Output = 0.55f * Color + 0.25f * vPageColor + 0.2f * Result.ShadowFactor.xxx;
}
else if ( VisualizeModeId == VIRTUAL_SHADOW_MAP_VISUALIZE_GENERAL_DEBUG )
{
Output = 0.8f*Result.GeneralDebug + 0.2f*Result.ShadowFactor.xxx;
}
else if ( VisualizeModeId == VIRTUAL_SHADOW_MAP_VISUALIZE_NANITE_OVERDRAW )
{
// This is just to get the raw physical address
FVirtualShadowMapSampleResult RawResult = SampleVirtualShadowMapTranslatedWorld(
VisualizeVirtualShadowMapId, TranslatedWorldPosition);
const float OverdrawScale = clamp(VisualizeNaniteOverdrawScale / 100.0f, 0.0f, 1.0f);
const float OverdrawCount = float(VirtualShadowMap.PhysicalPagePool.Load(uint4(RawResult.PhysicalTexelAddress.xy, 2, 0)));
const float OverdrawColor = 1 - exp2(-OverdrawCount * OverdrawScale);
Output = ColorMapInferno(OverdrawColor);
}
/* TODO: Enable this and add to menu
else if ( VisualizeModeId == VIRTUAL_SHADOW_MAP_VISUALIZE_OCCLUDER_DISTANCE )
{
Output = (Result.OccluderDistance / 1000.0f).xxx;
}
*/
else
{
bWriteOutput = false;
}
}
if (bWriteOutput)
{
OutVisualize[PixelPos] = float4(Output, 1.0f);
}
}
#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(ScreenRayLength * SceneDepth).y;
const float Noise = InterleavedGradientNoise( SvPosition.xy, View.StateFrameIndexMod8 );
const FProjectionShadingInfo ShadingInfo = GetProjectionShadingInfo(PixelPos);
FSubsurfaceOpacityMFP SubsurfaceOpacityMFP = GetInitialisedSubsurfaceOpacityMFP();
if (!bIsHairInput)
{
SubsurfaceOpacityMFP = GetSubsurfaceOpacityFromGbuffer(PixelPos);
}
FVirtualShadowMapSampleResult VisualizeResult = InitVirtualShadowMapSampleResult();
#if ONE_PASS_PROJECTION
const FCulledLightsGridData LightGridData = VirtualShadowMapGetLightsGrid(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(), LightGridData.NumLights);
uint4 ShadowMask = InitializePackedShadowMask();
LOOP
for (uint Index = 0; Index < LightCount; ++Index)
{
const FLocalLightData LightData = VirtualShadowMapGetLocalLightData(LightGridData, Index);
int VirtualShadowMapId = LightData.VirtualShadowMapId;
if (VirtualShadowMapId != INDEX_NONE)
{
FLightShaderParameters Light = ConvertFromLocal( LightData );
FVirtualShadowMapSampleResult Result = ProjectLight(
VirtualShadowMapId,
Light,
ShadingInfo,
PixelPos,
SceneDepth,
ScreenRayLengthWorld,
TranslatedWorldPosition,
Noise,
SubsurfaceOpacityMFP);
PackShadowMask(ShadowMask, Result.ShadowFactor, Index);
if (VisualizeVirtualShadowMapId == VirtualShadowMapId)
{
VisualizeResult = Result;
}
}
}
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 (VisualizeVirtualShadowMapId == VirtualShadowMapId)
{
VisualizeResult = Result;
}
}
#endif // ONE_PASS_PROJECTION
#if VISUALIZE_OUTPUT
OutputVisualize( PixelPos - View.ViewRectMin.xy, TranslatedWorldPosition, VisualizeResult );
#endif // VISUALIZE_OUTPUT
}