// Copyright Epic Games, Inc. All Rights Reserved. /*============================================================================= LumenCardSceneLighting.usf =============================================================================*/ #include "../Common.ush" #include "../BRDF.ush" #include "LumenFloatQuantization.ush" #include "LumenCardCommon.ush" #include "LumenCardTile.ush" #include "LumenCardTileShadowDownsampleFactor.ush" #include "LumenSceneLighting.ush" #include "SurfaceCache/LumenSurfaceCacheSampling.ush" #include "Radiosity/LumenRadiosity.ush" #ifndef THREADGROUP_SIZE #define THREADGROUP_SIZE 1 #endif RWStructuredBuffer RWCardPageBuffer; StructuredBuffer CardPageLastUsedBuffer; StructuredBuffer CardPageHighResLastUsedBuffer; RWStructuredBuffer RWDirectLightingCardPageIndexAllocator; RWStructuredBuffer RWDirectLightingCardPageIndexData; RWStructuredBuffer RWIndirectLightingCardPageIndexAllocator; RWStructuredBuffer RWIndirectLightingCardPageIndexData; RWStructuredBuffer RWPriorityHistogram; StructuredBuffer PriorityHistogram; RWStructuredBuffer RWMaxUpdateBucket; StructuredBuffer MaxUpdateBucket; RWStructuredBuffer RWCardPageTileAllocator; StructuredBuffer CardPageTileAllocator; float MaxDistanceFromFrustumToPrioritize; uint FreezeUpdateFrame; uint CardPageNum; uint MaxDirectLightingTilesToUpdate; uint MaxIndirectLightingTilesToUpdate; float FirstClipmapWorldExtentRcp; uint IndirectLightingHistoryValid; uint NumCameraOrigins; float4 WorldCameraOrigins[LUMEN_MAX_VIEWS]; float4 CameraFrustumPlanes[LUMEN_MAX_VIEWS * LUMEN_MAX_CAMERA_PLANES]; float DirectLightingUpdateFactor; float IndirectLightingUpdateFactor; /** * Batch clear all resources required for the subsequent card context update pass */ [numthreads(THREADGROUP_SIZE, 1, 1)] void ClearCardUpdateContextCS( uint3 DispatchThreadId : SV_DispatchThreadID) { uint ElementIndex = DispatchThreadId.x; if (ElementIndex < 1) { RWDirectLightingCardPageIndexAllocator[ElementIndex] = 0; RWIndirectLightingCardPageIndexAllocator[ElementIndex] = 0; } if (ElementIndex < CARD_UPDATE_CONTEXT_MAX * MAX_UPDATE_BUCKET_STRIDE) { RWMaxUpdateBucket[ElementIndex] = 0; } if (ElementIndex < CARD_UPDATE_CONTEXT_MAX * CARD_PAGE_TILE_ALLOCATOR_STRIDE) { RWCardPageTileAllocator[ElementIndex] = 0; } if (ElementIndex < CARD_UPDATE_CONTEXT_MAX * PRIORITY_HISTOGRAM_SIZE) { RWPriorityHistogram[ElementIndex] = 0; } } uint GetMaxTilesToUpdate(uint CardUpdateContext) { return CardUpdateContext == CARD_UPDATE_CONTEXT_DIRECT_LIGHTING ? MaxDirectLightingTilesToUpdate : MaxIndirectLightingTilesToUpdate; } uint GetLastLightingUpdateFrameIndex(FLumenCardPageData CardPage, uint CardUpdateContext) { return CardUpdateContext == CARD_UPDATE_CONTEXT_DIRECT_LIGHTING ? CardPage.LastDirectLightingUpdateFrameIndex : CardPage.LastIndirectLightingUpdateFrameIndex; } uint GetUpdateFactor(uint CardUpdateContext) { return CardUpdateContext == CARD_UPDATE_CONTEXT_DIRECT_LIGHTING ? DirectLightingUpdateFactor : IndirectLightingUpdateFactor; } uint GetNumCardPageTiles(FLumenCardPageData CardPage) { return (CardPage.SizeInTexels.x * CardPage.SizeInTexels.y) / (CARD_TILE_SIZE * CARD_TILE_SIZE); } uint GetPriorityBucketIndex(FLumenCardData Card, FLumenCardPageData CardPage, uint CardPageIndex, uint CardUpdateContext) { uint LastLightingUpdateFrameIndex = GetLastLightingUpdateFrameIndex(CardPage, CardUpdateContext); const float UpdateFactor = GetUpdateFactor(CardUpdateContext); // FramesSinceLastUpdated >= 1 uint FramesSinceLastUpdated = SurfaceCacheUpdateFrameIndex - LastLightingUpdateFrameIndex; float UpdateSpeed = 1.0f; { bool bNearAnyFrustum = false; float DistanceFromViewer = 100000000.0f; for (uint ViewIndex = 0; ViewIndex < NumCameraOrigins; ViewIndex++) { float3 CardSpaceViewPosition = mul(WorldCameraOrigins[ViewIndex].xyz - Card.Origin, Card.WorldToLocalRotation); float3 CardPageLocalCenter; float3 CardPageLocalExtent; GetCardPageLocalBBox(CardPage, Card, CardPageLocalCenter, CardPageLocalExtent); DistanceFromViewer = min(DistanceFromViewer, sqrt(ComputeSquaredDistanceFromBoxToPoint(CardPageLocalCenter, CardPageLocalExtent, CardSpaceViewPosition))); // Check whether it's near frustum float3 CardPageWorldCenter = mul(Card.WorldToLocalRotation, CardPageLocalCenter) + DFFastToTranslatedWorld(Card.Origin, GetPreViewTranslation(ViewIndex)); float3 CardPageWorldExtent = mul(abs(Card.WorldToLocalRotation), CardPageLocalExtent); if (MaxDistanceFromFrustumToPrioritize >= 0.0f) { bool bNearFrustum = true; for (uint PlaneIndex = 0; PlaneIndex < LUMEN_MAX_CAMERA_PLANES; ++PlaneIndex) { float DistanceFromPageCenterToPlane = dot(CameraFrustumPlanes[LUMEN_MAX_CAMERA_PLANES * ViewIndex + PlaneIndex], float4(CardPageWorldCenter, 1.0f)); float Radius = dot(CardPageWorldExtent, abs(CameraFrustumPlanes[LUMEN_MAX_CAMERA_PLANES * ViewIndex + PlaneIndex].xyz)); bNearFrustum = bNearFrustum && (DistanceFromPageCenterToPlane <= Radius + MaxDistanceFromFrustumToPrioritize); } if (bNearFrustum) { bNearAnyFrustum = true; } } } UpdateSpeed = 1.0f / (1.0f + max(DistanceFromViewer * FirstClipmapWorldExtentRcp, 0.0f)); // Speedup updates for card pages near frustum if (bNearAnyFrustum) { UpdateSpeed *= 2.0f; } } // Update speed based on the feedback #if SURFACE_CACHE_FEEDBACK { const uint LastUsedHighResFrameIndex = CardPageHighResLastUsedBuffer[CardPageIndex]; // Override update rate for cards used recently if (LastUsedHighResFrameIndex + 2 >= SurfaceCacheUpdateFrameIndex && LastUsedHighResFrameIndex <= SurfaceCacheUpdateFrameIndex) { UpdateSpeed = 2.0f; } } #endif if (LastLightingUpdateFrameIndex == 0) { // Page wasn't ever updated FramesSinceLastUpdated = 2048; } // Map desired page update speed to buckets and place most important pages in bucket 0 uint BucketIndex = PRIORITY_HISTOGRAM_SIZE - 1 - clamp(log2(max(4.0f * FramesSinceLastUpdated * UpdateSpeed, 1.0f)), 0, PRIORITY_HISTOGRAM_SIZE - 1); return BucketIndex; } void BuildUpdatePriorityHistogram(FLumenCardData Card, FLumenCardPageData CardPage, uint CardPageIndex, uint NumCardPageTiles, uint CardUpdateContext) { uint PriorityBucketIndex = GetPriorityBucketIndex(Card, CardPage, CardPageIndex, CardUpdateContext); InterlockedAdd(RWPriorityHistogram[CardUpdateContext * PRIORITY_HISTOGRAM_SIZE + PriorityBucketIndex], NumCardPageTiles); } /** * Iterate over all pages and build a histogram of card update priorities */ [numthreads(THREADGROUP_SIZE, 1, 1)] void BuildPageUpdatePriorityHistogramCS( uint3 DispatchThreadId : SV_DispatchThreadID) { uint IndexInIndexBuffer = DispatchThreadId.x; if (IndexInIndexBuffer < CardPageNum) { uint CardPageIndex = IndexInIndexBuffer; FLumenCardPageData CardPage = GetLumenCardPageData(CardPageIndex); FLumenCardData Card = GetLumenCardData(CardPage.CardIndex); const uint NumCardPageTiles = GetNumCardPageTiles(CardPage); if (NumCardPageTiles > 0) { BuildUpdatePriorityHistogram(Card, CardPage, CardPageIndex, NumCardPageTiles, CARD_UPDATE_CONTEXT_DIRECT_LIGHTING); BuildUpdatePriorityHistogram(Card, CardPage, CardPageIndex, NumCardPageTiles, CARD_UPDATE_CONTEXT_INDIRECT_LIGHTING); } } } void SelectMaxUpdateBucket(uint CardUpdateContext) { const uint MaxTilesToUpdate = GetMaxTilesToUpdate(CardUpdateContext); uint UpdateTileSum = 0; uint PriorityBucketIndex = 0; uint PriorityBucketMaxTiles = MaxTilesToUpdate; for (; PriorityBucketIndex < PRIORITY_HISTOGRAM_SIZE; ++PriorityBucketIndex) { uint TilesPerBucket = PriorityHistogram[CardUpdateContext * PRIORITY_HISTOGRAM_SIZE + PriorityBucketIndex]; if (UpdateTileSum + TilesPerBucket >= MaxTilesToUpdate) { PriorityBucketMaxTiles = MaxTilesToUpdate - UpdateTileSum; break; } UpdateTileSum += TilesPerBucket; } RWMaxUpdateBucket[MAX_UPDATE_BUCKET_STRIDE * CardUpdateContext + 0] = PriorityBucketIndex; RWMaxUpdateBucket[MAX_UPDATE_BUCKET_STRIDE * CardUpdateContext + 1] = PriorityBucketMaxTiles; } /** * Compute max bucket histogram to update and how many tiles should be updated in that last bucket */ [numthreads(THREADGROUP_SIZE, 1, 1)] void SelectMaxUpdateBucketCS( uint3 GroupId : SV_GroupID, uint3 GroupThreadId : SV_GroupThreadID, uint3 DispatchThreadId : SV_DispatchThreadID) { if (GroupId.x == 0 && GroupThreadId.x == 0) { SelectMaxUpdateBucket(CARD_UPDATE_CONTEXT_DIRECT_LIGHTING); } else if (GroupId.x == 1 && GroupThreadId.x == 0) { SelectMaxUpdateBucket(CARD_UPDATE_CONTEXT_INDIRECT_LIGHTING); } } bool BuildCardsUpdateList( FLumenCardData Card, FLumenCardPageData CardPage, uint CardPageIndex, uint NumCardPageTiles, uint CardUpdateContext, RWStructuredBuffer RWCardPageIndexAllocator, RWStructuredBuffer RWCardPageIndexData) { const uint MaxTilesToUpdate = GetMaxTilesToUpdate(CardUpdateContext); const uint MaxUpdateBucketIndex = MaxUpdateBucket[MAX_UPDATE_BUCKET_STRIDE * CardUpdateContext + 0]; const uint MaxUpdateBucketMaxTiles = MaxUpdateBucket[MAX_UPDATE_BUCKET_STRIDE * CardUpdateContext + 1]; // Update everything up to the max selected priority bucket uint PriorityBucketIndex = GetPriorityBucketIndex(Card, CardPage, CardPageIndex, CardUpdateContext); bool bUpdateThisPage = PriorityBucketIndex <= MaxUpdateBucketIndex; if (bUpdateThisPage && PriorityBucketIndex == MaxUpdateBucketIndex) { // Can't update more than MaxUpdateBucketMaxTiles in the max bucket to preserve the general order uint NumAllocatedTilesInMaxUpdateBucket = 0; InterlockedAdd(RWCardPageTileAllocator[CARD_PAGE_TILE_ALLOCATOR_STRIDE * CardUpdateContext + 1], NumCardPageTiles, NumAllocatedTilesInMaxUpdateBucket); if (!(NumAllocatedTilesInMaxUpdateBucket + NumCardPageTiles <= MaxUpdateBucketMaxTiles)) { bUpdateThisPage = false; } } if (bUpdateThisPage) { bUpdateThisPage = false; uint NumAllocatedTiles = 0; InterlockedAdd(RWCardPageTileAllocator[CARD_PAGE_TILE_ALLOCATOR_STRIDE * CardUpdateContext + 0], NumCardPageTiles, NumAllocatedTiles); if (NumAllocatedTiles + NumCardPageTiles <= MaxTilesToUpdate) { uint NextIndex = 0; InterlockedAdd(RWCardPageIndexAllocator[0], 1, NextIndex); if (NextIndex < CardPageNum) { RWCardPageIndexData[NextIndex] = CardPageIndex; bUpdateThisPage = true; } } } return bUpdateThisPage; } /** * Iterate over all cards and pick first N for update based on the histogram max update bucket */ [numthreads(THREADGROUP_SIZE, 1, 1)] void BuildCardsUpdateListCS( uint3 DispatchThreadId : SV_DispatchThreadID) { uint IndexInIndexBuffer = DispatchThreadId.x; if (IndexInIndexBuffer < CardPageNum) { const uint CardPageIndex = IndexInIndexBuffer; FLumenCardPageData CardPage = GetLumenCardPageData(CardPageIndex); FLumenCardData Card = GetLumenCardData(CardPage.CardIndex); const uint NumCardPageTiles = GetNumCardPageTiles(CardPage); if (NumCardPageTiles > 0) { bool bUpdatedCardPage = false; // On radiosity atlas reset invalidate all history data if (IndirectLightingHistoryValid == 0) { CardPage.LastIndirectLightingUpdateFrameIndex = 0; bUpdatedCardPage = true; } if (BuildCardsUpdateList( Card, CardPage, CardPageIndex, NumCardPageTiles, CARD_UPDATE_CONTEXT_DIRECT_LIGHTING, RWDirectLightingCardPageIndexAllocator, RWDirectLightingCardPageIndexData)) { CardPage.LastDirectLightingUpdateFrameIndex = SurfaceCacheUpdateFrameIndex; CardPage.DirectLightingTemporalIndex += 1; bUpdatedCardPage = true; } if (BuildCardsUpdateList( Card, CardPage, CardPageIndex, NumCardPageTiles, CARD_UPDATE_CONTEXT_INDIRECT_LIGHTING, RWIndirectLightingCardPageIndexAllocator, RWIndirectLightingCardPageIndexData)) { CardPage.LastIndirectLightingUpdateFrameIndex = SurfaceCacheUpdateFrameIndex; CardPage.IndirectLightingTemporalIndex = CardPage.IndirectLightingTemporalIndex + 1; bUpdatedCardPage = true; } if (bUpdatedCardPage && FreezeUpdateFrame == 0) { SetCardPageUpdateData(CardPageIndex, CardPage); } } } } RWBuffer RWDirectLightingDrawCardPageIndicesIndirectArgs; RWBuffer RWDirectLightingDispatchCardPageIndicesIndirectArgs; RWBuffer RWIndirectLightingDrawCardPageIndicesIndirectArgs; RWBuffer RWIndirectLightingDispatchCardPageIndicesIndirectArgs; StructuredBuffer DirectLightingCardPageIndexAllocator; StructuredBuffer IndirectLightingCardPageIndexAllocator; uint VertexCountPerInstanceIndirect; [numthreads(THREADGROUP_SIZE, 1, 1)] void SetCardPageIndexIndirectArgsCS(uint3 DispatchThreadId : SV_DispatchThreadID) { if (DispatchThreadId.x == 0) { { uint NumPageIndices = DirectLightingCardPageIndexAllocator[0]; // FRHIDrawIndirectParameters RWDirectLightingDrawCardPageIndicesIndirectArgs[0] = VertexCountPerInstanceIndirect; RWDirectLightingDrawCardPageIndicesIndirectArgs[1] = NumPageIndices; RWDirectLightingDrawCardPageIndicesIndirectArgs[2] = 0; RWDirectLightingDrawCardPageIndicesIndirectArgs[3] = 0; // Thread per page WriteDispatchIndirectArgs(RWDirectLightingDispatchCardPageIndicesIndirectArgs, 0, DivideAndRoundUp64(NumPageIndices), 1, 1); // Thread per tile WriteDispatchIndirectArgs(RWDirectLightingDispatchCardPageIndicesIndirectArgs, 1, 4 * NumPageIndices, 1, 1); } { uint NumPageIndices = IndirectLightingCardPageIndexAllocator[0]; // FRHIDrawIndirectParameters RWIndirectLightingDrawCardPageIndicesIndirectArgs[0] = VertexCountPerInstanceIndirect; RWIndirectLightingDrawCardPageIndicesIndirectArgs[1] = NumPageIndices; RWIndirectLightingDrawCardPageIndicesIndirectArgs[2] = 0; RWIndirectLightingDrawCardPageIndicesIndirectArgs[3] = 0; // Thread per page WriteDispatchIndirectArgs(RWIndirectLightingDispatchCardPageIndicesIndirectArgs, 0, DivideAndRoundUp64(NumPageIndices), 1, 1); // Thread per tile WriteDispatchIndirectArgs(RWIndirectLightingDispatchCardPageIndicesIndirectArgs, 1, 4 * NumPageIndices, 1, 1); } } } RWStructuredBuffer RWQuadAllocator; RWStructuredBuffer RWQuadData; StructuredBuffer CardPageIndexAllocator; StructuredBuffer CardPageIndexData; float2 IndirectLightingAtlasSize; void RasterizeToCardsVS( uint VertexId : SV_VertexID, uint InstanceId : SV_InstanceID, out FCardVSToPS CardInterpolants, out float4 OutPosition : SV_POSITION ) { float2 TexCoord = float2(0.0f, 0.0f); TexCoord.x += VertexId == 1 || VertexId == 2 || VertexId == 4 ? 1.0f : 0.0f; TexCoord.y += VertexId == 2 || VertexId == 4 || VertexId == 5 ? 1.0f : 0.0f; uint QuadIndex = InstanceId.x; CardInterpolants = (FCardVSToPS)0; OutPosition = 0; if (QuadIndex < CardPageIndexAllocator[0]) { uint CardPageIndex = CardPageIndexData[QuadIndex]; FLumenCardPageData CardPage = GetLumenCardPageData(CardPageIndex); FLumenCardData Card = GetLumenCardData(CardPage.CardIndex); float2 ScreenUV = lerp(CardPage.PhysicalAtlasUVRect.xy, CardPage.PhysicalAtlasUVRect.zw, TexCoord); float2 AtlasUV = ScreenUV; #if RADIOSITY_ATLAS_DOWNSAMPLE_FACTOR == 1 float2 IndirectLightingAtlasUV = AtlasUV; #else // When sampling from a downsampled Indirect Lighting atlas we need to appropriately clamp input UVs to prevent bilinear reading outside of the valid area float2 CardWidthInTexels = (CardPage.PhysicalAtlasUVRect.zw - CardPage.PhysicalAtlasUVRect.xy) * IndirectLightingAtlasSize; float2 ClampBorder = 0.5f / CardWidthInTexels; float2 IndirectLightingTexCoord = clamp(TexCoord, ClampBorder, 1.0f - ClampBorder); float2 IndirectLightingAtlasUV = lerp(CardPage.PhysicalAtlasUVRect.xy, CardPage.PhysicalAtlasUVRect.zw, IndirectLightingTexCoord); #endif float2 ScreenPosition = float2(2.0f, -2.0f) * ScreenUV + float2(-1.0f, 1.0f); OutPosition = float4(ScreenPosition, 0, 1); float2 QuadCorner = -2.0f * TexCoord + 1.0f; CardInterpolants.AtlasUV = AtlasUV; CardInterpolants.IndirectLightingAtlasUV = IndirectLightingAtlasUV; CardInterpolants.CardUV = lerp(CardPage.CardUVRect.xy, CardPage.CardUVRect.zw, TexCoord); CardInterpolants.CardTileIndex = 0; CardInterpolants.CardPageIndex = CardPageIndex; } } SamplerState BilinearClampedSampler; StructuredBuffer CardTiles; RWTexture2D RWFinalLightingAtlas; float2 IndirectLightingAtlasHalfTexelSize; [numthreads(CARD_TILE_SIZE, CARD_TILE_SIZE, 1)] void CombineLumenSceneLightingCS( uint3 GroupId : SV_GroupID, uint3 GroupThreadId : SV_GroupThreadID) { uint CardTileIndex = GroupId.x; uint2 TexelCoordInTile = GroupThreadId.xy; FCardTileData CardTile = UnpackCardTileData(CardTiles[CardTileIndex]); FLumenCardPageData CardPage = GetLumenCardPageData(CardTile.CardPageIndex); FLumenCardData Card = GetLumenCardData(CardPage.CardIndex); uint2 CoordInCardPage = CARD_TILE_SIZE * CardTile.TileCoord + TexelCoordInTile; uint2 AtlasCoord = CardPage.PhysicalAtlasCoord + CoordInCardPage; float2 AtlasUV = CardPage.PhysicalAtlasUVRect.xy + CardPage.PhysicalAtlasUVTexelScale * (CoordInCardPage + 0.5); #if RADIOSITY_ATLAS_DOWNSAMPLE_FACTOR == 1 float2 IndirectLightingAtlasUV = AtlasUV; #else // When sampling from a downsampled Indirect Lighting atlas we need to appropriately clamp input UVs to prevent bilinear reading outside of the valid area float2 IndirectLightingAtlasUV = clamp(AtlasUV, CardPage.PhysicalAtlasUVRect.xy + IndirectLightingAtlasHalfTexelSize, CardPage.PhysicalAtlasUVRect.zw - IndirectLightingAtlasHalfTexelSize); #endif float3 Albedo = Texture2DSampleLevel(AlbedoAtlas, BilinearClampedSampler, AtlasUV, 0).xyz; float3 Emissive = Texture2DSampleLevel(EmissiveAtlas, BilinearClampedSampler, AtlasUV, 0).xyz; float3 DirectLighting = Texture2DSampleLevel(DirectLightingAtlas, BilinearClampedSampler, AtlasUV, 0).xyz; float3 IndirectLighting = Texture2DSampleLevel(IndirectLightingAtlas, BilinearClampedSampler, IndirectLightingAtlasUV, 0).xyz; float3 FinalLighting = CombineFinalLighting(Albedo, Emissive, DirectLighting, IndirectLighting); RWFinalLightingAtlas[AtlasCoord] = QuantizeForFloatRenderTarget(FinalLighting, int3(AtlasCoord, View.StateFrameIndexMod8 + 2)); } void ClearLumenCardsPS( out float4 Target0 : SV_Target0 #if NUM_TARGETS > 1 ,out float4 Target1 : SV_Target1 #endif ) { Target0 = float4(0.0f, 0.0f, 0.0f, 0.0f); #if NUM_TARGETS > 1 Target1 = float4(0.0f, 0.0f, 0.0f, 0.0f); #endif } void ClearLumenCardCapturePS( out float4 OutAlbedo : SV_Target0, out float4 OutNormals : SV_Target1, out float4 OutEmissive : SV_Target2 ) { OutAlbedo = float4(0.0f, 0.0f, 0.0f, 0.0f); OutNormals = float4(0.5f, 0.5f, 0.0f, 0.0f); OutEmissive = float4(0.0f, 0.0f, 0.0f, 0.0f); } #ifdef ResampleLightingHistoryToCardCaptureAtlasCS #define RESAMPLE_TILE_SHADOW_DOWNSAMPLE_FACTOR (FEATURE_LEVEL >= FEATURE_LEVEL_SM6 || PLATFORM_SUPPORTS_SM6_0_WAVE_OPERATIONS) Buffer NewCardTileResampleData; Buffer NewCardPageResampleData; Buffer RectCoordBuffer; Texture2D RadiosityNumFramesAccumulatedAtlas; RWTexture2D RWDirectLightingCardCaptureAtlas; RWTexture2D RWRadiosityCardCaptureAtlas; RWTexture2D RWRadiosityNumFramesAccumulatedCardCaptureAtlas; uint CardCaptureAtlasWidthInTiles; [numthreads(CARD_TILE_SIZE, CARD_TILE_SIZE, 1)] void ResampleLightingHistoryToCardCaptureAtlasCS(uint3 GroupId : SV_GroupID, uint3 GroupThreadId : SV_GroupThreadID, uint GroupThreadIndex : SV_GroupIndex) { const uint TileIndex = GroupId.x; const uint PackedTileData = NewCardTileResampleData[TileIndex]; const uint2 TileCoordInRect = uint2(BitFieldExtractU32(PackedTileData, 4, 0), BitFieldExtractU32(PackedTileData, 4, 4)); const uint RectIndex = BitFieldExtractU32(PackedTileData, 24, 8); const uint4 RectCoord = RectCoordBuffer[RectIndex]; const uint2 TexelCoordInRect = TileCoordInRect * CARD_TILE_SIZE + GroupThreadId.xy; const uint2 RectSize = RectCoord.zw - RectCoord.xy; const float2 RectUV = (TexelCoordInRect + float2(0.5, 0.5)) / RectSize; float3 OutDirectLightingCardCaptureAtlas = 0; float3 OutRadiosityCardCaptureAtlas = 0; float OutRadiosityNumFramesAccumulatedCardCaptureAtlas = 0; uint4 ShadowDownsampleFactorLow = 0; uint4 ShadowDownsampleFactorHigh = 0; uint CardIndex = NewCardPageResampleData[RectIndex * 2 + 0].x; if (CardIndex != LUMEN_INVALID_CARD_INDEX) { // Load new card attributes manually float4 NewCardUVRect = asfloat(NewCardPageResampleData[RectIndex * 2 + 1]); float2 NewCardUV = RectUV * (NewCardUVRect.zw - NewCardUVRect.xy) + NewCardUVRect.xy; // LumenCardScene contains the old card structure during the resample FLumenCardData OldCard = GetLumenCardData(CardIndex); // Assuming card extent hasn't changed float2 LocalSamplePosition = GetCardLocalPosition(OldCard.LocalExtent, NewCardUV, 0.0f).xy; FLumenCardSample CardSample = ComputeSurfaceCacheSample(OldCard, CardIndex, LocalSamplePosition, 0.0f, true); if (CardSample.bValid) { OutDirectLightingCardCaptureAtlas = Texture2DSampleLevel(DirectLightingAtlas, GlobalBilinearClampedSampler, CardSample.PhysicalAtlasUV, 0.0f).xyz; OutRadiosityCardCaptureAtlas = Texture2DSampleLevel(IndirectLightingAtlas, GlobalBilinearClampedSampler, CardSample.IndirectLightingPhysicalAtlasUV, 0.0f).xyz; OutRadiosityNumFramesAccumulatedCardCaptureAtlas = Texture2DSampleLevel(RadiosityNumFramesAccumulatedAtlas, GlobalBilinearClampedSampler, CardSample.IndirectLightingPhysicalAtlasUV, 0.0f).x; #if RESAMPLE_TILE_SHADOW_DOWNSAMPLE_FACTOR const uint2 TileCoord = CardSample.PhysicalAtlasUV * LumenCardScene.PhysicalAtlasSize / CARD_TILE_SIZE; const uint PhysicalAtlasWidthInTiles = LumenCardScene.PhysicalAtlasSize.x / CARD_TILE_SIZE; LoadLumenCardTileShadowDownsampleFactor(ShadowDownsampleFactorLow, ShadowDownsampleFactorHigh, TileCoord, PhysicalAtlasWidthInTiles); #endif } } const uint2 WriteTexelCoord = RectCoord.xy + TexelCoordInRect; const uint2 WriteTileCoord = WriteTexelCoord / CARD_TILE_SIZE; RWDirectLightingCardCaptureAtlas[WriteTexelCoord] = float4(OutDirectLightingCardCaptureAtlas, 0.f); RWRadiosityCardCaptureAtlas[WriteTexelCoord] = float4(OutRadiosityCardCaptureAtlas, 0.f); RWRadiosityNumFramesAccumulatedCardCaptureAtlas[WriteTexelCoord] = OutRadiosityNumFramesAccumulatedCardCaptureAtlas; #if RESAMPLE_TILE_SHADOW_DOWNSAMPLE_FACTOR ShadowDownsampleFactorLow = WaveActiveBitAnd(ShadowDownsampleFactorLow); ShadowDownsampleFactorHigh = WaveActiveBitAnd(ShadowDownsampleFactorHigh); #endif if (GroupThreadIndex == 0) { WriteLumenCardTileShadowDownsampleFactor(ShadowDownsampleFactorLow, ShadowDownsampleFactorHigh, WriteTileCoord, CardCaptureAtlasWidthInTiles); } } #endif Texture2D AlbedoCardCaptureAtlas; Texture2D EmissiveCardCaptureAtlas; Texture2D DirectLightingCardCaptureAtlas; Texture2D RadiosityCardCaptureAtlas; Texture2D RadiosityNumFramesAccumulatedCardCaptureAtlas; uint2 CardCaptureAtlasSizeInTiles; uint OutputAtlasWidthInTiles; void CopyCardCaptureLightingToAtlasPS( float4 SvPosition : SV_POSITION, float2 AtlasUV : TEXCOORD0, out float3 OutDirectLightingAtlas : SV_Target0, out float3 OutFinalLightingAtlas : SV_Target1 #if INDIRECT_LIGHTING , out float3 OutRadiosityAtlas : SV_Target2 , out float OutRadiosityNumFramesAccumulatedAtlas : SV_Target3 #endif ) { float3 Albedo = 0.0f; float3 Emissive = 0.0f; float3 DirectLighting = 0.0f; float3 IndirectLighting = 0.0f; float RadiosityNumFramesAccumulated = 0.0f; uint4 ShadowDownsampleFactorLow = 0; uint4 ShadowDownsampleFactorHigh = 0; #if RESAMPLE { Albedo = Texture2DSampleLevel(AlbedoCardCaptureAtlas, GlobalPointClampedSampler, AtlasUV, 0).xyz; Emissive = Texture2DSampleLevel(EmissiveCardCaptureAtlas, GlobalPointClampedSampler, AtlasUV, 0).xyz; DirectLighting = Texture2DSampleLevel(DirectLightingCardCaptureAtlas, GlobalPointClampedSampler, AtlasUV, 0).xyz; IndirectLighting = Texture2DSampleLevel(RadiosityCardCaptureAtlas, GlobalPointClampedSampler, AtlasUV, 0).xyz; RadiosityNumFramesAccumulated = Texture2DSampleLevel(RadiosityNumFramesAccumulatedCardCaptureAtlas, GlobalPointClampedSampler, AtlasUV, 0).x; const uint2 TileCoord = AtlasUV * CardCaptureAtlasSizeInTiles; LoadLumenCardTileShadowDownsampleFactor(ShadowDownsampleFactorLow, ShadowDownsampleFactorHigh, TileCoord, CardCaptureAtlasSizeInTiles.x); } #endif OutDirectLightingAtlas = DirectLighting; OutFinalLightingAtlas = CombineFinalLighting(Albedo, Emissive, DirectLighting, IndirectLighting); #if INDIRECT_LIGHTING { OutRadiosityAtlas = IndirectLighting; OutRadiosityNumFramesAccumulatedAtlas = RadiosityNumFramesAccumulated; } #endif WriteLumenCardTileShadowDownsampleFactor(ShadowDownsampleFactorLow, ShadowDownsampleFactorHigh, SvPosition.xy / CARD_TILE_SIZE, OutputAtlasWidthInTiles); }