LumenScreenProbeGather:TraceMeshSDF

LumenScreenProbeTracing.usf

[numthreads( 64 , 1, 1)]
void ScreenProbeTraceMeshSDFsCS(
	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);
		TraceMeshSDFs(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 TraceMeshSDFs(
	uint2 ScreenProbeAtlasCoord,
	uint2 TraceTexelCoord,
	uint ScreenProbeIndex,
	float TraceHitDistance)
{
	uint2 ScreenProbeScreenPosition = GetScreenProbeScreenPosition(ScreenProbeIndex);
	uint2 ScreenTileCoord = GetScreenTileCoord(ScreenProbeScreenPosition);
	uint ProbeTracingResolution =  0  ? ScreenProbeLightSampleResolutionXY : ScreenProbeTracingOctahedronResolution;

	{
		float2 ScreenUV = GetScreenUVFromScreenProbePosition(ScreenProbeScreenPosition);
		float SceneDepth = GetScreenProbeDepth(ScreenProbeAtlasCoord);

		uint2 TraceBufferCoord = GetTraceBufferCoord(ScreenProbeAtlasCoord, TraceTexelCoord);
		float3 TranslatedWorldPosition = GetTranslatedWorldPositionFromScreenUV(ScreenUV, SceneDepth);
		float3 WorldPosition = TranslatedWorldPosition -  LWCToFloat( GetPrimaryView() .PreViewTranslation) ;

		bool bHit = false;
		bool bMoving = false;

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

			FConeTraceInput TraceInput;
			TraceInput.Setup(SamplePosition, TranslatedSamplePosition, RayWorldDirection, ConeHalfAngle, MinSampleRadius, max(MinTraceDistance, TraceHitDistance - SurfaceBias * 2), MaxTraceDistance, StepFactor);
			TraceInput.VoxelTraceStartDistance = min(MaxMeshSDFTraceDistance, TraceDistance);
			TraceInput.bDitheredTransparency = true;
			TraceInput.DitherScreenCoord = ScreenTileCoord * ProbeTracingResolution + TraceTexelCoord;

			uint CardGridCellIndex = ComputeCardGridCellIndex(ScreenProbeScreenPosition -  GetPrimaryView() .ViewRectMinAndSize.xy, SceneDepth);
			TraceInput.NumMeshSDFs = NumGridCulledMeshSDFObjects[CardGridCellIndex];
			TraceInput.MeshSDFStartOffset = GridCulledMeshSDFObjectStartOffsetArray[CardGridCellIndex];
			TraceInput.CardInterpolateInfluenceRadius = CardInterpolateInfluenceRadius;
			TraceInput.bCalculateHitVelocity = true;

			FConeTraceResult TraceResult;
			ConeTraceLumenSceneCards(TraceInput, TraceResult);

			TraceInput.NumHeightfields = NumGridCulledHeightfieldObjects[CardGridCellIndex];
			TraceInput.HeightfieldStartOffset = GridCulledHeightfieldObjectStartOffsetArray[CardGridCellIndex];
			ConeTraceLumenSceneHeightfields(TraceInput, TraceResult);
			float3 Lighting = TraceResult.Lighting;
			float Transparency = TraceResult.Transparency;
			float OpaqueHitDistance = TraceResult.OpaqueHitDistance;

			{
				float3 HitWorldPosition = SamplePosition + RayWorldDirection * OpaqueHitDistance;
				bMoving = IsTraceMoving(WorldPosition, SceneDepth, ScreenProbeAtlasCoord, HitWorldPosition, TraceResult.WorldVelocity);
			}

			float DistanceFromViewpoint = length( LWCToFloat( GetPrimaryView() .WorldCameraOrigin)  - WorldPosition);
			float DistanceFade = saturate(6 * DistanceFromViewpoint / CardTraceEndDistanceFromCamera - 5);

			Transparency = lerp(Transparency, 1, DistanceFade);

			if (Transparency < InterleavedGradientNoise(TraceBufferCoord + 0.5f, 0))
			{
				bHit = true;
			}

			if (bHit)
			{
				Lighting *= 1 - DistanceFade;
				Lighting += GetSkylightLeaking(RayWorldDirection, OpaqueHitDistance);
				Lighting *= View_PreExposure;
					RWTraceRadiance[TraceBufferCoord] = Lighting;
			}

			TraceHitDistance = OpaqueHitDistance + length(WorldPosition - SamplePosition);
		}

		WriteTraceHit(TraceBufferCoord, min(TraceHitDistance, MaxTraceDistance), bHit, bMoving);
	}
}
void ConeTraceLumenSceneCards(
	FConeTraceInput TraceInput,
	inout FConeTraceResult OutResult)
{
	OutResult = (FConeTraceResult)0;
	OutResult.Transparency = 1;
	OutResult.OpaqueHitDistance = TraceInput.MaxTraceDistance;


	if (TraceInput.VoxelTraceStartDistance > TraceInput.MinTraceDistance)
	{
		FConeTraceInput CardTraceInput = TraceInput;
		CardTraceInput.MaxTraceDistance = TraceInput.VoxelTraceStartDistance;

		ConeTraceMeshSDFsAndInterpolateFromCards(CardTraceInput, OutResult);
	}

}
void ConeTraceMeshSDFsAndInterpolateFromCards(
	FConeTraceInput TraceInput,
	inout FConeTraceResult OutResult)
{
	FTraceMeshSDFResult TraceMeshSDFResult;
	TraceMeshSDFResult.HitDistance = TraceInput.MaxTraceDistance;
	TraceMeshSDFResult.HitObject = 0;

	for (uint GridCulledMeshSDFIndex = 0; GridCulledMeshSDFIndex < TraceInput.NumMeshSDFs; GridCulledMeshSDFIndex++)
	{
		uint ObjectIndex = GridCulledMeshSDFObjectIndicesArray[TraceInput.MeshSDFStartOffset + GridCulledMeshSDFIndex];

		RayTraceSingleMeshSDF(
			TraceInput.ConeOrigin,
			TraceInput.ConeDirection,
			TraceInput.TanConeAngle,
			TraceInput.MinTraceDistance,
			TraceInput.MaxTraceDistance,
			ObjectIndex,
			TraceInput.bExpandSurfaceUsingRayTimeInsteadOfMaxDistance,
			TraceInput.InitialMaxDistance,
			TraceInput.bDitheredTransparency,
			TraceInput.DitherScreenCoord,
			TraceMeshSDFResult);
	}

	if (TraceMeshSDFResult.HitDistance < TraceInput.MaxTraceDistance)
	{
		FTraceMeshSDFDerivedData TraceSDFData = CalculateMeshSDFDerivedData(
			TraceInput.ConeOrigin,
			TraceInput.ConeDirection,
			TraceInput.MaxTraceDistance,
			TraceInput.bCalculateHitVelocity,
			TraceMeshSDFResult);

		float3 InterpolatePosition = TraceInput.ConeOrigin + TraceInput.ConeDirection * TraceMeshSDFResult.HitDistance;
		float InterpolateRadius = TraceMeshSDFResult.HitDistance * TraceInput.TanConeAngle;

		uint AtlasId =  0 ;
		OutResult.Lighting = SampleLumenMeshCards(
			TraceInput.DitherScreenCoord,
			TraceSDFData.MeshCardsIndex,
			InterpolatePosition,
			TraceSDFData.HitNormal,
			InterpolateRadius,
			TraceSDFData.SurfaceCacheBias,
			TraceInput.bHiResSurface,
			AtlasId).Lighting;
		OutResult.Transparency = 0;
		OutResult.WorldVelocity = TraceSDFData.WorldVelocity;
	}

	OutResult.OpaqueHitDistance = TraceMeshSDFResult.HitDistance;
}
void RayTraceSingleMeshSDF(
	float3 WorldRayStart, 
	float3 WorldRayDirection, 
	float TanConeHalfAngle,
	float MinTraceDistance,
	float MaxTraceDistance, 
	uint ObjectIndex,
	// The SDF surface is expanded to reduce leaking through thin surfaces, especially foliage meshes with bGenerateDistanceFieldAsIfTwoSided
	// Expanding as RayTime increases errors on the side of over-occlusion, especially at grazing angles, which can be desirable for diffuse GI.
	// Expanding as MaxDistance increases has less incorrect self-intersection which is desirable for reflections rays.
	bool bExpandSurfaceUsingRayTimeInsteadOfMaxDistance,
	float InitialMaxDistance,
	bool bDitheredTransparency,
	float2 DitherScreenCoord,
	inout FTraceMeshSDFResult TraceResult)
{
	FDFObjectData DFObjectData = LoadDFObjectData(ObjectIndex);
	float4x4 WorldToVolume = LWCHackToFloat(DFObjectData.WorldToVolume);

	if (!bDitheredTransparency || !DFObjectData.bMostlyTwoSided)
	{
		// Trace up to the current hit point
		MaxTraceDistance = min(MaxTraceDistance, TraceResult.HitDistance + DFObjectData.VolumeSurfaceBias);
	}

	float3 WorldRayEnd = WorldRayStart + WorldRayDirection * MaxTraceDistance;
	float3 VolumeRayStart = mul(float4(WorldRayStart, 1), WorldToVolume).xyz;
	float3 VolumeRayEnd = mul(float4(WorldRayEnd, 1), WorldToVolume).xyz;
	float3 VolumeRayDirection = VolumeRayEnd - VolumeRayStart;

	float VolumeMaxTraceDistance = length(VolumeRayDirection);
	float VolumeMinTraceDistance = VolumeMaxTraceDistance * (MinTraceDistance / MaxTraceDistance);
	VolumeRayDirection /= VolumeMaxTraceDistance;

	float2 VolumeSpaceIntersectionTimes = LineBoxIntersect(VolumeRayStart, VolumeRayEnd, -DFObjectData.VolumePositionExtent, DFObjectData.VolumePositionExtent);

	VolumeSpaceIntersectionTimes *= VolumeMaxTraceDistance;
	VolumeSpaceIntersectionTimes.x = max(VolumeSpaceIntersectionTimes.x, VolumeMinTraceDistance);

	BRANCH
	if (VolumeSpaceIntersectionTimes.x < VolumeSpaceIntersectionTimes.y)
	{
		uint MaxMipIndex = LoadDFAssetData(DFObjectData.AssetIndex, 0).NumMips - 1;
		// Start tracing at the highest resolution mip
		uint ReversedMipIndex = MaxMipIndex;
		FDFAssetData DFAssetMipData = LoadDFAssetData(DFObjectData.AssetIndex, ReversedMipIndex);

		#if !SDF_TRACING_TRAVERSE_MIPS
			ReversedMipIndex = MaxMipIndex;
			DFAssetMipData = LoadDFAssetData(DFObjectData.AssetIndex, ReversedMipIndex);
		#endif

		float Coverage = DFObjectData.bMostlyTwoSided && bDitheredTransparency ? 0.0f : 1.0f;
		float ExpandSurfaceScale = lerp(MeshSDFNotCoveredExpandSurfaceScale, 1.0f, Coverage);

		float SampleRayTime = VolumeSpaceIntersectionTimes.x;

		uint MaxSteps = 64;
		float MinStepSize = 1.0f / (16.0f * MaxSteps);
		uint StepIndex = 0;
		bool bHit = false;
		float MaxDistance = InitialMaxDistance;

		LOOP
		for (; StepIndex < MaxSteps; StepIndex++)
		{
			float3 SampleVolumePosition = VolumeRayStart + VolumeRayDirection * SampleRayTime;
			float DistanceField = SampleSparseMeshSignedDistanceField(SampleVolumePosition, DFAssetMipData);

			MaxDistance = max(DistanceField, MaxDistance);
			float ExpandSurfaceTime = bExpandSurfaceUsingRayTimeInsteadOfMaxDistance ? SampleRayTime : MaxDistance;

			// Expand the surface to find thin features, but only away from the start of the trace where it won't introduce incorrect self-occlusion
			// This still causes incorrect self-occlusion at grazing angles
			float ExpandSurfaceDistance = DFObjectData.VolumeSurfaceBias;
			const float ExpandSurfaceFalloff = 2.0f * ExpandSurfaceDistance;
			const float ExpandSurfaceAmount = ExpandSurfaceDistance * saturate(ExpandSurfaceTime / ExpandSurfaceFalloff) * ExpandSurfaceScale;

			float StepNoise = InterleavedGradientNoise(DitherScreenCoord.xy, View.StateFrameIndexMod8 * MaxSteps + StepIndex);

#if SDF_TRACING_TRAVERSE_MIPS

			float MaxEncodedDistance = DFAssetMipData.DistanceFieldToVolumeScaleBias.x + DFAssetMipData.DistanceFieldToVolumeScaleBias.y;

			// We reached the maximum distance of this mip's narrow band, use a lower resolution mip for next iteration
			if (abs(DistanceField) > MaxEncodedDistance && ReversedMipIndex > 0)
			{
				ReversedMipIndex--;
				DFAssetMipData = LoadDFAssetData(DFObjectData.AssetIndex, ReversedMipIndex);
			}
			// We are close to the surface, step back to safety and use a higher resolution mip for next iteration
			else if (abs(DistanceField) < .25f * MaxEncodedDistance && ReversedMipIndex < MaxMipIndex)
			{
				DistanceField -= 6.0f * DFObjectData.VolumeSurfaceBias;
				ReversedMipIndex++;
				DFAssetMipData = LoadDFAssetData(DFObjectData.AssetIndex, ReversedMipIndex);
			}
			else 
#endif
			if (DistanceField < ExpandSurfaceAmount 
				&& ReversedMipIndex == MaxMipIndex
				&& (!bDitheredTransparency || StepNoise * (1 - Coverage) <= MeshSDFDitheredTransparencyStepThreshold))
			{
				// One more step to the surface
				// Pull back by ExpandSurfaceAmount to improve the gradient computed off of the hit point
				SampleRayTime = clamp(SampleRayTime + DistanceField - ExpandSurfaceAmount, VolumeSpaceIntersectionTimes.x, VolumeSpaceIntersectionTimes.y);
				bHit = true;
				break;
			}

			float LocalMinStepSize = MinStepSize * lerp(MeshSDFNotCoveredMinStepScale, 1.0f, Coverage);
			float StepDistance = max(DistanceField, LocalMinStepSize);
			SampleRayTime += StepDistance;		

			if (SampleRayTime > VolumeSpaceIntersectionTimes.y + ExpandSurfaceAmount)
			{
				break;
			}
		}

		if (StepIndex == MaxSteps)
		{
			bHit = true;
		}

		if (bHit)
		{
			float NewHitDistance = length(VolumeRayDirection * SampleRayTime * DFObjectData.VolumeToWorldScale);

			if (NewHitDistance < TraceResult.HitDistance)
			{
				TraceResult.HitObject = ObjectIndex;
				TraceResult.HitDistance = NewHitDistance;
			}
		}
	}
}
/**
 * Sample surface cache
 */
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;
}

 

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

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值