Added denoising.

This commit is contained in:
Justin Marshall
2026-05-21 07:03:42 -07:00
parent aec93deaa0
commit ce78a355f5
5 changed files with 119 additions and 37 deletions
+65 -20
View File
@@ -3398,9 +3398,19 @@ float EstimateSpecularRoughness(float3 specularAlbedo)
{
// No roughness map is available in this G-buffer path, so infer a stable
// perceptual roughness from the specular map strength. Brighter spec maps
// get tighter highlights; dark/missing maps stay broad and subdued.
// get tighter highlights, but keep a floor high enough that POM/normal-map
// detail cannot collapse into one-pixel GGX fireflies.
float peak = SpecularPeak3(saturate(specularAlbedo));
return clamp(lerp(0.68, 0.34, peak), 0.28, 0.72);
return clamp(lerp(0.70, 0.42, peak), 0.38, 0.74);
}
float3 LimitSpecularPeak(float3 c, float peakLimit)
{
c = max(c, 0.0);
float peak = SpecularPeak3(c);
if (peak > peakLimit && peak > 1.0e-5)
c *= peakLimit / peak;
return c;
}
float3 ComputeSpecular(
@@ -3463,7 +3473,7 @@ float3 ComputeSpecular(
float3 F = F0 + (1.0 - F0) * pow(1.0 - VoH, 5.0);
float specTerm = (D * G) / max(4.0 * NoL * NoV, 1.0e-4);
specTerm = min(specTerm, 5.0);
specTerm = min(specTerm, 3.0);
const float SPECULAR_ENERGY_SCALE = 2.35;
@@ -3477,7 +3487,7 @@ float3 ComputeSpecular(
specTerm *
SPECULAR_ENERGY_SCALE;
return clamp(specular, 0.0, 8.0);
return LimitSpecularPeak(specular, 4.0);
}
)"
R"(
@@ -3535,6 +3545,18 @@ float3 SampleCosineWorld(float3 N, inout uint rng)
N * localDir.z);
}
float3 SampleCosineWorldXi(float3 N, float2 xi)
{
float3 tangent, bitangent;
BuildOrthonormalBasis(N, tangent, bitangent);
float3 localDir = CosineSampleHemisphere(xi);
return normalize(
tangent * localDir.x +
bitangent * localDir.y +
N * localDir.z);
}
float3 SampleConeWorld(float3 centerDir, float coneRadius, inout uint rng)
{
float3 tangent, bitangent;
@@ -4374,7 +4396,21 @@ float3 EstimateReactiveScreenSpaceFinalGather(uint2 pixel, float3 worldPos, floa
}
)"
R"(
float3 TraceOneIndirectBouncePath(uint2 pixel, float3 worldPos, float3 N, float3 V, float3 baseAlbedo, inout uint rng)
float2 GetStratifiedBounceXi(uint2 pixel, float3 worldPos, float3 pathNormal, uint sampleIndex)
{
uint spp = max(gSamplesPerPixel, 1u);
uint sequenceLength = max(spp * 64u, 64u);
uint sequenceIndex = (sampleIndex + (gFrameIndex & 63u) * spp) % sequenceLength;
float rand = Hash12(
(float2)pixel +
worldPos.xy * 0.013 +
float2(pathNormal.x * 19.19, pathNormal.y * 37.37));
return Hammersley2D(sequenceIndex, sequenceLength, rand);
}
float3 TraceOneIndirectBouncePath(uint2 pixel, float3 worldPos, float3 N, float3 V, float3 baseAlbedo, uint sampleIndex, inout uint rng)
{
const float BOUNCE_TMAX = 1000000.0;
@@ -4393,7 +4429,10 @@ float3 TraceOneIndirectBouncePath(uint2 pixel, float3 worldPos, float3 N, float3
[loop]
for (uint depth = 0; depth < maxIndirectDepth; ++depth)
{
float3 bounceDir = SampleCosineWorld(pathNormal, rng);
float2 bounceXi = (depth == 0u)
? GetStratifiedBounceXi(pixel, pathPos, pathNormal, sampleIndex)
: Rand2(rng);
float3 bounceDir = SampleCosineWorldXi(pathNormal, bounceXi);
float NoD = saturate(dot(pathNormal, bounceDir));
float normalBias = lerp(gShadowBias * 3.0, gShadowBias * 0.75, NoD);
@@ -4456,6 +4495,7 @@ float3 TraceOneIndirectBouncePath(uint2 pixel, float3 worldPos, float3 N, float3
bouncedRadiance += skyTint * 0.045;
}
bouncedRadiance = CompressEmissiveRadiance(bouncedRadiance, depth == 0u ? 8.0 : 5.0);
accum += throughput * bouncedRadiance;
if ((hitGeoFlag & GEOMETRY_FLAG_UNLIT) != 0u)
@@ -4487,14 +4527,14 @@ float3 TraceOneIndirectBouncePath(uint2 pixel, float3 worldPos, float3 N, float3
return accum;
}
float3 EstimatePathTracedIndirectBounce(uint2 pixel, float3 worldPos, float3 N, float3 V, float3 baseAlbedo, inout uint rng)
float3 EstimatePathTracedIndirectBounce(uint2 pixel, float3 worldPos, float3 N, float3 V, float3 baseAlbedo, uint sampleIndex, inout uint rng)
{
if (gMaxBounces <= 1u)
return 0.0;
// One stochastic diffuse path per lighting sample. The path can contain
// several diffuse depths, while the temporal pass below handles convergence.
float3 accum = TraceOneIndirectBouncePath(pixel, worldPos, N, V, baseAlbedo, rng);
float3 accum = TraceOneIndirectBouncePath(pixel, worldPos, N, V, baseAlbedo, sampleIndex, rng);
const float INDIRECT_STRENGTH = 0.72;
return accum * INDIRECT_STRENGTH;
@@ -4714,7 +4754,8 @@ float ComputeSpecularReflectionDistanceFade(float hitT, float maxT)
return fade * fade;
}
)"
R"(
float3 EstimateRayTracedSpecularReflection(
uint2 pixel,
float3 worldPos,
@@ -4804,6 +4845,7 @@ float3 EstimateRayTracedSpecularReflection(
if (hasGBufferMaterial)
{
float3 hitV = SafeNormalizeOr(-R, V);
uint stableHitRng = InitRng(hitPixel, 0u, 0x5EECu);
reflectedRadiance = EstimateDirectLightingForBounceHit(
hitPixel,
hitPos,
@@ -4811,7 +4853,7 @@ float3 EstimateRayTracedSpecularReflection(
hitV,
hitAlbedo,
hitGeoFlag,
rng);
stableHitRng);
// Reflected glow maps should be visible in the reflection, but still
// remain direct radiance; this does not turn emissive into GI.
@@ -4827,6 +4869,8 @@ float3 EstimateRayTracedSpecularReflection(
}
}
reflectedRadiance = CompressEmissiveRadiance(reflectedRadiance, hasSpecularMap ? 5.0 : 3.5);
float cavityFade = lerp(0.42, 1.0, saturate(cavity));
float reflectionStrength = hasSpecularMap
? saturate(specPeak * 1.35)
@@ -4968,6 +5012,7 @@ void RayGen()
N,
V,
baseAlbedo,
s,
rng);
}
@@ -4975,7 +5020,7 @@ void RayGen()
lightingAccum += ApplyPrimaryDiffusePost(indirectLighting, ao, microShadow, isSkeletal);
}
uint reflectionRng = InitRng(pixel, gFrameIndex, 0x5EECu);
uint reflectionRng = InitRng(pixel, 0u, 0x5EECu);
float3 reflectedSpecular = EstimateRayTracedSpecularReflection(
pixel,
worldPos,
@@ -5334,9 +5379,9 @@ float3 SafeNormalTemporal(float3 n)
float3 ClampHistoryToCurrent(float3 history, float3 current)
{
// Loose temporal clamp: it removes GI fireflies and old lighting while still
// allowing bright muzzle-flash/door-light changes to appear in a few frames.
float3 radius = 0.20 + abs(current) * 0.55;
// Clamp enough to kill rare GI/volume fireflies before they enter history,
// while still allowing real dynamic light changes to pull the accumulator.
float3 radius = 0.12 + abs(current) * 0.42;
return clamp(history, current - radius, current + radius);
}
@@ -5359,7 +5404,7 @@ void TemporalAccumCS(uint3 dispatchThreadId : SV_DispatchThreadID)
}
float4 history = gHistoryTex.Load(int3(pixel, 0));
float historyCount = (gFrameIndex == 0u) ? 0.0 : clamp(history.a, 0.0, 31.0);
float historyCount = (gFrameIndex == 0u) ? 0.0 : clamp(history.a, 0.0, 63.0);
if (historyCount <= 0.0)
{
@@ -5374,14 +5419,14 @@ void TemporalAccumCS(uint3 dispatchThreadId : SV_DispatchThreadID)
float histLum = LuminanceTemporal(historyColor);
float relChange = abs(rawLum - histLum) / max(max(rawLum, histLum), 0.08);
// Base accumulation approaches 32 frames, but large lighting changes raise
// Base accumulation approaches 64 frames, but large lighting changes raise
// current-frame weight so the accumulator does not leave obvious trails.
float currentWeight = max(1.0 / (historyCount + 1.0), 0.055);
currentWeight = max(currentWeight, saturate(relChange * 0.28));
float currentWeight = max(1.0 / (historyCount + 1.0), 0.035);
currentWeight = max(currentWeight, saturate(relChange * 0.24));
currentWeight = saturate(currentWeight);
float3 resolved = lerp(historyColor, max(raw.rgb, 0.0), currentWeight);
float nextCount = min(historyCount + 1.0, 31.0);
float nextCount = min(historyCount + 1.0, 63.0);
gTemporalOutTex[pixel] = float4(resolved, raw.a);
gHistoryOutTex[pixel] = float4(resolved, nextCount);
@@ -7323,4 +7368,4 @@ void glRaytracingShowAllInstances(void)
for (int i = 0; i < GL_RAYTRACING_MAX_RENDER_WORLDS; ++i)
glRaytracingSetAllInstancesVisibleUnlocked(&g_glRaytracingScene.worlds[i], 1);
}
}