Z-fighting on mesh eyes while seated on a spinning (llTargetOmega) object, only at certain spin angles #424

Open
opened 2026-10-05 11:14:49 +00:00 by sky_line · 0 comments
Owner

Z-fighting on eye meshes (layered eye parts: eyeball / iris / highlight shells that sit very close together) while the avatar is sitting on an object that rotates, almost certainly via llTargetOmega. It only shows at certain angles as the object spins, and is fine at other angles and when not seated on a spinner. Reported by the owner, 2026-10-05.

Related: #418 (spinning linksets drift out of sync; the GPU target-omega path).

Why this smells like a timing/pose mismatch, not plain depth precision

Eye layers are normally offset by fractions of a millimetre. They only fight if two layers are transformed slightly differently. Sitting on a spinner is exactly when different transform paths can disagree:

  • GPU target omega (llcleaninstancestore.cpp composeOwnedPose, the "GPU TARGET OMEGA" block): the seat object spins on the GPU using phase cam_time.w - t0. Attachments are explicitly excluded ("Attachments keep the CPU-applied spin"), and so is the avatar.

  • The seated avatar follows its parent's rotation on the CPU (LL's applyAngularVelocity accumulation, read at the time the skeleton is updated / the skin palette is computed).

  • Eye meshes may mix paths:

    • rigged eye meshes are skinned by the palette (pre-skin compute, possibly a previous frame's palette or a different sample time);
    • non-rigged eye attachments on the head use the attachment-point transform (joint world matrix at a possibly different time);
    • BOM / system eyes use yet another path.

    If two layers come from different paths, or the same path evaluated at different times (CPU spin integration vs the world clock, palette from frame N-1 vs instance from frame N), they're offset by angular_velocity × Δt × radius. That error varies with the spin angle (projection vs view direction), which would explain "only at certain angles".

  • TAA / motion vectors: if the previous-frame transform used for reprojection differs between the layers (e.g. prev-instance rows vs skin history), the resolve can make thin layers flicker in and out. Check with TAA off.

To check

  1. Isolate: TAA/FSR off (does it persist?); RenderVKDebugView normal/base views; a non-rotating seat vs a rotating one.
  2. Classify the eye parts: rigged vs attachment vs system eyes; see which layer wins/loses.
  3. Compare the sample times of the seated avatar's skeleton pose and the eye attachments' transforms with the seat's GPU spin phase. Do they use the same clock (LLWorldClock) and the same frame?
  4. Check whether depth comparisons for these layers depend on the GPU spin at all. If the avatar isn't GPU-spun, the seat's GPU spin shouldn't matter for the avatar, unless the avatar's root pose is derived from the seat's CPU rotation (spun) while something else uses the unspun base pose (getRotation() * ~getAngularVelocityRot()). That mix-up would show up as an angle-dependent offset.

Fix direction

  • Make every transform of a seated avatar and its attachments (rigged palettes, attachment instances, eye layers) come from one consistent pose at one time: same clock and frame as the seat's spin.
  • Or, for a seated avatar on a GPU spinner, apply the spin consistently on one side (all GPU, or all CPU).
  • Make sure motion vectors / prev transforms are consistent for the same reason.

Repro

  • Sit on a prim running llTargetOmega(<0,0,1>, 0.5, 1.0) while wearing mesh eyes with layered shells.
  • Cam close on the face and watch the eyes through a full rotation.
  • Compare with Firestorm (CPU spin) for reference.
**Z-fighting on eye meshes** (layered eye parts: eyeball / iris / highlight shells that sit very close together) while the avatar is **sitting on an object that rotates**, almost certainly via `llTargetOmega`. It only shows **at certain angles** as the object spins, and is fine at other angles and when not seated on a spinner. Reported by the owner, 2026-10-05. Related: #418 (spinning linksets drift out of sync; the GPU target-omega path). ## Why this smells like a timing/pose mismatch, not plain depth precision Eye layers are normally offset by fractions of a millimetre. They only fight if two layers are transformed **slightly differently**. Sitting on a spinner is exactly when different transform paths can disagree: - **GPU target omega** (`llcleaninstancestore.cpp` `composeOwnedPose`, the "GPU TARGET OMEGA" block): the *seat object* spins on the GPU using phase `cam_time.w - t0`. Attachments are explicitly excluded ("Attachments keep the CPU-applied spin"), and so is the avatar. - The **seated avatar** follows its parent's rotation on the CPU (LL's `applyAngularVelocity` accumulation, read at the time the skeleton is updated / the skin palette is computed). - **Eye meshes may mix paths:** - rigged eye meshes are skinned by the palette (pre-skin compute, possibly a previous frame's palette or a different sample time); - non-rigged eye attachments on the head use the attachment-point transform (joint world matrix at a possibly different time); - BOM / system eyes use yet another path. If two layers come from different paths, or the same path evaluated at different times (CPU spin integration vs the world clock, palette from frame N-1 vs instance from frame N), they're offset by `angular_velocity × Δt × radius`. That error **varies with the spin angle** (projection vs view direction), which would explain "only at certain angles". - **TAA / motion vectors:** if the previous-frame transform used for reprojection differs between the layers (e.g. prev-instance rows vs skin history), the resolve can make thin layers flicker in and out. Check with TAA off. ## To check 1. **Isolate:** TAA/FSR off (does it persist?); `RenderVKDebugView` normal/base views; a non-rotating seat vs a rotating one. 2. **Classify the eye parts:** rigged vs attachment vs system eyes; see which layer wins/loses. 3. **Compare the sample times** of the seated avatar's skeleton pose and the eye attachments' transforms with the seat's GPU spin phase. Do they use the same clock (`LLWorldClock`) and the same frame? 4. **Check whether depth comparisons for these layers depend on the GPU spin** at all. If the avatar isn't GPU-spun, the seat's GPU spin shouldn't matter for the avatar, unless the avatar's root pose is derived from the seat's *CPU* rotation (spun) while something else uses the *unspun* base pose (`getRotation() * ~getAngularVelocityRot()`). That mix-up would show up as an angle-dependent offset. ## Fix direction - Make every transform of a seated avatar and its attachments (rigged palettes, attachment instances, eye layers) come from **one consistent pose at one time**: same clock and frame as the seat's spin. - Or, for a seated avatar on a GPU spinner, apply the spin consistently on one side (all GPU, or all CPU). - Make sure motion vectors / prev transforms are consistent for the same reason. ## Repro - Sit on a prim running `llTargetOmega(<0,0,1>, 0.5, 1.0)` while wearing mesh eyes with layered shells. - Cam close on the face and watch the eyes through a full rotation. - Compare with Firestorm (CPU spin) for reference.
sky_line added this to the Slipstream project 2026-10-06 15:04:13 +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#424
No description provided.