Skip to content

Screen Space - Camera canvases mis-scale and follow the viewer: Camera.fieldOfView is XR-written while a session runs #274

Open

Description

@dfattal

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:

  1. 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.
  2. A rig-less camera still receives head-tracked poses, which is surprising in exactly the same way No way to author a 2D scene: every camera renders stereo while the session runs, and the mode-switch API is undocumented #267 was. No way to author a 2D scene: every camera renders stereo while the session runs, and the mode-switch API is undocumented #267 solved the rendering mode; it did not give a way to opt a single camera out of tracking.

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.

Activity

  1. dfattal commented on Sep 1, 2026

    @dfattal
    CollaboratorAuthor

    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.

  2. added 5 commits that reference this issue on Sep 1, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions