1489 lines
50 KiB
HLSL
1489 lines
50 KiB
HLSL
// Copyright Epic Games, Inc. All Rights Reserved.
|
|
|
|
/*=============================================================================================
|
|
PathTracingRayGenShader.usf: Reference path tracing
|
|
===============================================================================================*/
|
|
|
|
#define SIMPLIFIED_MATERIAL_SHADER 1
|
|
#define RANDSEQ_UNROLL_SOBOL 0
|
|
#define USE_PATH_TRACING_LIGHT_GRID 1
|
|
|
|
#include "/Engine/Private/Common.ush"
|
|
#include "/Engine/Private/PostProcessCommon.ush"
|
|
#include "/Engine/Private/RectLight.ush"
|
|
#include "/Engine/Private/RayTracing/RayTracingCommon.ush"
|
|
#include "/Engine/Private/RayTracing/RayTracingDirectionalLight.ush"
|
|
#include "/Engine/Private/RayTracing/RayTracingRectLight.ush"
|
|
#include "/Engine/Private/RayTracing/RayTracingSphereLight.ush"
|
|
#include "/Engine/Private/RayTracing/RayTracingCapsuleLight.ush"
|
|
|
|
#include "/Engine/Private/PathTracing/PathTracingCommon.ush"
|
|
#include "/Engine/Private/PathTracing/Light/PathTracingLightGrid.ush"
|
|
#include "/Engine/Private/RayTracing/RayTracingHitGroupCommon.ush"
|
|
|
|
#include "BatchedTiles.ush"
|
|
#include "/Engine/Private/PathTracing/Material/FirstBounceRayGuidingCommon.ush"
|
|
|
|
int NumRayGuidingTrialSamples;
|
|
RWTexture2D<uint> RayGuidingLuminance;
|
|
Texture2D<float> RayGuidingCDFX;
|
|
Texture2D<float> RayGuidingCDFY;
|
|
|
|
#include "IrradianceCachingCommon.ush"
|
|
|
|
#include "/Engine/Private/ShadingModels.ush"
|
|
#include "/Engine/Private/PathTracing/Utilities/PathTracingRandomSequence.ush"
|
|
#include "/Engine/Private/PathTracing/Light/PathTracingLightSampling.ush"
|
|
#include "/Engine/Private/PathTracing/Material/PathTracingMaterialSampling.ush"
|
|
#include "/Engine/Private/PathTracing/Material/PathTracingRadianceProbe.ush"
|
|
|
|
#include "/Engine/Shared/LightDefinitions.h"
|
|
|
|
#include "LightmapEncoding.ush"
|
|
#include "LightmapCommon.ush"
|
|
|
|
int LastInvalidationFrame;
|
|
int NumTotalSamples;
|
|
|
|
Texture2D<float4> GBufferWorldPosition;
|
|
Texture2D<float4> GBufferWorldNormal;
|
|
Texture2D<float4> GBufferShadingNormal;
|
|
RWTexture2D<float4> IrradianceAndSampleCount;
|
|
RWTexture2D<float4> SHDirectionality;
|
|
RWTexture2D<float4> SHCorrectionAndStationarySkyLightBentNormal;
|
|
|
|
RWTexture2D<float4> OutputTileAtlas;
|
|
|
|
void GenerateCosineNormalRay(
|
|
float3 TranslatedWorldPosition,
|
|
float3 WorldNormal,
|
|
inout RandomSequence RandSequence,
|
|
out float3 RayOrigin,
|
|
out float3 RayDirection,
|
|
out float3 TangentDirection,
|
|
out float RayTMin,
|
|
out float RayTMax,
|
|
out float RayPdf
|
|
)
|
|
{
|
|
// Draw random variable
|
|
float2 BufferSize = View.BufferSizeAndInvSize.xy;
|
|
float2 RandSample = RandomSequence_GenerateSample2D(RandSequence);
|
|
|
|
// Perform cosine-hemispherical sampling and convert to world-space
|
|
float4 Direction_Tangent = CosineSampleHemisphere(RandSample);
|
|
TangentDirection = Direction_Tangent.xyz;
|
|
float3 Direction_World = TangentToWorld(Direction_Tangent.xyz, WorldNormal);
|
|
|
|
RayOrigin = TranslatedWorldPosition;
|
|
RayDirection = Direction_World;
|
|
RayTMin = 0.01;
|
|
RayTMax = 1e20;
|
|
RayPdf = 1.0f;
|
|
}
|
|
|
|
struct FThreeBandSHVectorFloat
|
|
{
|
|
float4 V0;
|
|
float4 V1;
|
|
float V2;
|
|
};
|
|
|
|
struct FThreeBandSHVectorRGBFloat
|
|
{
|
|
FThreeBandSHVectorFloat R;
|
|
FThreeBandSHVectorFloat G;
|
|
FThreeBandSHVectorFloat B;
|
|
};
|
|
|
|
FThreeBandSHVectorFloat SHBasisFunction3Float(float3 InputVector)
|
|
{
|
|
FThreeBandSHVectorFloat Result;
|
|
// These are derived from simplifying SHBasisFunction in C++
|
|
Result.V0.x = 0.282095f;
|
|
Result.V0.y = -0.488603f * InputVector.y;
|
|
Result.V0.z = 0.488603f * InputVector.z;
|
|
Result.V0.w = -0.488603f * InputVector.x;
|
|
|
|
half3 VectorSquared = InputVector * InputVector;
|
|
Result.V1.x = 1.092548f * InputVector.x * InputVector.y;
|
|
Result.V1.y = -1.092548f * InputVector.y * InputVector.z;
|
|
Result.V1.z = 0.315392f * (3.0f * VectorSquared.z - 1.0f);
|
|
Result.V1.w = -1.092548f * InputVector.x * InputVector.z;
|
|
Result.V2 = 0.546274f * (VectorSquared.x - VectorSquared.y);
|
|
|
|
return Result;
|
|
}
|
|
|
|
#define WorldPositionScalar 0.00001f
|
|
|
|
#ifndef USE_IRRADIANCE_CACHING
|
|
#define USE_IRRADIANCE_CACHING 0
|
|
#endif
|
|
|
|
static const uint MaxBounces = 32;
|
|
|
|
// 0: only Material sampling
|
|
// 1: only Light sampling
|
|
// 2: both Material and Light
|
|
static const uint MISMode = 2;
|
|
|
|
static const uint ApproximateCaustics = 1;
|
|
static const uint EnableCameraBackfaceCulling = 0;
|
|
static const uint EnableEmissive = 1;
|
|
static const float MaxNormalBias = 0.1;
|
|
|
|
RaytracingAccelerationStructure TLAS;
|
|
uint SceneVisibleLightCount;
|
|
|
|
void AccumulateRadiance(inout float3 TotalRadiance, float3 PathRadiance)
|
|
{
|
|
float MaxPathIntensity = 0;
|
|
if (MaxPathIntensity > 0)
|
|
{
|
|
// User asked for path contributions to be clamped to reduce fireflies.
|
|
// Depending on how aggressive this value is, the image could be quite biased
|
|
TotalRadiance += min(PathRadiance, MaxPathIntensity);
|
|
}
|
|
else
|
|
{
|
|
// Just average values directly
|
|
TotalRadiance += PathRadiance;
|
|
}
|
|
}
|
|
|
|
bool IsInvalidSurfaceHit(FPathTracingPayload Payload)
|
|
{
|
|
return !Payload.IsFrontFace() && !Payload.IsMaterialTransmissive() && !Payload.IsMaterialTwoSided();
|
|
}
|
|
|
|
FPathTracingPayload TraceTransparentRay(FRayDesc Ray, bool IsCameraRay, bool LastBounce, bool IncludeEmission, uint2 LaunchIndex, uint NumLights, inout RandomSequence RandSequence, inout float3 PathThroughput, inout float3 Radiance)
|
|
{
|
|
const uint RayFlags = IsCameraRay && EnableCameraBackfaceCulling ? RAY_FLAG_CULL_BACK_FACING_TRIANGLES : 0;
|
|
const uint MissShaderIndex = 0;
|
|
float SelectionWeightSum = 0;
|
|
float3 PayloadThroughput = PathThroughput;
|
|
FPathTracingPayload Payload;
|
|
if (!IncludeEmission && LastBounce)
|
|
{
|
|
Payload.SetMiss();
|
|
PathThroughput = 0;
|
|
return Payload;
|
|
}
|
|
for (;;)
|
|
{
|
|
FPackedPathTracingPayload PackedPayload = InitPathTracingPayload(IsCameraRay ? PATHTRACER_SCATTER_CAMERA : PATHTRACER_SCATTER_DIFFUSE, 1.0);
|
|
// Trace the ray
|
|
TraceRay(
|
|
TLAS,
|
|
RayFlags,
|
|
PATHTRACER_MASK_ALL,
|
|
RAY_TRACING_SHADER_SLOT_MATERIAL,
|
|
RAY_TRACING_NUM_SHADER_SLOTS,
|
|
MissShaderIndex,
|
|
Ray.GetNativeDesc(),
|
|
PackedPayload);
|
|
|
|
// Loop over lights to capture their contribution
|
|
// #dxr_todo: if we have lots of lights, having some hierarchical structure would be better ....
|
|
for (uint LightId = 0; LightId < NumLights; ++LightId)
|
|
{
|
|
FRayDesc LightRay = Ray;
|
|
LightRay.TMax = PackedPayload.IsMiss() ? Ray.TMax : PackedPayload.HitT;
|
|
float3 LightRadiance = TraceLight(LightRay, LightId).Radiance;
|
|
AccumulateRadiance(Radiance, PathThroughput * LightRadiance);
|
|
}
|
|
|
|
if (PackedPayload.IsMiss())
|
|
{
|
|
// Ray didn't hit any real geometry, so nothing left to do
|
|
break;
|
|
}
|
|
|
|
// Unpack the payload
|
|
FPathTracingPayload HitPayload = UnpackPathTracingPayload(PackedPayload, Ray);
|
|
|
|
// add in surface emission
|
|
if (IncludeEmission)
|
|
{
|
|
AccumulateRadiance(Radiance, PathThroughput * HitPayload.Radiance);
|
|
}
|
|
|
|
if (!LastBounce)
|
|
{
|
|
float3 Contrib = PathThroughput * EstimateMaterialAlbedo(HitPayload);
|
|
|
|
float SelectionWeight = max3(Contrib.x, Contrib.y, Contrib.z);
|
|
SelectionWeightSum += SelectionWeight;
|
|
bool AcceptHit = false;
|
|
|
|
// weighted reservoir sampling
|
|
if (SelectionWeight > 0)
|
|
{
|
|
if (SelectionWeight < SelectionWeightSum)
|
|
{
|
|
// the acceptance probability is not 1.0
|
|
// generate a random number to see if we should accept this hit
|
|
float RandValue = RandomSequence_GenerateSample1D(RandSequence);
|
|
AcceptHit = RandValue * SelectionWeightSum < SelectionWeight;
|
|
}
|
|
else
|
|
{
|
|
// accept automatically on the first hit
|
|
AcceptHit = true;
|
|
}
|
|
}
|
|
|
|
if (AcceptHit)
|
|
{
|
|
// stash this hit for next time
|
|
Payload = HitPayload;
|
|
PayloadThroughput = PathThroughput / SelectionWeight;
|
|
}
|
|
}
|
|
|
|
if (IsInvalidSurfaceHit(HitPayload))
|
|
{
|
|
// Propagate to Payload and terminate immediately upon invalid surface hits
|
|
Payload = HitPayload;
|
|
break;
|
|
}
|
|
|
|
// prepare next step around the loop
|
|
// retrace the exact same ray with TMin one ulp past the hit we just found
|
|
PathThroughput *= HitPayload.TransparencyColor;
|
|
Ray.TMin = asfloat(asuint(HitPayload.HitT) + 1);
|
|
|
|
if (all(PathThroughput == 0))
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (SelectionWeightSum > 0)
|
|
{
|
|
// if we stored a valid hit in the payload, reset the path throughput to this point
|
|
PathThroughput = PayloadThroughput * SelectionWeightSum;
|
|
}
|
|
else
|
|
{
|
|
PathThroughput = 0;
|
|
Payload.SetMiss();
|
|
}
|
|
return Payload;
|
|
}
|
|
|
|
float3 TraceTransparentVisibilityRay(FRayDesc Ray, float PathRoughness)
|
|
{
|
|
const uint RayFlags = RAY_FLAG_ACCEPT_FIRST_HIT_AND_END_SEARCH | RAY_FLAG_SKIP_CLOSEST_HIT_SHADER;
|
|
const uint InstanceInclusionMask = PATHTRACER_MASK_SHADOW;
|
|
const uint RayContributionToHitGroupIndex = RAY_TRACING_SHADER_SLOT_SHADOW;
|
|
const uint MultiplierForGeometryContributionToShaderIndex = RAY_TRACING_NUM_SHADER_SLOTS;
|
|
const uint MissShaderIndex = 0;
|
|
|
|
FPackedPathTracingPayload PackedPayload = InitPathTracingVisibilityPayload(PathRoughness);
|
|
TraceRay(
|
|
TLAS,
|
|
RayFlags,
|
|
InstanceInclusionMask,
|
|
RayContributionToHitGroupIndex,
|
|
MultiplierForGeometryContributionToShaderIndex,
|
|
MissShaderIndex,
|
|
Ray.GetNativeDesc(),
|
|
PackedPayload);
|
|
if (PackedPayload.IsHit())
|
|
{
|
|
// We didn't run the miss shader, therefore we must have hit something opaque (or reached full opacity)
|
|
return 0.0;
|
|
}
|
|
return PackedPayload.GetRayThroughput();
|
|
}
|
|
|
|
void PathTracingKernel(
|
|
in uint RenderPassIndex,
|
|
float3 TranslatedWorldPosition,
|
|
float3 ShadingNormal,
|
|
inout RandomSequence RandSequence,
|
|
inout uint SampleIndex,
|
|
inout bool bIsValidSample,
|
|
inout float3 RadianceValue,
|
|
inout float3 RadianceDirection,
|
|
inout float3 DirectLightingIrradiance,
|
|
#ifdef LIGHTMAP_PATH_TRACING_MAIN_RG
|
|
inout FL2SHAndCorrection DirectLightingSH,
|
|
#else
|
|
inout FThreeBandSHVectorRGBFloat DirectLightingSH,
|
|
#endif
|
|
inout float LuminanceForFirstBounceRayGuiding,
|
|
inout float3 SkyLightBentNormal,
|
|
inout float4 PrimaryRandSample)
|
|
{
|
|
uint2 LaunchIndex = uint2(0, 0);
|
|
|
|
#if USE_IRRADIANCE_CACHING
|
|
bool bShouldEmitGeometryHitPoint = false;
|
|
|
|
FFinalGatherHitPoint IrradianceCacheFinalGatherHitPoint = (FFinalGatherHitPoint)0;
|
|
float RadianceProbe_Pdf = 1.0f / (2 * PI);
|
|
|
|
uint NewEntrySize = 32;
|
|
int NearestCacheEntryID = -1;
|
|
#endif
|
|
|
|
float3 Radiance = 0;
|
|
|
|
// GPULightmass's SampleEmitter(): generate a fake camera ray hitting the texel
|
|
FRayDesc Ray;
|
|
Ray.Origin = TranslatedWorldPosition + ShadingNormal;
|
|
Ray.Direction = -ShadingNormal;
|
|
Ray.TMin = -0.01f;
|
|
Ray.TMax = 1.01f;
|
|
|
|
// This array will hold a CDF for light picking
|
|
// Seed the array with a uniform CDF at first so that we always have a valid CDF
|
|
float LightPickingCdf[RAY_TRACING_LIGHT_COUNT_MAXIMUM];
|
|
|
|
Ray.Direction = normalize(Ray.Direction);
|
|
|
|
// path state variables (these cary information between bounces)
|
|
float3 PathThroughput = 1.0;
|
|
float PathRoughness = 0;
|
|
|
|
// number of directly visible lights for the first bounce
|
|
uint NumVisibleLights = SceneVisibleLightCount;
|
|
|
|
for (int Bounce = 0; Bounce <= MaxBounces; Bounce++)
|
|
{
|
|
const bool bIsCameraRay = Bounce == 0;
|
|
const bool bIsLastBounce = Bounce == MaxBounces;
|
|
const bool bIncludeEmissive = EnableEmissive;
|
|
FPathTracingPayload Payload = (FPathTracingPayload)0;
|
|
|
|
if (bIsCameraRay)
|
|
{
|
|
// GPULightmass fakes a 'camera' ray that hits the lightmap texel perpendicularly
|
|
Payload.TranslatedWorldPos = TranslatedWorldPosition;
|
|
Payload.WorldNormal = ShadingNormal;
|
|
Payload.WorldGeoNormal = ShadingNormal;
|
|
Payload.Radiance = 0;
|
|
Payload.BaseColor = 1;
|
|
Payload.SubsurfaceColor = 0;
|
|
Payload.BSDFOpacity = 1;
|
|
Payload.TransparencyColor = 0;
|
|
Payload.ShadingModelID = SHADINGMODELID_DEFAULT_LIT;
|
|
Payload.PrimitiveLightingChannelMask = 0b111;
|
|
Payload.HitT = 1.0f;
|
|
Payload.SetFrontFace();
|
|
}
|
|
else
|
|
{
|
|
Payload = TraceTransparentRay(Ray, false , bIsLastBounce, bIncludeEmissive, LaunchIndex, NumVisibleLights, RandSequence, PathThroughput, Radiance);
|
|
}
|
|
|
|
if (Payload.IsMiss())
|
|
{
|
|
// we didn't hit anything selectable for further shading, we are done
|
|
break;
|
|
}
|
|
|
|
if (IsInvalidSurfaceHit(Payload))
|
|
{
|
|
bIsValidSample = false;
|
|
break;
|
|
}
|
|
|
|
#if USE_IRRADIANCE_CACHING
|
|
float3 TexelPosition = TranslatedWorldPosition - DFHackToFloat(PrimaryView.PreViewTranslation);
|
|
float3 HitPosition = Payload.TranslatedWorldPos - DFHackToFloat(PrimaryView.PreViewTranslation);
|
|
float DistanceFromTexelToHit = length(HitPosition - TexelPosition);
|
|
bool bRejectedByCornerRejection = RenderPassIndex >= GetIrradianceCachingQuality() && DistanceFromTexelToHit < IrradianceCachingParameters.Spacing * IrradianceCachingParameters.CornerRejection;
|
|
|
|
// Compute an overestimated upper bound for the number of samples we need for this texel by assuming
|
|
// all the rays from all IC building passes are sent from this single texel
|
|
float CellArea = IrradianceCachingParameters.Spacing * IrradianceCachingParameters.Spacing;
|
|
float TotalSamplesOverAllICPasses = GPreviewLightmapPhysicalTileSize * GPreviewLightmapPhysicalTileSize * GetIrradianceCachingQuality();
|
|
float DistanceFraction = CellArea / (2 * PI * DistanceFromTexelToHit * DistanceFromTexelToHit);
|
|
|
|
// Cache entries too far away have a very low chance of being reused
|
|
// Don't bother placing or looking them up
|
|
bool bRejectedByMaxLookupDistace = false;
|
|
if (TotalSamplesOverAllICPasses * DistanceFraction < 1)
|
|
{
|
|
bRejectedByMaxLookupDistace = true;
|
|
}
|
|
|
|
if (NearestCacheEntryID == -1 && Bounce >= 1 && !bRejectedByCornerRejection && !bRejectedByMaxLookupDistace)
|
|
{
|
|
bool bIrradianceQuerySuccessful = false;
|
|
bool bGeometryQuerySuccessful = false;
|
|
|
|
uint NearestRecordIndex = 0;
|
|
float3 RecordIrradiance;
|
|
|
|
uint Index;
|
|
if (ICHashTableFind(EncodeICHashKey(HitPosition, Payload.WorldNormal, IrradianceCachingParameters.Spacing), Index))
|
|
{
|
|
uint RecordIndex = IrradianceCachingParameters.RWHashToIndex[Index];
|
|
|
|
uint4 RecordIrradianceAndSampleCount = IrradianceCachingParameters.IrradianceCacheRecords[RecordIndex];
|
|
uint BackfaceHitsCount = IrradianceCachingParameters.IrradianceCacheRecordBackfaceHits[RecordIndex].a;
|
|
|
|
// We accept a cache entry, if:
|
|
// 1) The entry has been built by other lightmap tiles and contains more samples than we will ever need.
|
|
// 2) We're outside of IC building passes. An entry with low sample count means we just need that much - still, we're going to require a minimum of 4 samples to remove outliers from potential cache thrashing
|
|
|
|
bool bShouldAcceptCacheEntry = false;
|
|
|
|
uint SampleCountThreshold;
|
|
|
|
if (RenderPassIndex < GetIrradianceCachingQuality())
|
|
{
|
|
SampleCountThreshold = TotalSamplesOverAllICPasses * DistanceFraction;
|
|
}
|
|
else
|
|
{
|
|
SampleCountThreshold = 4;
|
|
}
|
|
|
|
bShouldAcceptCacheEntry = RecordIrradianceAndSampleCount.w >= SampleCountThreshold;
|
|
|
|
#if IC_BACKFACE_DETECTION
|
|
// Reject the entry if it can see back faces
|
|
if (RenderPassIndex >= GetIrradianceCachingQuality() && BackfaceHitsCount > RecordIrradianceAndSampleCount.w * BackfaceThresholdRejection)
|
|
{
|
|
bShouldAcceptCacheEntry = false;
|
|
}
|
|
|
|
// Terminate the path if the entry contains too many back face hits which means it is very likely to be fully inside geometry
|
|
if (RenderPassIndex >= GetIrradianceCachingQuality() && BackfaceHitsCount > RecordIrradianceAndSampleCount.w * BackfaceThresholdInsideGeometry)
|
|
{
|
|
bIsValidSample = false;
|
|
break;
|
|
}
|
|
#endif
|
|
|
|
if (bShouldAcceptCacheEntry)
|
|
{
|
|
bIrradianceQuerySuccessful = true;
|
|
|
|
RecordIrradiance = asfloat(RecordIrradianceAndSampleCount.xyz) / RecordIrradianceAndSampleCount.w;
|
|
}
|
|
|
|
bGeometryQuerySuccessful = true;
|
|
NearestRecordIndex = RecordIndex;
|
|
}
|
|
|
|
if (bIrradianceQuerySuccessful)
|
|
{
|
|
// Successful query. Terminate path immediately.
|
|
Radiance += RecordIrradiance * PathThroughput;
|
|
LuminanceForFirstBounceRayGuiding += Luminance(RecordIrradiance * PathThroughput);
|
|
break;
|
|
}
|
|
else
|
|
{
|
|
// Failed query.
|
|
if (Bounce == 1)
|
|
{
|
|
// Prevent modification to the cache outside IC building passes
|
|
if (RenderPassIndex < GetIrradianceCachingQuality())
|
|
{
|
|
if (!bGeometryQuerySuccessful)
|
|
{
|
|
// Only use the very first few passes to place new entries to reduce cache thrashing
|
|
bool bShouldPlaceNewEntry = RenderPassIndex < 16;
|
|
|
|
if (bShouldPlaceNewEntry)
|
|
{
|
|
bShouldEmitGeometryHitPoint = true;
|
|
IrradianceCacheFinalGatherHitPoint.WorldPosition = HitPosition;
|
|
IrradianceCacheFinalGatherHitPoint.WorldNormal = Payload.WorldNormal;
|
|
NewEntrySize = IrradianceCachingParameters.Spacing;
|
|
break;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
NearestCacheEntryID = NearestRecordIndex;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
#endif
|
|
|
|
FLightLoopCount LightLoopCount = LightGridLookup(Payload.TranslatedWorldPos);
|
|
|
|
// Choose a random number for both Light sampling and BxDF sampling
|
|
float4 RandSample = RandomSequence_GenerateSample4D(RandSequence);
|
|
|
|
float LightPickingCdfSum = 0;
|
|
|
|
// If we are using Light sampling and the material can use it ...
|
|
if (MISMode != 0 && SceneLightCount > 0)
|
|
{
|
|
// Choose a light and sample it
|
|
float3 TranslatedWorldPos = Payload.TranslatedWorldPos;
|
|
float3 WorldNormal = Payload.WorldNormal;
|
|
uint PrimitiveLightingChannelMask = Payload.PrimitiveLightingChannelMask;
|
|
|
|
bool IsTransmissiveMaterial = ENABLE_TRANSMISSION && Payload.IsMaterialTransmissive();
|
|
|
|
for (uint Index = 0, Num = LightLoopCount.NumLights; Index < Num; ++Index)
|
|
{
|
|
uint LightIndex = GetLightId(Index, LightLoopCount);
|
|
LightPickingCdfSum += EstimateLight(LightIndex, TranslatedWorldPos, WorldNormal, PrimitiveLightingChannelMask, IsTransmissiveMaterial);
|
|
LightPickingCdf[Index] = LightPickingCdfSum;
|
|
}
|
|
|
|
if (LightPickingCdfSum > 0)
|
|
{
|
|
// init worked
|
|
int LightId;
|
|
float LightPickPdf = 0;
|
|
|
|
SelectLight(RandSample.x * LightPickingCdfSum, LightLoopCount.NumLights, LightPickingCdf, LightId, LightPickPdf);
|
|
|
|
LightId = GetLightId(LightId, LightLoopCount);
|
|
|
|
FLightSample LightSample = SampleLight(LightId, RandSample.yz, TranslatedWorldPos, WorldNormal);
|
|
|
|
LightPickPdf /= LightPickingCdfSum;
|
|
|
|
LightSample.RadianceOverPdf /= LightPickPdf;
|
|
LightSample.Pdf *= LightPickPdf;
|
|
if (LightSample.Pdf > 0)
|
|
{
|
|
float3 Visibility = float3(1, 1, 1);
|
|
|
|
if (CastsShadow(LightId))
|
|
{
|
|
// for transmissive materials, bias the position to the other side of the surface if the light is coming from behind
|
|
const float SignedPositionBias = IsTransmissiveMaterial ? sign(dot(Payload.WorldNormal, LightSample.Direction)) : 1.0;
|
|
|
|
FRayDesc LightRay;
|
|
LightRay.Origin = TranslatedWorldPos;
|
|
LightRay.TMin = 0;
|
|
LightRay.Direction = LightSample.Direction;
|
|
LightRay.TMax = LightSample.Distance;
|
|
ApplyRayBias(LightRay.Origin, Payload.HitT, SignedPositionBias * Payload.WorldGeoNormal);
|
|
|
|
float AvgRoughness = ApproximateCaustics ? GetAverageRoughness(Payload) : 0.0;
|
|
|
|
Visibility = TraceTransparentVisibilityRay(LightRay, AvgRoughness);
|
|
LightSample.RadianceOverPdf *= Visibility;
|
|
}
|
|
|
|
// #dxr_todo: Is it cheaper to fire the ray first? Or eval the material first?
|
|
if (any(LightSample.RadianceOverPdf > 0))
|
|
{
|
|
// Evaluate material
|
|
FMaterialEval MaterialEval;
|
|
if (Bounce == 0)
|
|
{
|
|
MaterialEval = RadianceProbe_EvalMaterial(LightSample.Direction, Payload);
|
|
}
|
|
else
|
|
{
|
|
MaterialEval = EvalMaterial(-Ray.Direction, LightSample.Direction, Payload, float2(1.0, 0.0));
|
|
}
|
|
|
|
|
|
// Record the contribution
|
|
float3 LightContrib = PathThroughput * LightSample.RadianceOverPdf * MaterialEval.Weight * MaterialEval.Pdf;
|
|
float3 BentNormalVector = LightSample.Direction / LightSample.Pdf / PI * Visibility;
|
|
if (MISMode == 2)
|
|
{
|
|
LightContrib *= MISWeightPower(LightSample.Pdf, MaterialEval.Pdf);
|
|
BentNormalVector *= MISWeightPower(LightSample.Pdf, MaterialEval.Pdf);
|
|
}
|
|
|
|
// Record the contribution
|
|
if (Bounce > 0)
|
|
{
|
|
AccumulateRadiance(Radiance, LightContrib);
|
|
LuminanceForFirstBounceRayGuiding += Luminance(LightContrib);
|
|
}
|
|
else
|
|
{
|
|
// GPU Lightmass records contribution of direct lighting separately, and only for static lights
|
|
if (!IsStationary(LightId))
|
|
{
|
|
#ifdef LIGHTMAP_PATH_TRACING_MAIN_RG
|
|
float TangentZ = saturate(dot(LightSample.Direction, Payload.WorldNormal));
|
|
DirectLightingSH.AddIncomingRadiance(Luminance(LightContrib), LightSample.Direction, TangentZ);
|
|
DirectLightingIrradiance += LightContrib * TangentZ;
|
|
#else
|
|
DirectLightingSH.R.V0 = SHBasisFunction3Float(LightSample.Direction).V0 * LightContrib.r;
|
|
DirectLightingSH.R.V1 = SHBasisFunction3Float(LightSample.Direction).V1 * LightContrib.r;
|
|
DirectLightingSH.R.V2 = SHBasisFunction3Float(LightSample.Direction).V2 * LightContrib.r;
|
|
DirectLightingSH.G.V0 = SHBasisFunction3Float(LightSample.Direction).V0 * LightContrib.g;
|
|
DirectLightingSH.G.V1 = SHBasisFunction3Float(LightSample.Direction).V1 * LightContrib.g;
|
|
DirectLightingSH.G.V2 = SHBasisFunction3Float(LightSample.Direction).V2 * LightContrib.g;
|
|
DirectLightingSH.B.V0 = SHBasisFunction3Float(LightSample.Direction).V0 * LightContrib.b;
|
|
DirectLightingSH.B.V1 = SHBasisFunction3Float(LightSample.Direction).V1 * LightContrib.b;
|
|
DirectLightingSH.B.V2 = SHBasisFunction3Float(LightSample.Direction).V2 * LightContrib.b;
|
|
#endif
|
|
}
|
|
|
|
if (IsStationary(LightId) && IsEnvironmentLight(LightId) && CastsShadow(LightId))
|
|
{
|
|
SkyLightBentNormal += BentNormalVector;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Sample material
|
|
FMaterialSample MaterialSample;
|
|
if (Bounce == 0)
|
|
{
|
|
MaterialSample = RadianceProbe_SampleMaterial(Payload, RandSample.xyz);
|
|
}
|
|
else
|
|
{
|
|
MaterialSample = SampleMaterial(-Ray.Direction, Payload, RandSample.xyz);
|
|
}
|
|
|
|
if (MaterialSample.Pdf < 0 || asuint(MaterialSample.Pdf) > 0x7F800000)
|
|
{
|
|
// Pdf became invalid (either negative or NaN)
|
|
Radiance = float3(1, 0, 1);
|
|
break;
|
|
}
|
|
|
|
if (!(MaterialSample.Pdf > 0))
|
|
{
|
|
// No valid direction -- we are done
|
|
break;
|
|
}
|
|
|
|
float3 NextPathThroughput = PathThroughput * MaterialSample.Weight;
|
|
if (!any(NextPathThroughput > 0))
|
|
{
|
|
// no energy left in this path
|
|
break;
|
|
}
|
|
|
|
// Russian roulette:
|
|
// The probability of keeping the path should be roughly proportional to the weight at the current shade point,
|
|
// but just using MaterialWeight would miss out on cases where the path throughput changes color (like in a cornell
|
|
// box when bouncing between walls of different colors). So use the ratio of the brightest color channel in the
|
|
// previous and next throughput.
|
|
// The second tweak is to add a sqrt() around the probability to soften the termination probability (paths will last
|
|
// a little longer). This allows paths to go a bit deeper than the naive heuristic while still allowing them to terminate
|
|
// early. This makes RR effective from the very first bounce without needing to delay it.
|
|
float ContinuationProb = sqrt(saturate(max(NextPathThroughput.x, max(NextPathThroughput.y, NextPathThroughput.z)) / max(PathThroughput.x, max(PathThroughput.y, PathThroughput.z))));
|
|
if (ContinuationProb < 1)
|
|
{
|
|
// If there is some chance we should terminate the ray, draw an extra random value
|
|
float RussianRouletteRand = RandSample.w;
|
|
//RussianRouletteRand = RandomSequence_GenerateSample1D(RandSequence);
|
|
if (RussianRouletteRand >= ContinuationProb)
|
|
{
|
|
// stochastically terminate the path
|
|
break;
|
|
}
|
|
PathThroughput = NextPathThroughput / ContinuationProb;
|
|
}
|
|
else
|
|
{
|
|
PathThroughput = NextPathThroughput;
|
|
}
|
|
|
|
// Update ray according to material sample
|
|
Ray.Origin = Payload.TranslatedWorldPos;
|
|
Ray.Direction = MaterialSample.Direction;
|
|
Ray.TMin = 0;
|
|
Ray.TMax = RAY_DEFAULT_T_MAX;
|
|
ApplyRayBias(Ray.Origin, Payload.HitT, MaterialSample.PositionBiasSign * Payload.WorldGeoNormal);
|
|
|
|
if (ApproximateCaustics)
|
|
{
|
|
// enlarge roughness based on the chosen lobe roughness
|
|
PathRoughness = max(PathRoughness, MaterialSample.Roughness);
|
|
}
|
|
|
|
if (Bounce == 0)
|
|
{
|
|
RadianceDirection = Ray.Direction;
|
|
#if USE_IRRADIANCE_CACHING
|
|
RadianceProbe_Pdf = MaterialSample.Pdf;
|
|
#endif
|
|
PrimaryRandSample = RandSample;
|
|
}
|
|
|
|
// If we are using Material sampling for lights
|
|
if (MISMode != 1)
|
|
{
|
|
// Check which lights can be seen by the material ray and trace a dedicated shadow ray
|
|
// While it would be possible to just loop around and use the indirect ray to do this, it would prevent the application
|
|
// of shadow ray specific logic for transparent shadows or various per light tricks like shadow casting
|
|
const bool bUseMIS = MISMode == 2 && LightPickingCdfSum > 0;
|
|
for (uint Index = 0, Num = LightLoopCount.NumMISLights; Index < Num; ++Index)
|
|
{
|
|
uint LightId = GetLightId(Index, LightLoopCount);
|
|
if ((Payload.PrimitiveLightingChannelMask & GetLightingChannelMask(LightId)) == 0)
|
|
{
|
|
// light does not affect the current ray
|
|
continue;
|
|
}
|
|
|
|
FLightHit LightResult = TraceLight(Ray, LightId);
|
|
|
|
if (LightResult.IsMiss())
|
|
{
|
|
continue;
|
|
}
|
|
|
|
float3 LightContrib = PathThroughput * LightResult.Radiance;
|
|
float3 BentNormalVector = PathThroughput * MaterialSample.Direction / PI;
|
|
|
|
if (bUseMIS)
|
|
{
|
|
float PreviousCdfValue = 0.0;
|
|
BRANCH if (Index > 0)
|
|
{
|
|
PreviousCdfValue = LightPickingCdf[Index - 1];
|
|
}
|
|
float LightPickPdf = (LightPickingCdf[Index] - PreviousCdfValue) / LightPickingCdfSum;
|
|
|
|
LightContrib *= MISWeightPower(MaterialSample.Pdf, LightResult.Pdf * LightPickPdf);
|
|
|
|
// Separate direct lighting (bounce 0) for GPU Lightmass
|
|
BentNormalVector *= MISWeightPower(MaterialSample.Pdf, LightResult.Pdf * LightPickPdf);
|
|
}
|
|
|
|
if (any(LightContrib > 0))
|
|
{
|
|
if (CastsShadow(LightId))
|
|
{
|
|
FRayDesc LightRay = Ray;
|
|
LightRay.TMax = LightResult.HitT;
|
|
float3 Visibility = TraceTransparentVisibilityRay(LightRay, PathRoughness);
|
|
LightContrib *= Visibility;
|
|
|
|
// Separate direct lighting (bounce 0) for GPU Lightmass
|
|
BentNormalVector *= Visibility;
|
|
}
|
|
|
|
if (Bounce > 0)
|
|
{
|
|
// the light made some contribution, and there was nothing along the shadow ray
|
|
AccumulateRadiance(Radiance, LightContrib);
|
|
}
|
|
else // Bounce == 0
|
|
{
|
|
if (!IsStationary(LightId))
|
|
{
|
|
#ifdef LIGHTMAP_PATH_TRACING_MAIN_RG
|
|
float TangentZ = saturate(dot(MaterialSample.Direction, Payload.WorldNormal));
|
|
DirectLightingSH.AddIncomingRadiance(Luminance(LightContrib), MaterialSample.Direction, TangentZ);
|
|
DirectLightingIrradiance += LightContrib * TangentZ;
|
|
#else
|
|
DirectLightingSH.R.V0 = SHBasisFunction3Float(MaterialSample.Direction).V0 * LightContrib.r;
|
|
DirectLightingSH.R.V1 = SHBasisFunction3Float(MaterialSample.Direction).V1 * LightContrib.r;
|
|
DirectLightingSH.R.V2 = SHBasisFunction3Float(MaterialSample.Direction).V2 * LightContrib.r;
|
|
DirectLightingSH.G.V0 = SHBasisFunction3Float(MaterialSample.Direction).V0 * LightContrib.g;
|
|
DirectLightingSH.G.V1 = SHBasisFunction3Float(MaterialSample.Direction).V1 * LightContrib.g;
|
|
DirectLightingSH.G.V2 = SHBasisFunction3Float(MaterialSample.Direction).V2 * LightContrib.g;
|
|
DirectLightingSH.B.V0 = SHBasisFunction3Float(MaterialSample.Direction).V0 * LightContrib.b;
|
|
DirectLightingSH.B.V1 = SHBasisFunction3Float(MaterialSample.Direction).V1 * LightContrib.b;
|
|
DirectLightingSH.B.V2 = SHBasisFunction3Float(MaterialSample.Direction).V2 * LightContrib.b;
|
|
#endif
|
|
}
|
|
|
|
if (IsStationary(LightId) && IsEnvironmentLight(LightId) && CastsShadow(LightId))
|
|
{
|
|
SkyLightBentNormal += BentNormalVector;
|
|
}
|
|
}
|
|
|
|
LuminanceForFirstBounceRayGuiding += Luminance(LightContrib);
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
// from this point on, we don't need to include lights in the trace call
|
|
// because NEE handled it for us
|
|
NumVisibleLights = 0;
|
|
}
|
|
|
|
#if USE_IRRADIANCE_CACHING
|
|
if(bIsValidSample)
|
|
{
|
|
if(bShouldEmitGeometryHitPoint)
|
|
{
|
|
EmitGeometryHitPoint(IrradianceCacheFinalGatherHitPoint, NewEntrySize);
|
|
}
|
|
|
|
if(NearestCacheEntryID != -1)
|
|
{
|
|
float3 IrradianceToAccumulate = Radiance * RadianceProbe_Pdf; // Revert the radiance probe pdf as we're caching the first bounce outgoing radiance
|
|
ATOMIC_ADD_FLOAT(IrradianceCachingParameters.IrradianceCacheRecords[NearestCacheEntryID].r, IrradianceToAccumulate.r);
|
|
ATOMIC_ADD_FLOAT(IrradianceCachingParameters.IrradianceCacheRecords[NearestCacheEntryID].g, IrradianceToAccumulate.g);
|
|
ATOMIC_ADD_FLOAT(IrradianceCachingParameters.IrradianceCacheRecords[NearestCacheEntryID].b, IrradianceToAccumulate.b);
|
|
InterlockedAdd(IrradianceCachingParameters.IrradianceCacheRecords[NearestCacheEntryID].a, 1);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
#if IC_BACKFACE_DETECTION
|
|
if(NearestCacheEntryID != -1)
|
|
{
|
|
InterlockedAdd(IrradianceCachingParameters.IrradianceCacheRecordBackfaceHits[NearestCacheEntryID].a, 1);
|
|
}
|
|
#endif
|
|
}
|
|
#endif
|
|
|
|
RadianceValue = Radiance;
|
|
}
|
|
|
|
[shader("raygeneration")]
|
|
void LightmapPathTracingMainRG()
|
|
{
|
|
uint2 BatchedLaunchIndex = DispatchRaysIndex().xy;
|
|
|
|
uint2 LaunchIndex = uint2(BatchedLaunchIndex.x % GPreviewLightmapPhysicalTileSize, BatchedLaunchIndex.y);
|
|
int TileIndex = BatchedLaunchIndex.x / GPreviewLightmapPhysicalTileSize;
|
|
uint2 TexelIndexInPool = LaunchIndex + BatchedTiles[TileIndex].WorkingSetPosition;
|
|
uint2 TexelIndexInScratch = LaunchIndex + BatchedTiles[TileIndex].ScratchPosition;
|
|
|
|
float3 WorldNormal = GBufferWorldNormal[TexelIndexInScratch].xyz;
|
|
float3 ShadingNormal = GBufferShadingNormal[TexelIndexInScratch].xyz;
|
|
bool bMaterialTwoSided = GBufferShadingNormal[TexelIndexInScratch].w;
|
|
|
|
float4 EncodedGBufferWorldPosition = GBufferWorldPosition[TexelIndexInScratch];
|
|
|
|
bool bGBufferSampleValid = true;
|
|
if (EncodedGBufferWorldPosition.w == 0.0f)
|
|
{
|
|
bGBufferSampleValid = false;
|
|
}
|
|
|
|
float3 TranslatedWorldPosition = EncodedGBufferWorldPosition.xyz / WorldPositionScalar + DFHackToFloat(PrimaryView.PreViewTranslation); // RT_LWC_TODO
|
|
|
|
int EffectiveRenderPassIndex = BatchedTiles[TileIndex].RenderPassIndex;
|
|
#if USE_IRRADIANCE_CACHING
|
|
if (EffectiveRenderPassIndex >= GetIrradianceCachingQuality())
|
|
{
|
|
EffectiveRenderPassIndex -= GetIrradianceCachingQuality();
|
|
|
|
#if USE_FIRST_BOUNCE_RAY_GUIDING
|
|
if (EffectiveRenderPassIndex >= NumRayGuidingTrialSamples)
|
|
{
|
|
EffectiveRenderPassIndex -= NumRayGuidingTrialSamples;
|
|
}
|
|
#endif
|
|
}
|
|
#endif
|
|
|
|
if (EffectiveRenderPassIndex == 0)
|
|
{
|
|
IrradianceAndSampleCount[TexelIndexInPool]= float4(0, 0, 0, 0);
|
|
SHDirectionality[TexelIndexInPool] = float4(0, 0, 0, 0);
|
|
SHCorrectionAndStationarySkyLightBentNormal[TexelIndexInPool] = float4(0, 0, 0, 0);
|
|
}
|
|
|
|
if (NumTotalSamples <= 0 || (NumTotalSamples > 0 && BatchedTiles[TileIndex].RenderPassIndex < NumTotalSamples - 1))
|
|
{
|
|
bool bAnyHemispherePathSampleValid = false;
|
|
|
|
if (bGBufferSampleValid)
|
|
{
|
|
// Needs a seed that is only related to position in virtual space to avoid seams due to per-tile calculation
|
|
uint2 VirtualTextureSpacePosition = BatchedTiles[TileIndex].VirtualTilePosition + LaunchIndex - uint2(2, 2);
|
|
uint Seed = VirtualTextureSpacePosition.y * BatchedTiles[TileIndex].LightmapSize.x + VirtualTextureSpacePosition.x;
|
|
|
|
float4 PrimaryRandSample = float4(-1, -1, -1, -1);
|
|
|
|
// Evaluate both hemispheres for two sided materials and add the lighting up naively
|
|
UNROLL_N(2)
|
|
for (int HemisphereIndex = 0; HemisphereIndex < (bMaterialTwoSided ? 2 : 1); HemisphereIndex++)
|
|
{
|
|
RandomSequence RandSequence;
|
|
RandomSequence_Initialize(RandSequence, Seed, EffectiveRenderPassIndex);
|
|
|
|
uint SampleIndex = 2;
|
|
|
|
float3 RadianceValue = 0;
|
|
float3 RadianceDirection = 0;
|
|
float3 DirectLightingIrradiance = 0;
|
|
#ifdef LIGHTMAP_PATH_TRACING_MAIN_RG
|
|
FL2SHAndCorrection DirectLightingSH = (FL2SHAndCorrection)0;
|
|
#else
|
|
FThreeBandSHVectorRGBFloat DirectLightingSH = (FThreeBandSHVectorRGBFloat)0;
|
|
#endif
|
|
float LuminanceForFirstBounceRayGuiding = 0;
|
|
float3 SkyLightBentNormal = 0;
|
|
|
|
float3 EffectiveShadingNormal = HemisphereIndex == 0 ? ShadingNormal : -ShadingNormal;
|
|
|
|
bool bPathSampleValid = true;
|
|
|
|
PathTracingKernel(
|
|
BatchedTiles[TileIndex].RenderPassIndex,
|
|
TranslatedWorldPosition,
|
|
EffectiveShadingNormal,
|
|
RandSequence,
|
|
SampleIndex,
|
|
bPathSampleValid,
|
|
RadianceValue,
|
|
RadianceDirection,
|
|
DirectLightingIrradiance,
|
|
DirectLightingSH,
|
|
LuminanceForFirstBounceRayGuiding,
|
|
SkyLightBentNormal,
|
|
PrimaryRandSample
|
|
);
|
|
|
|
if (any(isnan(RadianceValue)) || any(RadianceValue < 0) || any(isinf(RadianceValue)))
|
|
{
|
|
bPathSampleValid = false;
|
|
}
|
|
|
|
if (bPathSampleValid)
|
|
{
|
|
#if USE_FIRST_BOUNCE_RAY_GUIDING
|
|
int MinRenderPassIndex = 0;
|
|
int MaxRenderPassIndex = NumRayGuidingTrialSamples;
|
|
|
|
#if USE_IRRADIANCE_CACHING
|
|
MinRenderPassIndex += GetIrradianceCachingQuality();
|
|
MaxRenderPassIndex += GetIrradianceCachingQuality();
|
|
#endif
|
|
|
|
if (BatchedTiles[TileIndex].RenderPassIndex >= MinRenderPassIndex && BatchedTiles[TileIndex].RenderPassIndex < MaxRenderPassIndex)
|
|
{
|
|
float2 Point = PrimaryRandSample.yx;
|
|
int2 ClusterPosition = clamp((int2)LaunchIndex - int2(2, 2), int2(0, 0), int2(63, 63)) / TEXEL_CLUSTER_SIZE;
|
|
float2 JitteredBin = Point * DIRECTIONAL_BINS_ONE_DIM;// - float2(0.5f, 0.5f) + PrimaryRandSample.xy;
|
|
int2 PositionInBin = clamp(JitteredBin, float2(0, 0), float2(DIRECTIONAL_BINS_ONE_DIM - 1, DIRECTIONAL_BINS_ONE_DIM - 1));
|
|
int2 FinalPosition = ClusterPosition * DIRECTIONAL_BINS_ONE_DIM + PositionInBin;
|
|
|
|
{
|
|
float Illuminance = LuminanceForFirstBounceRayGuiding * saturate(dot(RadianceDirection, EffectiveShadingNormal));
|
|
// For positive floats we can cast them to uint and use atomic max directly
|
|
InterlockedMax(RayGuidingLuminance[BatchedTiles[TileIndex].WorkingSetPosition / GPreviewLightmapPhysicalTileSize * CDF_TILE_SIZE + FinalPosition], asuint(max(Illuminance, 0)));
|
|
}
|
|
}
|
|
#endif
|
|
|
|
{
|
|
FL2SHAndCorrection SH = (FL2SHAndCorrection)0;
|
|
|
|
float TangentZ = saturate(dot(RadianceDirection, EffectiveShadingNormal));
|
|
SH.AddIncomingRadiance(Luminance(RadianceValue), RadianceDirection, TangentZ);
|
|
|
|
IrradianceAndSampleCount[TexelIndexInPool].rgb += float3(RadianceValue * TangentZ / PI);
|
|
SHDirectionality[TexelIndexInPool] += SH.L2SHCoefficients;
|
|
SHCorrectionAndStationarySkyLightBentNormal[TexelIndexInPool].x += SH.Correction;
|
|
}
|
|
|
|
#ifdef LIGHTMAP_PATH_TRACING_MAIN_RG
|
|
{
|
|
IrradianceAndSampleCount[TexelIndexInPool].rgb += DirectLightingIrradiance / PI;
|
|
SHDirectionality[TexelIndexInPool] += DirectLightingSH.L2SHCoefficients;
|
|
SHCorrectionAndStationarySkyLightBentNormal[TexelIndexInPool].x += DirectLightingSH.Correction;
|
|
}
|
|
#endif
|
|
|
|
SHCorrectionAndStationarySkyLightBentNormal[TexelIndexInPool].yzw += SkyLightBentNormal;
|
|
}
|
|
|
|
bAnyHemispherePathSampleValid = bAnyHemispherePathSampleValid || bPathSampleValid;
|
|
}
|
|
}
|
|
|
|
uint SampleCount = asuint(IrradianceAndSampleCount[TexelIndexInPool].w);
|
|
|
|
#if 0 // Smooth invalidation. This path is currently disabled to avoid inconsistency when saving baked results.
|
|
if (BatchedTiles[TileIndex].FrameIndex < LastInvalidationFrame)
|
|
{
|
|
if (SampleCount > 0)
|
|
{
|
|
float L = Luminance(IrradianceAndSampleCount[TexelIndexInPool].rgb / SampleCount);
|
|
int HistoryWeight = 2.0f / (1.0f + L * L);
|
|
IrradianceAndSampleCount[TexelIndexInPool].rgb = IrradianceAndSampleCount[TexelIndexInPool].rgb / SampleCount * HistoryWeight;
|
|
SHDirectionality[TexelIndexInPool] = SHDirectionality[TexelIndexInPool] / SampleCount * HistoryWeight;
|
|
SampleCount = HistoryWeight;
|
|
}
|
|
}
|
|
#endif
|
|
|
|
if (bAnyHemispherePathSampleValid)
|
|
SampleCount++;
|
|
|
|
IrradianceAndSampleCount[TexelIndexInPool].a = asfloat(SampleCount);
|
|
}
|
|
|
|
#if 0 // Debug: Ray guiding PDF visualization
|
|
{
|
|
int2 ClusterPosition = clamp((int2)LaunchIndex - int2(2, 2), int2(0, 0), int2(63, 63)) / TEXEL_CLUSTER_SIZE;
|
|
int2 PositionInBin = clamp((int2)LaunchIndex - int2(2, 2), int2(0, 0), int2(63, 63)) % DIRECTIONAL_BINS_ONE_DIM;
|
|
int2 FinalPosition = ClusterPosition * DIRECTIONAL_BINS_ONE_DIM + PositionInBin;
|
|
|
|
IrradianceAndSampleCount[TexelIndexInPool].rgb = (
|
|
asfloat(RayGuidingCDF[BatchedTiles[TileIndex].WorkingSetPosition / GPreviewLightmapPhysicalTileSize * CDF_TILE_SIZE + LaunchIndex]).xxx / (1 + asuint(IrradianceAndSampleCount[TexelIndexInPool].a))
|
|
);
|
|
|
|
SHDirectionality[TexelIndexInPool] = float4(1, 0, 0, 0);
|
|
SHCorrectionAndStationarySkyLightBentNormal[TexelIndexInPool].x = 1;
|
|
}
|
|
#endif
|
|
|
|
#if 0 // Debug: GBuffer shading normal visualization
|
|
{
|
|
const half LogBlackPoint = 0.01858136;
|
|
|
|
float3 ShadingNormal = GBufferShadingNormal[TexelIndexInScratch].xyz * 0.5f + 0.5f;
|
|
|
|
IrradianceAndSampleCount[TexelIndexInPool].rgb = ShadingNormal;
|
|
IrradianceAndSampleCount[TexelIndexInPool].a = asfloat(1);
|
|
|
|
SHDirectionality[TexelIndexInPool] = float4(1, 0, 0, 0);
|
|
SHCorrectionAndStationarySkyLightBentNormal[TexelIndexInPool].x = 1;
|
|
}
|
|
#endif
|
|
}
|
|
|
|
#include "BrickAllocationDefs.ush"
|
|
|
|
uint FrameNumber;
|
|
float4 VolumeMin;
|
|
float4 VolumeSize;
|
|
int3 IndirectionTextureDim;
|
|
Texture3D<uint4> IndirectionTexture;
|
|
Buffer<uint4> BrickRequests;
|
|
int NumTotalBricks;
|
|
int BrickBatchOffset;
|
|
|
|
RWTexture3D<float4> AmbientVector;
|
|
RWTexture3D<float4> SHCoefficients0R;
|
|
RWTexture3D<float4> SHCoefficients1R;
|
|
RWTexture3D<float4> SHCoefficients0G;
|
|
RWTexture3D<float4> SHCoefficients1G;
|
|
RWTexture3D<float4> SHCoefficients0B;
|
|
RWTexture3D<float4> SHCoefficients1B;
|
|
RWTexture3D<float4> SkyBentNormal;
|
|
RWTexture3D<UNORM float> DirectionalLightShadowing;
|
|
|
|
uint MortonEncode3(uint3 Pixel)
|
|
{
|
|
uint Morton = MortonCode3(Pixel.x & 0xFF) | (MortonCode3(Pixel.y & 0xFF) << 1) | (MortonCode3(Pixel.z & 0xFF) << 1);
|
|
return Morton;
|
|
}
|
|
|
|
// Must match C++
|
|
struct FLightShaderConstants
|
|
{
|
|
float3 PositionHigh;
|
|
float InvRadius;
|
|
float3 Color;
|
|
float FalloffExponent;
|
|
float3 Direction;
|
|
float SpecularScale;
|
|
float DiffuseScale;
|
|
float3 Tangent;
|
|
float SourceRadius;
|
|
float3 PositionLow;
|
|
float SourceLength;
|
|
float2 SpotAngles;
|
|
float SoftSourceRadius;
|
|
float RectLightBarnCosAngle;
|
|
float RectLightBarnLength;
|
|
|
|
void FillLightShaderParameters(inout FLightShaderParameters LightShaderParameters)
|
|
{
|
|
const FDFVector3 WorldPosition = MakeDFVector3(PositionHigh, PositionLow);
|
|
|
|
LightShaderParameters.TranslatedWorldPosition = DFFastAddDemote(WorldPosition, PrimaryView.PreViewTranslation);
|
|
LightShaderParameters.InvRadius = InvRadius;
|
|
LightShaderParameters.Color = Color;
|
|
LightShaderParameters.FalloffExponent = FalloffExponent;
|
|
LightShaderParameters.Direction = Direction;
|
|
LightShaderParameters.SpecularScale = SpecularScale;
|
|
LightShaderParameters.DiffuseScale = DiffuseScale;
|
|
LightShaderParameters.Tangent = Tangent;
|
|
LightShaderParameters.SourceRadius = SourceRadius;
|
|
LightShaderParameters.SpotAngles = SpotAngles;
|
|
LightShaderParameters.SoftSourceRadius = SoftSourceRadius;
|
|
LightShaderParameters.SourceLength = SourceLength;
|
|
LightShaderParameters.RectLightBarnCosAngle = RectLightBarnCosAngle;
|
|
LightShaderParameters.RectLightBarnLength = RectLightBarnLength;
|
|
}
|
|
};
|
|
|
|
bool GenerateOcclusionRay(
|
|
int LightType,
|
|
FLightShaderParameters LightParameters,
|
|
float3 TranslatedWorldPosition,
|
|
float3 WorldNormal,
|
|
float2 RandSample,
|
|
inout float3 RayOrigin,
|
|
inout float3 RayDirection,
|
|
inout float RayTMin,
|
|
inout float RayTMax
|
|
)
|
|
{
|
|
if (LightType == LIGHT_TYPE_DIRECTIONAL)
|
|
{
|
|
GenerateDirectionalLightOcclusionRay(
|
|
LightParameters,
|
|
TranslatedWorldPosition, WorldNormal,
|
|
RandSample,
|
|
/* out */ RayOrigin,
|
|
/* out */ RayDirection,
|
|
/* out */ RayTMin,
|
|
/* out */ RayTMax);
|
|
}
|
|
else if (LightType == LIGHT_TYPE_POINT || LightType == LIGHT_TYPE_SPOT)
|
|
{
|
|
if (LightType == LIGHT_TYPE_SPOT)
|
|
{
|
|
// before generating a shadow ray, make sure we are inside the cone of the light
|
|
float3 LightDirection = normalize(LightParameters.TranslatedWorldPosition - TranslatedWorldPosition);
|
|
float CosAngle = LightParameters.SpotAngles.x;
|
|
if (!(dot(LightDirection, LightParameters.Direction) >= CosAngle))
|
|
{
|
|
// outside the cone of the light, skip all work
|
|
return false;
|
|
}
|
|
}
|
|
float RayPdf;
|
|
if (LightParameters.SourceLength > 0.0)
|
|
{
|
|
return GenerateCapsuleLightOcclusionRayWithSolidAngleSampling(
|
|
LightParameters,
|
|
TranslatedWorldPosition, WorldNormal,
|
|
RandSample,
|
|
/* out */ RayOrigin,
|
|
/* out */ RayDirection,
|
|
/* out */ RayTMin,
|
|
/* out */ RayTMax,
|
|
/* out */ RayPdf);
|
|
}
|
|
return GenerateSphereLightOcclusionRayWithSolidAngleSampling(
|
|
LightParameters,
|
|
TranslatedWorldPosition, WorldNormal,
|
|
RandSample,
|
|
/* out */ RayOrigin,
|
|
/* out */ RayDirection,
|
|
/* out */ RayTMin,
|
|
/* out */ RayTMax,
|
|
/* out */ RayPdf);
|
|
}
|
|
else if (LightType == LIGHT_TYPE_RECT)
|
|
{
|
|
float RayPdf = 0.0;
|
|
return GenerateRectLightOcclusionRay(
|
|
LightParameters,
|
|
TranslatedWorldPosition, WorldNormal,
|
|
RandSample,
|
|
/* out */ RayOrigin,
|
|
/* out */ RayDirection,
|
|
/* out */ RayTMin,
|
|
/* out */ RayTMax,
|
|
/* out */ RayPdf);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
StructuredBuffer<FLightShaderConstants> LightShaderParametersArray;
|
|
|
|
[shader("raygeneration")]
|
|
void VolumetricLightmapPathTracingMainRG()
|
|
{
|
|
uint BrickVolumeSize = 4 * 4 * 4;
|
|
uint BrickIndex = DispatchRaysIndex().x / BrickVolumeSize + BrickBatchOffset;
|
|
if (BrickIndex >= NumTotalBricks) return;
|
|
uint CellIndex = DispatchRaysIndex().x % BrickVolumeSize;
|
|
uint3 CellPosInBrick = ComputeBrickLayoutPosition(CellIndex, uint3(4, 4, 4));
|
|
uint PaddedBrickSize = 4 + 1;
|
|
uint3 VoxelPos = ComputeBrickLayoutPosition(DispatchRaysIndex().x / BrickVolumeSize, uint3(256, 256, 256)) * PaddedBrickSize + CellPosInBrick;
|
|
|
|
bool bIsValidSample = true;
|
|
|
|
int EffectiveRenderPassIndex = FrameNumber;
|
|
#if USE_IRRADIANCE_CACHING
|
|
if (EffectiveRenderPassIndex >= GetIrradianceCachingQuality())
|
|
{
|
|
EffectiveRenderPassIndex -= GetIrradianceCachingQuality();
|
|
}
|
|
#endif
|
|
|
|
if (EffectiveRenderPassIndex == 0)
|
|
{
|
|
AmbientVector[VoxelPos] = float4(0, 0, 0, 0);
|
|
|
|
SHCoefficients0R[VoxelPos] = float4(0, 0, 0, 0);
|
|
SHCoefficients1R[VoxelPos] = float4(0, 0, 0, 0);
|
|
|
|
SHCoefficients0G[VoxelPos] = float4(0, 0, 0, 0);
|
|
SHCoefficients1G[VoxelPos] = float4(0, 0, 0, 0);
|
|
|
|
SHCoefficients0B[VoxelPos] = float4(0, 0, 0, 0);
|
|
SHCoefficients1B[VoxelPos] = float4(0, 0, 0, 0);
|
|
|
|
SkyBentNormal[VoxelPos] = float4(0, 0, 0, 0);
|
|
}
|
|
|
|
int3 CellPosInVLM = (BrickRequests[BrickIndex].xyz * 4 + CellPosInBrick) * BrickRequests[BrickIndex].w;
|
|
uint Seed = ComputeBrickLinearAddress(CellPosInVLM, IndirectionTextureDim * 4);
|
|
|
|
float3 RandSample = float3(
|
|
Halton(MortonEncode3(CellPosInVLM) + EffectiveRenderPassIndex, 2),
|
|
Halton(MortonEncode3(CellPosInVLM) + EffectiveRenderPassIndex, 3),
|
|
Halton(MortonEncode3(CellPosInVLM) + EffectiveRenderPassIndex, 5)
|
|
);
|
|
float3 Jitter = RandSample;
|
|
|
|
float3 JitteredCellPosInVLM = (BrickRequests[BrickIndex].xyz * 4 + CellPosInBrick + Jitter - float3(0.5, 0.5, 0.5)) * BrickRequests[BrickIndex].w;
|
|
float3 DetailCellSize = VolumeSize.xyz / IndirectionTextureDim / 4;
|
|
float3 WorldPosition = VolumeMin.xyz + DetailCellSize * JitteredCellPosInVLM; // RT_LWC_TODO
|
|
float3 TranslatedWorldPosition = WorldPosition + DFHackToFloat(PrimaryView.PreViewTranslation); // RT_LWC_TODO
|
|
|
|
int NumValidHalfSamples = 0;
|
|
|
|
UNROLL_N(2)
|
|
for (int HalfSampleIndex = 0; HalfSampleIndex < 2; HalfSampleIndex++)
|
|
{
|
|
float3 ShadingNormal = float3(0, 0, (HalfSampleIndex == 0) ? 1 : -1);
|
|
|
|
float3 RadianceValue = 0;
|
|
float3 RadianceDirection = 0;
|
|
float3 DirectLightingIrradiance = 0;
|
|
#ifdef LIGHTMAP_PATH_TRACING_MAIN_RG
|
|
FL2SHAndCorrection DirectLightingSH = (FL2SHAndCorrection)0;
|
|
#else
|
|
FThreeBandSHVectorRGBFloat DirectLightingSH = (FThreeBandSHVectorRGBFloat)0;
|
|
#endif
|
|
float LuminanceForFirstBounceRayGuiding = 0;
|
|
float3 SkyLightBentNormal = 0;
|
|
float4 PrimaryRandSample;
|
|
|
|
if (bIsValidSample)
|
|
{
|
|
RandomSequence RandSequence;
|
|
RandomSequence_Initialize(RandSequence, Seed, EffectiveRenderPassIndex);
|
|
uint SampleIndex = 4;
|
|
|
|
PathTracingKernel(
|
|
FrameNumber,
|
|
TranslatedWorldPosition,
|
|
ShadingNormal,
|
|
RandSequence,
|
|
SampleIndex,
|
|
bIsValidSample,
|
|
RadianceValue,
|
|
RadianceDirection,
|
|
DirectLightingIrradiance,
|
|
DirectLightingSH,
|
|
LuminanceForFirstBounceRayGuiding,
|
|
SkyLightBentNormal,
|
|
PrimaryRandSample);
|
|
}
|
|
|
|
if (any(isnan(RadianceValue)) || any(RadianceValue < 0))
|
|
{
|
|
bIsValidSample = false;
|
|
}
|
|
|
|
if (bIsValidSample)
|
|
{
|
|
NumValidHalfSamples++;
|
|
|
|
FThreeBandSHVectorRGBFloat SH;
|
|
SH.R.V0 = SHBasisFunction3Float(RadianceDirection).V0 * RadianceValue.r;
|
|
SH.R.V1 = SHBasisFunction3Float(RadianceDirection).V1 * RadianceValue.r;
|
|
SH.R.V2 = SHBasisFunction3Float(RadianceDirection).V2 * RadianceValue.r;
|
|
SH.G.V0 = SHBasisFunction3Float(RadianceDirection).V0 * RadianceValue.g;
|
|
SH.G.V1 = SHBasisFunction3Float(RadianceDirection).V1 * RadianceValue.g;
|
|
SH.G.V2 = SHBasisFunction3Float(RadianceDirection).V2 * RadianceValue.g;
|
|
SH.B.V0 = SHBasisFunction3Float(RadianceDirection).V0 * RadianceValue.b;
|
|
SH.B.V1 = SHBasisFunction3Float(RadianceDirection).V1 * RadianceValue.b;
|
|
SH.B.V2 = SHBasisFunction3Float(RadianceDirection).V2 * RadianceValue.b;
|
|
|
|
AmbientVector[VoxelPos].x += SH.R.V0.x;
|
|
AmbientVector[VoxelPos].y += SH.G.V0.x;
|
|
AmbientVector[VoxelPos].z += SH.B.V0.x;
|
|
|
|
SHCoefficients0R[VoxelPos] += float4(SH.R.V0.yzw, SH.R.V1.x);
|
|
SHCoefficients1R[VoxelPos] += float4(SH.R.V1.yzw, SH.R.V2);
|
|
|
|
SHCoefficients0G[VoxelPos] += float4(SH.G.V0.yzw, SH.G.V1.x);
|
|
SHCoefficients1G[VoxelPos] += float4(SH.G.V1.yzw, SH.G.V2);
|
|
|
|
SHCoefficients0B[VoxelPos] += float4(SH.B.V0.yzw, SH.B.V1.x);
|
|
SHCoefficients1B[VoxelPos] += float4(SH.B.V1.yzw, SH.B.V2);
|
|
|
|
if (HalfSampleIndex == 0)
|
|
{
|
|
SkyBentNormal[VoxelPos].rgb += SkyLightBentNormal;
|
|
}
|
|
|
|
#ifndef LIGHTMAP_PATH_TRACING_MAIN_RG
|
|
AmbientVector[VoxelPos].x += DirectLightingSH.R.V0.x;
|
|
AmbientVector[VoxelPos].y += DirectLightingSH.G.V0.x;
|
|
AmbientVector[VoxelPos].z += DirectLightingSH.B.V0.x;
|
|
|
|
SHCoefficients0R[VoxelPos] += float4(DirectLightingSH.R.V0.yzw, DirectLightingSH.R.V1.x);
|
|
SHCoefficients1R[VoxelPos] += float4(DirectLightingSH.R.V1.yzw, DirectLightingSH.R.V2);
|
|
|
|
SHCoefficients0G[VoxelPos] += float4(DirectLightingSH.G.V0.yzw, DirectLightingSH.G.V1.x);
|
|
SHCoefficients1G[VoxelPos] += float4(DirectLightingSH.G.V1.yzw, DirectLightingSH.G.V2);
|
|
|
|
SHCoefficients0B[VoxelPos] += float4(DirectLightingSH.B.V0.yzw, DirectLightingSH.B.V1.x);
|
|
SHCoefficients1B[VoxelPos] += float4(DirectLightingSH.B.V1.yzw, DirectLightingSH.B.V2);
|
|
#endif
|
|
}
|
|
}
|
|
|
|
if (NumValidHalfSamples > 0)
|
|
{
|
|
uint SampleCount = asuint(AmbientVector[VoxelPos].w);
|
|
AmbientVector[VoxelPos].w = asfloat(SampleCount + 1);
|
|
}
|
|
|
|
// 32 samples for single sample stationary directional light shadowing
|
|
if (FrameNumber < 32 && length(LightShaderParametersArray[0].Direction) > 0)
|
|
{
|
|
if (FrameNumber == 0)
|
|
{
|
|
DirectionalLightShadowing[VoxelPos] = 0;
|
|
}
|
|
|
|
FLightShaderParameters LightParameters;
|
|
LightShaderParametersArray[0].FillLightShaderParameters(LightParameters);
|
|
|
|
float2 RandSample = float2(
|
|
Halton(StrongIntegerHash(Seed) + FrameNumber, 5),
|
|
Halton(StrongIntegerHash(Seed) + FrameNumber, 7)
|
|
);
|
|
|
|
FRayDesc Ray;
|
|
bool bIsValidRay = GenerateOcclusionRay(
|
|
LIGHT_TYPE_DIRECTIONAL,
|
|
LightParameters,
|
|
TranslatedWorldPosition, float3(0, 0, 1),
|
|
RandSample,
|
|
/* out */ Ray.Origin,
|
|
/* out */ Ray.Direction,
|
|
/* out */ Ray.TMin,
|
|
/* out */ Ray.TMax);
|
|
|
|
if (bIsValidRay)
|
|
{
|
|
uint RayFlags = RAY_FLAG_ACCEPT_FIRST_HIT_AND_END_SEARCH;
|
|
|
|
FMinimalPayload MinimalPayload = TraceLightmapVisibilityRay(
|
|
TLAS,
|
|
RayFlags,
|
|
Ray);
|
|
|
|
DirectionalLightShadowing[VoxelPos] += (MinimalPayload.IsMiss() ? 1 : 0) / 32.0f;
|
|
}
|
|
}
|
|
}
|
|
|
|
Buffer<int> LightTypeArray;
|
|
Buffer<int> ChannelIndexArray;
|
|
Buffer<int> LightSampleIndexArray;
|
|
RWTexture2D<float4> ShadowMask;
|
|
RWTexture2D<float4> ShadowMaskSampleCount;
|
|
|
|
[shader("raygeneration")]
|
|
void StationaryLightShadowTracingMainRG()
|
|
{
|
|
uint2 BatchedLaunchIndex = DispatchRaysIndex().xy;
|
|
|
|
uint2 LaunchIndex = uint2(BatchedLaunchIndex.x % GPreviewLightmapPhysicalTileSize, BatchedLaunchIndex.y);
|
|
int TileIndex = BatchedLaunchIndex.x / GPreviewLightmapPhysicalTileSize;
|
|
uint2 TexelIndexInPool = LaunchIndex + BatchedTiles[TileIndex].WorkingSetPosition;
|
|
uint2 TexelIndexInScratch = LaunchIndex + BatchedTiles[TileIndex].ScratchPosition;
|
|
|
|
int ChannelIndex = ChannelIndexArray[TileIndex];
|
|
|
|
bool bGBufferSampleValid = true;
|
|
|
|
float4 EncodedGBufferWorldPosition = GBufferWorldPosition[TexelIndexInScratch];
|
|
|
|
if (EncodedGBufferWorldPosition.w == 0.0f)
|
|
{
|
|
bGBufferSampleValid = false;
|
|
}
|
|
|
|
float3 TranslatedWorldPosition = EncodedGBufferWorldPosition.xyz / WorldPositionScalar + DFHackToFloat(PrimaryView.PreViewTranslation); // RT_LWC_TODO
|
|
|
|
if (bGBufferSampleValid)
|
|
{
|
|
float3 WorldNormal = GBufferWorldNormal[TexelIndexInScratch].xyz;
|
|
float3 ShadingNormal = GBufferShadingNormal[TexelIndexInScratch].xyz;
|
|
bool bMaterialTwoSided = GBufferShadingNormal[TexelIndexInScratch].w;
|
|
|
|
// Needs a seed that is only related to position in virtual space to avoid seams due to per-tile calculation
|
|
uint2 VirtualTextureSpacePosition = BatchedTiles[TileIndex].VirtualTilePosition + LaunchIndex - uint2(2, 2);
|
|
uint Seed = VirtualTextureSpacePosition.y * BatchedTiles[TileIndex].LightmapSize.x + VirtualTextureSpacePosition.x;
|
|
|
|
int LightType = LightTypeArray[TileIndex];
|
|
FLightShaderParameters LightParameters;
|
|
LightShaderParametersArray[TileIndex].FillLightShaderParameters(LightParameters);
|
|
|
|
bool bAnyHemisphereRaySampleValid = false;
|
|
uint Visibility = 0;
|
|
|
|
UNROLL_N(2)
|
|
for (int HemisphereIndex = 0; HemisphereIndex < (bMaterialTwoSided ? 2 : 1); HemisphereIndex++)
|
|
{
|
|
float2 RandSample = float2(
|
|
Halton(StrongIntegerHash(Seed) + LightSampleIndexArray[TileIndex], 5),
|
|
Halton(StrongIntegerHash(Seed) + LightSampleIndexArray[TileIndex], 7)
|
|
);
|
|
|
|
float3 EffectiveWorldNormal = HemisphereIndex == 0 ? WorldNormal : -WorldNormal;
|
|
|
|
bool bRaySampleValid = true;
|
|
|
|
FRayDesc Ray;
|
|
bRaySampleValid &= GenerateOcclusionRay(
|
|
LightType,
|
|
LightParameters,
|
|
TranslatedWorldPosition, EffectiveWorldNormal,
|
|
RandSample,
|
|
/* out */ Ray.Origin,
|
|
/* out */ Ray.Direction,
|
|
/* out */ Ray.TMin,
|
|
/* out */ Ray.TMax);
|
|
|
|
ApplyRayBias(Ray.Origin, LightType == LIGHT_TYPE_DIRECTIONAL ? 1.0f : max(Ray.TMax - Ray.TMin, 0.0f), EffectiveWorldNormal);
|
|
|
|
if (bRaySampleValid)
|
|
{
|
|
uint RayFlags = RAY_FLAG_ACCEPT_FIRST_HIT_AND_END_SEARCH;
|
|
|
|
FMinimalPayload MinimalPayload = TraceLightmapVisibilityRay(
|
|
TLAS,
|
|
RayFlags,
|
|
Ray);
|
|
|
|
Visibility = max(Visibility, MinimalPayload.IsMiss() ? 1 : 0);
|
|
}
|
|
|
|
bAnyHemisphereRaySampleValid = bAnyHemisphereRaySampleValid || bRaySampleValid;
|
|
}
|
|
|
|
if (bAnyHemisphereRaySampleValid)
|
|
{
|
|
ShadowMask[TexelIndexInPool][ChannelIndex] = asfloat(asuint(ShadowMask[TexelIndexInPool][ChannelIndex]) + Visibility);
|
|
uint SampleCount = asuint(ShadowMaskSampleCount[TexelIndexInPool][ChannelIndex]);
|
|
SampleCount++;
|
|
ShadowMaskSampleCount[TexelIndexInPool][ChannelIndex] = asfloat(SampleCount);
|
|
}
|
|
}
|
|
}
|