PICO OpenXR: Fix SPS mono culling frustum incorrectly culling edge objects

https://github.com/EpicGames/UnrealEngine/pull/14830

OpenXR: Fix SPS mono culling frustum incorrectly culling edge objects with toe-out views

The eSSE_MONOSCOPIC branch in GetStereoProjectionMatrix() built the unified

culling frustum by directly min/max-ing each eye’s XrFovf angles. Those angles

are expressed in eye-local space, so on HMDs with toe-out (left/right eyes

rotated outward) the merged frustum was narrower than the actual combined

visible area. Objects near the view edge that were visible to at least one eye

were incorrectly culled before reaching the rasterizer.

Root cause

----------

XrFovf.angleLeft/Right/Up/Down are offsets relative to each eye’s own forward

axis. When the two eye orientations differ (toe-out), simply taking min/max of

the raw angles ignores the per-eye rotation and produces a frustum that does

not enclose all visible directions in head/reference space.

Fix

---

Instead of merging raw angles, project each eye’s four frustum-corner rays

(tan-angle direction vectors, one per FOV boundary) into head/reference space

using the eye’s pose.orientation, then compute the encompassing FOV from those

rotated directions:

1. Construct four corner direction vectors in eye-local XR space:

(tan(angleLeft/Right), tan(angleUp/Down), -1)

2. Convert orientation via ToFQuat() and rotate each corner with

FQuat::RotateVector() (reuses the existing OpenXRCore.h isometry).

3. Map the rotated UE-space vector back to XR space.

4. Back-project onto the Z=-1 plane to recover head-space tan-angles.

5. min/max across all corners and both eyes to get the final FOV.

When eyes are parallel (identity orientation) the new path is mathematically

identical to the old min/max and produces bit-identical results.

Verified on PICO device with OpenXR:

- Eye orientations showed non-zero y component (confirmed toe-out present).

- Raw per-eye FOV: Left=-47.50 Right=47.50 Up=43.00 Down=-43.00 (degrees).

- Merged head-space FOV after fix: Left=-54.62 Right=54.62 Up=41.98 Down=-49.98.

- Edge-object culling artefacts no longer reproducible.

[Attachment Removed]

The new clear pr version:https://github.com/EpicGames/UnrealEngine/pull/14861

  1. The old mono culling path could build the culling frustum from an eye-biased pose and a simple merged FOV, which was not guaranteed to contain both left and right eye view volumes.
  2. The fix computes a conservative mono pose and mono projection from the actual OpenXR eye poses and FOVs.
  3. The mono origin is centered from the eye poses, then shifted backward along the mono view direction so the combined frustum can contain both eye origins and their view directions.
  4. The mono orientation is derived from the combined eye forward/up directions, so it follows rotated or pitched eye poses without being biased by asymmetric FOVs.
  5. The mono projection is recomputed in mono pose space from the transformed eye frustum bounds, instead of directly min/max merging the original eye FOV angles.
  6. SceneView now reconstructs the head-centered base pose before applying the mono pose/projection, preventing the culling frustum from being built directly from one stereo eye.
  7. The override far plane is now computed from the same mono location and mono direction as the mono culling frustum, avoiding pose/projection mismatch when a far clipping override is active.
    [Attachment Removed]

this has been integrated in UE5 CL 56700721, thanks so much for the PR

[Attachment Removed]