From 26dd7e649e749771940e08a10dbe3596a451ef73 Mon Sep 17 00:00:00 2001 From: Justin Marshall Date: Sun, 24 May 2026 15:52:51 -0700 Subject: [PATCH] Skeletal pixels now bypass the internal temporal accumulato --- neo/engine/opengl/gl_d3d12raylight.cpp | 18 ++++++++++++++++-- neo/engine/opengl/gl_d3d12shim.cpp | 6 +++++- 2 files changed, 21 insertions(+), 3 deletions(-) diff --git a/neo/engine/opengl/gl_d3d12raylight.cpp b/neo/engine/opengl/gl_d3d12raylight.cpp index 9a6ce124..d008ce64 100644 --- a/neo/engine/opengl/gl_d3d12raylight.cpp +++ b/neo/engine/opengl/gl_d3d12raylight.cpp @@ -5399,6 +5399,10 @@ float3 EstimateRayTracedSpecularReflection( R"( float3 ApplyRealisticOutputCurve(float3 color) { + color.r = ((color.r == color.r) && abs(color.r) < 65504.0) ? color.r : 0.0; + color.g = ((color.g == color.g) && abs(color.g) < 65504.0) ? color.g : 0.0; + color.b = ((color.b == color.b) && abs(color.b) < 65504.0) ? color.b : 0.0; + // Final photographic shoulder only: it does not change light radius or // attenuation, but it prevents intense local lights/specular/bloom from // clipping into a flat white patch. Values below 1.0 are left untouched. @@ -5935,6 +5939,18 @@ void TemporalAccumCS(uint3 dispatchThreadId : SV_DispatchThreadID) float4 history = gHistoryTex.Load(int3(pixel, 0)); float historyCount = (gFrameIndex == 0u) ? 0.0 : clamp(history.a, 0.0, 63.0); + // This temporal accumulator has no reprojection/object history validation. + // Animated characters can inherit the previous frame's wall/background color + // at the same screen pixel, which shows up as grey path-trace-only dirt even + // though the current G-buffer is clean. Keep skeletal pixels current-frame + // and let the following geometry-aware spatial pass do the filtering. + if (isSkeletal) + { + gTemporalOutTex[pixel] = raw; + gHistoryOutTex[pixel] = float4(raw.rgb, 0.0); + return; + } + if (historyCount <= 0.0) { gTemporalOutTex[pixel] = raw; @@ -5952,8 +5968,6 @@ void TemporalAccumCS(uint3 dispatchThreadId : SV_DispatchThreadID) // current-frame weight so the accumulator does not leave obvious trails. float currentWeight = max(1.0 / (historyCount + 1.0), 0.035); currentWeight = max(currentWeight, saturate(relChange * 0.24)); - if (isSkeletal) - currentWeight = max(currentWeight, 0.25); currentWeight = saturate(currentWeight); float3 resolved = lerp(historyColor, max(raw.rgb, 0.0), currentWeight); diff --git a/neo/engine/opengl/gl_d3d12shim.cpp b/neo/engine/opengl/gl_d3d12shim.cpp index dcfcdb06..e1da11af 100644 --- a/neo/engine/opengl/gl_d3d12shim.cpp +++ b/neo/engine/opengl/gl_d3d12shim.cpp @@ -4122,6 +4122,7 @@ VSOut VSMain(VSIn i) float3 worldBinormal = mul((float3x3)gModelMatrix, i.binormal); currClip.z = 0.5 * (currClip.z + currClip.w); + prevClip.z = 0.5 * (prevClip.z + prevClip.w); o.pos = currClip; o.currClip = currClip; @@ -4366,6 +4367,7 @@ VSOut DSMain(HSConstOut tessFactors, float3 bary : SV_DomainLocation, const Outp float3 worldNormal = mul((float3x3)gModelMatrix, objNormal); currClip.z = 0.5 * (currClip.z + currClip.w); + prevClip.z = 0.5 * (prevClip.z + prevClip.w); o.pos = currClip; o.currClip = currClip; @@ -7571,7 +7573,9 @@ static sl::Result QD3D12_SetStreamlineCommonConstants(sl::FrameToken& frameToken memcpy(&consts.clipToPrevClip, g_gl.cameraState.clipToPrevClip.m, sizeof(float) * 16); memcpy(&consts.prevClipToClip, g_gl.cameraState.prevClipToClip.m, sizeof(float) * 16); consts.jitterOffset = { g_gl.jitterX, g_gl.jitterY }; - consts.mvecScale = { 1.0f, 1.0f }; // motion vectors are written in PreviousUV - CurrentUV + const float motionScaleX = g_currentWindow ? (float)g_currentWindow->renderWidth : 1.0f; + const float motionScaleY = g_currentWindow ? (float)g_currentWindow->renderHeight : 1.0f; + consts.mvecScale = { motionScaleX, motionScaleY }; // motion vectors are written in PreviousUV - CurrentUV consts.cameraPinholeOffset = { 0.0f, 0.0f }; memcpy(&consts.cameraPos, g_gl.cameraState.cameraPos, sizeof(float) * 3); memcpy(&consts.cameraRight, g_gl.cameraState.cameraRight, sizeof(float) * 3);