Temporal Accumulation — the Grain Fix

The cloud surface, before & after · real frames off the live GPU · same camera path, the denoiser the only difference

The Godot-matched cloud baseline still had one flaw: a fine grainy sparkle crawling over the surface — like TV static sitting on the vapour. It comes from two things we do on purpose to keep the clouds fast: we render them at half resolution, and we nudge each pixel's ray by a little dithered jitter. Cheap, but grainy.

The fix is temporal accumulation — instead of trusting one noisy frame, we blend each frame with the last few. The jitter is different every frame, so averaging a handful of them cancels the sparkle and leaves the real shape behind. It's the same trick Godot's clouds use, and it's the reason their stills look creamy.

It's on by default now at the live lab. Every clip below flies the identical camera path with the denoiser OFF on the left and ON on the right — so the only thing changing is the grain.

Grain (surface sparkle, lower = smoother)
0.539 0.389
below Godot's own 0.404 — at the reference bar
The darks (the shadows that give form)
unchanged
denoised without flattening shape or lifting the floor
Cost on the GPU
~0 ms
one extra half-res pass — effectively free

Orbit

circling the mass

Circling one cloud tower. On the left the lit faces crawl with sandy speckle; on the right that static is gone and the crowns read as smooth vapour — and the shadowed flanks keep their depth.

DENOISER OFFthe raw grain
DENOISER ONtemporal accumulation

Truck

slide sideways

Sliding sideways across the deck — a pure translation, the hard case for this kind of denoiser. The grain still clears, and watch the edges where the sky shows through: no smearing, no ghost-trails.

DENOISER OFFthe raw grain
DENOISER ONtemporal accumulation

Hard yaw

fast spin — the stress test

A fast spin in place — the worst case for a frame-blending denoiser, where you'd expect ghost-trails if the reprojection were wrong. It stays clean: a touch of natural motion-softening during the whip, and it snaps back to crisp the moment you stop. No trailing, no double-edges.

DENOISER ONfast yaw — no ghosting

Ground-up

the player's vantage

From below the deck, looking up — where the player actually lives. Smooth surface, form intact. (The one rough spot, the chunky edge on the far horizon band, is a separate half-resolution issue — it's there with the denoiser off too — and it's the next thing to chase.)

DENOISER ONbelow the deck, looking up

How it works — in plain terms

  1. Where was this pixel last frame? For each pixel we know the cloud's 3-D position, so we re-project it through last frame's camera to find the same spot in the previous picture. (A "motion vector.")
  2. Is that history trustworthy? If last frame's camera couldn't see that spot — it was off-screen, behind us, or a cloud edge moved across it — we throw the old value away and start fresh. That's what stops the smearing.
  3. Blend. Otherwise we mix 70% of the smoothed history with 30% of the new frame. The jitter differs every frame, so over a handful of frames the grain averages out and the true shape stays.

Ported line-faithful from Godot's SunshineClouds2 (SunshineCloudsCompute.glsl · accumulation_decay = 0.7). The grain it removes is the same one we proved last session by averaging 24 frames offline — this is that proof, built to run live.

Captured at 960×540, 24 fps, from the live three.js port (module=5 · atmo off), denoiser on by default. Try it yourself at the live lab — add ?accum=0 to switch the denoiser off and see the raw grain. Temporal accumulation fixes the grain only; colour, weather and the mid-shadow softness are a separate job, deliberately parked.