670 lines
23 KiB
HLSL
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);
|
|
} |