Files
UnrealEngine/Engine/Shaders/Private/MegaLights/MegaLightsSampling.usf
2025-05-18 13:04:45 +08:00

675 lines
27 KiB
HLSL

// Copyright Epic Games, Inc. All Rights Reserved.
#ifndef NUM_SAMPLES_PER_PIXEL_1D
#define NUM_SAMPLES_PER_PIXEL_1D 1
#define NUM_SAMPLES_PER_PIXEL_2D_X 1
#define NUM_SAMPLES_PER_PIXEL_2D_Y 1
#endif
#ifndef TILE_TYPE
#define TILE_TYPE TILE_MODE_EMPTY
#endif
// When loading SSS checkerboard pixel, do not adjust DiffuseColor/SpecularColor to preserve specular and diffuse lighting values for each pixel
#define ALLOW_SSS_MATERIAL_OVERRIDE 0
// Disable hair backlit during sample generation as Hair's TT term creates too much false positive:
// the TT terms is very strong, and is in most case occluded by underlying geometry (head/body/...)
#define HAIR_BSDF_BACKLIT 0
#define USE_IES_PROFILE 1
#define USE_LIGHT_FUNCTION_ATLAS 1
#include "../Common.ush"
#include "/Engine/Shared/MegaLightsDefinitions.h"
// Substrate tile for faster shading
#if SUBSTRATE_ENABLED
#if TILE_TYPE == TILE_MODE_SIMPLE_SHADING || TILE_TYPE == TILE_MODE_SIMPLE_SHADING_RECT || TILE_TYPE == TILE_MODE_SIMPLE_SHADING_RECT_TEXTURED || TILE_TYPE == TILE_MODE_EMPTY
#define SUBSTRATE_FASTPATH 1
#define SUBSTRATE_SINGLEPATH 0
#define SUBSTRATE_COMPLEXSPECIALPATH 0
#elif TILE_TYPE == TILE_MODE_SINGLE_SHADING || TILE_TYPE == TILE_MODE_SINGLE_SHADING_RECT || TILE_TYPE == TILE_MODE_SINGLE_SHADING_RECT_TEXTURED
#define SUBSTRATE_FASTPATH 0
#define SUBSTRATE_SINGLEPATH 1
#define SUBSTRATE_COMPLEXSPECIALPATH 0
#elif TILE_TYPE == TILE_MODE_COMPLEX_SHADING || TILE_TYPE == TILE_MODE_COMPLEX_SHADING_RECT || TILE_TYPE == TILE_MODE_COMPLEX_SHADING_RECT_TEXTURED
#define SUBSTRATE_FASTPATH 0
#define SUBSTRATE_SINGLEPATH 0
#define SUBSTRATE_COMPLEXSPECIALPATH 0
#else // Special TILE_MODE_COMPLEX_SPECIAL_SHADING || TILE_MODE_COMPLEX_SPECIAL_SHADING_RECT || TILE_MODE_COMPLEX_SPECIAL_SHADING_RECT_TEXTURED
#define SUBSTRATE_FASTPATH 0
#define SUBSTRATE_SINGLEPATH 0
#define SUBSTRATE_COMPLEXSPECIALPATH 1
#endif
#endif
#if SUBSTRATE_FASTPATH || SUBSTRATE_SINGLEPATH
#define SUBSTRATE_LOAD_FROM_MATERIALCONTAINER 0
#endif
#include "../BlueNoise.ush"
#include "MegaLightsShading.ush"
#include "MegaLightsRayTracing.ush"
#include "MegaLightsSampling.ush"
#include "../LightFunctionAtlas/LightFunctionAtlasCommon.usf"
// Allow register spilling on certain platform to avoid shader compilation issue
#if SUBSTRATE_ENABLED
#pragma warning(disable:7203)
#endif
ADAPTIVE_LICM
struct FLightTargetPDF
{
float Weight;
};
FLightTargetPDF InitLightTargetPDF()
{
FLightTargetPDF LightTargetPDF;
LightTargetPDF.Weight = 0.0f;
return LightTargetPDF;
}
FLightTargetPDF GetLocalLightTargetPDF(FDeferredLightData LightData, float3 TranslatedWorldPosition, FMegaLightsMaterial Material, uint2 ScreenCoord, inout FShaderPrintContext DebugContext)
{
float3 CameraVector = normalize(TranslatedWorldPosition - View.TranslatedWorldCameraOrigin);
float4 LightAttenuation = 1.0f;
float Dither = 0.5f;
float SurfaceShadow = 1.0f;
float AmbientOcclusion = 1.0f;
LightData.ShadowedBits = 0;
#if INPUT_TYPE == INPUT_TYPE_HAIRSTRANDS
if (Material.bIsHair)
{
LightData.HairTransmittance = EvaluateDualScattering(Material.GBuffer, CameraVector, -LightData.Direction);
}
#endif
FDeferredLightingSplit SplitLighting = GetMegaLightsSplitLighting(
TranslatedWorldPosition, CameraVector, Material, AmbientOcclusion,
LightData, LightAttenuation, Dither, ScreenCoord,
SurfaceShadow);
//float Lum = Luminance(SplitLighting.DiffuseLighting.xyz + SplitLighting.SpecularLighting.xyz) * View.PreExposure;
float Lum = SplitLighting.LightingLuminance * View.PreExposure;
if (LightData.IESAtlasIndex >= 0 && Lum > 0.01f)
{
Lum *= ComputeLightProfileMultiplier(TranslatedWorldPosition, LightData.TranslatedWorldPosition, -LightData.Direction, LightData.Tangent, LightData.IESAtlasIndex);
}
// Simulate tonemapping
FLightTargetPDF LightTargetPDF = InitLightTargetPDF();
LightTargetPDF.Weight = log2(Lum + 1.0f);
return LightTargetPDF;
}
uint2 DownsampledViewMin;
uint2 DownsampledViewSize;
float MinSampleWeight;
uint2 NumSamplesPerPixel;
int UseIESProfiles;
int UseLightFunctionAtlas;
int DebugMode;
uint DebugVisualizeLight;
uint DebugLightId;
RWTexture2D<float> RWDownsampledSceneDepth;
RWTexture2D<UNORM float3> RWDownsampledSceneWorldNormal;
RWTexture2D<uint> RWLightSamples;
RWTexture2D<uint> RWLightSampleRays;
Texture2D MegaLightsDepthHistory;
float4 HistoryScreenPositionScaleBias;
float4 HistoryUVMinMax;
float4 HistoryGatherUVMinMax;
float4 HistoryBufferSizeAndInvSize;
StructuredBuffer<uint> DownsampledTileAllocator;
StructuredBuffer<uint> DownsampledTileData;
StructuredBuffer<uint> VisibleLightHashHistory;
StructuredBuffer<uint> VisibleLightMaskHashHistory;
uint2 HistoryVisibleLightHashViewMinInTiles;
uint2 HistoryVisibleLightHashViewSizeInTiles;
uint GuideByHistoryMode;
float GuideByHistoryHiddenRatio;
float AreaLightHiddenPDFWeight;
uint DownsampledTileDataStride;
float2 DownsampledBufferInvSize;
uint2 GetSampleCoord(uint2 DownsampledScreenCoord, uint LightSampleX, uint LightSampleY)
{
return DownsampledScreenCoord * NumSamplesPerPixel + uint2(LightSampleX, LightSampleY);
}
uint2 GetSampleCoord(uint2 DownsampledScreenCoord, uint LightSampleIndex)
{
return GetSampleCoord(DownsampledScreenCoord, LightSampleIndex % NUM_SAMPLES_PER_PIXEL_2D_X, LightSampleIndex / NUM_SAMPLES_PER_PIXEL_2D_X);
}
uint GetLightVisibilityMask(uint VisibleLightMaskHash[VISIBLE_LIGHT_HASH_SIZE], uint PrevLocalLightIndex)
{
uint Hash = PCGHash(PrevLocalLightIndex);
uint WrappedLocalLightIndex = (Hash >> 16) % 32;
uint VisibilityMask = (VisibleLightMaskHash[WrappedLocalLightIndex / 8] >> (4 * (WrappedLocalLightIndex % 8))) & 0xF;
return VisibilityMask;
}
void SampleLight(
uint2 ScreenCoord,
float3 TranslatedWorldPosition,
const FMegaLightsMaterial Material,
uint LightingChannelMask,
uint VisibleLightHash[VISIBLE_LIGHT_HASH_SIZE],
bool bHasValidHistory,
uint ForwardLightIndex,
FDeferredLightData LightData,
uint LightSceneId,
uint LightLightingChannelMask,
uint PrevForwardLightIndex,
inout FLightSampler VisibleLightSampler,
inout FLightSampler HiddenLightSampler,
inout FShaderPrintContext DebugContext)
{
if ((LightingChannelMask & LightLightingChannelMask) == 0)
{
return;
}
if (UseLightFunctionAtlas == 0)
{
LightData.LightFunctionAtlasLightIndex = 0;
}
if (UseIESProfiles == 0)
{
LightData.IESAtlasIndex = -1;
}
if (!IsRectLightTileType(TILE_TYPE))
{
LightData.bRectLight = false;
}
if (!IsTexturedLightTileType(TILE_TYPE))
{
LightData.RectLightData.AtlasData.AtlasMaxLevel = MAX_RECT_ATLAS_MIP;
}
FLightTargetPDF LightTargetPDF = GetLocalLightTargetPDF(LightData, TranslatedWorldPosition, Material, ScreenCoord, DebugContext);
bool bWasVisibleInLastFrame = false;
#if GUIDE_BY_HISTORY
if (LightTargetPDF.Weight > MinSampleWeight && PrevForwardLightIndex >= 0)
{
bWasVisibleInLastFrame = GetLightVisibility(VisibleLightHash, PrevForwardLightIndex);
}
#endif
if (DebugContext.bIsActive)
{
Newline(DebugContext);
Print(DebugContext, LightSceneId, Select(LightSceneId == DebugLightId, FontSelected, FontValue));
Print(DebugContext, ForwardLightIndex, Select(LightSceneId == DebugLightId, FontSelected, FontValue));
Print(DebugContext, LightTargetPDF.Weight, Select(LightTargetPDF.Weight > MinSampleWeight, FontWhite, FontGrey));
Print(DebugContext, LightData.LightFunctionAtlasLightIndex, Select(LightData.LightFunctionAtlasLightIndex != 0, FontWhite, FontGrey));
Print(DebugContext, LightData.IESAtlasIndex, Select(LightData.IESAtlasIndex != -1, FontWhite, FontGrey));
Print(DebugContext, bWasVisibleInLastFrame ? 1u : 0u, Select(bWasVisibleInLastFrame, FontWhite, FontGrey));
if ((DebugVisualizeLight != 0 && LightSceneId == DebugLightId) || DebugVisualizeLight == 2)
{
AddSphereTWS(DebugContext, LightData.TranslatedWorldPosition, 10.0f, float4(0, 1, 0, 1));
AddLineTWS(DebugContext, TranslatedWorldPosition, LightData.TranslatedWorldPosition, float4(0, 1, 0, 1));
}
}
if (LightTargetPDF.Weight > MinSampleWeight)
{
if (bWasVisibleInLastFrame)
{
AddLightSample(VisibleLightSampler, LightTargetPDF.Weight, ForwardLightIndex, bWasVisibleInLastFrame);
}
else
{
AddLightSample(HiddenLightSampler, LightTargetPDF.Weight, ForwardLightIndex, bWasVisibleInLastFrame);
}
}
}
void SampleLight(
uint2 ScreenCoord,
float3 TranslatedWorldPosition,
const FMegaLightsMaterial Material,
uint LightingChannelMask,
uint VisibleLightHash[VISIBLE_LIGHT_HASH_SIZE],
bool bHasValidHistory,
uint LocalLightIndex,
inout FLightSampler VisibleLightSampler,
inout FLightSampler HiddenLightSampler,
inout FShaderPrintContext DebugContext)
{
const FLocalLightData LocalLightData = GetLocalLightData(LocalLightIndex, 0);
FDeferredLightData LightData = ConvertToDeferredLight(LocalLightData);
SampleLight(ScreenCoord, TranslatedWorldPosition, Material, LightingChannelMask, VisibleLightHash, bHasValidHistory,
LocalLightIndex, LightData, LocalLightData.Internal.LightSceneId, UnpackLightingChannelMask(LocalLightData), LocalLightData.Internal.PrevLocalLightIndex,
VisibleLightSampler, HiddenLightSampler, DebugContext);
}
void SampleDirectionalLight(
uint DirectionalLightIndex,
uint2 ScreenCoord,
float3 TranslatedWorldPosition,
const FMegaLightsMaterial Material,
uint LightingChannelMask,
uint VisibleLightHash[VISIBLE_LIGHT_HASH_SIZE],
bool bHasValidHistory,
inout FLightSampler VisibleLightSampler,
inout FLightSampler HiddenLightSampler,
inout FShaderPrintContext DebugContext)
{
uint LightIndex = ForwardLightStruct.DirectionalLightIndices[DirectionalLightIndex];
FForwardLightData ForwardLightData = GetForwardLightData(LightIndex, 0);
FDeferredLightData LightData = ConvertToDeferredLight(ForwardLightData);
LightData.bRadialLight = false;
LightData.bInverseSquared = false;
SampleLight(ScreenCoord, TranslatedWorldPosition, Material, LightingChannelMask, VisibleLightHash, bHasValidHistory,
LightIndex, LightData, ForwardLightData.LightSceneId, UnpackLightingChannelMask(ForwardLightData), ForwardLightData.PrevLocalLightIndex,
VisibleLightSampler, HiddenLightSampler, DebugContext);
}
/**
* Run one thread per sample and generate new light samples for tracing
*/
[numthreads(THREADGROUP_SIZE, THREADGROUP_SIZE, 1)]
void GenerateLightSamplesCS(
uint3 GroupId : SV_GroupID,
uint3 GroupThreadId : SV_GroupThreadID,
uint3 DispatchThreadId : SV_DispatchThreadID)
{
uint LinearThreadIndex = GroupThreadId.y * THREADGROUP_SIZE + GroupThreadId.x;
uint DownsampledTileIndex = GroupId.x;
if (DownsampledTileIndex < DownsampledTileAllocator[TILE_TYPE])
{
uint LocalCandidateLightHiMask = 0;
uint2 DownsampledTileCoord = UnpackTile(DownsampledTileData[DownsampledTileIndex + TILE_TYPE * DownsampledTileDataStride]);
uint2 DownsampledScreenCoord = DownsampledTileCoord * TILE_SIZE + GroupThreadId.xy;
const bool bForceSimpleShading = IsSimpleShadingTileType(TILE_TYPE);
if (all(DownsampledScreenCoord < DownsampledViewMin + DownsampledViewSize))
{
uint2 ScreenCoord = DownsampledScreenCoordToScreenCoord(DownsampledScreenCoord);
FShaderPrintContext DebugContext = InitDebugContext(DownsampledScreenCoord, /*bDownsampled*/ true, float2(0.05, 0.05));
const float2 ScreenUV = (ScreenCoord + 0.5f) * View.BufferSizeAndInvSize.zw;
const FMegaLightsMaterial Material = LoadMaterial(ScreenUV, ScreenCoord, bForceSimpleShading);
const float SceneDepth = Material.Depth;
if (SceneDepth > 0)
{
const float3 TranslatedWorldPosition = GetTranslatedWorldPositionFromScreenUV(ScreenUV, SceneDepth);
const uint EyeIndex = 0; // TODO
const uint GridIndex = ComputeLightGridCellIndex(ScreenCoord - View.ViewRectMin.xy, SceneDepth, EyeIndex);
const FCulledLightsGridHeader CulledLightsGridHeader = GetCulledLightsGridHeader(GridIndex);
const uint NumLightsInGridCell = min(CulledLightsGridHeader.NumMegaLights, GetMaxLightsPerCell());
const uint NumLocalLights = GetNumLocalLights();
const uint LightingChannelMask = GetSceneLightingChannel(ScreenCoord);
bool bHasValidHistory = true;
uint2 PrevScreenCoord = ScreenCoord;
#define REPROJECT_HISTORY_FOR_GUIDING 1
#if GUIDE_BY_HISTORY && REPROJECT_HISTORY_FOR_GUIDING
{
bHasValidHistory = false;
float2 ScreenPosition = (ScreenUV - View.ScreenPositionScaleBias.wz) / View.ScreenPositionScaleBias.xy;
float3 HistoryScreenPosition = GetHistoryScreenPosition(ScreenPosition, ScreenUV, ConvertToDeviceZ(SceneDepth));
float2 HistoryScreenUV = HistoryScreenPosition.xy * HistoryScreenPositionScaleBias.xy + HistoryScreenPositionScaleBias.wz;
bool bHistoryWasOnScreen = all(HistoryScreenUV >= HistoryUVMinMax.xy) && all(HistoryScreenUV <= HistoryUVMinMax.zw);
HistoryScreenUV = clamp(HistoryScreenUV, HistoryGatherUVMinMax.xy, HistoryGatherUVMinMax.zw);
uint2 HistoryScreenCoord = floor(HistoryScreenUV * HistoryBufferSizeAndInvSize.xy - 0.5f);
float2 HistoryBilinearWeights = frac(HistoryScreenUV * HistoryBufferSizeAndInvSize.xy - 0.5f);
float2 HistoryGatherUV = (HistoryScreenCoord + 1.0f) * HistoryBufferSizeAndInvSize.zw;
float4 HistorySampleSceneDepth4 = MegaLightsDepthHistory.GatherRed(GlobalPointClampedSampler, HistoryGatherUV).wzxy;
HistorySampleSceneDepth4.x = ConvertFromDeviceZ(HistorySampleSceneDepth4.x);
HistorySampleSceneDepth4.y = ConvertFromDeviceZ(HistorySampleSceneDepth4.y);
HistorySampleSceneDepth4.z = ConvertFromDeviceZ(HistorySampleSceneDepth4.z);
HistorySampleSceneDepth4.w = ConvertFromDeviceZ(HistorySampleSceneDepth4.w);
float ReprojectedSceneDepth = ConvertFromDeviceZ(HistoryScreenPosition.z);
float DisocclusionDistanceThreshold = 0.03f;
const float4 DistanceToHistoryValue = abs(HistorySampleSceneDepth4 - ReprojectedSceneDepth);
float4 DepthWeights = select(DistanceToHistoryValue >= ReprojectedSceneDepth * DisocclusionDistanceThreshold, 0.0f, 1.0f);
if (any(DepthWeights > 0.01f) && bHistoryWasOnScreen)
{
bHasValidHistory = true;
}
PrevScreenCoord = HistoryScreenCoord;
}
#endif
if (DebugContext.bIsActive)
{
#if SUBSTRATE_ENABLED
const uint MaxTileType = TILE_MODE_MAX;
#else
const uint MaxTileType = TILE_MODE_MAX_LEGACY;
#endif
Print(DebugContext, TEXT("MegaLights"), FontTitle);
Newline(DebugContext);
Print(DebugContext, TEXT("TileType | Num "), FontSilver);
for (uint TileTypeIndex = 0; TileTypeIndex < MaxTileType; ++TileTypeIndex)
{
Newline(DebugContext);
PrintTileTypeString(DebugContext, TileTypeIndex, FontValue);
Print(DebugContext, TEXT(": "));
Print(DebugContext, DownsampledTileAllocator[TileTypeIndex], FontValue);
}
Newline(DebugContext);
Newline(DebugContext);
Print(DebugContext, TEXT("GenerateSamples"), FontTitle);
Newline(DebugContext);
Print(DebugContext, TEXT("ScreenCoord : "));
Print(DebugContext, ScreenCoord.x, FontValue);
Print(DebugContext, ScreenCoord.y, FontValue);
Newline(DebugContext);
Print(DebugContext, TEXT("Roughness : "));
Print(DebugContext, Material.Roughness, FontValue);
Newline(DebugContext);
Print(DebugContext, TEXT("TileType : "));
PrintTileTypeString(DebugContext, TILE_TYPE, Select(IsSimpleShadingTileType(TILE_TYPE), FontGreen, FontRed));
Newline(DebugContext);
Print(DebugContext, TEXT("View.PreExposure : "));
Print(DebugContext, View.PreExposure, FontValue);
Newline(DebugContext);
Print(DebugContext, TEXT("NumLightsInGridCell: "));
Print(DebugContext, NumLightsInGridCell, FontValue);
Newline(DebugContext);
Print(DebugContext, TEXT("NumLocalLights : "));
Print(DebugContext, NumLocalLights, Select(NumLocalLights < MAX_LOCAL_LIGHT_INDEX + 1, FontLightGreen, FontRed));
Newline(DebugContext);
Print(DebugContext, TEXT("ValidGuideHistory : "));
Print(DebugContext, bHasValidHistory, FontValue);
Newline(DebugContext);
Print(DebugContext, TEXT("LightId | LocalLightId | Weight | LFAtlas | IESAtlas | History"), FontSilver);
}
uint VisibleLightHash[VISIBLE_LIGHT_HASH_SIZE];
uint VisibleLightMaskHash[VISIBLE_LIGHT_HASH_SIZE];
for (uint IndexInHash = 0; IndexInHash < VISIBLE_LIGHT_HASH_SIZE; ++IndexInHash)
{
VisibleLightHash[IndexInHash] = 0xFFFFFFFF;
VisibleLightMaskHash[IndexInHash] = 0xFFFFFFFF;
#if GUIDE_BY_HISTORY
{
const uint2 PrevScreenTileCoord = clamp(PrevScreenCoord / TILE_SIZE, HistoryVisibleLightHashViewMinInTiles, HistoryVisibleLightHashViewMinInTiles + HistoryVisibleLightHashViewSizeInTiles - 1);
const uint HistoryBufferBase = VISIBLE_LIGHT_HASH_SIZE * (PrevScreenTileCoord.y * HistoryVisibleLightHashViewSizeInTiles.x + PrevScreenTileCoord.x);
VisibleLightHash[IndexInHash] = VisibleLightHashHistory[HistoryBufferBase + IndexInHash];
VisibleLightMaskHash[IndexInHash] = VisibleLightMaskHashHistory[HistoryBufferBase + IndexInHash];
}
#endif
}
const float RandomScalar = BlueNoiseScalar(DownsampledScreenCoord, MegaLightsStateFrameIndex);
FLightSampler LightSampler = InitLightSampler(RandomScalar);
FLightSampler HiddenLightSampler = InitLightSampler(1.0f - RandomScalar);
const uint ScalarGridIndex = WaveReadLaneFirst(GridIndex);
const bool bScalarGridCell = WaveActiveAllTrue(ScalarGridIndex == GridIndex);
if (bScalarGridCell)
{
FCulledLightsGridHeader CulledLightsGridHeader = GetCulledLightsGridHeader(ScalarGridIndex);
uint NumLightsInGridCell = min(CulledLightsGridHeader.NumMegaLights, GetMaxLightsPerCell());
uint GridLightIndex = 0;
while(GridLightIndex < NumLightsInGridCell)
{
uint LocalLightIndex = GetCulledLightDataGrid(CulledLightsGridHeader.MegaLightsDataStartIndex + GridLightIndex);
if (LocalLightIndex >= MAX_LOCAL_LIGHT_INDEX)
{
break;
}
++GridLightIndex;
SampleLight(ScreenCoord, TranslatedWorldPosition, Material, LightingChannelMask, VisibleLightHash, bHasValidHistory, LocalLightIndex, LightSampler, HiddenLightSampler, DebugContext);
}
}
else
{
uint GridLightIndex = 0;
while(GridLightIndex < NumLightsInGridCell)
{
const uint VectorLocalLightIndex = GetCulledLightDataGrid(CulledLightsGridHeader.MegaLightsDataStartIndex + GridLightIndex);
if (VectorLocalLightIndex >= MAX_LOCAL_LIGHT_INDEX)
{
break;
}
uint LocalLightIndex = WaveActiveMin(VectorLocalLightIndex);
if (LocalLightIndex == VectorLocalLightIndex)
{
++GridLightIndex;
SampleLight(ScreenCoord, TranslatedWorldPosition, Material, LightingChannelMask, VisibleLightHash, bHasValidHistory, LocalLightIndex, LightSampler, HiddenLightSampler, DebugContext);
}
}
}
// sample directional lights
for (uint Index = ForwardLightStruct.DirectionalMegaLightsSupportedStartIndex; Index < ForwardLightStruct.NumDirectionalLights; ++Index)
{
SampleDirectionalLight(Index, ScreenCoord, TranslatedWorldPosition, Material, LightingChannelMask, VisibleLightHash, bHasValidHistory, LightSampler, HiddenLightSampler, DebugContext);
}
if (DebugContext.bIsActive)
{
Newline(DebugContext);
Print(DebugContext, TEXT("Visible weight sum : "));
Print(DebugContext, LightSampler.WeightSum, FontValue);
Newline(DebugContext);
Print(DebugContext, TEXT("Hidden weight sum : "));
Print(DebugContext, HiddenLightSampler.WeightSum, FontValue);
}
CombineLightSamplers(LightSampler, HiddenLightSampler, bHasValidHistory, GuideByHistoryHiddenRatio, DebugContext);
if (DebugContext.bIsActive)
{
Newline(DebugContext);
Newline(DebugContext);
Print(DebugContext, TEXT("LightId | Weight | History | AsVis | UV"), FontSilver);
}
// Finalize samples
for (uint LightSampleIndex = 0; LightSampleIndex < NUM_SAMPLES_PER_PIXEL_1D; ++LightSampleIndex)
{
FCandidateLightSample CandidateLightSample = UnpackCandidateLightSample(LightSampler.PackedSamples[LightSampleIndex]);
FLightSample LightSample = InitLightSample();
FLightSampleRay LightSampleRay = InitLightSampleRay();
LightSample.bVisible = true;
LightSample.bGuidedAsVisible = true;
LightSample.LocalLightIndex = CandidateLightSample.LocalLightIndex;
LightSample.Weight = CandidateLightSample.Weight;
const FForwardLightData ForwardLightData = GetForwardLightData(LightSample.LocalLightIndex, 0);
FDeferredLightData LightData = ConvertToDeferredLight(ForwardLightData);
uint2 SampleCoord = GetSampleCoord(DownsampledScreenCoord, LightSampleIndex);
LightSampleRay.UV = BlueNoiseVec2(SampleCoord, MegaLightsStateFrameIndex);
// Disable screen tracing on hair material as it is very noisy and can cause light leak when light and view are opposed.
LightSampleRay.bSupportScreenTrace = !Material.bIsHair;
// Subsurface and two-sided need special treatment as they need lighting from behind the object too
if (Material.bHasBackfaceDiffuse)
{
LightSampleRay.bBackfaceDiffuse = true;
}
#if GUIDE_BY_HISTORY
if (bHasValidHistory && ForwardLightData.PrevLocalLightIndex >= 0)
{
uint VisibleInLastFrameMask = GetLightVisibilityMask(VisibleLightMaskHash, ForwardLightData.PrevLocalLightIndex);
LightSample.bGuidedAsVisible = CandidateLightSample.bLightWasVisible;
if (GuideByHistoryMode == 2)
{
// If entire light is fully visible or invisible then no need to guide samples towards the visible parts
const bool bAreaLight = LightData.SourceRadius > 0.0f || LightData.SourceLength > 0.0f;
if (CandidateLightSample.bLightWasVisible && VisibleInLastFrameMask != 0 && VisibleInLastFrameMask != 0xF && bAreaLight)
{
// Probabilities of hitting of 2x2 light UV regions based on the visibility from the previous frame
float Weight00 = VisibleInLastFrameMask & 0x1 ? 1.0f : AreaLightHiddenPDFWeight;
float Weight10 = VisibleInLastFrameMask & 0x2 ? 1.0f : AreaLightHiddenPDFWeight;
float Weight01 = VisibleInLastFrameMask & 0x4 ? 1.0f : AreaLightHiddenPDFWeight;
float Weight11 = VisibleInLastFrameMask & 0x8 ? 1.0f : AreaLightHiddenPDFWeight;
// Warp samples across X to fit desired distribution
float SplitX = (Weight00 + Weight01) / (Weight00 + Weight10 + Weight01 + Weight11);
if (LightSampleRay.UV.x < SplitX)
{
LightSampleRay.UV.x = LightSampleRay.UV.x / SplitX;
LightSampleRay.UV.x *= 0.5f;
}
else
{
LightSampleRay.UV.x = (LightSampleRay.UV.x - SplitX) / (1.0f - SplitX);
LightSampleRay.UV.x = LightSampleRay.UV.x * 0.5f + 0.5f;
}
// Warp samples across Y to fit desired distribution
float SplitY = LightSampleRay.UV.x < 0.5f ? (Weight00 / (Weight00 + Weight01)) : (Weight10 / (Weight10 + Weight11));
if (LightSampleRay.UV.y < SplitY)
{
LightSampleRay.UV.y = LightSampleRay.UV.y / SplitY;
LightSampleRay.UV.y *= 0.5f;
}
else
{
LightSampleRay.UV.y = (LightSampleRay.UV.y - SplitY) / (1.0f - SplitY);
LightSampleRay.UV.y = LightSampleRay.UV.y * 0.5f + 0.5f;
}
float SelectedWeight;
if (LightSampleRay.UV.x < 0.5f)
{
SelectedWeight = LightSampleRay.UV.y < 0.5f ? Weight00 : Weight01;
}
else
{
SelectedWeight = LightSampleRay.UV.y < 0.5f ? Weight10 : Weight11;
}
// Fixup weights to match new sample distribution
float PerLightWeightSum = Weight00 + Weight10 + Weight01 + Weight11;
LightSample.bGuidedAsVisible = SelectedWeight >= PerLightWeightSum / 4.0f;
LightSample.Weight = LightSample.Weight * SelectedWeight * 4.0f / PerLightWeightSum;
}
}
}
#endif
if (DebugContext.bIsActive)
{
Newline(DebugContext);
Print(DebugContext, ForwardLightData.LightSceneId, Select(ForwardLightData.LightSceneId == DebugLightId, FontSelected, FontValue));
Print(DebugContext, LightSample.Weight, FontValue);
const uint VisibleInLastFrameMask = GetLightVisibilityMask(VisibleLightMaskHash, ForwardLightData.PrevLocalLightIndex);
uint VisibleInLastFrameMaskDebug = 0;
VisibleInLastFrameMaskDebug += VisibleInLastFrameMask & 0x1 ? 1000 : 0;
VisibleInLastFrameMaskDebug += VisibleInLastFrameMask & 0x2 ? 100 : 0;
VisibleInLastFrameMaskDebug += VisibleInLastFrameMask & 0x4 ? 10 : 0;
VisibleInLastFrameMaskDebug += VisibleInLastFrameMask & 0x8 ? 1 : 0;
Print(DebugContext, VisibleInLastFrameMaskDebug, FontValue);
Print(DebugContext, LightSample.bGuidedAsVisible ? 1u : 0u, FontValue);
Print(DebugContext, LightSampleRay.UV, FontValue);
if (DebugMode == DEBUG_MODE_VISUALIZE_SAMPLING)
{
const uint2 SampleCoord = GetSampleCoord(DownsampledScreenCoord, LightSampleIndex);
const FLightSampleTrace LightSampleTrace = GetLightSampleTrace(TranslatedWorldPosition, LightSample.LocalLightIndex, LightSampleRay.UV);
float4 RayColor = float4(LightData.Color.xyz / Luminance(LightData.Color.xyz), 1.0f);
AddLineTWS(DebugContext, TranslatedWorldPosition, TranslatedWorldPosition + LightSampleTrace.Direction * LightSampleTrace.Distance, RayColor);
}
}
if (LightSample.LocalLightIndex != MAX_LOCAL_LIGHT_INDEX)
{
const bool bCastShadows = UnpackCastShadow(ForwardLightData);
LightSampleRay.bCompleted = bCastShadows ? false : true;
LightSample.Weight = LightSampler.WeightSum / (NUM_SAMPLES_PER_PIXEL_1D * LightSample.Weight);
}
RWLightSamples[SampleCoord] = PackLightSample(LightSample);
RWLightSampleRays[SampleCoord] = PackLightSampleRay(LightSampleRay);
}
}
else
{
for (uint LightSampleIndex = 0; LightSampleIndex < NUM_SAMPLES_PER_PIXEL_1D; ++LightSampleIndex)
{
RWLightSamples[GetSampleCoord(DownsampledScreenCoord, LightSampleIndex)] = PackLightSample(InitLightSample());
}
}
RWDownsampledSceneDepth[DownsampledScreenCoord] = SceneDepth;
RWDownsampledSceneWorldNormal[DownsampledScreenCoord] = EncodeNormal(Material.WorldNormalForPositionBias);
}
}
}
/**
* Clear data for empty tiles, which won't be processed by GenerateLightSamplesCS
*/
[numthreads(THREADGROUP_SIZE, THREADGROUP_SIZE, 1)]
void ClearLightSamplesCS(
uint3 GroupId : SV_GroupID,
uint3 GroupThreadId : SV_GroupThreadID,
uint3 DispatchThreadId : SV_DispatchThreadID)
{
uint DownsampledTileIndex = GroupId.x;
if (DownsampledTileIndex < DownsampledTileAllocator[TILE_MODE_EMPTY])
{
uint2 DownsampledTileCoord = UnpackTile(DownsampledTileData[DownsampledTileIndex + TILE_MODE_EMPTY * DownsampledTileDataStride]);
uint2 DownsampledScreenCoord = DownsampledTileCoord * TILE_SIZE + GroupThreadId.xy;
if (all(DownsampledScreenCoord < DownsampledViewMin + DownsampledViewSize))
{
for (uint LightSampleY = 0; LightSampleY < NumSamplesPerPixel.y; ++LightSampleY)
{
for (uint LightSampleX = 0; LightSampleX < NumSamplesPerPixel.x; ++LightSampleX)
{
const uint2 SampleCoord = GetSampleCoord(DownsampledScreenCoord, LightSampleX, LightSampleY);
RWLightSamples[SampleCoord] = PackLightSample(InitLightSample());
RWLightSampleRays[SampleCoord] = PackLightSampleRay(InitLightSampleRay());
}
}
}
}
}