You've already forked UnrealEngineUWP
mirror of
https://github.com/izzy2lost/UnrealEngineUWP.git
synced 2026-03-26 18:15:20 -07:00
#jira UE-171685 #rb Charles.deRousiers, tim.doerries [CL 30763421 by wouter dek in ue5-main branch]
576 lines
23 KiB
Plaintext
576 lines
23 KiB
Plaintext
// Copyright Epic Games, Inc. All Rights Reserved.
|
|
|
|
/*=============================================================================
|
|
VirtualShadowMapPageMarking.usf:
|
|
=============================================================================*/
|
|
|
|
#include "/Engine/Shared/SingleLayerWaterDefinitions.h"
|
|
#include "../HairStrands/HairStrandsVisibilityCommonStruct.ush"
|
|
#include "../Common.ush"
|
|
#include "../WaveOpUtil.ush"
|
|
#include "../LightGridCommon.ush"
|
|
#include "../SceneTexturesCommon.ush"
|
|
#include "../DeferredShadingCommon.ush"
|
|
#include "../HairStrands/HairStrandsVisibilityCommon.ush"
|
|
#include "../HairStrands/HairStrandsTileCommon.ush"
|
|
#include "../Substrate/Substrate.ush"
|
|
#include "VirtualShadowMapProjectionDirectional.ush"
|
|
#include "VirtualShadowMapProjectionSpot.ush"
|
|
#include "VirtualShadowMapProjectionCommon.ush"
|
|
#include "VirtualShadowMapStats.ush"
|
|
#include "VirtualShadowMapLightGrid.ush"
|
|
|
|
// 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
|
|
#define INPUT_TYPE_GBUFFER_AND_WATER_DEPTH 2
|
|
|
|
// Flags generated by per-pixel pass to determine which pages are required to provide shadow for the visible geometry
|
|
RWStructuredBuffer<uint> OutPageRequestFlags;
|
|
|
|
StructuredBuffer<int> DirectionalLightIds;
|
|
|
|
float PageDilationBorderSizeDirectional;
|
|
float PageDilationBorderSizeLocal;
|
|
uint InputType;
|
|
uint bCullBackfacingPixels;
|
|
uint NumDirectionalLightSmInds;
|
|
uint MipModeLocal;
|
|
|
|
int GetBiasedClipmapLevel(FVirtualShadowMapProjectionShaderData BaseProjectionData, float3 TranslatedWorldPosition)
|
|
{
|
|
float BiasedLevel = CalcAbsoluteClipmapLevel(BaseProjectionData, TranslatedWorldPosition) + BaseProjectionData.ResolutionLodBias + VirtualShadowMap.GlobalResolutionLodBias;
|
|
return int(floor(BiasedLevel));
|
|
}
|
|
|
|
uint GetMipLevelLocal(int VirtualShadowMapId, float3 TranslatedWorldPosition, float SceneDepth)
|
|
{
|
|
FVirtualShadowMapProjectionShaderData ProjectionData = GetVirtualShadowMapProjectionData(VirtualShadowMapId);
|
|
|
|
// If local lights are near the primary view the combined offset should be small
|
|
float3 ViewToShadowTranslation = DFFastLocalSubtractDemote(ProjectionData.PreViewTranslation, PrimaryView.PreViewTranslation);
|
|
float3 ShadowTranslatedWorldPosition = TranslatedWorldPosition + ViewToShadowTranslation;
|
|
|
|
float Footprint = VirtualShadowMapCalcPixelFootprintLocal(ProjectionData, ShadowTranslatedWorldPosition, SceneDepth);
|
|
|
|
float MipLevelFloat = log2(Footprint) + ProjectionData.ResolutionLodBias + VirtualShadowMap.GlobalResolutionLodBias;
|
|
uint MipLevel = uint(max(floor(MipLevelFloat), 0.0f));
|
|
MipLevel = min(MipLevel, (VSM_MAX_MIP_LEVELS - 1U));
|
|
|
|
if (MipModeLocal == 1)
|
|
{
|
|
// Even mips
|
|
MipLevel &= ~(1U);
|
|
}
|
|
else if (MipModeLocal == 2)
|
|
{
|
|
// Odd mips
|
|
MipLevel |= 1U;
|
|
}
|
|
|
|
return MipLevel;
|
|
}
|
|
|
|
void MarkPageAddress(uint PageOffset, uint Flags)
|
|
{
|
|
checkStructuredBufferAccessSlow(OutPageRequestFlags, PageOffset);
|
|
OutPageRequestFlags[PageOffset] = Flags;
|
|
}
|
|
|
|
void MarkPage(uint VirtualShadowMapId, uint MipLevel, float3 TranslatedWorldPosition, bool bUsePageDilation, float2 PageDilationOffset)
|
|
{
|
|
FVirtualShadowMapProjectionShaderData ProjectionData = GetVirtualShadowMapProjectionData(VirtualShadowMapId);
|
|
|
|
// MarkPage (or mark pixel pages) should never run for a distant light.
|
|
checkSlow(!IsSinglePageVirtualShadowMap(VirtualShadowMapId));
|
|
|
|
float3 ViewToShadowTranslation = DFFastLocalSubtractDemote(ProjectionData.PreViewTranslation, PrimaryView.PreViewTranslation);
|
|
float3 ShadowTranslatedWorldPosition = TranslatedWorldPosition + ViewToShadowTranslation;
|
|
float4 ShadowUVz = mul(float4(ShadowTranslatedWorldPosition, 1.0f), ProjectionData.TranslatedWorldToShadowUVMatrix);
|
|
ShadowUVz.xyz /= ShadowUVz.w;
|
|
|
|
// Check overlap vs the shadow map space
|
|
// NOTE: XY test not really needed anymore with the precise cone test in the caller, but we'll leave it for the moment
|
|
bool bInClip = ShadowUVz.w > 0.0f &&
|
|
all(and(ShadowUVz.xyz <= ShadowUVz.w,
|
|
ShadowUVz.xyz >= float3(-ShadowUVz.ww, 0.0f)));
|
|
if (!bInClip)
|
|
{
|
|
return;
|
|
}
|
|
// Normal pages marked through pixel processing are not "coarse" and should include "detail geometry" - i.e., all geometry
|
|
uint Flags = VSM_ALLOCATED_FLAG | VSM_DETAIL_GEOMETRY_FLAG;
|
|
|
|
uint MaxPageAddress = CalcLevelDimsPages(MipLevel) - 1U;
|
|
float2 PageAddressFloat = ShadowUVz.xy * CalcLevelDimsPages(MipLevel);
|
|
uint2 PageAddress = clamp(uint2(PageAddressFloat), 0U, MaxPageAddress);
|
|
uint PageOffset = CalcPageOffset(VirtualShadowMapId, MipLevel, PageAddress);
|
|
MarkPageAddress(PageOffset, Flags);
|
|
|
|
// PageDilationBorderSize == 0 implies PageDilationOffset.xy == 0
|
|
if (bUsePageDilation)
|
|
{
|
|
uint2 PageAddress2 = clamp(uint2(PageAddressFloat + PageDilationOffset), 0U, MaxPageAddress);
|
|
uint PageOffset2 = CalcPageOffset(VirtualShadowMapId, MipLevel, PageAddress2);
|
|
if (PageOffset2 != PageOffset)
|
|
{
|
|
MarkPageAddress(PageOffset2, Flags);
|
|
}
|
|
uint2 PageAddress3 = clamp(uint2(PageAddressFloat - PageDilationOffset), 0U, MaxPageAddress);
|
|
uint PageOffset3 = CalcPageOffset(VirtualShadowMapId, MipLevel, PageAddress3);
|
|
if (PageOffset3 != PageOffset)
|
|
{
|
|
MarkPageAddress(PageOffset3, Flags);
|
|
}
|
|
}
|
|
}
|
|
|
|
void MarkPageClipmap(
|
|
FVirtualShadowMapProjectionShaderData ProjectionData,
|
|
bool bUsePageDilation,
|
|
float2 PageDilationOffset,
|
|
float3 TranslatedWorldPosition)
|
|
{
|
|
const int ClipmapLevel = GetBiasedClipmapLevel(ProjectionData, TranslatedWorldPosition);
|
|
int ClipmapIndex = max(0, ClipmapLevel - ProjectionData.ClipmapLevel);
|
|
if (ClipmapIndex < ProjectionData.ClipmapLevelCountRemaining)
|
|
{
|
|
MarkPage(ProjectionData.VirtualShadowMapId + ClipmapIndex, 0, TranslatedWorldPosition, bUsePageDilation, PageDilationOffset);
|
|
}
|
|
}
|
|
|
|
//
|
|
RWStructuredBuffer<uint> OutPrunedLightGridData;
|
|
RWStructuredBuffer<uint> OutPrunedNumCulledLightsGrid;
|
|
|
|
[numthreads(VSM_DEFAULT_CS_GROUP_X, 1, 1)]
|
|
void PruneLightGridCS(uint GridLinearIndex : SV_DispatchThreadID)
|
|
{
|
|
uint EyeIndex = 0;
|
|
const FLightGridData GridData = GetLightGridData(EyeIndex);
|
|
|
|
if (GridLinearIndex >= GridData.CulledGridSize.x * GridData.CulledGridSize.y * GridData.CulledGridSize.z)
|
|
{
|
|
return;
|
|
}
|
|
const FCulledLightsGridData CulledLightGridData = GetCulledLightsGrid(GridLinearIndex, EyeIndex);
|
|
|
|
uint NumRetainedLights = 0U;
|
|
|
|
// First pass add the non-distant lights with VSMs
|
|
LOOP
|
|
for (uint Index = 0; Index < CulledLightGridData.NumLights; ++Index)
|
|
{
|
|
int VirtualShadowMapId = GetLocalLightData(CulledLightGridData.DataStartIndex + Index, EyeIndex).VirtualShadowMapId;
|
|
if (VirtualShadowMapId != INDEX_NONE && !IsSinglePageVirtualShadowMap(VirtualShadowMapId))
|
|
{
|
|
// Copy light grid data index
|
|
checkStructuredBufferAccessSlow(OutPrunedLightGridData, CulledLightGridData.DataStartIndex + NumRetainedLights);
|
|
OutPrunedLightGridData[CulledLightGridData.DataStartIndex + NumRetainedLights++] = GetCulledLightDataGrid(CulledLightGridData.DataStartIndex + Index);
|
|
}
|
|
}
|
|
|
|
// Second pass add the distant lights with VSMs after
|
|
// NOTE: We could add these to the end in the first pass and then re-pack them instead, but this works
|
|
// well enough for moderate light counts.
|
|
LOOP
|
|
for (uint Index = 0; Index < CulledLightGridData.NumLights; ++Index)
|
|
{
|
|
int VirtualShadowMapId = GetLocalLightData(CulledLightGridData.DataStartIndex + Index, EyeIndex).VirtualShadowMapId;
|
|
// Discard any light without a VSM
|
|
if (VirtualShadowMapId != INDEX_NONE && IsSinglePageVirtualShadowMap(VirtualShadowMapId))
|
|
{
|
|
checkStructuredBufferAccessSlow(OutPrunedLightGridData, CulledLightGridData.DataStartIndex + NumRetainedLights);
|
|
OutPrunedLightGridData[CulledLightGridData.DataStartIndex + NumRetainedLights++] = GetCulledLightDataGrid(CulledLightGridData.DataStartIndex + Index);
|
|
}
|
|
}
|
|
|
|
checkStructuredBufferAccessSlow(OutPrunedNumCulledLightsGrid, GridLinearIndex);
|
|
OutPrunedNumCulledLightsGrid[GridLinearIndex] = NumRetainedLights;
|
|
}
|
|
|
|
uint2 PixelStride;
|
|
|
|
#if PERMUTATION_WATER_DEPTH
|
|
Texture2D<float> SingleLayerWaterDepthTexture;
|
|
StructuredBuffer< uint > SingleLayerWaterTileMask;
|
|
int2 SingleLayerWaterTileViewRes;
|
|
#endif
|
|
|
|
#if PERMUTATION_TRANSLUCENCY_DEPTH
|
|
uint FrontLayerMode;
|
|
float4 FrontLayerHistoryUVMinMax;
|
|
float4 FrontLayerHistoryScreenPositionScaleBias;
|
|
float4 FrontLayerHistoryBufferSizeAndInvSize;
|
|
Texture2D<float> FrontLayerTranslucencyDepthTexture;
|
|
Texture2D<float4> FrontLayerTranslucencyNormalTexture;
|
|
#endif
|
|
|
|
struct FPositionData
|
|
{
|
|
float SceneDepth;
|
|
float4 SvPosition;
|
|
float3 TranslatedWorldPosition;
|
|
float DeviceZ;
|
|
};
|
|
|
|
FPositionData InitPositionData(uint2 PixelPos, float DeviceZ, bool bIsValid)
|
|
{
|
|
FPositionData Out = (FPositionData)0;
|
|
if (bIsValid)
|
|
{
|
|
Out.DeviceZ = DeviceZ;
|
|
Out.SceneDepth = ConvertFromDeviceZ(Out.DeviceZ);
|
|
Out.SvPosition = float4(float2(PixelPos) + 0.5f, Out.DeviceZ, 1.0f);
|
|
Out.TranslatedWorldPosition = SvPositionToTranslatedWorld(Out.SvPosition);
|
|
}
|
|
return Out;
|
|
}
|
|
|
|
// Note: we use the tile size defined by the water as the group-size - this is needed because the tile mask testing code relies on the size being the same to scalarize efficiently.
|
|
[numthreads(SLW_TILE_SIZE_XY, SLW_TILE_SIZE_XY, 1)]
|
|
void GeneratePageFlagsFromPixels(
|
|
uint3 InGroupId : SV_GroupID,
|
|
uint GroupIndex : SV_GroupIndex,
|
|
uint3 GroupThreadId : SV_GroupThreadID,
|
|
uint3 DispatchThreadId : SV_DispatchThreadID)
|
|
{
|
|
#if PERMUTATION_INPUT_TYPE == INPUT_TYPE_HAIRSTRANDS
|
|
uint2 GroupId = InGroupId.xy;
|
|
if (HairStrands.bHairTileValid>0)
|
|
{
|
|
const uint TileCount = HairStrands.HairTileCount[HAIRTILE_HAIR_ALL];
|
|
const uint LinearIndex = InGroupId.x + InGroupId.y * HairStrands.HairTileCountXY.x;
|
|
if (LinearIndex >= TileCount)
|
|
{
|
|
return;
|
|
}
|
|
GroupId = HairStrands.HairTileData[LinearIndex];
|
|
}
|
|
#else // PERMUTATION_INPUT_TYPE == INPUT_TYPE_GBUFFER || PERMUTATION_INPUT_TYPE == INPUT_TYPE_GBUFFER_AND_WATER_DEPTH
|
|
const uint2 GroupId = InGroupId.xy;
|
|
#endif
|
|
const uint2 PixelLocalPos = DispatchThreadId.xy * PixelStride;
|
|
const uint2 PixelPos = uint2(View.ViewRectMin.xy) + PixelLocalPos;
|
|
|
|
if (any(PixelPos >= uint2(View.ViewRectMin.xy + View.ViewSizeAndInvSize.xy)))
|
|
{
|
|
return;
|
|
}
|
|
|
|
half3 WorldNormal = half3(0, 0, 0);
|
|
bool bBackfaceCull = bCullBackfacingPixels != 0;
|
|
float DeviceZ = 0;
|
|
float DeviceZWater = 0;
|
|
float DeviceZTranslucency = 0;
|
|
bool bIsValid = true;
|
|
bool bIsWaterDepthValid = false;
|
|
bool bIsTranslucencyDepthValid= false;
|
|
#if PERMUTATION_INPUT_TYPE == INPUT_TYPE_HAIRSTRANDS
|
|
{
|
|
DeviceZ = HairStrands.HairOnlyDepthTexture.Load(uint3(PixelPos, 0)).x;
|
|
bIsValid = DeviceZ > 0;
|
|
bBackfaceCull = false;
|
|
}
|
|
#else // PERMUTATION_INPUT_TYPE == INPUT_TYPE_GBUFFER
|
|
{
|
|
DeviceZ = LookupDeviceZ(PixelPos);
|
|
#if SUBSTRATE_ENABLED
|
|
const FSubstrateTopLayerData TopLayerData = SubstrateUnpackTopLayerData(Substrate.TopLayerTexture.Load(uint3(PixelPos, 0)));
|
|
FSubstrateAddressing SubstrateAddressing = GetSubstratePixelDataByteOffset(PixelPos, uint2(View.BufferSizeAndInvSize.xy), Substrate.MaxBytesPerPixel);
|
|
FSubstratePixelHeader SubstratePixelHeader = UnpackSubstrateHeaderIn(Substrate.MaterialTextureArray, SubstrateAddressing, Substrate.TopLayerTexture);
|
|
WorldNormal = TopLayerData.WorldNormal;
|
|
bIsValid = SubstratePixelHeader.IsSubstrateMaterial();
|
|
bBackfaceCull = bBackfaceCull && !SubstratePixelHeader.HasSubsurface();
|
|
#else // !SUBSTRATE_ENABLED
|
|
FGBufferData GBuffer = GetGBufferDataUint(PixelPos, true);
|
|
WorldNormal = GBuffer.WorldNormal;
|
|
// Excluding unlit to avoid including processing sky dome
|
|
bIsValid = GBuffer.ShadingModelID != SHADINGMODELID_UNLIT;
|
|
bBackfaceCull = bBackfaceCull && !IsSubsurfaceModel(GBuffer.ShadingModelID);
|
|
#endif // SUBSTRATE_ENABLED
|
|
|
|
#if PERMUTATION_WATER_DEPTH
|
|
// Assume valid if there is no mask
|
|
bIsWaterDepthValid = SingleLayerWaterTileViewRes.x <= 0;
|
|
// Skip the mask pass if the rect is zero
|
|
if (!bIsWaterDepthValid)
|
|
{
|
|
// uint2 WaterTileIndex = DispatchThreadId.xy * PixelStride / SLW_TILE_SIZE_XY; <=>
|
|
// uint2 WaterTileIndex = (groupId*SLW_TILE_SIZE_XY + threadgroupIndex) * PixelStride / SLW_TILE_SIZE_XY; <=>
|
|
// uint2 WaterTileIndex = groupId * PixelStride + threadgroupIndex * PixelStride / SLW_TILE_SIZE_XY; <=>
|
|
// uint2 WaterTileMin = groupId * PixelStride + {0,0} * PixelStride / SLW_TILE_SIZE_XY; <=> groupId * PixelStride
|
|
// uint2 WaterTileMax = groupId * PixelStride + (SLW_TILE_SIZE_XY-1) * PixelStride / SLW_TILE_SIZE_XY; <=>
|
|
// uint2 WaterTileMax = groupId * PixelStride + (SLW_TILE_SIZE_XY * PixelStride - PixelStride) / SLW_TILE_SIZE_XY; <=>
|
|
// uint2 WaterTileMax = groupId * PixelStride + PixelStride - PixelStride / SLW_TILE_SIZE_XY; <~>
|
|
// uint2 WaterTileMax = groupId * PixelStride + PixelStride; <~>
|
|
|
|
// Bit-scalarized version, must test rect
|
|
uint2 WaterTileMin = GroupId * PixelStride;
|
|
uint2 WaterTileMax = WaterTileMin + PixelStride;
|
|
|
|
for (uint WaterTileY = WaterTileMin.y; WaterTileY < WaterTileMax.y && !bIsWaterDepthValid; ++WaterTileY)
|
|
{
|
|
for (uint WaterTileX = WaterTileMin.x; WaterTileX < WaterTileMax.x; ++WaterTileX)
|
|
{
|
|
uint MaskLinearIndex = uint(SingleLayerWaterTileViewRes.x) * WaterTileY + WaterTileX;
|
|
if ((SingleLayerWaterTileMask[MaskLinearIndex / 32U] & (1U << (MaskLinearIndex % 32U))) != 0U)
|
|
{
|
|
bIsWaterDepthValid = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if (bIsWaterDepthValid)
|
|
{
|
|
DeviceZWater = SingleLayerWaterDepthTexture.Load(uint3(PixelPos, 0)).x;
|
|
bIsWaterDepthValid = DeviceZWater != DeviceZ;
|
|
}
|
|
#endif // PERMUTATION_WATER_DEPTH
|
|
|
|
#if PERMUTATION_TRANSLUCENCY_DEPTH
|
|
// Same frame front layer depth
|
|
if (FrontLayerMode == 0)
|
|
{
|
|
DeviceZTranslucency = FrontLayerTranslucencyDepthTexture.Load(uint3(PixelPos, 0)).x;
|
|
const float4 EncodedData = FrontLayerTranslucencyNormalTexture.Load(uint3(PixelPos, 0));
|
|
bIsTranslucencyDepthValid = EncodedData.w > 0 && DeviceZTranslucency > 0;
|
|
}
|
|
// Previous frame Front layer depth reprojection
|
|
else
|
|
{
|
|
const float2 ScreenPosition = SvPositionToScreenPosition(float4(PixelPos, 0, 1)).xy;
|
|
|
|
float3 HistoryScreenPosition = float3(ScreenPosition, DeviceZ);
|
|
const float4 ThisClip = float4(HistoryScreenPosition, 1);
|
|
//float4 PrevClip = mul(ThisClip, View.ClipToPrevClip); //<=== doesn't contain AA offsets
|
|
const float4 PrevClip = mul(ThisClip, View.ClipToPrevClipWithAA);
|
|
const float3 PrevScreen = PrevClip.xyz / PrevClip.w;
|
|
const float3 Velocity = HistoryScreenPosition - PrevScreen;
|
|
// TODO handle dynamic object by using velocity buffer?
|
|
HistoryScreenPosition -= Velocity;
|
|
|
|
const float2 HistoryUV = HistoryScreenPosition.xy * FrontLayerHistoryScreenPositionScaleBias.xy + FrontLayerHistoryScreenPositionScaleBias.wz;
|
|
bool bHistoryValid = true;
|
|
FLATTEN
|
|
if (any(HistoryUV > FrontLayerHistoryUVMinMax.zw) || any(HistoryUV < FrontLayerHistoryUVMinMax.xy))
|
|
{
|
|
bHistoryValid = false;
|
|
}
|
|
|
|
if (bHistoryValid)
|
|
{
|
|
const float2 HistoryPixelPos = HistoryUV * FrontLayerHistoryBufferSizeAndInvSize.xy;
|
|
DeviceZTranslucency = FrontLayerTranslucencyDepthTexture.Load(uint3(HistoryPixelPos, 0)).x;
|
|
const float4 EncodedData = FrontLayerTranslucencyNormalTexture.Load(uint3(PixelPos, 0));
|
|
bIsTranslucencyDepthValid = EncodedData.w > 0 && DeviceZTranslucency > 0;
|
|
}
|
|
}
|
|
#endif
|
|
}
|
|
#endif // PERMUTATION_INPUT_TYPE == INPUT_TYPE_GBUFFER
|
|
|
|
if (!bIsValid && !bIsWaterDepthValid && !bIsTranslucencyDepthValid)
|
|
{
|
|
return;
|
|
}
|
|
|
|
const FPositionData Primary = InitPositionData(PixelPos, DeviceZ, true);
|
|
|
|
#if PERMUTATION_WATER_DEPTH
|
|
const FPositionData Water = InitPositionData(PixelPos, DeviceZWater, bIsWaterDepthValid);
|
|
#endif
|
|
|
|
#if PERMUTATION_TRANSLUCENCY_DEPTH
|
|
const FPositionData Translucency = InitPositionData(PixelPos, DeviceZTranslucency, bIsTranslucencyDepthValid);
|
|
#endif
|
|
|
|
// Dither pattern for page dilation
|
|
// We don't need to to check all 8 adjacent pages; as long as there's at least a single pixel near the edge
|
|
// the adjacent one will get mapped. In practice only checking one diagonal seems to work fine and have minimal
|
|
// overhead.
|
|
const float2 PageDilationDither = float2(
|
|
(GroupIndex & 1) ? 1.0f : -1.0f,
|
|
(GroupIndex & 2) ? 1.0f : -1.0f);
|
|
|
|
// Directional lights
|
|
{
|
|
const bool bUsePageDilation = PageDilationBorderSizeDirectional > 0.0f;
|
|
const float2 PageDilationOffset = PageDilationBorderSizeDirectional * PageDilationDither;
|
|
|
|
for (uint Index = 0; Index < NumDirectionalLightSmInds; ++Index)
|
|
{
|
|
int VirtualShadowMapId = DirectionalLightIds[Index];
|
|
FVirtualShadowMapProjectionShaderData ProjectionData = GetVirtualShadowMapProjectionData(VirtualShadowMapId);
|
|
|
|
bool bBackfaceCulledGBuffer = false;
|
|
#if PERMUTATION_INPUT_TYPE != INPUT_TYPE_HAIRSTRANDS
|
|
// Backface test if requested
|
|
if (bBackfaceCull && IsBackfaceToDirectionalLight(WorldNormal, ProjectionData.LightDirection, ProjectionData.LightSourceRadius))
|
|
{
|
|
bBackfaceCulledGBuffer = true;
|
|
}
|
|
#endif
|
|
|
|
if (!bBackfaceCulledGBuffer)
|
|
{
|
|
MarkPageClipmap(ProjectionData, bUsePageDilation, PageDilationOffset, Primary.TranslatedWorldPosition);
|
|
}
|
|
|
|
#if PERMUTATION_WATER_DEPTH
|
|
if (bIsWaterDepthValid)
|
|
{
|
|
MarkPageClipmap(ProjectionData, bUsePageDilation, PageDilationOffset, Water.TranslatedWorldPosition);
|
|
}
|
|
#endif // PERMUTATION_INPUT_TYPE == INPUT_TYPE_GBUFFER_AND_WATER_DEPTH
|
|
|
|
|
|
#if PERMUTATION_TRANSLUCENCY_DEPTH
|
|
if (bIsTranslucencyDepthValid)
|
|
{
|
|
MarkPageClipmap(ProjectionData, bUsePageDilation, PageDilationOffset, Translucency.TranslatedWorldPosition);
|
|
}
|
|
#endif
|
|
}
|
|
}
|
|
|
|
// Local lights
|
|
{
|
|
const bool bUsePageDilation = PageDilationBorderSizeLocal > 0.0f;
|
|
const float2 PageDilationOffset = PageDilationBorderSizeLocal * PageDilationDither;
|
|
|
|
uint2 LocalPosition = PixelPos - uint2(View.ViewRectMin.xy);
|
|
const FCulledLightsGridData LightGridData = VirtualShadowMapGetLightsGrid(LocalPosition, Primary.SceneDepth);
|
|
|
|
LOOP
|
|
for (uint Index = 0; Index < LightGridData.NumLights; ++Index)
|
|
{
|
|
const FLocalLightData LightData = VirtualShadowMapGetLocalLightData(LightGridData, Index);
|
|
|
|
// If we hit a distant light, we're done as we sorted those to the end. See PruneLightGridCS.
|
|
if (IsSinglePageVirtualShadowMap(LightData.VirtualShadowMapId))
|
|
{
|
|
break;
|
|
}
|
|
|
|
// Relative to PrimaryView
|
|
const float3 LightTranslatedWorldPosition = LightData.LightPositionAndInvRadius.xyz;
|
|
float LengthSquared = length2(LightTranslatedWorldPosition - Primary.TranslatedWorldPosition);
|
|
|
|
if (LengthSquared > Square(1.0f / LightData.LightPositionAndInvRadius.w))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
float3 ToLight = normalize(LightTranslatedWorldPosition - Primary.TranslatedWorldPosition);
|
|
|
|
// TODO: Can optimize these tests by fusing them together
|
|
// Also do precise cone test, since froxels are pretty coarse at times.
|
|
if (dot(ToLight, LightData.LightDirectionAndShadowMask.xyz) < GetLightSpotAngles(LightData).x)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
const uint LightTypeAndPackedShadowMapChannelMask = asuint(LightData.LightDirectionAndShadowMask.w);
|
|
const uint LightType = UnpackLightType(LightTypeAndPackedShadowMapChannelMask);
|
|
|
|
const bool bIsRectLight = LightType == LIGHT_TYPE_RECT;
|
|
if (bIsRectLight)
|
|
{
|
|
const bool bIsBehindRectLight = dot(ToLight, LightData.LightDirectionAndShadowMask.xyz) < 0;
|
|
if (bIsBehindRectLight)
|
|
{
|
|
continue;
|
|
}
|
|
}
|
|
|
|
// TODO: Precise radius test necessary?
|
|
|
|
#if PERMUTATION_INPUT_TYPE != INPUT_TYPE_HAIRSTRANDS
|
|
// Backface test if requested
|
|
if (bBackfaceCull && IsBackfaceToLocalLight(ToLight, WorldNormal, GetLightSourceRadius(LightData)))
|
|
{
|
|
continue;
|
|
}
|
|
#endif
|
|
int VirtualShadowMapId = LightData.VirtualShadowMapId;
|
|
bool bSpotLight = GetLightSpotAngles(LightData).x > -2.0f;
|
|
if( !bSpotLight )
|
|
{
|
|
VirtualShadowMapId += VirtualShadowMapGetCubeFace(-ToLight);
|
|
}
|
|
|
|
uint MipLevel = GetMipLevelLocal(VirtualShadowMapId, Primary.TranslatedWorldPosition, Primary.SceneDepth);
|
|
MarkPage(VirtualShadowMapId, MipLevel, Primary.TranslatedWorldPosition, bUsePageDilation, PageDilationOffset);
|
|
}
|
|
}
|
|
}
|
|
|
|
uint bMarkCoarsePagesLocal;
|
|
int ClipmapFirstLevel;
|
|
uint ClipmapIndexMask;
|
|
uint bIncludeNonNaniteGeometry;
|
|
|
|
[numthreads(VSM_DEFAULT_CS_GROUP_X, 1, 1)]
|
|
void MarkCoarsePages(uint DispatchThreadId : SV_DispatchThreadID)
|
|
{
|
|
// Remap thread ID [0..NumShadowMaps) to full / single page ranges.
|
|
uint VirtualShadowMapId = DispatchThreadId;
|
|
if (VirtualShadowMapId < VirtualShadowMap.NumFullShadowMaps)
|
|
{
|
|
VirtualShadowMapId += VSM_MAX_SINGLE_PAGE_SHADOW_MAPS;
|
|
}
|
|
else
|
|
{
|
|
VirtualShadowMapId -= VirtualShadowMap.NumFullShadowMaps;
|
|
if (VirtualShadowMapId >= VirtualShadowMap.NumSinglePageShadowMaps)
|
|
{
|
|
return;
|
|
}
|
|
}
|
|
|
|
FVirtualShadowMapProjectionShaderData ProjectionData = GetVirtualShadowMapProjectionData(VirtualShadowMapId);
|
|
|
|
// NOTE: Coarse pages are very large and tend to get invalidated a lot due to anything in the scene moving
|
|
// Rendering non-nanite geometry into these pages can be very expensive and thus isn't always desirable.
|
|
uint Flags = VSM_ALLOCATED_FLAG;
|
|
|
|
if (ProjectionData.bUnreferenced)
|
|
{
|
|
// Don't mark any pages for lights not referenced this render
|
|
}
|
|
else if (ProjectionData.LightType == LIGHT_TYPE_DIRECTIONAL)
|
|
{
|
|
// Idea here is the clipmaps already cover supersets of lower levels
|
|
// Thus to get coarser pages we can just mark the center page(s) offset by a level/LOD bias
|
|
// The limit on how far dense data goes out from the camera then becomes the world space size of the marked page(s) on the coarses clipmap
|
|
// We could of course mark a broader set of pages in the coarses clipmap level, but the effective radius
|
|
// even from just marking a single one is usually already large enough for the systems that need this
|
|
// data (volumetric fog, translucent light volume).
|
|
uint ClipmapIndex = uint(ProjectionData.ClipmapLevel - ClipmapFirstLevel);
|
|
if (((ClipmapIndexMask >> ClipmapIndex) & 1) != 0)
|
|
{
|
|
// TODO: Optimize this... can be boiled down to be just in terms of the snap offsets
|
|
float3 OriginTranslatedWorld = ProjectionData.ClipmapWorldOriginOffset;
|
|
float4 ShadowUVz = mul(float4(OriginTranslatedWorld, 1.0f), ProjectionData.TranslatedWorldToShadowUVMatrix);
|
|
float2 VirtualTexelAddressFloat = ShadowUVz.xy * float(CalcLevelDimsTexels(0));
|
|
float2 PageAddressFloat = VirtualTexelAddressFloat * float(1.0f / VSM_PAGE_SIZE);
|
|
// NOTE: Page addresses round down/truncate normally, so grab the surrounding 4
|
|
int4 PageAddressLowHigh = int4(floor(PageAddressFloat - 0.5f), ceil(PageAddressFloat - 0.5f));
|
|
|
|
MarkPageAddress(CalcPageOffset(VirtualShadowMapId, 0, PageAddressLowHigh.xy), Flags);
|
|
MarkPageAddress(CalcPageOffset(VirtualShadowMapId, 0, PageAddressLowHigh.xw), Flags);
|
|
MarkPageAddress(CalcPageOffset(VirtualShadowMapId, 0, PageAddressLowHigh.zy), Flags);
|
|
MarkPageAddress(CalcPageOffset(VirtualShadowMapId, 0, PageAddressLowHigh.zw), Flags);
|
|
}
|
|
}
|
|
// Note: always mark last mip for "distant" light
|
|
else if (bMarkCoarsePagesLocal != 0U || IsSinglePageVirtualShadowMap(VirtualShadowMapId))
|
|
{
|
|
// Mark last mip
|
|
uint PageOffset = CalcPageOffset(VirtualShadowMapId, VSM_MAX_MIP_LEVELS - 1, uint2(0, 0));
|
|
MarkPageAddress(PageOffset, Flags);
|
|
}
|
|
}
|