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).
Summary
The foreground-only clip (
foregroundOnlyClip/ nativeclip_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
foregroundOnlyClipOFF 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_planeis 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.Camera.SetStereoProjectionMatrixinDisplayXRDisplay/Camera.OnCameraPreRender), so the render clips at the display plane and matches the silhouette mask (which also uses the native matrices viaGetStereoMatrices).SetStereoProjectionMatrixand rebuilds each eye's projection fromXrView.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:
Camera.farClipPlanefree (hit-test stays full range — matches the existing design).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).