Skip to content

Commit 3cc7008

Browse files
committed
Suppress lightning ghosting in the cloud temporal smoother
Flashes embedded into the 0.92 history EMA and outlived themselves 3-10x, so a camera move dragged a reprojected imprint of the lit deck across the sky. A CPU-tracked fade (live flash + 0.25 s tail) now collapses the history weight to ~0.18 while a flash could still be in history, so it tracks near-live and never embeds. Inert when no lightning is active. Details in docs/fork-touchpoints.md.
1 parent 0790912 commit 3cc7008

5 files changed

Lines changed: 51 additions & 2 deletions

File tree

docs/fork-touchpoints.md

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2408,3 +2408,16 @@ Look-check driven ("thunderstorm looks like a normal day — the sky is blue, th
24082408
*Stale "neutral in every preset" gate comment updated in `writeBlendedToDerivedLayer`.*
24092409

24102410
---
2411+
2412+
## Workstream — Lightning ghost suppression (fork — 2026-07-14)
2413+
2414+
A lightning flash embedded into the cloud temporal smoother's ~1 s EMA (fixed 0.92 history weight in `evalSkyRadiance`) outlived itself 3–10x; on camera move the reprojected history dragged a sharp-edged "old frame" imprint of the flash-lit deck across the sky. The strike scheduler now tracks a ghost-suppression signal — 1 while a flash is live, decaying over ~0.25 s after — and the temporal blend collapses its history weight by `x(1 - 0.8 * fade)` (0.92 → ~0.18 at full fade, ~2-frame convergence), so the flash never embeds. The jitter the EMA normally hides is masked by the flash itself; inert at fade 0. Validated in-game.
2415+
2416+
- **`src/dxvk/shaders/rtx/pass/atmosphere/atmosphere_args.h`** — fork-owned change.
2417+
*Reclaims the `pad_cloudSunsetWarmth` slot as `lightningHistoryFade`; CB layout unchanged.*
2418+
- **`src/dxvk/rtx_render/rtx_atmosphere.h` / `rtx_atmosphere.cpp`** — fork-owned change.
2419+
*`m_lightningHistoryFade` tracked at the end of `advanceLightning` (max of envelope and a tau-0.25 s decay); published by `getAtmosphereArgs`; zeroed in `normalizeForSkyLutCache`.*
2420+
- **`src/dxvk/shaders/rtx/pass/atmosphere/atmosphere_sky.slangh`** — fork-owned change.
2421+
*Temporal-smoothing block scales `kHistoryWeight` by the fade before the history lerp.*
2422+
2423+
---

src/dxvk/rtx_render/rtx_atmosphere.cpp

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -368,6 +368,7 @@ namespace {
368368
args.lightningStrikePosKm = vec3(0.0f, 0.0f, 0.0f);
369369
args.lightningFlashIntensity = 0.0f;
370370
args.lightningEnvelope = 0.0f;
371+
args.lightningHistoryFade = 0.0f;
371372
}
372373

373374
// Quantize one direction-vector component to the granularity step.
@@ -681,6 +682,7 @@ AtmosphereArgs RtxAtmosphere::getAtmosphereArgs() const {
681682
args.lightningEnvelope = m_lightningEnvelope;
682683
args.lightningFlashIntensity = m_lightningEnvelope * std::max(RtxOptions::lightningFlashIntensity(), 0.0f);
683684
args.lightningColor = RtxOptions::lightningColor();
685+
args.lightningHistoryFade = m_lightningHistoryFade;
684686
}
685687

686688
// Cloud volumetric / appearance enhancements
@@ -1698,6 +1700,7 @@ void RtxAtmosphere::requestLightningStrike() {
16981700
void RtxAtmosphere::advanceLightning(float dt) {
16991701
if (!RtxOptions::lightningEnable()) {
17001702
m_lightningEnvelope = 0.0f;
1703+
m_lightningHistoryFade = 0.0f;
17011704
m_lightningPulsesLeft = 0;
17021705
s_lightningStrikeRequested.store(false); // don't bank a Test Strike while disabled
17031706
return;
@@ -1766,6 +1769,21 @@ void RtxAtmosphere::advanceLightning(float dt) {
17661769
m_lightningPulsesLeft = static_cast<int>(rand01() * 3.0f); // 0-2 restrikes
17671770
m_lightningTimeToPulse = 0.04f + 0.11f * rand01();
17681771
}
1772+
1773+
// Ghost-suppression window (fork — 2026-07-14). Tracks the envelope but
1774+
// decays ~3.5x slower, so it covers the whole flicker PLUS the frames right
1775+
// after, while any flash residue could still be sitting in the cloud
1776+
// temporal history. evalSkyRadiance collapses its EMA history weight by
1777+
// this factor — without it a 100-300 ms flash embeds into the ~1 s history
1778+
// and a camera move drags a reprojected "old frame" imprint of the lit
1779+
// deck across the sky. Computed AFTER the fire block so a fresh strike
1780+
// raises it the same frame its envelope peaks.
1781+
constexpr float kHistoryFadeTau = 0.25f;
1782+
m_lightningHistoryFade = std::max(std::min(m_lightningEnvelope, 1.0f),
1783+
m_lightningHistoryFade * std::exp(-dt / kHistoryFadeTau));
1784+
if (m_lightningHistoryFade < 1e-3f) {
1785+
m_lightningHistoryFade = 0.0f;
1786+
}
17691787
}
17701788

17711789
void RtxAtmosphere::dispatchCloudRender(Rc<DxvkContext> ctx) {

src/dxvk/rtx_render/rtx_atmosphere.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -443,6 +443,7 @@ class RtxAtmosphere : public CommonDeviceObject {
443443
// the envelope / restrike / inter-arrival model.
444444
Vector3 m_lightningStrikePosKm { 0.0f, 0.0f, 0.0f };
445445
float m_lightningEnvelope { 0.0f }; // raw flicker envelope [0..~1.2]
446+
float m_lightningHistoryFade { 0.0f }; // ghost-suppression window (outlasts the envelope)
446447
int m_lightningPulsesLeft { 0 }; // restrike pulses of the active flash
447448
float m_lightningTimeToPulse { 0.0f }; // seconds to the next restrike pulse
448449
uint32_t m_lightningRngState { 0x9E3779B9u }; // xorshift32 state

src/dxvk/shaders/rtx/pass/atmosphere/atmosphere_args.h

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -255,7 +255,13 @@ struct AtmosphereArgs {
255255

256256
float cloudThickness; // Cloud-slab vertical depth, km
257257
float cloudLayer2TypeSpread; // [0,1] cloud-type variation for layer 2 (independent of layer 1)
258-
float pad_cloudSunsetWarmth; // was cloudSunsetWarmth (removed 2026-06-21 — no shader consumer)
258+
float lightningHistoryFade; // [0..1] lightning ghost-suppression signal (fork — 2026-07-14):
259+
// 1 while a flash is live, decaying over ~0.25 s after it ends.
260+
// evalSkyRadiance collapses the cloud temporal-history weight by
261+
// this factor so the flash never embeds into the ~1 s EMA (the
262+
// reprojected "old frame" ghost on camera move). Reuses the former
263+
// pad_cloudSunsetWarmth slot; CB layout unchanged. Zeroed in
264+
// normalizeForSkyLutCache (per-frame animated, never feeds a bake).
259265
uint cloudViewSamples; // Ray-march steps through cloud slab
260266

261267
// ----- Spatial variation fields (Nubis-style weather) -----

src/dxvk/shaders/rtx/pass/atmosphere/atmosphere_sky.slangh

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1022,7 +1022,18 @@ vec3 evalSkyRadiance(
10221022
// a stable signal in ~30 frames at 60Hz (~0.5s) while still strongly
10231023
// suppressing the per-frame jitter variance DLSS sees.
10241024
const float kHistoryWeight = 0.92f;
1025-
vec4 blended = lerp(currentPremul, history, kHistoryWeight);
1025+
// Lightning ghost suppression (fork — 2026-07-14). A 100-300 ms flash
1026+
// embedded into this ~1 s EMA outlives itself 3-10x, and on camera
1027+
// move the reprojected history drags a bright "old frame" imprint of
1028+
// the lit deck across the sky (sharp-edged where reprojection leaves
1029+
// the old viewport). While a flash is live — plus a ~0.25 s trailing
1030+
// window (CPU-side lightningHistoryFade) — collapse the history
1031+
// weight so the flash tracks near-live and never embeds. The jitter
1032+
// noise the EMA normally hides is masked by the flash itself; weight
1033+
// returns to 0.92 as the fade expires. Inert at fade 0.
1034+
float historyWeight = kHistoryWeight
1035+
* (1.0f - 0.8f * saturate(args.lightningHistoryFade));
1036+
vec4 blended = lerp(currentPremul, history, historyWeight);
10261037
smoothedCloud = vec4(blended.rgb, blended.a);
10271038
} else {
10281039
smoothedCloud = currentPremul;

0 commit comments

Comments
 (0)