Files
UnrealEngine/Engine/Shaders/Private/PathTracing/Material/PathTracingRadianceProbe.ush
2025-05-18 13:04:45 +08:00

249 lines
7.3 KiB
HLSL

// Copyright Epic Games, Inc. All Rights Reserved.
/*=============================================================================================
PathTracingLambert.usf: Lambertian BRDF sampling functions
===============================================================================================*/
#pragma once
#if 1 // Cosine-weighted concentric sampling with optional guiding
#ifdef LIGHTMAP_PATH_TRACING_MAIN_RG
#include "FirstBounceRayGuidingCommon.ush"
#endif
float2 InverseConcentricMapping(float2 p)
{
// Handle degeneracy at the origin
if (p.x == 0 && p.y == 0) return float2(0, 0);
float r = sqrt(p.x * p.x + p.y * p.y);
float theta = atan2(p.y, p.x);
if (theta < -PI/4) theta += 2*PI;
float a, b;
if (theta < PI / 4) // region 1
{
a = r;
b = theta * a / (PI/4);
}
else if (theta < 3*PI/4) // region 2
{
b = r;
a = -(theta - PI/2) * b / (PI/4);
}
else if (theta < 5*PI/4) // region 3
{
a = -r;
b = (theta - PI) * a / (PI/4);
}
else // region 4
{
b = -r;
a = -(theta - 3*PI/2) * b / (PI/4);
}
float x = (a + 1) / 2;
float y = (b + 1) / 2;
return float2(x, y);
}
FMaterialSample RadianceProbe_SampleMaterial(
FPathTracingPayload Payload,
float3 RandSample
)
{
float PdfAdjust = 1.0f;
float2 SamplePoint = RandSample.yx;
#ifdef LIGHTMAP_PATH_TRACING_MAIN_RG
#if USE_FIRST_BOUNCE_RAY_GUIDING
uint2 BatchedLaunchIndex = DispatchRaysIndex().xy;
uint2 LaunchIndex = uint2(BatchedLaunchIndex.x % GPreviewLightmapPhysicalTileSize, BatchedLaunchIndex.y);
int TileIndex = BatchedLaunchIndex.x / GPreviewLightmapPhysicalTileSize;
int2 ClusterPosition = clamp((int2)LaunchIndex - int2(2, 2), int2(0, 0), int2(63, 63)) / TEXEL_CLUSTER_SIZE;
int MinRenderPassIndex = NumRayGuidingTrialSamples;
#if USE_IRRADIANCE_CACHING
MinRenderPassIndex += GetIrradianceCachingQuality();
#endif
if (BatchedTiles[TileIndex].RenderPassIndex >= MinRenderPassIndex)
{
float LastRowPrefixSum = 0;
float RowPrefixSum = 0;
int Y = 0;
UNROLL
for (int y = 0; y < DIRECTIONAL_BINS_ONE_DIM; y++)
{
int2 WritePos = int2(y % 4, y / 4);
int2 FinalPosition = ClusterPosition * DIRECTIONAL_BINS_ONE_DIM / 4 + WritePos;
LastRowPrefixSum = RowPrefixSum;
RowPrefixSum = RayGuidingCDFY[BatchedTiles[TileIndex].WorkingSetPosition / GPreviewLightmapPhysicalTileSize * CDF_TILE_SIZE / 4 + FinalPosition];
if (RowPrefixSum > SamplePoint.y)
{
SamplePoint.y = (y + (SamplePoint.y - LastRowPrefixSum) / (RowPrefixSum - LastRowPrefixSum)) / DIRECTIONAL_BINS_ONE_DIM;
PdfAdjust *= (RowPrefixSum - LastRowPrefixSum) * DIRECTIONAL_BINS_ONE_DIM;
Y = y;
break;
}
}
float LastPrefixSum = 0;
float PrefixSum = 0;
UNROLL
for (int x = 0; x < DIRECTIONAL_BINS_ONE_DIM; x++)
{
int2 FinalPosition = ClusterPosition * DIRECTIONAL_BINS_ONE_DIM + int2(x, Y);
LastPrefixSum = PrefixSum;
PrefixSum = RayGuidingCDFX[BatchedTiles[TileIndex].WorkingSetPosition / GPreviewLightmapPhysicalTileSize * CDF_TILE_SIZE + FinalPosition];
if (PrefixSum > SamplePoint.x)
{
SamplePoint.x = (x + (SamplePoint.x - LastPrefixSum) / (PrefixSum - LastPrefixSum)) / DIRECTIONAL_BINS_ONE_DIM;
PdfAdjust *= (PrefixSum - LastPrefixSum) * DIRECTIONAL_BINS_ONE_DIM;
break;
}
}
}
#endif
#endif
float3 N_World = Payload.WorldNormal;
#ifdef LIGHTMAP_PATH_TRACING_MAIN_RG
// Use cosine hemisphere sampling for surface lightmaps
float4 SampledValue = CosineSampleHemisphereConcentric(SamplePoint);
#else
// Use uniform hemisphere sampling for volumetric lightmaps (the two hemispheres are meant to be merged)
float4 SampledValue = UniformSampleHemisphere(SamplePoint);
#endif
float3 OutDirection = TangentToWorld(SampledValue.xyz, N_World);
float OutPdf = SampledValue.w * PdfAdjust;
// As the name suggests, radiance probe has a combined throughput (Pdf * Weight) == 1
float OutWeight = (OutPdf > 0.0f) ? (1.0f / OutPdf) : 0;
return CreateMaterialSample(OutDirection, OutWeight, OutPdf, 1.0, 1.0, PATHTRACER_SCATTER_DIFFUSE);
}
FMaterialEval RadianceProbe_EvalMaterial(
float3 L_World,
FPathTracingPayload Payload
)
{
float3 N_World = Payload.WorldNormal;
float PdfAdjust = 1.0f;
#ifdef LIGHTMAP_PATH_TRACING_MAIN_RG
#if USE_FIRST_BOUNCE_RAY_GUIDING
float3 TangentDirection = WorldToTangent(L_World, N_World);
float2 PrimarySample = InverseConcentricMapping(TangentDirection.xy);
uint2 BatchedLaunchIndex = DispatchRaysIndex().xy;
uint2 LaunchIndex = uint2(BatchedLaunchIndex.x % GPreviewLightmapPhysicalTileSize, BatchedLaunchIndex.y);
int TileIndex = BatchedLaunchIndex.x / GPreviewLightmapPhysicalTileSize;
int2 ClusterPosition = clamp((int2)LaunchIndex - int2(2, 2), int2(0, 0), int2(63, 63)) / TEXEL_CLUSTER_SIZE;
int MinRenderPassIndex = NumRayGuidingTrialSamples;
#if USE_IRRADIANCE_CACHING
MinRenderPassIndex += GetIrradianceCachingQuality();
#endif
if (BatchedTiles[TileIndex].RenderPassIndex >= MinRenderPassIndex)
{
float LastPrefixSum = 0;
float PrefixSum;
float LastRowPrefixSum = 0;
float RowPrefixSum;
int Y = floor(PrimarySample.y * DIRECTIONAL_BINS_ONE_DIM);
{
int2 WritePos = int2(Y % 4, Y / 4);
int2 FinalPosition = ClusterPosition * DIRECTIONAL_BINS_ONE_DIM / 4 + WritePos;
RowPrefixSum = RayGuidingCDFY[BatchedTiles[TileIndex].WorkingSetPosition / GPreviewLightmapPhysicalTileSize * CDF_TILE_SIZE / 4 + FinalPosition];
}
if (Y > 0)
{
int2 WritePos = int2((Y - 1) % 4, (Y - 1) / 4);
int2 FinalPosition = ClusterPosition * DIRECTIONAL_BINS_ONE_DIM / 4 + WritePos;
LastRowPrefixSum = RayGuidingCDFY[BatchedTiles[TileIndex].WorkingSetPosition / GPreviewLightmapPhysicalTileSize * CDF_TILE_SIZE / 4 + FinalPosition];
}
PdfAdjust *= (RowPrefixSum - LastRowPrefixSum) * DIRECTIONAL_BINS_ONE_DIM;
int X = floor(PrimarySample.x * DIRECTIONAL_BINS_ONE_DIM);
{
int2 FinalPosition = ClusterPosition * DIRECTIONAL_BINS_ONE_DIM + int2(X, Y);
PrefixSum = RayGuidingCDFX[BatchedTiles[TileIndex].WorkingSetPosition / GPreviewLightmapPhysicalTileSize * CDF_TILE_SIZE + FinalPosition];
}
if (X > 0)
{
int2 FinalPosition = ClusterPosition * DIRECTIONAL_BINS_ONE_DIM + int2(X - 1, Y);
LastPrefixSum = RayGuidingCDFX[BatchedTiles[TileIndex].WorkingSetPosition / GPreviewLightmapPhysicalTileSize * CDF_TILE_SIZE + FinalPosition];
}
PdfAdjust *= (PrefixSum - LastPrefixSum) * DIRECTIONAL_BINS_ONE_DIM;
}
#endif
#endif
float NoL = max(dot(N_World, L_World), 0);
#ifdef LIGHTMAP_PATH_TRACING_MAIN_RG
float OutPdf = NoL / PI * PdfAdjust;
#else
float OutPdf = 1 / (2 * PI) * PdfAdjust;
#endif
// As the name suggests, radiance probe has a combined throughput (Pdf * Weight) == 1
float OutWeight = (OutPdf > 0.0f) ? (1.0f / OutPdf) : 0;
return CreateMaterialEval(OutWeight, OutPdf);
}
#else
FMaterialSample RadianceProbe_SampleMaterial(
FPathTracingPayload Payload,
float3 RandSample)
{
float3 N_World = Payload.WorldNormal;
float4 SampledValue = CosineSampleHemisphere(RandSample.xy);
return CreateMaterialSample(TangentToWorld(SampledValue.xyz, N_World), 1.0, SampledValue.w, 1.0, 1.0, PATHTRACER_SCATTER_DIFFUSE);
}
FMaterialEval RadianceProbe_EvalMaterial(
float3 L_World,
FPathTracingPayload Payload
)
{
float3 N_World = Payload.WorldNormal;
float NoL = saturate(dot(N_World, L_World));
return CreateMaterialEval(Payload.DiffuseColor, NoL / PI);
}
#endif