Fixed lighting bounce bugs.

This commit is contained in:
Justin Marshall
2026-05-04 20:22:36 -07:00
parent 9e8bf6012c
commit e7b2d3f5a7
2 changed files with 430 additions and 99 deletions
+427 -96
View File
@@ -720,6 +720,7 @@ struct glRaytracingSceneState_t
static glRaytracingSceneState_t g_glRaytracingScene;
void glRaytracingClear(void);
static void glRaytracingLightingResetDenoiseHistory(void);
static void glRaytracingReleaseWorldResources(glRaytracingRenderWorld_t* world)
{
@@ -830,6 +831,7 @@ static void glRaytracingMarkWorldNeedsRebuild(glRaytracingRenderWorld_t* world)
world->tlasNeedsRebuild = 1;
world->tlasNeedsUpdate = 0;
glRaytracingLightingResetDenoiseHistory();
}
static void glRaytracingMarkWorldNeedsUpdate(glRaytracingRenderWorld_t* world)
@@ -839,6 +841,7 @@ static void glRaytracingMarkWorldNeedsUpdate(glRaytracingRenderWorld_t* world)
if (!world->tlasNeedsRebuild)
world->tlasNeedsUpdate = 1;
glRaytracingLightingResetDenoiseHistory();
}
static void glRaytracingMarkAllWorldsNeedRebuild(void)
@@ -848,6 +851,8 @@ static void glRaytracingMarkAllWorldsNeedRebuild(void)
if (g_glRaytracingScene.worlds[i].alive)
glRaytracingMarkWorldNeedsRebuild(&g_glRaytracingScene.worlds[i]);
}
glRaytracingLightingResetDenoiseHistory();
}
static uint32_t glRaytracingCountAliveInstances(const glRaytracingRenderWorld_t* world)
@@ -2120,8 +2125,12 @@ struct glRaytracingLightingState_t
glRaytracingBuffer_t hitTable;
ComPtr<ID3D12PipelineState> denoisePSO;
ComPtr<ID3D12PipelineState> temporalPSO;
glRaytracingTexture_t pathTraceTexture;
glRaytracingTexture_t temporalTexture;
glRaytracingTexture_t historyTexture[2];
glRaytracingTexture_t denoiseTemp[2];
uint32_t currentHistoryIndex;
glRaytracingBuffer_t denoiseConstantBuffer[3];
UINT denoiseWidth;
UINT denoiseHeight;
@@ -2137,6 +2146,7 @@ struct glRaytracingLightingState_t
denoiseWidth = 0;
denoiseHeight = 0;
denoiseFormat = DXGI_FORMAT_UNKNOWN;
currentHistoryIndex = 0;
frameCounter = 0;
externalDenoiser = false;
initialized = false;
@@ -2158,20 +2168,24 @@ enum glRaytracingLightingDescriptorIndex_t
GLR_DESC_PATHTRACE_SRV = 6,
GLR_DESC_DENOISE_A_SRV = 7,
GLR_DESC_DENOISE_B_SRV = 8,
GLR_DESC_PATHTRACE_UAV = 9,
GLR_DESC_DENOISE_A_UAV = 10,
GLR_DESC_DENOISE_B_UAV = 11,
GLR_DESC_OUTPUT_UAV = 12,
GLR_DESC_COUNT = 13,
GLR_DESC_SRV_COUNT = 9,
GLR_DESC_UAV_COUNT = 4
GLR_DESC_HISTORY_SRV = 9,
GLR_DESC_TEMPORAL_SRV = 10,
GLR_DESC_PATHTRACE_UAV = 11,
GLR_DESC_DENOISE_A_UAV = 12,
GLR_DESC_DENOISE_B_UAV = 13,
GLR_DESC_OUTPUT_UAV = 14,
GLR_DESC_TEMPORAL_UAV = 15,
GLR_DESC_HISTORY_UAV = 16,
GLR_DESC_COUNT = 17,
GLR_DESC_SRV_COUNT = 11,
GLR_DESC_UAV_COUNT = 6
};
static void glRaytracingLightingResetDenoiseHistory(void)
{
// Kept under the old name so existing call sites keep compiling. The denoiser
// below is no longer a temporal history blend; this only restarts stochastic
// sample indexing after material/camera/light changes.
// Kept under the old name so existing call sites keep compiling. This now
// invalidates the temporal GI accumulator as well as restarting stochastic
// sample indexing after material/camera/light/scene changes.
g_glRaytracingLighting.frameCounter = 0;
g_glRaytracingLighting.constants.frameIndex = 0;
}
@@ -3626,10 +3640,9 @@ float3 EstimatePathTracedVolumetricScattering(uint2 pixel, float3 worldPos, inou
R"(
float3 EstimateFastBounceLight(float3 hitPos, float3 hitN, Light Lgt)
{
// Secondary-bounce lighting needs to be cheap. The primary pass already casts
// detailed visibility rays. Here we evaluate every active light analytically
// without extra shadow rays so all lights still contribute to GI, but the
// indirect path no longer explodes into many TraceRay() calls per pixel.
// Cheap unshadowed estimate used as the all-lights baseline for secondary
// GI. A small shadowed subset below corrects this baseline so every light
// still bounces, but important occlusion is no longer missing from GI.
if (Lgt.type == GL_RAYTRACING_LIGHT_TYPE_POINT)
{
float3 toLight = Lgt.position - hitPos;
@@ -3642,9 +3655,9 @@ float3 EstimateFastBounceLight(float3 hitPos, float3 hitN, Light Lgt)
if (atten <= 0.0)
return 0.0;
float wrap = 0.35;
float wrap = 0.32;
float nDotL = saturate((dot(hitN, L) + wrap) / (1.0 + wrap));
return Lgt.color * (Lgt.intensity * atten * nDotL);
return clamp(Lgt.color * (Lgt.intensity * atten * nDotL), 0.0, 8.0);
}
else if (Lgt.type == GL_RAYTRACING_LIGHT_TYPE_SPOT)
{
@@ -3658,14 +3671,12 @@ float3 EstimateFastBounceLight(float3 hitPos, float3 hitN, Light Lgt)
if (atten <= 0.0)
return 0.0;
float wrap = 0.35;
float wrap = 0.32;
float nDotL = saturate((dot(hitN, L) + wrap) / (1.0 + wrap));
return Lgt.color * (Lgt.intensity * atten * nDotL);
return clamp(Lgt.color * (Lgt.intensity * atten * nDotL), 0.0, 8.0);
}
else if (Lgt.type == GL_RAYTRACING_LIGHT_TYPE_RECT)
{
// Use the rect center for the bounce estimate. It is stable and avoids
// spending another random sample plus visibility ray on secondary hits.
float3 toCenter = Lgt.position - hitPos;
float centerDist = length(toCenter);
if (centerDist <= 0.01)
@@ -3689,59 +3700,139 @@ float3 EstimateFastBounceLight(float3 hitPos, float3 hitN, Light Lgt)
if (faceTerm <= 0.0)
return 0.0;
return clamp(Lgt.color * (Lgt.intensity * nDotL * faceTerm * atten), 0.0, 4.0);
return clamp(Lgt.color * (Lgt.intensity * nDotL * faceTerm * atten), 0.0, 8.0);
}
return 0.0;
}
float3 EstimateShadowedBounceLight(uint2 hitPixel, float3 hitPos, float3 hitN, float3 hitV, float3 hitAlbedo, Light Lgt, inout uint rng)
{
float3 spec = 0.0;
float3 diffuse = 0.0;
// Use the same visibility-capable direct-light samplers as the primary hit.
// This supplies proper next-event estimation at secondary hits instead of the
// old unoccluded light-list approximation.
if (Lgt.type == GL_RAYTRACING_LIGHT_TYPE_POINT)
{
diffuse = PathTraceDirectPointLight(hitPixel, hitPos, hitN, hitV, hitAlbedo, Lgt, rng, spec);
}
else if (Lgt.type == GL_RAYTRACING_LIGHT_TYPE_SPOT)
{
diffuse = PathTraceDirectSpotLight(hitPos, hitN, hitV, hitAlbedo, Lgt, rng, spec);
}
else if (Lgt.type == GL_RAYTRACING_LIGHT_TYPE_RECT)
{
diffuse = PathTraceDirectRectLight(hitPixel, hitPos, hitN, hitV, hitAlbedo, Lgt, rng, spec);
}
return clamp(diffuse, 0.0, 12.0);
}
float3 EstimateBounceSkyLighting(float3 hitPos, float3 hitN, inout uint rng)
{
const float SKY_TMAX = 1000000.0;
uint skySamples = (gSamplesPerPixel >= 4u) ? 2u : 1u;
float3 accum = 0.0;
[loop]
for (uint i = 0u; i < skySamples; ++i)
{
float3 skyDir = (i == 0u)
? SampleConeWorld(GetSkyLightDirection10AM(), 0.18, rng)
: SampleCosineWorld(hitN, rng);
float NoSky = saturate(dot(hitN, skyDir));
if (NoSky <= 0.0)
continue;
float visibility = TraceVisibilityBiased(hitPos, hitN, skyDir, SKY_TMAX);
accum += GetSkyRadiance(skyDir) * visibility * NoSky;
}
return accum / (float)skySamples;
}
float3 EstimateDirectLightingForBounceHit(uint2 hitPixel, float3 hitPos, float3 hitN, float3 hitV, float3 hitAlbedo, uint hitGeoFlag, inout uint rng)
{
bool hitIsSkeletal = (hitGeoFlag & GEOMETRY_FLAG_SKELETAL) != 0u;
bool hitIsUnlit = (hitGeoFlag & GEOMETRY_FLAG_UNLIT) != 0u;
// Treat unlit G-buffer surfaces as emissive-ish for bounce purposes. This is
// useful for light cards / bright UI-like surfaces that deliberately bypass
// the regular lighting pass.
hitN = SafeNormalizeOr(hitN, float3(0.0, 0.0, 1.0));
hitV = SafeNormalizeOr(hitV, -hitN);
hitAlbedo = saturate(hitAlbedo);
// Treat unlit G-buffer surfaces as simple emissive bounce cards. Clamp so a
// white UI/light-card cannot become an uncontrolled firefly in the GI path.
if (hitIsUnlit)
return hitAlbedo;
return clamp(hitAlbedo * 2.0 + GetSkyRadiance(hitN) * 0.04, 0.0, 6.0);
// Cheap environment term. The expensive sky visibility probes stay in the
// primary lighting path; doing them again for every secondary hit was a major
// source of the framerate drop.
float upness = saturate(hitN.z * 0.5 + 0.5);
float3 lighting = gAmbientColor.rgb * (gAmbientColor.a * 0.025);
lighting += GetSkyRadiance(hitN) * (0.06 + 0.10 * upness);
float3 lighting = gAmbientColor.rgb * (gAmbientColor.a * 0.035);
// Still walk the full light list, but do not cast secondary-hit shadow rays.
// That preserves the "all lights bounce" behavior while making the cost
// roughly one extra bounce TraceRay() per indirect path instead of one bounce
// TraceRay() plus many more visibility TraceRay() calls.
// Real occluded sky contribution at the secondary hit. The previous GI path
// used a fixed sky term, so corners/cavities received too much indirect light.
lighting += EstimateBounceSkyLighting(hitPos, hitN, rng) * (0.14 + 0.10 * upness);
// Baseline: every light contributes to bounced radiance, so small dynamic
// lights do not vanish just because they were not chosen by the stochastic
// next-event-estimation budget.
float3 fastAllLights = 0.0;
[loop]
for (uint i = 0; i < gLightCount; ++i)
{
lighting += EstimateFastBounceLight(hitPos, hitN, gLights[i]);
fastAllLights += EstimateFastBounceLight(hitPos, hitN, gLights[i]);
}
lighting += fastAllLights;
// Visibility correction: replace a small rotating subset of the unshadowed
// baseline with real shadowed direct lighting. Temporal accumulation in the
// compute pass below makes this converge without tracing every light at every
// bounce hit.
uint correctionBudget = 0u;
if (gLightCount > 0u)
{
correctionBudget = (gLightCount <= 3u) ? gLightCount : 2u;
if (gSamplesPerPixel >= 4u)
correctionBudget = min(gLightCount, correctionBudget + 1u);
}
if (hitIsSkeletal)
lighting *= 1.10;
if (correctionBudget > 0u)
{
uint start = PcgHash(rng ^ 0x9E3779B9u) % gLightCount;
rng = PcgHash(rng + 0xBB67AE85u);
uint stride = (gLightCount > 1u) ? (1u + (PcgHash(rng ^ 0x3C6EF372u) % (gLightCount - 1u))) : 1u;
rng = PcgHash(rng + 0xA54FF53Au);
// Return outgoing diffuse radiance from the bounce surface. The caller adds
// this as incoming indirect light at the primary surface; primary albedo is
// applied later in RayGen just like direct lighting.
return max(hitAlbedo * max(lighting, 0.0), 0.0);
[loop]
for (uint c = 0u; c < correctionBudget; ++c)
{
uint lightIndex = (start + c * stride) % gLightCount;
Light Lgt = gLights[lightIndex];
float3 fast = EstimateFastBounceLight(hitPos, hitN, Lgt);
float3 shadowed = EstimateShadowedBounceLight(hitPixel, hitPos, hitN, hitV, hitAlbedo, Lgt, rng);
lighting += shadowed - fast;
}
}
//if (hitIsSkeletal)
// lighting *= 1.10;
// Outgoing diffuse radiance from the bounce surface. The primary surface's
// albedo is applied later in RayGen, so only the secondary hit albedo belongs
// here.
return clamp(hitAlbedo * max(lighting, 0.0), 0.0, 16.0);
}
)"
R"(
float3 EstimateReactiveScreenSpaceFinalGather(uint2 pixel, float3 worldPos, float3 N, float3 V, float3 baseAlbedo, inout uint rng)
{
// Cheap one-frame final gather / irradiance reuse. This is intentionally not
// temporal: light and material changes show up immediately, while the costly
// DXR budget stays at one stochastic bounce ray. The gather samples current
// G-buffer surfaces around the shaded pixel and shades them analytically as
// bounce emitters, so nearby lit/colorful visible surfaces push GI without
// adding more TraceRay() calls.
// Current-frame final gather / irradiance reuse. The gather samples nearby
// G-buffer surfaces, shades them as bounce emitters, and now traces short
// visibility rays so color bleed does not leak through walls.
static const int2 kGatherTaps[6] =
{
int2( 7, 3),
@@ -3755,8 +3846,8 @@ float3 EstimateReactiveScreenSpaceFinalGather(uint2 pixel, float3 worldPos, floa
uint sampleBudget = 5u;
// Keep the pass cheap when many lights or high SPP are active. This path
// has no DXR rays, but it still evaluates bounce lighting against the light
// list, so adapt the screen-space sample count instead of raising ray count.
// evaluates bounce lighting against the light list and traces short
// visibility rays, so adapt the sample count instead of raising ray count.
if (gLightCount > 4u)
sampleBudget = 4u;
if (gLightCount > 10u)
@@ -3833,6 +3924,15 @@ float3 EstimateReactiveScreenSpaceFinalGather(uint2 pixel, float3 worldPos, floa
if (formWeight <= 1e-5)
continue;
// Prevent screen-space final gather from leaking through walls. This is
// a short visibility ray, not another diffuse bounce, and it fixes the
// most obvious missing GI occlusion cases in doorways/corners.
float visibility = TraceVisibilityBiased(worldPos, N, dirToSample, dist);
if (visibility <= 0.0)
continue;
formWeight *= visibility;
float3 sampleView = SafeNormalizeOr(-dirToSample, V);
float3 outgoingRadiance = EstimateDirectLightingForBounceHit(
uint2(sp),
@@ -3865,13 +3965,9 @@ float3 TraceOneIndirectBouncePath(uint2 pixel, float3 worldPos, float3 N, float3
{
const float BOUNCE_TMAX = 1000000.0;
uint maxIndirectDepth = (gMaxBounces > 1u) ? 1u : 0u;
// Extra diffuse depths are very expensive because each depth launches another
// DXR ray. Keep the common 1-2 SPP mode to one indirect hit. Higher SPP can
// opt into one additional diffuse depth, capped at two total secondary hits.
if (gSamplesPerPixel >= 4u)
maxIndirectDepth = min(gMaxBounces - 1u, 2u);
uint maxIndirectDepth = (gMaxBounces > 1u) ? min(gMaxBounces - 1u, 3u) : 0u;
if (gSamplesPerPixel <= 1u)
maxIndirectDepth = min(maxIndirectDepth, 2u);
float3 accum = 0.0;
float3 throughput = 1.0;
@@ -3899,9 +3995,10 @@ float3 TraceOneIndirectBouncePath(uint2 pixel, float3 worldPos, float3 N, float3
if (!hit)
{
// A miss is ordinary environment lighting for the path. Keep this
// modest because the main pass already has stable direct sky terms.
accum += throughput * GetSkyRadiance(bounceDir) * 0.18;
// Environment miss. This is part of the path throughput and is now
// allowed at every diffuse depth, not only at the first miss.
float missScale = (depth == 0u) ? 0.28 : 0.42;
accum += throughput * GetSkyRadiance(bounceDir) * missScale;
break;
}
@@ -3939,14 +4036,11 @@ float3 TraceOneIndirectBouncePath(uint2 pixel, float3 worldPos, float3 N, float3
if (!hasGBufferMaterial)
{
// IMPORTANT: this is the path that made some lights look like they
// were not bouncing. The TLAS can hit an off-screen or camera-hidden
// surface, and the old code returned only a tiny sky fallback because
// it could not fetch albedo/normal from the G-buffer. Still shade the
// real ray hit against every active light using a neutral diffuse
// material so point, spot, and rect lights all contribute to bounce.
float3 skyTint = GetSkyRadiance(SafeNormalizeOr(reflect(bounceDir, pathNormal), float3(0.0, 0.0, 1.0)));
bouncedRadiance += skyTint * 0.035;
// The ray hit real TLAS geometry, but material lookup via camera
// G-buffer failed because the surface is hidden/off-screen. Keep the
// bounce alive with a neutral material and a little environment tint.
float3 skyTint = GetSkyRadiance(SafeNormalizeOr(reflect(bounceDir, hitNormal), float3(0.0, 0.0, 1.0)));
bouncedRadiance += skyTint * 0.045;
}
accum += throughput * bouncedRadiance;
@@ -3954,12 +4048,21 @@ float3 TraceOneIndirectBouncePath(uint2 pixel, float3 worldPos, float3 N, float3
if ((hitGeoFlag & GEOMETRY_FLAG_UNLIT) != 0u)
break;
// Cosine-weighted diffuse sampling cancels the Lambertian cosine/pdf term,
// so the path throughput is the surface albedo plus a conservative energy
// scale to keep multiple G-buffer-assisted bounces stable.
throughput *= saturate(hitAlbedo) * 0.68;
// Cosine-weighted diffuse sampling cancels the Lambertian cosine/pdf term;
// the remaining throughput is just the secondary surface albedo. Use a
// conservative energy scale because material data for off-screen hits is a
// G-buffer approximation.
throughput *= saturate(hitAlbedo) * 0.72;
if (max(max(throughput.x, throughput.y), throughput.z) < 0.02)
float continuation = clamp(max(max(throughput.x, throughput.y), throughput.z), 0.12, 0.92);
if (depth >= 1u)
{
if (Rand(rng) > continuation)
break;
throughput /= continuation;
}
if (max(max(throughput.x, throughput.y), throughput.z) < 0.015)
break;
pathPos = hitPos;
@@ -3976,13 +4079,11 @@ float3 EstimatePathTracedIndirectBounce(uint2 pixel, float3 worldPos, float3 N,
if (gMaxBounces <= 1u)
return 0.0;
// Keep the realtime GI ray budget flat: one stochastic DXR bounce path per
// lighting sample. Extra GI coverage now comes from the reactive final
// gather in RayGen, which uses current-frame G-buffer reuse instead of more
// secondary TraceRay() calls.
// 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);
const float INDIRECT_STRENGTH = 0.58;
const float INDIRECT_STRENGTH = 0.72;
return accum * INDIRECT_STRENGTH;
}
@@ -3991,8 +4092,8 @@ float3 ApplyPrimaryDiffusePost(float3 lightingAccum, float ao, float microShadow
lightingAccum *= ao;
lightingAccum *= microShadow;
if (isSkeletal)
lightingAccum *= 1.2;
// if (isSkeletal)
// lightingAccum *= 1.2;
return max(lightingAccum, 0.0);
}
@@ -4001,8 +4102,8 @@ float3 ApplyPrimarySpecularPost(float3 specularAccum, float ao, bool isSkeletal)
{
specularAccum *= ao;
if (isSkeletal)
specularAccum *= 1.15;
//if (isSkeletal)
// specularAccum *= 1.15;
return max(specularAccum, 0.0);
}
@@ -4035,8 +4136,8 @@ float3 PathTraceDeterministicLighting(
lightingAccum += skyColor * (0.70 * skyVis);
lightingAccum += ambientSkyVis * (skyColorRGB * 0.15);
if (isSkeletal)
lightingAccum += 0.1;
//if (isSkeletal)
// lightingAccum += 0.1;
// The current direct-light evaluators are deterministic. Keep the inout RNG
// argument only because the functions share the same signature as stochastic
@@ -4122,7 +4223,7 @@ void RayGen()
// Evaluate this deterministic screen-space irradiance reuse once per
// pixel, not once per SPP. That makes the GI more responsive without
// multiplying the light-list work inside the stochastic sample loop.
uint gatherRng = InitRng(pixel, 0u, 1337u);
uint gatherRng = InitRng(pixel, gFrameIndex, 1337u);
reactiveFinalGather = EstimateReactiveScreenSpaceFinalGather(
pixel,
worldPos,
@@ -4156,10 +4257,10 @@ void RayGen()
[loop]
for (uint s = 0; s < spp; ++s)
{
// The current denoiser is spatial, not temporal. Do not use
// gFrameIndex for the primary GI seed or the same pixel flickers
// forever instead of presenting a stable signal for the a-trous pass.
uint rng = InitRng(pixel, 0u, s);
// Frame-vary the GI path now that a temporal accumulator is present.
// This lets the stochastic light subset, sky sample, and diffuse path
// converge instead of staying locked to one noisy sample pattern.
uint rng = InitRng(pixel, gFrameIndex, s);
indirectAccum += EstimatePathTracedIndirectBounce(
pixel,
worldPos,
@@ -4228,6 +4329,7 @@ Texture2D<float4> gPositionTex : register(t4);
Texture2D<float4> gPathTraceTex : register(t6);
Texture2D<float4> gDenoiseATex : register(t7);
Texture2D<float4> gDenoiseBTex : register(t8);
Texture2D<float4> gTemporalTex : register(t10);
RWTexture2D<float4> gRayOutputTex : register(u0);
RWTexture2D<float4> gDenoiseAOutTex : register(u1);
@@ -4273,7 +4375,7 @@ float3 RemodulateLighting(float3 lighting, float3 albedo)
float4 LoadDenoiseSource(int2 p)
{
if (gDenoisePassIndex == 0u)
return gPathTraceTex.Load(int3(p, 0));
return gTemporalTex.Load(int3(p, 0));
if (gDenoisePassIndex == 1u)
return gDenoiseATex.Load(int3(p, 0));
return gDenoiseBTex.Load(int3(p, 0));
@@ -4379,7 +4481,8 @@ void DenoiseCS(uint3 dispatchThreadId : SV_DispatchThreadID)
float weightSum = 0.0;
// Three-pass a-trous wavelet filter. The CPU dispatches this with step
// widths 1, 2, and 4. It is geometry-aware and does not blend prior frames.
// widths 1, 2, and 4. It is geometry-aware; temporal GI accumulation happens
// before this pass.
[unroll]
for (int ky = 0; ky < 5; ++ky)
{
@@ -4457,6 +4560,111 @@ void DenoiseCS(uint3 dispatchThreadId : SV_DispatchThreadID)
}
)";
static const char* g_glRaytracingTemporalHlsl = R"(
cbuffer LightingCB : register(b0)
{
float4x4 gInvViewProj;
float4x4 gInvViewMatrix;
float4x4 gViewProj;
float4 gCameraPos;
float4 gAmbientColor;
float4 gScreenSize;
float gNormalReconstructZ;
uint gLightCount;
uint gEnableSpecular;
uint gEnableHalfLambert;
float gShadowBias;
uint gFrameIndex;
uint gSamplesPerPixel;
uint gMaxBounces;
uint gEnableDenoiser;
uint gDenoisePassIndex;
float gDenoiseStepWidth;
float gDenoiseStrength;
float gDenoisePhiColor;
float gDenoisePhiNormal;
float gDenoisePhiPosition;
float gBumpStrength;
};
Texture2D<float4> gAlbedoTex : register(t1);
Texture2D<float> gDepthTex : register(t2);
Texture2D<float4> gNormalTex : register(t3);
Texture2D<float4> gPositionTex : register(t4);
Texture2D<float4> gPathTraceTex : register(t6);
Texture2D<float4> gHistoryTex : register(t9);
RWTexture2D<float4> gTemporalOutTex : register(u4);
RWTexture2D<float4> gHistoryOutTex : register(u5);
float LuminanceTemporal(float3 c)
{
return dot(c, float3(0.2126, 0.7152, 0.0722));
}
float3 SafeNormalTemporal(float3 n)
{
float lenSq = max(dot(n, n), 1e-8);
return n * rsqrt(lenSq);
}
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;
return clamp(history, current - radius, current + radius);
}
[numthreads(8, 8, 1)]
void TemporalAccumCS(uint3 dispatchThreadId : SV_DispatchThreadID)
{
uint2 pixel = dispatchThreadId.xy;
if (pixel.x >= (uint)gScreenSize.x || pixel.y >= (uint)gScreenSize.y)
return;
float4 raw = gPathTraceTex.Load(int3(pixel, 0));
float depth = gDepthTex.Load(int3(pixel, 0));
if (depth <= 0.0 || depth >= 1.0 || gMaxBounces <= 1u)
{
gTemporalOutTex[pixel] = raw;
gHistoryOutTex[pixel] = float4(raw.rgb, 0.0);
return;
}
float4 history = gHistoryTex.Load(int3(pixel, 0));
float historyCount = (gFrameIndex == 0u) ? 0.0 : clamp(history.a, 0.0, 31.0);
if (historyCount <= 0.0)
{
gTemporalOutTex[pixel] = raw;
gHistoryOutTex[pixel] = float4(raw.rgb, 1.0);
return;
}
float3 historyColor = ClampHistoryToCurrent(max(history.rgb, 0.0), max(raw.rgb, 0.0));
float rawLum = LuminanceTemporal(max(raw.rgb, 0.0));
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
// 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));
currentWeight = saturate(currentWeight);
float3 resolved = lerp(historyColor, max(raw.rgb, 0.0), currentWeight);
float nextCount = min(historyCount + 1.0, 31.0);
gTemporalOutTex[pixel] = float4(resolved, raw.a);
gHistoryOutTex[pixel] = float4(resolved, nextCount);
}
)";
static ComPtr<IDxcBlob> glRaytracingLightingCompileLibrary(const char* src)
{
ComPtr<IDxcUtils> utils;
@@ -4946,12 +5154,34 @@ static int glRaytracingLightingCreateDenoisePipeline(void)
return 1;
}
static int glRaytracingLightingCreateTemporalPipeline(void)
{
ComPtr<IDxcBlob> dxil = glRaytracingLightingCompileCompute(g_glRaytracingTemporalHlsl, L"TemporalAccumCS");
if (!dxil)
return 0;
D3D12_COMPUTE_PIPELINE_STATE_DESC pso = {};
pso.pRootSignature = g_glRaytracingLighting.globalRootSig.Get();
pso.CS.pShaderBytecode = dxil->GetBufferPointer();
pso.CS.BytecodeLength = dxil->GetBufferSize();
GLR_CHECK(g_glRaytracingCmd.device->CreateComputePipelineState(
&pso,
IID_PPV_ARGS(&g_glRaytracingLighting.temporalPSO)));
return 1;
}
static int glRaytracingLightingEnsureDenoiseResources(UINT width, UINT height)
{
if (width == 0 || height == 0)
return 0;
if (g_glRaytracingLighting.pathTraceTexture.resource &&
g_glRaytracingLighting.temporalTexture.resource &&
g_glRaytracingLighting.historyTexture[0].resource &&
g_glRaytracingLighting.historyTexture[1].resource &&
g_glRaytracingLighting.denoiseTemp[0].resource &&
g_glRaytracingLighting.denoiseTemp[1].resource &&
g_glRaytracingLighting.denoiseWidth == width &&
@@ -4962,6 +5192,9 @@ static int glRaytracingLightingEnsureDenoiseResources(UINT width, UINT height)
}
g_glRaytracingLighting.pathTraceTexture = glRaytracingTexture_t();
g_glRaytracingLighting.temporalTexture = glRaytracingTexture_t();
g_glRaytracingLighting.historyTexture[0] = glRaytracingTexture_t();
g_glRaytracingLighting.historyTexture[1] = glRaytracingTexture_t();
g_glRaytracingLighting.denoiseTemp[0] = glRaytracingTexture_t();
g_glRaytracingLighting.denoiseTemp[1] = glRaytracingTexture_t();
@@ -4973,6 +5206,25 @@ static int glRaytracingLightingEnsureDenoiseResources(UINT width, UINT height)
D3D12_RESOURCE_STATE_UNORDERED_ACCESS,
D3D12_RESOURCE_FLAG_ALLOW_UNORDERED_ACCESS);
g_glRaytracingLighting.temporalTexture = glRaytracingCreateTexture2D(
g_glRaytracingCmd.device.Get(),
width,
height,
GL_RAYTRACING_DENOISE_FORMAT,
D3D12_RESOURCE_STATE_UNORDERED_ACCESS,
D3D12_RESOURCE_FLAG_ALLOW_UNORDERED_ACCESS);
for (int i = 0; i < 2; ++i)
{
g_glRaytracingLighting.historyTexture[i] = glRaytracingCreateTexture2D(
g_glRaytracingCmd.device.Get(),
width,
height,
GL_RAYTRACING_DENOISE_FORMAT,
D3D12_RESOURCE_STATE_NON_PIXEL_SHADER_RESOURCE,
D3D12_RESOURCE_FLAG_ALLOW_UNORDERED_ACCESS);
}
for (int i = 0; i < 2; ++i)
{
g_glRaytracingLighting.denoiseTemp[i] = glRaytracingCreateTexture2D(
@@ -4985,12 +5237,16 @@ static int glRaytracingLightingEnsureDenoiseResources(UINT width, UINT height)
}
if (!g_glRaytracingLighting.pathTraceTexture.resource ||
!g_glRaytracingLighting.temporalTexture.resource ||
!g_glRaytracingLighting.historyTexture[0].resource ||
!g_glRaytracingLighting.historyTexture[1].resource ||
!g_glRaytracingLighting.denoiseTemp[0].resource ||
!g_glRaytracingLighting.denoiseTemp[1].resource)
{
return 0;
}
g_glRaytracingLighting.currentHistoryIndex = 0;
g_glRaytracingLighting.denoiseWidth = width;
g_glRaytracingLighting.denoiseHeight = height;
g_glRaytracingLighting.denoiseFormat = GL_RAYTRACING_DENOISE_FORMAT;
@@ -5004,7 +5260,10 @@ static void glRaytracingLightingCreatePerPassDescriptors(
ID3D12Resource* rayOutputTexture,
ID3D12Resource* pathTraceTexture,
ID3D12Resource* denoiseATexture,
ID3D12Resource* denoiseBTexture)
ID3D12Resource* denoiseBTexture,
ID3D12Resource* historyReadTexture,
ID3D12Resource* historyWriteTexture,
ID3D12Resource* temporalTexture)
{
D3D12_CPU_DESCRIPTOR_HANDLE base = g_glRaytracingLighting.descriptorHeap->GetCPUDescriptorHandleForHeapStart();
@@ -5059,6 +5318,10 @@ static void glRaytracingLightingCreatePerPassDescriptors(
glRaytracingOffsetCpu(base, g_glRaytracingLighting.descriptorStride, GLR_DESC_DENOISE_A_SRV));
g_glRaytracingCmd.device->CreateShaderResourceView(denoiseBTexture, &denoiseSrv,
glRaytracingOffsetCpu(base, g_glRaytracingLighting.descriptorStride, GLR_DESC_DENOISE_B_SRV));
g_glRaytracingCmd.device->CreateShaderResourceView(historyReadTexture, &denoiseSrv,
glRaytracingOffsetCpu(base, g_glRaytracingLighting.descriptorStride, GLR_DESC_HISTORY_SRV));
g_glRaytracingCmd.device->CreateShaderResourceView(temporalTexture, &denoiseSrv,
glRaytracingOffsetCpu(base, g_glRaytracingLighting.descriptorStride, GLR_DESC_TEMPORAL_SRV));
D3D12_UNORDERED_ACCESS_VIEW_DESC rayOutputUav = {};
rayOutputUav.ViewDimension = D3D12_UAV_DIMENSION_TEXTURE2D;
@@ -5079,6 +5342,14 @@ static void glRaytracingLightingCreatePerPassDescriptors(
outputUav.Format = pass->outputFormat;
g_glRaytracingCmd.device->CreateUnorderedAccessView(pass->outputTexture, nullptr, &outputUav,
glRaytracingOffsetCpu(base, g_glRaytracingLighting.descriptorStride, GLR_DESC_OUTPUT_UAV));
D3D12_UNORDERED_ACCESS_VIEW_DESC temporalUav = {};
temporalUav.ViewDimension = D3D12_UAV_DIMENSION_TEXTURE2D;
temporalUav.Format = GL_RAYTRACING_DENOISE_FORMAT;
g_glRaytracingCmd.device->CreateUnorderedAccessView(temporalTexture, nullptr, &temporalUav,
glRaytracingOffsetCpu(base, g_glRaytracingLighting.descriptorStride, GLR_DESC_TEMPORAL_UAV));
g_glRaytracingCmd.device->CreateUnorderedAccessView(historyWriteTexture, nullptr, &temporalUav,
glRaytracingOffsetCpu(base, g_glRaytracingLighting.descriptorStride, GLR_DESC_HISTORY_UAV));
}
@@ -5130,13 +5401,19 @@ static bool glRaytracingLightingExecuteInternal(
glRaytracingLightingUploadConstantsTo(g_glRaytracingLighting.denoiseConstantBuffer[passIndex], denoiseConstants);
}
const uint32_t historyReadIndex = g_glRaytracingLighting.currentHistoryIndex & 1u;
const uint32_t historyWriteIndex = (g_glRaytracingLighting.currentHistoryIndex ^ 1u) & 1u;
glRaytracingLightingCreatePerPassDescriptors(
pass,
topLevelAS,
rayOutputTexture,
g_glRaytracingLighting.pathTraceTexture.resource.Get(),
g_glRaytracingLighting.denoiseTemp[0].resource.Get(),
g_glRaytracingLighting.denoiseTemp[1].resource.Get());
g_glRaytracingLighting.denoiseTemp[1].resource.Get(),
g_glRaytracingLighting.historyTexture[historyReadIndex].resource.Get(),
g_glRaytracingLighting.historyTexture[historyWriteIndex].resource.Get(),
g_glRaytracingLighting.temporalTexture.resource.Get());
if (!glRaytracingBeginCmd())
return false;
@@ -5194,12 +5471,61 @@ static bool glRaytracingLightingExecuteInternal(
if (useInternalDenoiser)
{
const UINT groupsX = (pass->width + 7u) / 8u;
const UINT groupsY = (pass->height + 7u) / 8u;
glRaytracingTransition(g_glRaytracingCmd.cmdList.Get(),
g_glRaytracingLighting.pathTraceTexture.resource.Get(),
g_glRaytracingLighting.pathTraceTexture.state,
D3D12_RESOURCE_STATE_NON_PIXEL_SHADER_RESOURCE);
g_glRaytracingLighting.pathTraceTexture.state = D3D12_RESOURCE_STATE_NON_PIXEL_SHADER_RESOURCE;
glRaytracingTransition(g_glRaytracingCmd.cmdList.Get(),
g_glRaytracingLighting.temporalTexture.resource.Get(),
g_glRaytracingLighting.temporalTexture.state,
D3D12_RESOURCE_STATE_UNORDERED_ACCESS);
g_glRaytracingLighting.temporalTexture.state = D3D12_RESOURCE_STATE_UNORDERED_ACCESS;
glRaytracingTransition(g_glRaytracingCmd.cmdList.Get(),
g_glRaytracingLighting.historyTexture[historyReadIndex].resource.Get(),
g_glRaytracingLighting.historyTexture[historyReadIndex].state,
D3D12_RESOURCE_STATE_NON_PIXEL_SHADER_RESOURCE);
g_glRaytracingLighting.historyTexture[historyReadIndex].state = D3D12_RESOURCE_STATE_NON_PIXEL_SHADER_RESOURCE;
glRaytracingTransition(g_glRaytracingCmd.cmdList.Get(),
g_glRaytracingLighting.historyTexture[historyWriteIndex].resource.Get(),
g_glRaytracingLighting.historyTexture[historyWriteIndex].state,
D3D12_RESOURCE_STATE_UNORDERED_ACCESS);
g_glRaytracingLighting.historyTexture[historyWriteIndex].state = D3D12_RESOURCE_STATE_UNORDERED_ACCESS;
g_glRaytracingCmd.cmdList->SetComputeRootSignature(g_glRaytracingLighting.globalRootSig.Get());
g_glRaytracingCmd.cmdList->SetComputeRootDescriptorTable(0,
glRaytracingOffsetGpu(gpuBase, g_glRaytracingLighting.descriptorStride, GLR_DESC_LIGHTS_SRV));
g_glRaytracingCmd.cmdList->SetComputeRootDescriptorTable(1,
glRaytracingOffsetGpu(gpuBase, g_glRaytracingLighting.descriptorStride, GLR_DESC_PATHTRACE_UAV));
g_glRaytracingCmd.cmdList->SetComputeRootConstantBufferView(2, g_glRaytracingLighting.constantBuffer.gpuVA);
g_glRaytracingCmd.cmdList->SetPipelineState(g_glRaytracingLighting.temporalPSO.Get());
g_glRaytracingCmd.cmdList->Dispatch(groupsX, groupsY, 1);
D3D12_RESOURCE_BARRIER temporalUavs[2] = {};
temporalUavs[0].Type = D3D12_RESOURCE_BARRIER_TYPE_UAV;
temporalUavs[0].UAV.pResource = g_glRaytracingLighting.temporalTexture.resource.Get();
temporalUavs[1].Type = D3D12_RESOURCE_BARRIER_TYPE_UAV;
temporalUavs[1].UAV.pResource = g_glRaytracingLighting.historyTexture[historyWriteIndex].resource.Get();
g_glRaytracingCmd.cmdList->ResourceBarrier(2, temporalUavs);
glRaytracingTransition(g_glRaytracingCmd.cmdList.Get(),
g_glRaytracingLighting.temporalTexture.resource.Get(),
g_glRaytracingLighting.temporalTexture.state,
D3D12_RESOURCE_STATE_NON_PIXEL_SHADER_RESOURCE);
g_glRaytracingLighting.temporalTexture.state = D3D12_RESOURCE_STATE_NON_PIXEL_SHADER_RESOURCE;
glRaytracingTransition(g_glRaytracingCmd.cmdList.Get(),
g_glRaytracingLighting.historyTexture[historyWriteIndex].resource.Get(),
g_glRaytracingLighting.historyTexture[historyWriteIndex].state,
D3D12_RESOURCE_STATE_NON_PIXEL_SHADER_RESOURCE);
g_glRaytracingLighting.historyTexture[historyWriteIndex].state = D3D12_RESOURCE_STATE_NON_PIXEL_SHADER_RESOURCE;
for (int i = 0; i < 2; ++i)
{
glRaytracingTransition(g_glRaytracingCmd.cmdList.Get(),
@@ -5221,10 +5547,7 @@ static bool glRaytracingLightingExecuteInternal(
glRaytracingOffsetGpu(gpuBase, g_glRaytracingLighting.descriptorStride, GLR_DESC_PATHTRACE_UAV));
g_glRaytracingCmd.cmdList->SetPipelineState(g_glRaytracingLighting.denoisePSO.Get());
const UINT groupsX = (pass->width + 7u) / 8u;
const UINT groupsY = (pass->height + 7u) / 8u;
// Pass 0: raw path trace -> temp A.
// Pass 0: temporally accumulated path trace -> temp A.
g_glRaytracingCmd.cmdList->SetComputeRootConstantBufferView(2, g_glRaytracingLighting.denoiseConstantBuffer[0].gpuVA);
g_glRaytracingCmd.cmdList->Dispatch(groupsX, groupsY, 1);
@@ -5272,6 +5595,9 @@ static bool glRaytracingLightingExecuteInternal(
if (!glRaytracingEndCmd())
return false;
if (useInternalDenoiser)
g_glRaytracingLighting.currentHistoryIndex = historyWriteIndex;
++g_glRaytracingLighting.frameCounter;
return true;
}
@@ -5324,6 +5650,9 @@ bool glRaytracingLightingInit(void)
if (!glRaytracingLightingCreateDenoisePipeline())
return false;
if (!glRaytracingLightingCreateTemporalPipeline())
return false;
memset(&g_glRaytracingLighting.constants, 0, sizeof(g_glRaytracingLighting.constants));
glRaytracingSetIdentity4x4(g_glRaytracingLighting.constants.invViewProj);
glRaytracingSetIdentity4x4(g_glRaytracingLighting.constants.invViewMatrix);
@@ -5402,6 +5731,7 @@ void glRaytracingLightingClearLights(bool clearPersistant)
g_glRaytracingLighting.constants.lightCount =
(uint32_t)g_glRaytracingLighting.cpuLights.size();
glRaytracingLightingResetDenoiseHistory();
glRaytracingLightingUpdateConstants();
}
@@ -5418,6 +5748,7 @@ bool glRaytracingLightingAddLight(const glRaytracingLight_t* light)
g_glRaytracingLighting.cpuLights.push_back(*light);
g_glRaytracingLighting.constants.lightCount = (uint32_t)g_glRaytracingLighting.cpuLights.size();
glRaytracingLightingResetDenoiseHistory();
glRaytracingLightingUpdateLights();
glRaytracingLightingUpdateConstants();
return true;
+3 -3
View File
@@ -187,9 +187,9 @@ void RB_DXDrawInteractions(void)
vLight->globalLightOrigin.x,
vLight->globalLightOrigin.y,
vLight->globalLightOrigin.z,
srcLight.lightRadius[0] * 2.0f,
srcLight.lightRadius[1] * 2.0f,
srcLight.lightRadius[2] * 2.0f,
srcLight.lightRadius[0] * 1.7f,
srcLight.lightRadius[1] * 1.7f,
srcLight.lightRadius[2] * 1.7f,
r, g, b,
intensity);