TAA: shimmering / jitter on water and distant mesh #204

Closed
opened 2026-09-30 06:30:51 +00:00 by sky_line · 2 comments
Owner

With TAA on (RenderFSAAType 3, and upscaling), water and mesh at far distances shimmer and jitter instead of settling to a stable image. Reported by the user in-world on the current build (after the #182 upscaler work).

Likely causes to investigate

Water

  • Motion vectors: the water surface draws after the pre-pass / motion-vector pass, so it probably has no correct motion vectors. It has camera-only motion at best, and its animated normals / reflections change every frame. History reprojection then either rejects every frame (flicker at 1 spp, jittered) or ghosts.
  • Reflections and SSR: mirror / probe / SSR contributions differ frame to frame and aren't jitter-aware.
  • Fix direction: mark water as reactive in the resolve (lower history weight, like FSR's reactive mask). Also either give it a camera-motion vector from its own depth, or exclude it from the jitter-sensitive clamping. Check whether water writes the unjittered display depth the resolve uses for disocclusion.

Distant mesh

  • Sub-pixel detail: thin, sub-pixel features at distance alias per jitter phase, so each phase hits a different sample. With the neighbourhood clamp this becomes flicker instead of converging.
  • Precision and depth: distance-dependent precision (see #188, reversed-Z). The 3x3 previous-depth search and own-depth disocclusion may wrongly reject history at far depths, where tiny depth deltas are large in relative terms. Log the rejection rate by distance.
  • Mip / LOD bias: the TAA LOD bias (taaLodBias) makes distant textures sharper. Sharper textures plus jitter produce more temporal aliasing. Compare with the bias off.
  • LOD switching: RenderDynamicLOD / LOD selection by distance can flip per frame near a threshold, making geometry pop rather than shimmer. Rule it out with a fixed LOD.
  • Distance fade / haze: atmospheric haze changes with depth and jitter; minor, but check.

To do

  1. Reproduce with the debug views: history weight / rejection mask, motion vectors, disocclusion. Compare TAA off / TAA on / TAA + upscaling, at native and at Performance.
  2. Water: add a reactive/transparency mask to the resolve for water and other non-motion-vector surfaces (alpha, particles).
  3. Distant mesh: tune the clamp (variance clipping with a wider gamma at distance), check the rejection thresholds against linear depth, and try an anti-flicker term (luminance-weighted history, as in FSR 2's lock / Karis anti-flicker).
  4. Verify the jitter sequence and phase count (16 at native, more when upscaling) don't produce visible low-frequency patterns.

Related: #182 (TAA upscaler), #188 (reversed-Z / precision), #84 (temporal pipeline).

With TAA on (RenderFSAAType 3, and upscaling), **water** and **mesh at far distances** shimmer and jitter instead of settling to a stable image. Reported by the user in-world on the current build (after the #182 upscaler work). ## Likely causes to investigate **Water** - **Motion vectors:** the water surface draws after the pre-pass / motion-vector pass, so it probably has no correct motion vectors. It has camera-only motion at best, and its animated normals / reflections change every frame. History reprojection then either rejects every frame (flicker at 1 spp, jittered) or ghosts. - **Reflections and SSR:** mirror / probe / SSR contributions differ frame to frame and aren't jitter-aware. - **Fix direction:** mark water as reactive in the resolve (lower history weight, like FSR's reactive mask). Also either give it a camera-motion vector from its own depth, or exclude it from the jitter-sensitive clamping. Check whether water writes the unjittered display depth the resolve uses for disocclusion. **Distant mesh** - **Sub-pixel detail:** thin, sub-pixel features at distance alias per jitter phase, so each phase hits a different sample. With the neighbourhood clamp this becomes flicker instead of converging. - **Precision and depth:** distance-dependent precision (see #188, reversed-Z). The 3x3 previous-depth search and own-depth disocclusion may wrongly reject history at far depths, where tiny depth deltas are large in relative terms. Log the rejection rate by distance. - **Mip / LOD bias:** the TAA LOD bias (`taaLodBias`) makes distant textures sharper. Sharper textures plus jitter produce more temporal aliasing. Compare with the bias off. - **LOD switching:** `RenderDynamicLOD` / LOD selection by distance can flip per frame near a threshold, making geometry pop rather than shimmer. Rule it out with a fixed LOD. - **Distance fade / haze:** atmospheric haze changes with depth and jitter; minor, but check. ## To do 1. Reproduce with the debug views: history weight / rejection mask, motion vectors, disocclusion. Compare TAA off / TAA on / TAA + upscaling, at native and at Performance. 2. Water: add a reactive/transparency mask to the resolve for water and other non-motion-vector surfaces (alpha, particles). 3. Distant mesh: tune the clamp (variance clipping with a wider gamma at distance), check the rejection thresholds against linear depth, and try an anti-flicker term (luminance-weighted history, as in FSR 2's lock / Karis anti-flicker). 4. Verify the jitter sequence and phase count (16 at native, more when upscaling) don't produce visible low-frequency patterns. Related: #182 (TAA upscaler), #188 (reversed-Z / precision), #84 (temporal pipeline).
Author
Owner

Additional report: moving avatars sometimes ghost (trail a faint copy) with TAA on.

Things to check for this one:

  • Rigged motion vectors: these come from prev_skinned (mPrevSkinPos, written by the pre-skin when u_prev is set). Check whether the previous-frame skinned positions are valid for every rigged instance every frame. A rigged instance's skinned range (rig_dst) is reallocated by the cull each frame, so prev_v[dst] must be written from the previous palette for the same instance in the current range, not read from last frame's buffer at a range that now belongs to someone else. Also check instances that were culled last frame, which have no history.
  • Frames with no motion vectors: frames where mvFrame is false (no pre-pass) leave the motion target cleared to 0. A moving avatar then reprojects with camera-only motion and drags history.
  • Avatars coming back: GPU-animated avatars vs mPrevJoints. When a joint-table slot comes back (copyVKAvatarAlive), check that the previous joints are reset so motion isn't computed from a stale pose.
  • Rejection: whether the neighbourhood clamp is loose enough to let a low-contrast avatar ghost against a similar background, and whether own-depth disocclusion catches the trailing edge.
  • Test: the TAA motion-vector debug view on a walking avatar. Vectors should be non-zero on the avatar and zero on the static background.
**Additional report:** moving avatars sometimes **ghost** (trail a faint copy) with TAA on. Things to check for this one: - **Rigged motion vectors:** these come from `prev_skinned` (`mPrevSkinPos`, written by the pre-skin when `u_prev` is set). Check whether the previous-frame skinned positions are valid for every rigged instance every frame. A rigged instance's skinned range (`rig_dst`) is reallocated by the cull each frame, so `prev_v[dst]` must be written from the *previous palette* for the same instance in the *current* range, not read from last frame's buffer at a range that now belongs to someone else. Also check instances that were culled last frame, which have no history. - **Frames with no motion vectors:** frames where `mvFrame` is false (no pre-pass) leave the motion target cleared to 0. A moving avatar then reprojects with camera-only motion and drags history. - **Avatars coming back:** GPU-animated avatars vs `mPrevJoints`. When a joint-table slot comes back (`copyVKAvatarAlive`), check that the previous joints are reset so motion isn't computed from a stale pose. - **Rejection:** whether the neighbourhood clamp is loose enough to let a low-contrast avatar ghost against a similar background, and whether own-depth disocclusion catches the trailing edge. - **Test:** the TAA motion-vector debug view on a walking avatar. Vectors should be non-zero on the avatar and zero on the static background.
sky_line added this to the Slipstream project 2026-10-06 15:04:15 +00:00
Author
Owner

Shipped in a published release and tested; closing (testing-lane sweep 2026-10-11).

Shipped in a published release and tested; closing (testing-lane sweep 2026-10-11).
sky_line 2026-10-11 13:24:51 +00:00
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
sky_line/Slipstream#204
No description provided.