Files
UnrealEngine/Engine/Shaders/Private/Lumen/SurfaceCache/LumenSurfaceCacheSampling.ush
2025-05-18 13:04:45 +08:00

670 lines
23 KiB
HLSL

// Copyright Epic Games, Inc. All Rights Reserved.
#pragma once
#include "LumenSurfaceCache.ush"
#include "../LumenCardCommon.ush"
Texture2D DirectLightingAtlas;
Texture2D IndirectLightingAtlas;
Texture2D FinalLightingAtlas;
Texture2D AlbedoAtlas;
Texture2D OpacityAtlas;
Texture2D NormalAtlas;
Texture2D EmissiveAtlas;
Texture2D DepthAtlas;
float OneOverCachedLightingPreExposure;
#if ENABLE_VISUALIZE_MODE == 1
#include "../LumenCardTileShadowDownsampleFactor.ush"
#include "../../Visualization.ush"
#define VISUALIZE_MODE_DISABLE 0
#define VISUALIZE_MODE_OVERVIEW 1
#define VISUALIZE_MODE_PERFORMANCE_OVERVIEW 2
#define VISUALIZE_MODE_LUMEN_SCENE 3
#define VISUALIZE_MODE_REFLECTION_VIEW 4
#define VISUALIZE_MODE_SURFACE_CACHE 5
#define VISUALIZE_MODE_GEOMETRY_NORMALS 6
#define VISUALIZE_MODE_DEDICATED_REFLECTION_RAYS 7
#define VISUALIZE_MODE_ALBEDO 8
#define VISUALIZE_MODE_NORMALS 9
#define VISUALIZE_MODE_EMISSIVE 10
#define VISUALIZE_MODE_OPACITY 11
#define VISUALIZE_MODE_CARD_WEIGHTS 12
#define VISUALIZE_MODE_DIRECT_LIGHTING 13
#define VISUALIZE_MODE_INDIRECT_LIGHTING 14
#define VISUALIZE_MODE_LOCAL_POSITION 15
#define VISUALIZE_MODE_VELOCITY 16
#define VISUALIZE_MODE_DIRECT_LIGHTING_UPDATES 17
#define VISUALIZE_MODE_INDIRECT_LIGHTING_UPDATES 18
#define VISUALIZE_MODE_LAST_USED_PAGE 19
#define VISUALIZE_MODE_LAST_USED_PAGE_HIGH_RES 20
#define VISUALIZE_MODE_TILE_SHADOW_DOWNSAMPLE_FACTOR 21
#define VISUALIZE_MODE_CARD_SHARING_ID 22
#define VISUALIZE_MODE_SCREENPROBEGATHER_FAST_UPDATE_MODE_AMOUNT 23
#define VISUALIZE_MODE_SCREENPROBEGATHER_NUM_FRAMES_ACCUMULATED 24
uint VisualizeMode;
Buffer<uint> MeshCardsIndexToCardSharingIdBuffer;
#endif
uint SurfaceCacheUpdateFrameIndex;
#if SURFACE_CACHE_FEEDBACK
uint SurfaceCacheFeedbackBufferSize;
RWStructuredBuffer<uint> RWCardPageLastUsedBuffer;
RWStructuredBuffer<uint> RWCardPageHighResLastUsedBuffer;
RWStructuredBuffer<uint> RWSurfaceCacheFeedbackBufferAllocator;
RWStructuredBuffer<uint2> RWSurfaceCacheFeedbackBuffer;
uint SurfaceCacheFeedbackBufferTileWrapMask;
uint2 SurfaceCacheFeedbackBufferTileJitter;
float SurfaceCacheFeedbackResLevelBias;
#endif
struct FLumenCardSample
{
uint CardIndex;
uint CardPageIndex;
float2 PhysicalAtlasUV;
float4 TexelBilinearWeights;
float2 IndirectLightingPhysicalAtlasUV;
uint2 PackedFeedback;
bool bValid;
};
// Must match Lumen::PhysicalPageSize and Lumen::VirtualPageSize in Lumen.h
#define VIRTUAL_PAGE_SIZE 127
#define PHYSICAL_PAGE_SIZE 128
#define MIN_CARD_RESOLUTION 8
#define MIN_RES_LEVEL 3 // 2^3 = MinCardResolution
#define MAX_RES_LEVEL 11
#define SUB_ALLOCATION_RES_LEVEL 7 // = log2(PHYSICAL_PAGE_SIZE)
uint2 ResLevelXYToSizeInPages(uint2 ResLevelXY)
{
return select(ResLevelXY > SUB_ALLOCATION_RES_LEVEL, 1u << (ResLevelXY - SUB_ALLOCATION_RES_LEVEL), 1);
}
uint2 GetSizeInPages(FLumenCardData Card, uint ResLevel)
{
uint2 ResLevelXY = ResLevel - Card.ResLevelToResLevelXYBias;
return ResLevelXYToSizeInPages(ResLevelXY);
}
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;
float2 IndirectLightingPhysicalAtlasUV = PhysicalAtlasUV;
// 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;
}
struct FCardSampleAccumulator
{
// Single stochastically selected sample
FLumenCardSample CardSample;
bool bHeightfield;
bool bValidMeshCardsIndex;
float MaxSampleWeight;
// Accumulated
float OpacitySum;
float3 DirectLightingSum;
float3 IndirectLightingSum;
float3 FinalLightingSum;
float SampleWeightSum;
};
void InitCardSampleAccumulator(inout FCardSampleAccumulator CardSampleAccumulator)
{
CardSampleAccumulator.MaxSampleWeight = 0.0f;
CardSampleAccumulator.OpacitySum = 0.0f;
CardSampleAccumulator.DirectLightingSum = 0.0f;
CardSampleAccumulator.IndirectLightingSum = 0.0f;
CardSampleAccumulator.FinalLightingSum = 0.0f;
CardSampleAccumulator.SampleWeightSum = 0.0f;
CardSampleAccumulator.CardSample = (FLumenCardSample) 0;
CardSampleAccumulator.CardSample.bValid = false;
CardSampleAccumulator.bHeightfield = false;
CardSampleAccumulator.bValidMeshCardsIndex = false;
}
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;
}
void SampleLumenCard(
float3 MeshCardsSpacePosition,
float3 MeshCardsSpaceNormal,
float SampleRadius,
float SurfaceCacheBias,
uint CardIndex,
float3 AxisWeights,
bool bHiResSurface,
bool bHeightfield,
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 DirectLighting = SampleSurfaceCacheAtlas(DirectLightingAtlas, CardSample.PhysicalAtlasUV, TexelWeights) * OneOverCachedLightingPreExposure;
float3 IndirectLighting = SampleSurfaceCacheAtlas(IndirectLightingAtlas, CardSample.IndirectLightingPhysicalAtlasUV, TexelWeights) * OneOverCachedLightingPreExposure;
float3 FinalLighting = SampleSurfaceCacheAtlas(FinalLightingAtlas, CardSample.PhysicalAtlasUV, TexelWeights) * OneOverCachedLightingPreExposure;
// Debug visualization
#if ENABLE_VISUALIZE_MODE == 1
{
if (VisualizeMode == VISUALIZE_MODE_DIRECT_LIGHTING)
{
FinalLighting = DirectLighting;
}
else if (VisualizeMode == VISUALIZE_MODE_INDIRECT_LIGHTING)
{
FinalLighting = IndirectLighting;
}
else if (VisualizeMode == VISUALIZE_MODE_ALBEDO || VisualizeMode == VISUALIZE_MODE_CARD_SHARING_ID)
{
float3 Albedo = DecodeSurfaceCacheAlbedo(SampleSurfaceCacheAtlas(AlbedoAtlas, CardSample.PhysicalAtlasUV, TexelWeights));
FinalLighting = Albedo * View.OneOverPreExposure;
}
else if (VisualizeMode == VISUALIZE_MODE_NORMALS)
{
FLumenCardData Card = GetLumenCardData(CardSample.CardIndex);
float3 WorldSpaceNormal = DecodeSurfaceCacheNormal(Card, SampleSurfaceCacheAtlas(NormalAtlas, CardSample.PhysicalAtlasUV, TexelWeights).xy);
FinalLighting = (WorldSpaceNormal * 0.5f + 0.5f) * View.OneOverPreExposure;
}
else if (VisualizeMode == VISUALIZE_MODE_EMISSIVE)
{
float3 Emissive = SampleSurfaceCacheAtlas(EmissiveAtlas, CardSample.PhysicalAtlasUV, TexelWeights) * OneOverCachedLightingPreExposure;
FinalLighting = Emissive;
}
else if (VisualizeMode == VISUALIZE_MODE_OPACITY)
{
FinalLighting = 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));
FinalLighting = RandomColor * View.OneOverPreExposure;
}
else if (VisualizeMode == VISUALIZE_MODE_TILE_SHADOW_DOWNSAMPLE_FACTOR)
{
uint2 TileCoordInAtlas = uint2(CardSample.PhysicalAtlasUV * LumenCardScene.PhysicalAtlasSize / 8);
uint AtlasWidthInTiles = LumenCardScene.PhysicalAtlasSize.x / 8;
float3 TileColor = GetLumenCardTileShadowDownsampleFactorVisualizationColor(TileCoordInAtlas, AtlasWidthInTiles);
// UV Grid overlay
float2 UVGrid = frac(CardSample.PhysicalAtlasUV * LumenCardScene.PhysicalAtlasSize / 8.0f);
UVGrid = abs(UVGrid * 2 - 1);
UVGrid = select(UVGrid > 0.98, float2(0, 0), float2(1, 1));
FinalLighting = View.OneOverPreExposure * UVGrid.x * UVGrid.y * TileColor;
}
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 *= 2.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);
}
FinalLighting = VisColor * View.OneOverPreExposure;
}
}
#endif
CardSampleAccumulator.OpacitySum += Opacity * CardSampleWeight;
CardSampleAccumulator.DirectLightingSum += DirectLighting * CardSampleWeight;
CardSampleAccumulator.IndirectLightingSum += IndirectLighting * CardSampleWeight;
CardSampleAccumulator.FinalLightingSum += FinalLighting * CardSampleWeight;
CardSampleAccumulator.SampleWeightSum += CardSampleWeight;
// Pick single sample based on the max weight
if (CardSampleWeight > CardSampleAccumulator.MaxSampleWeight)
{
CardSampleAccumulator.CardSample = CardSample;
CardSampleAccumulator.MaxSampleWeight = CardSampleWeight;
}
}
}
}
}
}
}
}
/**
* Sample surface cads, by accumulating cards samples into CardSampleAccumulator
*/
void SampleLumenMeshCards(
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)
inout FCardSampleAccumulator CardSampleAccumulator
)
{
if (MeshCardsIndex < LumenCardScene.NumMeshCards)
{
FLumenMeshCardsData MeshCardsData = GetLumenMeshCardsData(MeshCardsIndex);
if (MeshCardsData.bMostlyTwoSided)
{
// Bump bias on bMostlyTwoSided (foliage), where hits aren't very reliable
SurfaceCacheBias += 50.0f;
}
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);
}
// 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,
CardSampleAccumulator);
}
}
CardSampleAccumulator.bHeightfield = CardSampleAccumulator.bHeightfield || MeshCardsData.bHeightfield;
CardSampleAccumulator.bValidMeshCardsIndex = true;
// Debug visualization
#if ENABLE_VISUALIZE_MODE == 1
if (VisualizeMode == VISUALIZE_MODE_CARD_SHARING_ID)
{
if (CardSampleAccumulator.SampleWeightSum <= 0.0f)
{
CardSampleAccumulator.SampleWeightSum = 1.0f;
}
uint CardSharingId = MeshCardsIndexToCardSharingIdBuffer[MeshCardsIndex];
float3 DebugColor;
if (CardSharingId == 0xffffffff)
{
DebugColor = 0.0f;
}
else
{
DebugColor = IntToColor(CardSharingId) * View.OneOverPreExposure * CardSampleAccumulator.SampleWeightSum;
}
CardSampleAccumulator.FinalLightingSum = lerp(CardSampleAccumulator.FinalLightingSum, DebugColor, 0.9f);
}
#endif
}
}
struct FSurfaceCacheSample
{
float3 Radiance;
float3 DirectLighting;
float3 IndirectLighting;
float Opacity;
bool bValid;
bool bHeightfield;
};
FSurfaceCacheSample InitSurfaceCacheSample()
{
FSurfaceCacheSample SurfaceCacheSample;
SurfaceCacheSample.Radiance = 0.0f;
SurfaceCacheSample.DirectLighting = 0.0f;
SurfaceCacheSample.IndirectLighting = 0.0f;
SurfaceCacheSample.Opacity = 0.0f;
SurfaceCacheSample.bValid = false;
SurfaceCacheSample.bHeightfield = false;
return SurfaceCacheSample;
}
FSurfaceCacheSample EvaluateRayHitFromCardSampleAccumulator(
uint2 DitherScreenCoord,
float3 HitWorldPosition,
float3 HitWorldNormal,
FCardSampleAccumulator CardSampleAccumulator)
{
FSurfaceCacheSample SurfaceCacheSample = InitSurfaceCacheSample();
SurfaceCacheSample.bHeightfield = CardSampleAccumulator.bHeightfield;
if (CardSampleAccumulator.SampleWeightSum > 0.0f)
{
SurfaceCacheSample.Opacity = CardSampleAccumulator.OpacitySum / CardSampleAccumulator.SampleWeightSum;
SurfaceCacheSample.bValid = true;
SurfaceCacheSample.Radiance = CardSampleAccumulator.FinalLightingSum / CardSampleAccumulator.SampleWeightSum;
SurfaceCacheSample.DirectLighting = CardSampleAccumulator.DirectLightingSum / CardSampleAccumulator.SampleWeightSum;
SurfaceCacheSample.IndirectLighting = CardSampleAccumulator.IndirectLightingSum / CardSampleAccumulator.SampleWeightSum;
#if SURFACE_CACHE_FEEDBACK
{
// Write every n-th element
if (all((DitherScreenCoord & 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_SURFACE_CACHE || VisualizeMode == VISUALIZE_MODE_OPACITY)
&& CardSampleAccumulator.SampleWeightSum <= 0.0f)
{
SurfaceCacheSample.Radiance = (CardSampleAccumulator.bValidMeshCardsIndex ? float3(1, 0, 1) : float3(1, 1, 0)) * View.OneOverPreExposure;
}
if (VisualizeMode == VISUALIZE_MODE_GEOMETRY_NORMALS)
{
SurfaceCacheSample.Radiance = (HitWorldNormal * 0.5f + 0.5f) * View.OneOverPreExposure;
SurfaceCacheSample.Opacity = 1.0f;
SurfaceCacheSample.bValid = true;
}
}
#endif
return SurfaceCacheSample;
}
/**
* Calculate and return radiance for a hit point using surface cache and radiance field.
*/
FSurfaceCacheSample EvaluateRayHitFromSurfaceCache(
uint2 DitherScreenCoord,
uint MeshCardsIndex,
float3 HitWorldPosition,
float3 HitWorldNormal,
float SampleRadius,
float SurfaceCacheBias,
bool bHiResSurface)
{
FCardSampleAccumulator CardSampleAccumulator;
InitCardSampleAccumulator(CardSampleAccumulator);
SampleLumenMeshCards(
MeshCardsIndex,
HitWorldPosition,
HitWorldNormal,
SampleRadius,
SurfaceCacheBias,
bHiResSurface,
/*inout*/ CardSampleAccumulator);
return EvaluateRayHitFromCardSampleAccumulator(
DitherScreenCoord,
HitWorldPosition,
HitWorldNormal,
CardSampleAccumulator);
}