LumenScreenProbeGather:TraceVoxels

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);

}

你可能感兴趣的:(#,UE之Lumen,UE)