Skip to content

Foreground-only clip not honored under URP — render clips at wrong plane, mask/render mismatch #129

@dfattal

Description

@dfattal

Summary

The foreground-only clip (foregroundOnlyClip / native clip_at_display_plane) does not work correctly under URP. The silhouette cutout mask is computed at the correct per-view display-plane far, but the URP eye render clips at the wrong plane, so the displayed object no longer matches the cutout — worst when the object straddles the display plane (moving it in/out of plane). BiRP is unaffected.

Confirmed on a 2-view Leia panel (so this is independent of view count / #128): with foregroundOnlyClip OFF the cutout matches the rendered object perfectly; with it ON the render is clipped at a plane that does not match the virtual display, while the mask clips at the display plane.

Root cause

clip_at_display_plane is encoded only as the per-view far plane of the native Kooima projection matrix (native~/display3d_view.c:310-317, native~/camera3d_view.c:199); it does not touch the FoV angles.

  • BiRP consumes that native projection for the eye render (Camera.SetStereoProjectionMatrix in DisplayXRDisplay/Camera.OnCameraPreRender), so the render clips at the display plane and matches the silhouette mask (which also uses the native matrices via GetStereoMatrices).
  • URP ignores SetStereoProjectionMatrix and rebuilds each eye's projection from XrView.fov (+ Unity camera near/far), so the per-view display-plane far never reaches the render — it clips at the wrong plane. The mask still uses the correct per-view far → render/mask mismatch.

This is the same BiRP-vs-URP projection-consumption split noted in code (displayxr_hooks.cpp:456-458: "URP doesn't consume these — it reads each XRPass.GetProjMatrix from views[i].fov").

Fix (Option 1, chosen): material-agnostic depth-based clip as a URP ScriptableRendererFeature

After the eye render, a full-screen pass reads camera depth, reconstructs view-space depth, and zeroes alpha (transparent → desktop composites through) for pixels beyond the display-plane distance. Properties:

  • Material-agnostic (works on the app's URP/Lit content, not just our shaders).
  • Keeps Camera.farClipPlane free (hit-test stays full range — matches the existing design).
  • Uses the same display-plane distance the silhouette mask uses, so render and cutout stay locked.
  • N-view safe (world/view-space plane at the display, cyclopean).

Plugin ships the RendererFeature class (URP-guarded so BiRP-only projects don't need URP); apps add it to their URP renderer (or plugin setup auto-adds). Relies on per-pixel alpha output (Player Setting "Preserve Framebuffer Alpha"; see #127).

Related: #127 (URP transparent-overlay validation), #128 (N-view silhouette mask).

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions