DistanceFieldTracing
[numthreads( 64 , 1, 1)]
void LumenRadiosityDistanceFieldTracingCS(
uint DispatchThreadId : SV_DispatchThreadID)
{
uint CardTileIndex;
uint2 CoordInCardTile;
uint2 TraceTexelCoord;
UnswizzleTexelTraceCoords(DispatchThreadId, CardTileIndex, CoordInCardTile, TraceTexelCoord);
FRadiosityTexel RadiosityTexel = GetRadiosityTexelFromCardTile(CardTileIndex, CoordInCardTile);
if (RadiosityTexel.bInsideAtlas)
{
float3 Radiance = 0.0f;
float TraceHitDistance = MaxTraceDistance;
if (RadiosityTexel.bValid)
{
float3 WorldPosition = RadiosityTexel.WorldPosition;
float3 WorldNormal = RadiosityTexel.WorldNormal;
float3 WorldRayDirection;
float ConeHalfAngle;
float PDF;
GetRadiosityRay(RadiosityTexel, RadiosityTexel.CardCoord >> ProbeSpacingInRadiosityTexelsDivideShift, TraceTexelCoord, WorldRayDirection, ConeHalfAngle, PDF);
WorldPosition += WorldNormal * SurfaceBias;
float VoxelTraceStartDistance = CalculateVoxelTraceStartDistance(MinTraceDistance, MaxTraceDistance, MaxMeshSDFTraceDistance, false);
float3 SamplePosition = WorldPosition + SurfaceBias * WorldRayDirection;
float3 TranslatedSamplePosition = WorldPosition + LWCToFloat( GetPrimaryView() .PreViewTranslation) ;
FConeTraceInput TraceInput;
TraceInput.Setup(SamplePosition, TranslatedSamplePosition, WorldRayDirection, ConeHalfAngle, 0, MinTraceDistance, MaxTraceDistance, 1);
TraceInput.VoxelTraceStartDistance = VoxelTraceStartDistance;
TraceInput.SDFStepFactor = 1;
TraceInput.DitherScreenCoord = (RadiosityTexel.CardCoord >> ProbeSpacingInRadiosityTexelsDivideShift) + TraceTexelCoord;
FConeTraceResult TraceResult = (FConeTraceResult)0;
TraceResult.Transparency = 1;
{
RayTraceGlobalDistanceField(TraceInput, TraceResult);
}
if (TraceResult.Transparency < 0.5f)
{
Radiance = TraceResult.Lighting;
float3 HitPosition = SamplePosition + WorldRayDirection * (TraceResult.OpaqueHitDistance + TraceResult.ExpandSurfaceAmount);
TraceHitDistance = length(RadiosityTexel.WorldPosition - HitPosition);
}
else
{
Radiance = EvaluateSkyRadiance(WorldRayDirection);
}
float MaxLighting = max3(Radiance.x, Radiance.y, Radiance.z);
if (MaxLighting > MaxRayIntensity * View_OneOverPreExposure)
{
Radiance *= MaxRayIntensity * View_OneOverPreExposure / MaxLighting;
}
}
FCardTileData CardTile = GetCardTile(CardTileIndex);
FLumenCardPageData CardPage = GetLumenCardPageData(CardTile.CardPageIndex);
uint2 RadiosityProbeTracingAtlasCoord = GetRadiosityProbeAtlasCoord(CardPage, CardTile, CoordInCardTile) * HemisphereProbeResolution + TraceTexelCoord;
RWTraceRadianceAtlas[RadiosityProbeTracingAtlasCoord] = Radiance;
if (UseProbeOcclusion > 0)
{
RWTraceHitDistanceAtlas[RadiosityProbeTracingAtlasCoord] = TraceHitDistance;
}
}
}
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 = View_GlobalVolumeCenterAndExtent [SDFTraceResult.HitClipmapIndex].w * View_GlobalVolumeTexelSize ;
float RadianceFactor = 1.0f;
if (TraceInput.bExpandSurfaceUsingRayTimeInsteadOfMaxDistance)
{
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) * View_GlobalDistanceFieldClipmapSizeInPages ;
uint3 CellCoordInPage = frac(frac(PageTableCoord)) * 4 ;
uint CellOffsetInPage = ZOrder3DEncode(CellCoordInPage, log2( 4 ));
uint4 DistanceFieldObjectGridCell = GlobalDistanceFieldPageObjectGridBuffer[ ( 4 * 4 * 4 ) * Page.PageIndex + CellOffsetInPage];
for (uint ObjectIndexInList = 0; ObjectIndexInList < 4 ; ++ObjectIndexInList)
{
FObjectGridCellIndex GridCellIndex = UnpackObjectGridCellIndex(DistanceFieldObjectGridCell[ObjectIndexInList]);
if (GridCellIndex.bValid)
{
uint MeshCardsIndex = GetMeshCardsIndexFromSceneInstanceIndex(GridCellIndex.GPUSceneInstanceIndex);
if (MeshCardsIndex < LumenCardScene_NumMeshCards)
{
uint2 ScreenCoord = TraceInput.DitherScreenCoord;
float SurfaceCacheBias = 2.0f * ClipmapVoxelExtent;
float SampleRadius = 100.0f;
FSurfaceCacheSample SurfaceCacheSample = SampleLumenMeshCards(
ScreenCoord,
MeshCardsIndex,
SampleWorldPosition,
SampleWorldNormal,
SampleRadius,
SurfaceCacheBias,
false,
0
);
LightingSum += SurfaceCacheSample.LightingSum;
SampleWeightSum += SurfaceCacheSample.SampleWeightSum;
if (SampleWeightSum >= 0.9f)
{
break;
}
}
}
else
{
break;
}
}
}
float4 LightingAndAlpha = 0.0f;
if (SampleWeightSum > 0.0f)
{
LightingAndAlpha.xyz = RadianceFactor * (LightingSum / SampleWeightSum);
}
return LightingAndAlpha;
}
FSurfaceCacheSample SampleLumenMeshCards(
uint2 ScreenCoord,
uint MeshCardsIndex,
float3 WorldSpacePosition,
float3 WorldSpaceNormal,
float SampleRadius,
float SurfaceCacheBias,
bool bHiResSurface,
uint AtlasId)
{
FSurfaceCacheSample SurfaceCacheSample = InitSurfaceCacheSample();
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;
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];
}
{
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 << 0 );
}
FCardSampleAccumulator CardSampleAccumulator;
InitCardSampleAccumulator(CardSampleAccumulator);
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;
}
}
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)
{
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( View_SharedPointClampedSampler , 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)
{
if (IsSurfaceCacheDepthValid(TexelDepths[TexelIndex]))
{
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)
{
float TexelWeightSum = dot(TexelWeights, 1.0f);
TexelWeights /= TexelWeightSum;
float Opacity = SampleSurfaceCacheAtlas(OpacityAtlas, CardSample.PhysicalAtlasUV, TexelWeights).x;
float3 Lighting = 0.0f;
if (AtlasId == 0 )
{
Lighting = SampleSurfaceCacheAtlas(FinalLightingAtlas, CardSample.PhysicalAtlasUV, TexelWeights);
}
else if (AtlasId == 1 )
{
float3 DirectLighting = SampleSurfaceCacheAtlas(DirectLightingAtlas, CardSample.PhysicalAtlasUV, TexelWeights);
float3 IndirectLighting = SampleSurfaceCacheAtlas(IndirectLightingAtlas, CardSample.IndirectLightingPhysicalAtlasUV, TexelWeights);
Lighting = DirectLighting + IndirectLighting;
}
else
{
Lighting = SampleSurfaceCacheAtlas(IndirectLightingAtlas, CardSample.PhysicalAtlasUV, TexelWeights);
}
CardSampleAccumulator.LightingSum += Lighting * CardSampleWeight;
CardSampleAccumulator.OpacitySum += Opacity * CardSampleWeight;
CardSampleAccumulator.SampleWeightSum += CardSampleWeight;
if (CardSampleWeight > CardSampleAccumulator.MaxSampleWeight)
{
CardSampleAccumulator.CardSample = CardSample;
CardSampleAccumulator.MaxSampleWeight = CardSampleWeight;
}
}
}
}
}
}
}
}
FLumenCardSample ComputeSurfaceCacheSample(FLumenCardData Card, uint CardIndex, float2 LocalSamplePosition, float SampleRadius, bool bHiResSurface)
{
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) * 8 ;
AtlasBias.y = ((PageTableValue.x >> 12) & 0xFFF) * 8 ;
uint2 ResLevelXY;
ResLevelXY.x = (PageTableValue.x >> 24) & 0xF;
ResLevelXY.y = (PageTableValue.x >> 28) & 0xF;
const uint CardPageIndex = PageTableValue.y;
SizeInPages = ResLevelXYToSizeInPages(ResLevelXY);
PageCoord = CardUV * SizeInPages;
uint2 AtlasScale = select_internal(ResLevelXY > 7 , 128 ,(1u << ResLevelXY)) ;
float2 PageUV = frac(CardUV * SizeInPages);
float2 MinUVBorder = select_internal(PageCoord.xy == 0,0.0f,0.5f) ;
float2 MaxUVBorder = select_internal(PageCoord.xy + 1 == SizeInPages.xy,0.0f,0.5f) ;
float2 CoordInPage = (PageUV * (AtlasScale - MinUVBorder - MaxUVBorder)) + MinUVBorder;
CoordInPage = clamp(CoordInPage, 0.5f, AtlasScale - 1.0f - 0.5f);
float2 PhysicalAtlasUV = (CoordInPage + AtlasBias) * LumenCardScene_InvPhysicalAtlasSize;
float ILFactor = LumenCardScene_IndirectLightingAtlasDownsampleFactor;
float2 IndirectLightingPhysicalAtlasUV = (PageUV * (AtlasScale / ILFactor - 1.0f) + AtlasBias / ILFactor + 0.5f) * ILFactor * LumenCardScene_InvPhysicalAtlasSize;
uint2 PackedFeedback = 0;
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( View_SharedPointClampedSampler , AtlasUV);
float4 SampleY4 = AtlasTexture.GatherGreen( View_SharedPointClampedSampler , AtlasUV);
float4 SampleZ4 = AtlasTexture.GatherBlue( View_SharedPointClampedSampler , AtlasUV);
float3 Sample;
Sample.x = dot(SampleX4, TexelWeights);
Sample.y = dot(SampleY4, TexelWeights);
Sample.z = dot(SampleZ4, TexelWeights);
return Sample;
}