You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Screen Space - Camera canvases mis-scale and follow the viewer: Camera.fieldOfView is XR-written while a session runs #274
Reported by a partner integrator during the LeiaViewer port (internal ticket UNITY-133), while trying to build a flat 2D home screen.
Symptom
A Screen Space - Camera canvas inside a running DisplayXR app:
renders 1.4–3.3x too large, and
rescales and drifts as the viewer's head moves.
Removing the DisplayXR rig from the camera does not help: the session still drives that camera with a head-tracked projection ("look-around mono"), so the whole UI floats with the viewer's face. No rig != no tracking.
Mechanism
While a session is running, the provider hands Unity a full per-eye projection, and Unity's XR writes Camera.fieldOfView from it each frame — measured by the reporter at 76.5°–124.5° against an authored 60°. A Screen Space - Camera canvas sizes itself from Camera.fieldOfView, so it fits itself to the tracking-derived value while the actual rendering uses the projection matrix. Hence both the constant over-scale and the movement with the viewer.
This is not the rig writing the FOV — the rig only reads it, and in fact already caches the authored value to avoid exactly this feedback loop (Runtime/DisplayXRCamera.cs:107,210). The write comes from Unity's XR layer because a projection is supplied.
The working answer today
Verified on hardware in a standalone player:
no rig + DisplayXRSceneMode(TwoD) + a full-rect DisplayXRWindowSpaceUI + the Samples~/WindowSpaceUI mouse router
DisplayXRWindowSpaceUI is a composition layer at a fixed window rect, so it is immune to the camera pose and projection entirely. This is now documented in docs~/architecture/two-dimensional-scenes.md (via #272).
What is actually open here
The docs now steer people to the working configuration, but the underlying sharp edge remains:
Camera.fieldOfView is not the authored value while a session runs, and anything reading it (uGUI's Screen Space - Camera, third-party UI, gameplay code doing FOV math, cinematic tools) silently gets a tracking-derived number. Worth deciding whether the plugin can expose an "authored FOV" accessor, and whether the rig's existing cache should be public rather than each caller re-deriving it.
Neither has an obviously correct fix — a per-camera "ignore XR pose" opt-out cuts against how Unity's XR camera integration works. Filing so the sharp edge is tracked rather than rediscovered, and so the docs have something to point at.
Partial fix up as PR #276 — DisplayXRCamera.AuthoredFieldOfView.
The rig has always kept the authored FOV (it must — reading Camera.fieldOfView back while XR is writing it would feed the Kooima FOV into itself and collapse it); it was just private. Now exposed, and GetProviderTunables reads the same accessor so the projected value and the readable value cannot drift.
Second gap found while implementing it: an app could not change FOV at runtime at all. Assigning Camera.fieldOfView while a session is running is both overwritten each frame and ignored by the rig, which projects from its cache. The setter is now the supported path.
Item 2 of this issue is still open — a rig-less camera still receives head-tracked poses, and has no authored-FOV record at all since nothing captured one before XR started. I considered snapshotting every camera's FOV at startup and rejected it: it misses cameras created later, and it papers over the real answer, which is that screen-fixed UI belongs in DisplayXRWindowSpaceUI (a composition layer at a fixed window rect, immune to the camera entirely). Documented in docs~/architecture/two-dimensional-scenes.md rather than half-solved.
Activity
dfattal commentedon Sep 1, 2026
Partial fix up as PR #276 —
DisplayXRCamera.AuthoredFieldOfView.The rig has always kept the authored FOV (it must — reading
Camera.fieldOfViewback while XR is writing it would feed the Kooima FOV into itself and collapse it); it was just private. Now exposed, andGetProviderTunablesreads the same accessor so the projected value and the readable value cannot drift.Second gap found while implementing it: an app could not change FOV at runtime at all. Assigning
Camera.fieldOfViewwhile a session is running is both overwritten each frame and ignored by the rig, which projects from its cache. The setter is now the supported path.Item 2 of this issue is still open — a rig-less camera still receives head-tracked poses, and has no authored-FOV record at all since nothing captured one before XR started. I considered snapshotting every camera's FOV at startup and rejected it: it misses cameras created later, and it papers over the real answer, which is that screen-fixed UI belongs in
DisplayXRWindowSpaceUI(a composition layer at a fixed window rect, immune to the camera entirely). Documented indocs~/architecture/two-dimensional-scenes.mdrather than half-solved.feat(#274): expose the authored FOV, and make it settable at runtime (#…
docs: changelog entry for 2.15.0 (#266, #274, #277)
fix(#274): serialize the authored FOV so a scene load during a live s…
fix(#274): serialize the authored FOV so a scene load during a live s…
docs: changelog for 2.17.0 (batch: #244 #274 #291 #296)