TraceVoxels:
[numthreads( 64 , 1, 1)]
void ScreenProbeTraceVoxelsCS(
uint3 GroupId : SV_GroupID,
uint3 DispatchThreadId : SV_DispatchThreadID,
uint3 GroupThreadId : SV_GroupThreadID)
{
uint AllocatorIndex = 0 ? 1 : 0;
if (DispatchThreadId.x < CompactedTraceTexelAllocator[AllocatorIndex])
{
uint ScreenProbeIndex;
uint2 TraceTexelCoord;
float TraceHitDistance;
ReadTraceTexel(DispatchThreadId.x, ScreenProbeIndex, TraceTexelCoord, TraceHitDistance);
uint2 ScreenProbeAtlasCoord = uint2(ScreenProbeIndex % ScreenProbeAtlasViewSize.x, ScreenProbeIndex / ScreenProbeAtlasViewSize.x);
TraceVoxels(ScreenProbeAtlasCoord, TraceTexelCoord, ScreenProbeIndex, TraceHitDistance);
}
}
void ReadTraceTexel(uint DispatchThreadId, out uint ScreenProbeIndex, out uint2 TraceTexelCoord, out float TraceHitDistance)
{
DecodeTraceTexel(CompactedTraceTexelData[DispatchThreadId], ScreenProbeIndex, TraceTexelCoord, TraceHitDistance);
}
void DecodeTraceTexel(uint2 TraceTexelData, inout uint ScreenProbeIndex, inout uint2 TraceTexelCoord, inout float TraceHitDistance)
{
ScreenProbeIndex = TraceTexelData.x & 0xFFFFF;
TraceTexelCoord.x = (TraceTexelData.x >> 20) & 0x1F;
TraceTexelCoord.y = (TraceTexelData.x >> 25) & 0x1F;
TraceHitDistance = asfloat(TraceTexelData.y);
}
void TraceVoxels(
uint2 ScreenProbeAtlasCoord,
uint2 TraceTexelCoord,
uint ScreenProbeIndex,
float TraceHitDistance)
{
uint2 ScreenProbeScreenPosition = GetScreenProbeScreenPosition(ScreenProbeIndex);
uint2 ScreenTileCoord = GetScreenTileCoord(ScreenProbeScreenPosition);
uint ProbeTracingResolution = 0 ? ScreenProbeLightSampleResolutionXY : ScreenProbeTracingOctahedronResolution;
uint2 TraceBufferCoord = GetTraceBufferCoord(ScreenProbeAtlasCoord, TraceTexelCoord);
{
float2 ScreenUV = GetScreenUVFromScreenProbePosition(ScreenProbeScreenPosition);
float SceneDepth = GetScreenProbeDepth(ScreenProbeAtlasCoord);
bool bHit = false;
{
float3 TranslatedWorldPosition = GetTranslatedWorldPositionFromScreenUV(ScreenUV, SceneDepth);
float3 WorldPosition = TranslatedWorldPosition - LWCToFloat( GetPrimaryView() .PreViewTranslation) ;
float3 RayWorldDirection = 0;
float TraceDistance = MaxTraceDistance;
float ConeHalfAngle = 0;
GetScreenProbeTexelRay(
TraceBufferCoord,
TraceTexelCoord,
ScreenTileCoord,
TranslatedWorldPosition,
RayWorldDirection,
TraceDistance,
ConeHalfAngle);
float3 TranslatedSamplePosition = TranslatedWorldPosition + SurfaceBias * RayWorldDirection;
TranslatedSamplePosition += SurfaceBias * GetScreenProbeNormalForBiasing(ScreenProbeAtlasCoord, RayWorldDirection);
float3 SamplePosition = TranslatedSamplePosition - LWCToFloat( GetPrimaryView() .PreViewTranslation) ;
FRadianceCacheCoverage Coverage;
Coverage.bValid = false;
Coverage = GetRadianceCacheCoverage(WorldPosition, RayWorldDirection, InterleavedGradientNoise(ScreenTileCoord, (FixedJitterIndex < 0 ? View_StateFrameIndexMod8 : FixedJitterIndex) ));
if (Coverage.bValid)
{
TraceDistance = min(TraceDistance, Coverage.MinTraceDistanceBeforeInterpolation);
}
FConeTraceInput TraceInput;
TraceInput.Setup(SamplePosition, TranslatedSamplePosition, RayWorldDirection, ConeHalfAngle, MinSampleRadius, MinTraceDistance, TraceDistance, StepFactor);
TraceInput.VoxelTraceStartDistance = max(MinTraceDistance, TraceHitDistance - SurfaceBias * 2);
TraceInput.bDitheredTransparency = true;
TraceInput.DitherScreenCoord = ScreenTileCoord * ProbeTracingResolution + TraceTexelCoord;
FConeTraceResult TraceResult = (FConeTraceResult)0;
TraceResult.Lighting = 0;
TraceResult.Transparency = 1;
TraceResult.OpaqueHitDistance = TraceInput.MaxTraceDistance;
ConeTraceLumenSceneVoxels(TraceInput, TraceResult);
float TraceHitDistanceForSkyLeaking = TraceHitDistance;
if (TraceResult.Transparency <= .5f)
{
TraceHitDistance = TraceDistance;
TraceHitDistanceForSkyLeaking = TraceResult.OpaqueHitDistance;
bHit = true;
}
if (Coverage.bValid)
{
if (TraceResult.Transparency > .5f)
{
TraceHitDistance = MaxTraceDistance;
TraceHitDistanceForSkyLeaking = MaxTraceDistance;
}
SampleRadianceCacheAndApply(Coverage, WorldPosition, RayWorldDirection, ConeHalfAngle, TraceResult.Lighting, TraceResult.Transparency);
}
else
{
if (TraceResult.Transparency > .5f)
{
TraceHitDistance = MaxTraceDistance;
TraceHitDistanceForSkyLeaking = MaxTraceDistance;
}
ApplySkylightToTraceResult(RayWorldDirection, TraceResult);
if (TraceHitDistance >= GetProbeMaxHitDistance())
{
TraceHitDistance = MaxTraceDistance;
}
}
TraceResult.Lighting += GetSkylightLeaking(RayWorldDirection, TraceHitDistanceForSkyLeaking);
TraceResult.Lighting *= View_PreExposure;
RWTraceRadiance[TraceBufferCoord] = TraceResult.Lighting;
}
WriteTraceHit(TraceBufferCoord, TraceHitDistance, bHit, false);
}
}
void ConeTraceLumenSceneVoxels(
FConeTraceInput TraceInput,
inout FConeTraceResult OutResult)
{
#if SCENE_TRACE_VOXELS
if (TraceInput.VoxelTraceStartDistance < TraceInput.MaxTraceDistance)
{
FConeTraceInput VoxelTraceInput = TraceInput;
VoxelTraceInput.MinTraceDistance = TraceInput.VoxelTraceStartDistance;
FConeTraceResult VoxelTraceResult;
RayTraceGlobalDistanceField(VoxelTraceInput, VoxelTraceResult);
#if !VISIBILITY_ONLY_TRACE
OutResult.Lighting += VoxelTraceResult.Lighting * OutResult.Transparency;
#endif
OutResult.Transparency *= VoxelTraceResult.Transparency;
OutResult.NumSteps += VoxelTraceResult.NumSteps;
OutResult.OpaqueHitDistance = min(OutResult.OpaqueHitDistance, VoxelTraceResult.OpaqueHitDistance);
}
#endif
}
void RayTraceGlobalDistanceField(
FConeTraceInput TraceInput,
inout FConeTraceResult OutResult)
{
FGlobalSDFTraceResult SDFTraceResult;
{
FGlobalSDFTraceInput SDFTraceInput = SetupGlobalSDFTraceInput(TraceInput.ConeOrigin, TraceInput.ConeDirection, TraceInput.MinTraceDistance, TraceInput.MaxTraceDistance, TraceInput.SDFStepFactor, TraceInput.MinSDFStepFactor);
SDFTraceInput.bDitheredTransparency = TraceInput.bDitheredTransparency;
SDFTraceInput.DitherScreenCoord = TraceInput.DitherScreenCoord;
SDFTraceInput.bExpandSurfaceUsingRayTimeInsteadOfMaxDistance = TraceInput.bExpandSurfaceUsingRayTimeInsteadOfMaxDistance;
SDFTraceInput.InitialMaxDistance = TraceInput.InitialMaxDistance;
SDFTraceResult = RayTraceGlobalDistanceField(SDFTraceInput);
}
float4 LightingAndAlpha = float4(0, 0, 0, 1);
if (GlobalSDFTraceResultIsHit(SDFTraceResult))
{
LightingAndAlpha = EvaluateGlobalDistanceFieldHit(TraceInput, SDFTraceResult);
}
OutResult = (FConeTraceResult)0;
OutResult.Lighting = LightingAndAlpha.rgb;
OutResult.Transparency = LightingAndAlpha.a;
OutResult.NumSteps = SDFTraceResult.TotalStepsTaken;
OutResult.OpaqueHitDistance = GlobalSDFTraceResultIsHit(SDFTraceResult) ? SDFTraceResult.HitTime : TraceInput.MaxTraceDistance;
OutResult.ExpandSurfaceAmount = SDFTraceResult.ExpandSurfaceAmount;
}
FGlobalSDFTraceResult RayTraceGlobalDistanceField(FGlobalSDFTraceInput TraceInput)
{
FGlobalSDFTraceResult TraceResult;
TraceResult.HitTime = -1.0f;
TraceResult.HitClipmapIndex = 0;
TraceResult.TotalStepsTaken = 0;
TraceResult.ExpandSurfaceAmount = 0;
float TraceNoise = InterleavedGradientNoise(TraceInput.DitherScreenCoord.xy, View_StateFrameIndexMod8);
uint MinClipmapIndex = ComputeGlobalDistanceFieldClipmapIndex(TraceInput.WorldRayStart + TraceInput.MinTraceDistance * TraceInput.WorldRayDirection);
float MaxDistance = TraceInput.InitialMaxDistance;
float MinRayTime = TraceInput.MinTraceDistance;
[loop]
for (uint ClipmapIndex = MinClipmapIndex; ClipmapIndex < View_NumGlobalSDFClipmaps && TraceResult.HitTime < 0.0f; ++ClipmapIndex)
{
float ClipmapVoxelExtent = View_GlobalVolumeCenterAndExtent [ClipmapIndex].w * View_GlobalVolumeTexelSize ;
float MinStepSize = TraceInput.MinStepFactor * ClipmapVoxelExtent;
float ExpandSurfaceDistance = ClipmapVoxelExtent;
float ClipmapRayBias = ClipmapVoxelExtent * TraceInput.VoxelSizeRelativeBias;
float ClipmapRayLength = TraceInput.MaxTraceDistance - ClipmapVoxelExtent * TraceInput.VoxelSizeRelativeRayEndBias;
float3 GlobalVolumeCenter = View_GlobalVolumeCenterAndExtent [ClipmapIndex].xyz;
float GlobalVolumeExtent = View_GlobalVolumeCenterAndExtent [ClipmapIndex].w - ClipmapVoxelExtent;
float3 WorldRayEnd = TraceInput.WorldRayStart + TraceInput.WorldRayDirection * ClipmapRayLength;
float2 IntersectionTimes = LineBoxIntersect(TraceInput.WorldRayStart, WorldRayEnd, GlobalVolumeCenter - GlobalVolumeExtent.xxx, GlobalVolumeCenter + GlobalVolumeExtent.xxx);
IntersectionTimes.xy *= ClipmapRayLength;
IntersectionTimes.x = max(IntersectionTimes.x, MinRayTime);
IntersectionTimes.x = max(IntersectionTimes.x, ClipmapRayBias);
if (IntersectionTimes.x < IntersectionTimes.y)
{
MinRayTime = IntersectionTimes.y;
float SampleRayTime = IntersectionTimes.x;
const float ClipmapInfluenceRange = 4 * 2.0f * View_GlobalVolumeCenterAndExtent [ClipmapIndex].w * View_GlobalVolumeTexelSize ;
uint StepIndex = 0;
const uint MaxSteps = 256;
[loop]
for (; StepIndex < MaxSteps; ++StepIndex)
{
float3 SampleWorldPosition = TraceInput.WorldRayStart + TraceInput.WorldRayDirection * SampleRayTime;
float3 ClipmapVolumeUV = ComputeGlobalUV(SampleWorldPosition, ClipmapIndex);
float3 MipUV = ComputeGlobalMipUV(SampleWorldPosition, ClipmapIndex);
float DistanceFieldMipValue = Texture3DSampleLevel( View_GlobalDistanceFieldMipTexture , View_SharedTrilinearClampedSampler , MipUV, 0).x;
float DistanceField = DecodeGlobalDistanceFieldPageDistance(DistanceFieldMipValue, View_GlobalDistanceFieldMipFactor * ClipmapInfluenceRange);
float Coverage = 1;
FGlobalDistanceFieldPage Page = GetGlobalDistanceFieldPage(ClipmapVolumeUV, ClipmapIndex);
if (Page.bValid && DistanceFieldMipValue < View_GlobalDistanceFieldMipTransition )
{
float3 PageUV = ComputeGlobalDistanceFieldPageUV(ClipmapVolumeUV, Page);
if (Page.bCoverage)
{
float3 CoveragePageUV;
ComputeGlobalDistanceFieldPageUV(ClipmapVolumeUV, Page, PageUV, CoveragePageUV);
Coverage = Texture3DSampleLevel( View_GlobalDistanceFieldCoverageAtlasTexture , View_SharedTrilinearWrappedSampler , CoveragePageUV, 0).x;
}
float DistanceFieldValue = Texture3DSampleLevel( View_GlobalDistanceFieldPageAtlasTexture , View_SharedTrilinearWrappedSampler , PageUV, 0).x;
DistanceField = DecodeGlobalDistanceFieldPageDistance(DistanceFieldValue, ClipmapInfluenceRange);
}
MaxDistance = max(DistanceField, MaxDistance);
float ExpandSurfaceTime = TraceInput.bExpandSurfaceUsingRayTimeInsteadOfMaxDistance ? SampleRayTime - ClipmapRayBias : MaxDistance;
float ExpandSurfaceScale = lerp( View_NotCoveredExpandSurfaceScale , View_CoveredExpandSurfaceScale , Coverage);
const float ExpandSurfaceFalloff = 2.0f * ExpandSurfaceDistance;
const float ExpandSurfaceAmount = ExpandSurfaceDistance * saturate(ExpandSurfaceTime / ExpandSurfaceFalloff) * ExpandSurfaceScale;
float StepNoise = InterleavedGradientNoise(TraceInput.DitherScreenCoord.xy, View_StateFrameIndexMod8 * MaxSteps + StepIndex);
if (DistanceField < ExpandSurfaceAmount
&& (!TraceInput.bDitheredTransparency || (StepNoise * (1 - Coverage) <= View_DitheredTransparencyStepThreshold && TraceNoise * (1 - Coverage) <= View_DitheredTransparencyTraceThreshold )))
{
TraceResult.HitTime = max(SampleRayTime + DistanceField - ExpandSurfaceAmount, 0.0f);
TraceResult.HitClipmapIndex = ClipmapIndex;
TraceResult.ExpandSurfaceAmount = ExpandSurfaceAmount;
break;
}
float LocalMinStepSize = MinStepSize * lerp( View_NotCoveredMinStepScale , 1.0f, Coverage);
float StepDistance = max(DistanceField * TraceInput.StepFactor, LocalMinStepSize);
SampleRayTime += StepDistance;
if (SampleRayTime > IntersectionTimes.y || TraceResult.HitTime >= 0.0f)
{
break;
}
}
TraceResult.TotalStepsTaken += StepIndex;
}
}
return TraceResult;
}
float4 EvaluateGlobalDistanceFieldHit(FConeTraceInput TraceInput, FGlobalSDFTraceResult SDFTraceResult)
{
float3 SampleWorldPosition = TraceInput.ConeOrigin + TraceInput.ConeDirection * SDFTraceResult.HitTime;
float3 SampleWorldNormal = ComputeGlobalDistanceFieldNormal(SampleWorldPosition, SDFTraceResult.HitClipmapIndex, -TraceInput.ConeDirection);
float ClipmapVoxelExtent = GlobalVolumeCenterAndExtent[SDFTraceResult.HitClipmapIndex].w * GlobalVolumeTexelSize;
float RadianceFactor = 1.0f;
if (TraceInput.bExpandSurfaceUsingRayTimeInsteadOfMaxDistance)
{
// Make sure we don't self-intersect and cause leaking
RadianceFactor = smoothstep(1.5f * ClipmapVoxelExtent, 2.0f * ClipmapVoxelExtent, SDFTraceResult.HitTime);
}
if (TraceInput.bZeroRadianceIfRayStartsInsideGeometry && SDFTraceResult.HitTime <= TraceInput.MinTraceDistance)
{
RadianceFactor = 0.0f;
}
float3 LightingSum = 0.0f;
float SampleWeightSum = 0.0f;
float3 ClipmapVolumeUV = ComputeGlobalUV(SampleWorldPosition, SDFTraceResult.HitClipmapIndex);
FGlobalDistanceFieldPage Page = GetGlobalDistanceFieldPage(ClipmapVolumeUV, SDFTraceResult.HitClipmapIndex);
if (RadianceFactor > 0.0f && Page.bValid)
{
float3 PageTableCoord = saturate(ClipmapVolumeUV) * GlobalDistanceFieldClipmapSizeInPages;
uint3 CellCoordInPage = frac(frac(PageTableCoord)) * DISTANCE_FIELD_OBJECT_GRID_PAGE_RESOLUTION;
uint CellOffsetInPage = ZOrder3DEncode(CellCoordInPage, log2(DISTANCE_FIELD_OBJECT_GRID_PAGE_RESOLUTION));
uint4 DistanceFieldObjectGridCell = GlobalDistanceFieldPageObjectGridBuffer[DISTANCE_FIELD_OBJECT_GRID_PAGE_STRIDE * Page.PageIndex + CellOffsetInPage];
for (uint ObjectIndexInList = 0; ObjectIndexInList < DISTANCE_FIELD_OBJECT_GRID_CELL_SIZE; ++ObjectIndexInList)
{
FObjectGridCellIndex GridCellIndex = UnpackObjectGridCellIndex(DistanceFieldObjectGridCell[ObjectIndexInList]);
if (GridCellIndex.bValid)
{
uint MeshCardsIndex = GetMeshCardsIndexFromSceneInstanceIndex(GridCellIndex.GPUSceneInstanceIndex);
if (MeshCardsIndex < LumenCardScene.NumMeshCards)
{
uint2 ScreenCoord = TraceInput.DitherScreenCoord;
float SurfaceCacheBias = DISTANCE_FIELD_OBJECT_GRID_CARD_INTERPOLATION_RANGE_IN_VOXELS * ClipmapVoxelExtent;
float SampleRadius = 100.0f;
FSurfaceCacheSample SurfaceCacheSample = SampleLumenMeshCards(
ScreenCoord,
MeshCardsIndex,
SampleWorldPosition,
SampleWorldNormal,
SampleRadius,
SurfaceCacheBias,
/*bHiResSurface*/ false,
FINAL_LIGHTING_ATLAS_ID
);
LightingSum += SurfaceCacheSample.LightingSum;
SampleWeightSum += SurfaceCacheSample.SampleWeightSum;
if (SampleWeightSum >= 0.9f)
{
break;
}
}
}
else
{
break;
}
}
#define DEBUG_CELL_COUNTER 0
#if DEBUG_CELL_COUNTER
{
uint NumCells = 0;
for (uint ObjectIndexInList = 0; ObjectIndexInList < DISTANCE_FIELD_OBJECT_GRID_CELL_SIZE; ++ObjectIndexInList)
{
FObjectGridCellIndex GridCellIndex = UnpackObjectGridCellIndex(DistanceFieldObjectGridCell[ObjectIndexInList]);
if (GridCellIndex.bValid)
{
uint MeshCardsIndex = GetMeshCardsIndexFromSceneInstanceIndex(GridCellIndex.GPUSceneInstanceIndex);
if (MeshCardsIndex < LUMEN_INVALID_MESH_CARDS_INDEX)
{
++NumCells;
}
}
}
LightingSum = 0.0f;
if (NumCells == 1)
{
LightingSum = float3(1, 0, 0);
}
else if (NumCells == 2)
{
LightingSum = float3(0, 1, 0);
}
else if (NumCells == 3)
{
LightingSum = float3(0, 0, 1);
}
else
{
LightingSum = float3(1, 1, 1);
}
SampleWeightSum = 1.0f;
}
#endif
}
float4 LightingAndAlpha = 0.0f;
if (SampleWeightSum > 0.0f)
{
LightingAndAlpha.xyz = RadianceFactor * (LightingSum / SampleWeightSum);
}
// Debug visualization
#if ENABLE_VISUALIZE_MODE == 1
{
if (VisualizeMode == VISUALIZE_MODE_GEOMETRY_NORMALS)
{
LightingAndAlpha.xyz = (SampleWorldNormal * 0.5f + 0.5f) * View.OneOverPreExposure;
}
}
#endif
return LightingAndAlpha;
}
uint GetMeshCardsIndexFromSceneInstanceIndex(uint SceneInstanceIndex)
{
const uint MeshCardsIndex = LumenCardScene.SceneInstanceIndexToMeshCardsIndexBuffer.Load(4 * SceneInstanceIndex);
return MeshCardsIndex;
}
FSurfaceCacheSample SampleLumenMeshCards(
uint2 ScreenCoord,
uint MeshCardsIndex,
float3 WorldSpacePosition,
float3 WorldSpaceNormal,
float SampleRadius, // Cone footprint for mip map selection and feedback
float SurfaceCacheBias, // Card bounds and depth test bias in mesh space. Useful when traced geometry doesn't match the original mesh due to SDFs or geometry LOD
bool bHiResSurface, // Whether to allow to sample high res surface pages or use only the lowest quality ones (always resident)
uint AtlasId)
{
FSurfaceCacheSample SurfaceCacheSample = InitSurfaceCacheSample();
#if ENABLE_VISUALIZE_MODE == 1
if (VisualizeMode == VISUALIZE_MODE_SURFACE_CACHE || VisualizeMode == VISUALIZE_MODE_OPACITY)
{
SurfaceCacheSample.Lighting = (MeshCardsIndex < LumenCardScene.NumMeshCards ? float3(1, 0, 1) : float3(1, 1, 0)) * View.OneOverPreExposure;
}
#endif
if (MeshCardsIndex < LumenCardScene.NumMeshCards)
{
FLumenMeshCardsData MeshCardsData = GetLumenMeshCardsData(MeshCardsIndex);
float3 MeshCardsSpacePosition = mul(WorldSpacePosition - MeshCardsData.WorldOrigin, MeshCardsData.WorldToLocalRotation);
float3 MeshCardsSpaceNormal = mul(WorldSpaceNormal, MeshCardsData.WorldToLocalRotation);
uint CardMask = 0;
float3 AxisWeights = MeshCardsSpaceNormal * MeshCardsSpaceNormal;
// Pick cards by angle
if (AxisWeights.x > 0.0f)
{
CardMask |= MeshCardsData.CardLookup[MeshCardsSpaceNormal.x < 0.0f ? 0 : 1];
}
if (AxisWeights.y > 0.0f)
{
CardMask |= MeshCardsData.CardLookup[MeshCardsSpaceNormal.y < 0.0f ? 2 : 3];
}
if (AxisWeights.z > 0.0f)
{
CardMask |= MeshCardsData.CardLookup[MeshCardsSpaceNormal.z < 0.0f ? 4 : 5];
}
// Cull cards by AABB
{
uint CulledCardMask = 0;
while (CardMask != 0)
{
const uint NextBitIndex = firstbitlow(CardMask);
const uint NextBitMask = 1u << NextBitIndex;
CardMask ^= NextBitMask;
uint CardIndex = MeshCardsData.CardOffset + NextBitIndex;
FLumenCardData LumenCardData = GetLumenCardData(CardIndex);
if (all(abs(MeshCardsSpacePosition - LumenCardData.MeshCardsOrigin) <= LumenCardData.MeshCardsExtent + 0.5f * SurfaceCacheBias))
{
CulledCardMask |= NextBitMask;
}
}
CardMask = CulledCardMask;
}
if (MeshCardsData.bHeightfield)
{
CardMask = (1 << LUMEN_HEIGHTFIELD_LOCAL_CARD_INDEX);
}
FCardSampleAccumulator CardSampleAccumulator;
InitCardSampleAccumulator(CardSampleAccumulator);
// Sample cards
while (CardMask != 0)
{
const uint NextBitIndex = firstbitlow(CardMask);
CardMask ^= 1u << NextBitIndex;
uint CardIndex = MeshCardsData.CardOffset + NextBitIndex;
FLumenCardData LumenCardData = GetLumenCardData(CardIndex);
if (LumenCardData.bVisible)
{
SampleLumenCard(
MeshCardsSpacePosition,
MeshCardsSpaceNormal,
SampleRadius,
SurfaceCacheBias,
CardIndex,
AxisWeights,
bHiResSurface,
MeshCardsData.bHeightfield,
AtlasId,
CardSampleAccumulator);
}
}
if (CardSampleAccumulator.SampleWeightSum > 0.0f)
{
SurfaceCacheSample.LightingSum = CardSampleAccumulator.LightingSum;
SurfaceCacheSample.OpacitySum = CardSampleAccumulator.OpacitySum;
SurfaceCacheSample.SampleWeightSum = CardSampleAccumulator.SampleWeightSum;
SurfaceCacheSample.Lighting = CardSampleAccumulator.LightingSum / CardSampleAccumulator.SampleWeightSum;
SurfaceCacheSample.Opacity = CardSampleAccumulator.OpacitySum / CardSampleAccumulator.SampleWeightSum;
SurfaceCacheSample.bValid = true;
}
#if SURFACE_CACHE_FEEDBACK
{
// Write every n-th element
if (all((ScreenCoord & SurfaceCacheFeedbackBufferTileWrapMask) == SurfaceCacheFeedbackBufferTileJitter)
&& SurfaceCacheFeedbackBufferSize > 0
&& CardSampleAccumulator.SampleWeightSum > 0.1f)
{
#if SURFACE_CACHE_HIGH_RES_PAGES
{
uint WriteOffset = 0;
InterlockedAdd(RWSurfaceCacheFeedbackBufferAllocator[0], 1, WriteOffset);
if (WriteOffset < SurfaceCacheFeedbackBufferSize)
{
RWSurfaceCacheFeedbackBuffer[WriteOffset] = CardSampleAccumulator.CardSample.PackedFeedback;
}
RWCardPageHighResLastUsedBuffer[CardSampleAccumulator.CardSample.CardPageIndex] = SurfaceCacheUpdateFrameIndex;
}
#else
{
RWCardPageLastUsedBuffer[CardSampleAccumulator.CardSample.CardPageIndex] = SurfaceCacheUpdateFrameIndex;
}
#endif
}
}
#endif
// Debug visualization
#if ENABLE_VISUALIZE_MODE == 1
{
if (VisualizeMode == VISUALIZE_MODE_GEOMETRY_NORMALS)
{
SurfaceCacheSample.Lighting = (WorldSpaceNormal * 0.5f + 0.5f) * View.OneOverPreExposure;
}
}
#endif
}
return SurfaceCacheSample;
}
void SampleLumenCard(
float3 MeshCardsSpacePosition,
float3 MeshCardsSpaceNormal,
float SampleRadius,
float SurfaceCacheBias,
uint CardIndex,
float3 AxisWeights,
bool bHiResSurface,
bool bHeightfield,
uint AtlasId,
inout FCardSampleAccumulator CardSampleAccumulator)
{
if (CardIndex < LumenCardScene.NumCards)
{
FLumenCardData LumenCardData = GetLumenCardData(CardIndex);
if (LumenCardData.bVisible)
{
float3 CardSpacePosition = mul(MeshCardsSpacePosition - LumenCardData.MeshCardsOrigin, LumenCardData.MeshCardsToLocalRotation);
if (all(abs(CardSpacePosition) <= LumenCardData.LocalExtent + 0.5f * SurfaceCacheBias))
{
CardSpacePosition.xy = clamp(CardSpacePosition.xy, -LumenCardData.LocalExtent.xy, LumenCardData.LocalExtent.xy);
FLumenCardSample CardSample = ComputeSurfaceCacheSample(LumenCardData, CardIndex, CardSpacePosition.xy, SampleRadius, bHiResSurface);
if (CardSample.bValid)
{
// Card projection angle
float NormalWeight = 1.0f;
if (!bHeightfield)
{
if (LumenCardData.AxisAlignedDirection < 2)
{
NormalWeight = AxisWeights.x;
}
else if (LumenCardData.AxisAlignedDirection < 4)
{
NormalWeight = AxisWeights.y;
}
else
{
NormalWeight = AxisWeights.z;
}
}
if (NormalWeight > 0.0f)
{
float4 TexelDepths = DepthAtlas.Gather(GlobalPointClampedSampler, CardSample.PhysicalAtlasUV, 0.0f);
float NormalizedHitDistance = -(CardSpacePosition.z / LumenCardData.LocalExtent.z) * 0.5f + 0.5f;
float BiasTreshold = SurfaceCacheBias / LumenCardData.LocalExtent.z;
float BiasFalloff = 0.25f * BiasTreshold;
float4 TexelVisibility = 0.0f;
for (uint TexelIndex = 0; TexelIndex < 4; ++TexelIndex)
{
// Skip invalid texels
if (IsSurfaceCacheDepthValid(TexelDepths[TexelIndex]))
{
// No need to depth test heightfields
if (bHeightfield)
{
TexelVisibility[TexelIndex] = 1.0f;
}
else
{
TexelVisibility[TexelIndex] = 1.0f - saturate((abs(NormalizedHitDistance - TexelDepths[TexelIndex]) - BiasTreshold) / BiasFalloff);
}
}
}
float4 TexelWeights = CardSample.TexelBilinearWeights * TexelVisibility;
float CardSampleWeight = NormalWeight * dot(TexelWeights, 1.0f);
if (CardSampleWeight > 0.0f)
{
// Normalize weights
float TexelWeightSum = dot(TexelWeights, 1.0f);
TexelWeights /= TexelWeightSum;
float Opacity = SampleSurfaceCacheAtlas(OpacityAtlas, CardSample.PhysicalAtlasUV, TexelWeights).x;
float3 Lighting = 0.0f;
if (AtlasId == FINAL_LIGHTING_ATLAS_ID)
{
Lighting = SampleSurfaceCacheAtlas(FinalLightingAtlas, CardSample.PhysicalAtlasUV, TexelWeights);
}
else if (AtlasId == IRRADIANCE_ATLAS_ID)
{
float3 DirectLighting = SampleSurfaceCacheAtlas(DirectLightingAtlas, CardSample.PhysicalAtlasUV, TexelWeights);
float3 IndirectLighting = SampleSurfaceCacheAtlas(IndirectLightingAtlas, CardSample.IndirectLightingPhysicalAtlasUV, TexelWeights);
Lighting = DirectLighting + IndirectLighting;
}
else // if (AtlasId == INDIRECT_IRRADIANCE_ATLAS_ID)
{
Lighting = SampleSurfaceCacheAtlas(IndirectLightingAtlas, CardSample.PhysicalAtlasUV, TexelWeights);
}
// Debug visualization
#if ENABLE_VISUALIZE_MODE == 1
{
if (VisualizeMode == VISUALIZE_MODE_DIRECT_LIGHTING)
{
Lighting = SampleSurfaceCacheAtlas(DirectLightingAtlas, CardSample.PhysicalAtlasUV, TexelWeights);
}
else if (VisualizeMode == VISUALIZE_MODE_INDIRECT_LIGHTING)
{
Lighting = SampleSurfaceCacheAtlas(IndirectLightingAtlas, CardSample.IndirectLightingPhysicalAtlasUV, TexelWeights);
}
else if (VisualizeMode == VISUALIZE_MODE_ALBEDO)
{
Lighting = DecodeSurfaceCacheAlbedo(SampleSurfaceCacheAtlas(AlbedoAtlas, CardSample.PhysicalAtlasUV, TexelWeights)) * View.OneOverPreExposure;
}
else if (VisualizeMode == VISUALIZE_MODE_NORMALS)
{
FLumenCardData Card = GetLumenCardData(CardSample.CardIndex);
float3 WorldSpaceNormal = DecodeSurfaceCacheNormal(Card, SampleSurfaceCacheAtlas(NormalAtlas, CardSample.PhysicalAtlasUV, TexelWeights).xy);
Lighting = (WorldSpaceNormal * 0.5f + 0.5f) * View.OneOverPreExposure;
}
else if (VisualizeMode == VISUALIZE_MODE_EMISSIVE)
{
Lighting = SampleSurfaceCacheAtlas(EmissiveAtlas, CardSample.PhysicalAtlasUV, TexelWeights);
}
else if (VisualizeMode == VISUALIZE_MODE_OPACITY)
{
Lighting = Opacity * View.OneOverPreExposure;
Opacity = 1.0f;
}
else if (VisualizeMode == VISUALIZE_MODE_CARD_WEIGHTS)
{
float3 RandomColor;
RandomColor.x = (CardSample.CardIndex % 4) / 3.0f;
RandomColor.y = ((CardSample.CardIndex / 4) % 4) / 3.0f;
RandomColor.z = saturate(1.0f - RandomColor.x - RandomColor.y);
// UV Grid overlay
float2 UVGrid = frac(CardSample.PhysicalAtlasUV * LumenCardScene.PhysicalAtlasSize / 8.0f);
UVGrid = smoothstep(abs(UVGrid - 0.5f), 0.0f, 0.01f);
RandomColor *= lerp(0.25f, 1.0f, saturate(UVGrid.x * UVGrid.y));
Lighting = RandomColor * View.OneOverPreExposure;
}
else if (VisualizeMode == VISUALIZE_MODE_DIRECT_LIGHTING_UPDATES || VisualizeMode == VISUALIZE_MODE_INDIRECT_LIGHTING_UPDATES || VisualizeMode == VISUALIZE_MODE_LAST_USED_PAGE || VisualizeMode == VISUALIZE_MODE_LAST_USED_PAGE_HIGH_RES)
{
FLumenCardPageData LumenCardPage = GetLumenCardPageData(CardSample.CardPageIndex);
uint LastUpdateFrameIndex = 0;
float VisScale = 8.0f;
if (VisualizeMode == VISUALIZE_MODE_DIRECT_LIGHTING_UPDATES)
{
LastUpdateFrameIndex = LumenCardPage.LastDirectLightingUpdateFrameIndex;
}
else if (VisualizeMode == VISUALIZE_MODE_INDIRECT_LIGHTING_UPDATES)
{
LastUpdateFrameIndex = LumenCardPage.LastIndirectLightingUpdateFrameIndex;
VisScale = 32.0f;
}
else if (VisualizeMode == VISUALIZE_MODE_LAST_USED_PAGE)
{
#if SURFACE_CACHE_FEEDBACK
LastUpdateFrameIndex = RWCardPageLastUsedBuffer[CardSample.CardPageIndex];
#endif
}
else if (VisualizeMode == VISUALIZE_MODE_LAST_USED_PAGE_HIGH_RES)
{
#if SURFACE_CACHE_FEEDBACK
LastUpdateFrameIndex = RWCardPageHighResLastUsedBuffer[CardSample.CardPageIndex];
#endif
}
uint FramesSinceLastUpdated = SurfaceCacheUpdateFrameIndex - LastUpdateFrameIndex;
float3 VisColor = lerp(float3(1, 0, 0), float3(0, 0, 1), saturate(FramesSinceLastUpdated / VisScale));
if (FramesSinceLastUpdated < 1)
{
VisColor = float3(1, 1, 1);
}
Lighting = VisColor * View.OneOverPreExposure;
}
}
#endif
CardSampleAccumulator.LightingSum += Lighting * CardSampleWeight;
CardSampleAccumulator.OpacitySum += Opacity * CardSampleWeight;
CardSampleAccumulator.SampleWeightSum += CardSampleWeight;
// Pick single sample based on the max weight
if (CardSampleWeight > CardSampleAccumulator.MaxSampleWeight)
{
CardSampleAccumulator.CardSample = CardSample;
CardSampleAccumulator.MaxSampleWeight = CardSampleWeight;
}
}
}
}
}
}
}
}
FLumenCardSample ComputeSurfaceCacheSample(FLumenCardData Card, uint CardIndex, float2 LocalSamplePosition, float SampleRadius, bool bHiResSurface)
{
// CardUV in [0;1)
float2 CardUV = min(SamplePositonToCardUV(Card, LocalSamplePosition), 0.999999f);
uint2 SizeInPages = Card.SizeInPages;
uint PageTableOffset = Card.PageTableOffset;
if (bHiResSurface)
{
SizeInPages = Card.HiResSizeInPages;
PageTableOffset = Card.HiResPageTableOffset;
}
uint2 PageCoord = CardUV * SizeInPages;
uint LinearPageCoord = PageCoord.x + PageCoord.y * SizeInPages.x;
const uint PageTableIndex = PageTableOffset + LinearPageCoord;
const uint2 PageTableValue = LumenCardScene.PageTableBuffer.Load2(8 * PageTableIndex);
uint2 AtlasBias;
AtlasBias.x = ((PageTableValue.x >> 0) & 0xFFF) * MIN_CARD_RESOLUTION;
AtlasBias.y = ((PageTableValue.x >> 12) & 0xFFF) * MIN_CARD_RESOLUTION;
uint2 ResLevelXY;
ResLevelXY.x = (PageTableValue.x >> 24) & 0xF;
ResLevelXY.y = (PageTableValue.x >> 28) & 0xF;
// Mapped page index (sampled page may be pointing to another mip map level)
const uint CardPageIndex = PageTableValue.y;
// Recompute new SizeInPages and PageCoord, as sampled page may be pointing to an another mip map level
SizeInPages = ResLevelXYToSizeInPages(ResLevelXY);
PageCoord = CardUV * SizeInPages;
uint2 AtlasScale = select(ResLevelXY > SUB_ALLOCATION_RES_LEVEL, PHYSICAL_PAGE_SIZE, (1u << ResLevelXY));
float2 PageUV = frac(CardUV * SizeInPages);
// Page edges (which aren't card edges) need to be remapped from [0; PageSize] to [0.5; PageSize - 0.5]
// for correct bilinear filtering between pages and not reading texels outside of that page
float2 MinUVBorder = select(PageCoord.xy == 0, 0.0f, 0.5f);
float2 MaxUVBorder = select(PageCoord.xy + 1 == SizeInPages.xy, 0.0f, 0.5f);
float2 CoordInPage = (PageUV * (AtlasScale - MinUVBorder - MaxUVBorder)) + MinUVBorder;
// Card edges need to be clamped to [0.5; CardResolution - 1 - 0.5] so that bilinear filtering doesn't read texels from other cards
CoordInPage = clamp(CoordInPage, 0.5f, AtlasScale - 1.0f - 0.5f);
float2 PhysicalAtlasUV = (CoordInPage + AtlasBias) * LumenCardScene.InvPhysicalAtlasSize;
// Indirect lighting can be sampled from a downsampled atlas
float ILFactor = LumenCardScene.IndirectLightingAtlasDownsampleFactor;
float2 IndirectLightingPhysicalAtlasUV = (PageUV * (AtlasScale / ILFactor - 1.0f) + AtlasBias / ILFactor + 0.5f) * ILFactor * LumenCardScene.InvPhysicalAtlasSize;
// Compute packed feedback buffer value
uint2 PackedFeedback = 0;
#if SURFACE_CACHE_FEEDBACK && SURFACE_CACHE_HIGH_RES_PAGES
{
// Compute optimal res level, based on the cone width (SampleRadius)
float SampleResolution = max(Card.LocalExtent.x, Card.LocalExtent.y) / max(SampleRadius, 1.0f);
uint DesiredResLevel = clamp(log2(SampleResolution) + SurfaceCacheFeedbackResLevelBias, MIN_RES_LEVEL, MAX_RES_LEVEL);
uint2 LevelSizeInPages = GetSizeInPages(Card, DesiredResLevel);
uint2 LocalPageCoord = CardUV * LevelSizeInPages;
PackedFeedback.x = CardIndex | (DesiredResLevel << 24);
PackedFeedback.y = LocalPageCoord.x + (LocalPageCoord.y << 8);
}
#endif
float2 FracUV = frac(PhysicalAtlasUV * LumenCardScene.PhysicalAtlasSize + 0.5f + 1.0f / 512.0f);
float4 TexelBilinearWeights;
TexelBilinearWeights.x = (1.0 - FracUV.x) * (FracUV.y);
TexelBilinearWeights.y = (FracUV.x) * (FracUV.y);
TexelBilinearWeights.z = (FracUV.x) * (1 - FracUV.y);
TexelBilinearWeights.w = (1 - FracUV.x) * (1 - FracUV.y);
FLumenCardSample CardSample;
CardSample.CardIndex = CardIndex;
CardSample.CardPageIndex = CardPageIndex;
CardSample.PhysicalAtlasUV = PhysicalAtlasUV;
CardSample.TexelBilinearWeights = TexelBilinearWeights;
CardSample.IndirectLightingPhysicalAtlasUV = IndirectLightingPhysicalAtlasUV;
CardSample.bValid = ResLevelXY.x > 0;
CardSample.PackedFeedback = PackedFeedback;
return CardSample;
}
float3 SampleSurfaceCacheAtlas(Texture2D AtlasTexture, float2 AtlasUV, float4 TexelWeights)
{
float4 SampleX4 = AtlasTexture.GatherRed(GlobalPointClampedSampler, AtlasUV);
float4 SampleY4 = AtlasTexture.GatherGreen(GlobalPointClampedSampler, AtlasUV);
float4 SampleZ4 = AtlasTexture.GatherBlue(GlobalPointClampedSampler, AtlasUV);
float3 Sample;
Sample.x = dot(SampleX4, TexelWeights);
Sample.y = dot(SampleY4, TexelWeights);
Sample.z = dot(SampleZ4, TexelWeights);
return Sample;
RadianceCache
void SampleRadianceCacheAndApply(FRadianceCacheCoverage Coverage, float3 WorldSpacePosition, float3 WorldSpaceDirection, float ConeHalfAngle, inout float3 Lighting, inout float Transparency)
{
float3 RadianceCacheLighting = SampleRadianceCacheInterpolated(Coverage, WorldSpacePosition, WorldSpaceDirection, ConeHalfAngle);
if (OverrideCacheOcclusionLighting > 0)
{
Lighting = RadianceCacheLighting;
}
else if (ShowBlackRadianceCacheLighting == 0)
{
Lighting += RadianceCacheLighting * Transparency;
}
Transparency = 0.0f;
}
float3 SampleRadianceCacheInterpolated(FRadianceCacheCoverage Coverage, float3 WorldSpacePosition, float3 WorldSpaceDirection, float ConeHalfAngle)
{
float3 ProbeCoordFloat = WorldSpacePosition * GetWorldPositionToRadianceProbeCoordScale(Coverage.ClipmapIndex) + GetWorldPositionToRadianceProbeCoordBias(Coverage.ClipmapIndex);
float NumTexels = sqrt(1.0f - cos(ConeHalfAngle)) * RadianceProbeResolution;
float MipLevel = clamp(log2(NumTexels), 0, (float)FinalRadianceAtlasMaxMip);
float3 CornerProbeCoordFloat = ProbeCoordFloat - 0.5f;
int3 CornerProbeCoord = floor(CornerProbeCoordFloat);
float3 LerpAlphas = frac(CornerProbeCoordFloat);
float3 Lighting000 = SampleRadianceCacheProbeWithParallaxCorrection(CornerProbeCoord + int3(0, 0, 0), Coverage.ClipmapIndex, WorldSpacePosition, WorldSpaceDirection, MipLevel);
float3 Lighting001 = SampleRadianceCacheProbeWithParallaxCorrection(CornerProbeCoord + int3(0, 0, 1), Coverage.ClipmapIndex, WorldSpacePosition, WorldSpaceDirection, MipLevel);
float3 Lighting010 = SampleRadianceCacheProbeWithParallaxCorrection(CornerProbeCoord + int3(0, 1, 0), Coverage.ClipmapIndex, WorldSpacePosition, WorldSpaceDirection, MipLevel);
float3 Lighting011 = SampleRadianceCacheProbeWithParallaxCorrection(CornerProbeCoord + int3(0, 1, 1), Coverage.ClipmapIndex, WorldSpacePosition, WorldSpaceDirection, MipLevel);
float3 Lighting100 = SampleRadianceCacheProbeWithParallaxCorrection(CornerProbeCoord + int3(1, 0, 0), Coverage.ClipmapIndex, WorldSpacePosition, WorldSpaceDirection, MipLevel);
float3 Lighting101 = SampleRadianceCacheProbeWithParallaxCorrection(CornerProbeCoord + int3(1, 0, 1), Coverage.ClipmapIndex, WorldSpacePosition, WorldSpaceDirection, MipLevel);
float3 Lighting110 = SampleRadianceCacheProbeWithParallaxCorrection(CornerProbeCoord + int3(1, 1, 0), Coverage.ClipmapIndex, WorldSpacePosition, WorldSpaceDirection, MipLevel);
float3 Lighting111 = SampleRadianceCacheProbeWithParallaxCorrection(CornerProbeCoord + int3(1, 1, 1), Coverage.ClipmapIndex, WorldSpacePosition, WorldSpaceDirection, MipLevel);
float3 ZLerp00 = lerp(Lighting000, Lighting001, LerpAlphas.z);
float3 ZLerp01 = lerp(Lighting010, Lighting011, LerpAlphas.z);
float3 ZLerp10 = lerp(Lighting100, Lighting101, LerpAlphas.z);
float3 ZLerp11 = lerp(Lighting110, Lighting111, LerpAlphas.z);
float3 YLerp0 = lerp(ZLerp00, ZLerp01, LerpAlphas.y);
float3 YLerp1 = lerp(ZLerp10, ZLerp11, LerpAlphas.y);
return lerp(YLerp0, YLerp1, LerpAlphas.x);
}
float3 SampleRadianceCacheProbeWithParallaxCorrection(uint3 ProbeCoord, uint ProbeClipmapIndex, float3 WorldSpacePosition, float3 WorldSpaceDirection, float MipLevel)
{
float ProbeTMin = GetRadianceProbeTMin(ProbeClipmapIndex);
uint ProbeIndex = GetProbeIndexFromIndirectionTexture(ProbeCoord, ProbeClipmapIndex);
float3 ProbeWorldPosition = GetProbeWorldPosition(ProbeCoord, ProbeClipmapIndex, ProbeIndex);
float3 ReprojectedDirection = WorldSpaceDirection;
float ReprojectionRadius = ReprojectionRadiusScale * ProbeTMin;
float3 IntersectionPosition = WorldSpacePosition + WorldSpaceDirection * RayIntersectSphere(WorldSpacePosition, WorldSpaceDirection, float4(ProbeWorldPosition, ReprojectionRadius)).y;
ReprojectedDirection = IntersectionPosition - ProbeWorldPosition;
return SampleRadianceCacheProbe(ProbeIndex, ReprojectedDirection, MipLevel);
}
uint GetProbeIndexFromIndirectionTexture(uint3 ProbeCoord, uint ClipmapIndex)
{
uint3 ProbeIndirectionTextureCoord = uint3(ProbeCoord.x + ClipmapIndex * RadianceProbeClipmapResolution, ProbeCoord.yz);
return RadianceProbeIndirectionTexture.Load(uint4(ProbeIndirectionTextureCoord, 0));
}
float3 SampleRadianceCacheProbe(uint ProbeIndex, float3 WorldSpaceDirection, float MipLevel)
{
float2 ProbeUV = UnitVectorToOctahedron(WorldSpaceDirection) * 0.5 + 0.5;
uint2 ProbeAtlasCoord = FinalProbeResolution * uint2(ProbeIndex & ProbeAtlasResolutionModuloMask, ProbeIndex >> ProbeAtlasResolutionDivideShift);
float2 ProbeTexelCoord = ProbeUV * RadianceProbeResolution + (1u << FinalRadianceAtlasMaxMip);
float2 ProbeAtlasUV = (ProbeAtlasCoord + ProbeTexelCoord) * InvProbeFinalRadianceAtlasResolution;
float3 UnmappedDebugColor = 0.0f;
return ProbeIndex == 0xFFFFFFFF ? UnmappedDebugColor : RadianceCacheFinalRadianceAtlas.SampleLevel( View_SharedBilinearClampedSampler , ProbeAtlasUV, MipLevel);
}