How to recenter in OpenXR?

I’ve been struggling with this issue for months, looking through all kinds of documentation and forum questions, and I’m no closer to figuring this out: How do I, in an OpenXR project, recenter the player?

With floor set as the XROrigin’s origin, long pressing my Quest 2’s home button doesn’t recenter myself at all. Apparently this is normal. If I set it to device, I recenter but my height is reset as well, which I don’t want. This is also apparently normal. The thread here looked promising, but the event doesn’t fire when I recenter, either through Link or through an actual build.

When I switched to the Oculus plugin, everything recenters perfectly on floor origin without changing height, so I know it’s possible. But then all my other code broke. Anyway, I want my game to be accessible to everyone so I have to use OpenXR. How are you guys doing this?

We addressed this issue with OpenXR 1.9. You can manually update your manifest to the latest version and recentering should work just fine in-app.

1 Like

It works! Thank you so much, for some reason it wouldn’t automatically update, I had to uninstall and reinstall the package.

1 Like

How do you actually recenter with code?

You cannot unfortunately. It requires the os to trigger it. This might change as everyone rolls out OpenXR 1.1, but I’m not sure if it will yet,

2 Likes

What I’ve found as a workaround is to change the origin. Setting origin to floor then back to device a second later fixed some recentering issues for me (though it’s noticeable to the player - I do this during transitions).

1 Like

Can you keep us updated on this aspect? Any news about accessing this operation through code?

Please any update on this?

There are two questions being asked in this thread.

1) How do you allow a system-level recenter with Unity’s OpenXR plugin?

The short answer: Use “Floor” for the tracking origin in Unity and then set OpenXRSettings.SetAllowRecentering to true. (Actually, it seems this is the default for “Floor” now, and you should set SetAllowRecentering to false if you don’t want a system-level recenter, except the current PC OpenXR runtime don’t currently let you prevent system-level recentering…)

The long answer:

According to the OpenXR 1.0 spec (which is what everything is currently using), there are reference spaces for tracking that are relevant to this conversation: LOCAL, STAGE, and optionally LOCAL FLOOR.

  • LOCAL allows system-level recentering, but it is centered on the head. It is intended for “seated scale” games.

  • STAGE is centered on the boundary rectangle and does not allow system-level recentering. It is intended for “room scale” games.

  • LOCAL FLOOR, provided by an optional extension, is centered on the floor under the player and allows a system-level recenter. It is intended for “standing scale” games.

Now, for the confusing part. In Unity’s XR API, you can set the tracking origin to “device” or “floor”.

  • When using Unity’s OpenXR plugin, “device” uses the LOCAL OpenXR space (allows system recentering, but is centered on the head)

  • “Floor” used to use the STAGE OpenXR space (does not allow recentering, always centered on the boundary), but I think now defaults to LOCAL FLOOR (allows recentering, centered on floor under user), if it’s available.

  • The 1.9 release of Unity’s OpenXR plugin added OpenXRSettings.SetAllowRecentering, which if set to true will make “Floor” use the OpenXR LOCAL FLOOR space if the OpenXR runtime being used on the user’s system supports it, and if set to false will use STAGE. If the OpenXR runtime being used on the user’s system does not support LOCAL FLOOR, Unity will try to emulate it presumably by watching for system recenter events to the LOCAL space and then applying offsets to the LOCAL or STAGE space.

So which OpenXR runtimes support LOCAL FLOOR? Here’s the even more confusing part…

  • The Meta Quest OpenXR runtime correctly implements LOCAL, STAGE, and the optional LOCAL FLOOR space, as defined by the OpenXR spec. It seems Meta authored the LOCAL FLOOR extension so they could use it on Quest, so this makes sense.

  • Both Valve’s SteamVR OpenXR runtime and Meta’s Rift/Link/PC OpenXR runtime incorrectly allow system-level recentering for the STAGE space, so it works like LOCAL FLOOR even though it should not.

  • Valve has recently added support for the LOCAL FLOOR space to SteamVR’s OpenXR runtime. I convinced them to make it so STAGE is not recenterable and to add LOCAL FLOOR for that use case instead, which they did, but then they reverted the change to the STAGE space recentering a couple of weeks later because some games were incorrectly relying on STAGE being able to be recentered.

  • Meta’s Rift/Link/PC OpenXR runtime does not support LOCAL FLOOR, even though Meta authored the extension (but as mentioned above, their STAGE space incorrectly works like LOCAL FLOOR).

Hopefully the OpenXR 1.1 spec, which now has LOCAL FLOOR as part of the core spec instead of an extension, will unify the correct behavior across all runtimes so that LOCAL FLOOR is the space standing games should use if they want recentering and STAGE is the space they should use if they don’t. It would also be great if Unity added a third option to their XR API to differentiate between these two spaces instead of grouping them both under “Floor” and requiring the call to SetAllowRecentering to choose between the two.

2) How do you force a recenter with Unity’s OpenXR plugin?

You just do it yourself. Give your tracking origin game object a parent. When you want to recenter, offset the tracking origin game object inside its parent so that the player is now positioned where you want them to be. Use OpenXRFeature.OnAppSpaceChange to watch for system recenters, and when that happens, clear your manual offset.

I’m kind of surprised Unity doesn’t expose their own recenter function that does this behind-the-scenes, since it seems like they’re already doing something like it when they emulate the LOCAL FLOOR space on platforms where it is not available.

4 Likes

Thanks for such a detailed post, I’m also surprised the Unity is not providing this basic recentering with offsets. I’ve tried searching the whole xr origin and locomotion related classes but found nothing

With 2 - Force recenter with Unity’s OpenXR Plugin

Why not use XROrigin.MoveCameraToWorldLocation(), and XROrigin.MatchOriginUpOriginForward() ?

The problem I’m having is that I can move the player to the correct location, I just can’t get the height correct, I want to be able to the origin relative to the floor.

EDIT: I ended up using TeleportationProvider.TeleportationProvider(), I realised it does this already :smile:

Why do the newer versions of OpenXR not show up in unity’s Package Manager? The docs show 1.13.1 is the latest, but package manager says 1.8.2 that I have installed is the latest (OpenXR Plugin | OpenXR Plugin | 1.13.1)

Also, can I just update manifest.json to 1.9.1 (or 1.13.1?) and then unity will update package-lock.json?

how is this not fixed yet, still having this problem in 2022.3. Package manager still showing 1.8.2 as the latest release

Good summary. One extra thing I found was that when using Unity OpenXR on Meta Quest, sometimes the initial tracking pose had a constant offset. If I moved the XROrigin to compensate for this, then user-initiated recentering operations would recenter on the XROrigin which had the offset.

Fortunately, invoking OpenXRSettings.RefreshRecenterSpace() immediately on launch clears the initial tracking pose offset and from that point onwards recenter events are correct.

I don’t have any insight into what this method does under the hood, as it calls through to a native method, but it works, which was a relief after many futile attempts at other fixes, including the no-op XRInputSubsystem.TryRecenter (which does nothing).

Can you explain a little bit more when you call OpenXRSettings.RefreshRecenterSpace()?
Do you move XROrigin manual or not?
My problem is that when I launch the app with boundary room scaling enabled, my UI appears somewhere far behind the user.
If I long-press the meta button to invoke the system reset, everything goes back to normal.

That sounds like exactly the problem I had.

My fix was to call to OpenXRSettings.RefreshRecenterSpace() exactly once, immediately after the XR initialisation is complete. To track when that is, I wrote a custom OpenXRFeature class that allowed me to watch the native XRSessionState and to wait for it to become VISIBLE or FOCUSSED, but I believe that in recent Unity OpenXR versions there is now a new utility Class OpenXRUtility | OpenXR Plugin | 1.13.0. As soon as that state is reached, I call OpenXRSettings.RefreshRecenterSpace(). This is before even my rig is intantiated. From that point on, I can move the XROrigin as needed, and the user can use the Recenter button or control and it all works as desired.

It helps to know what is going on underneath. The HMD tracking pose is always set as a local pose on the camera object, and the XR runtime sends a “recenter” operation, the OpenXR runtime zeroes that local pose out. I believe that OpenXRSettings.RefreshRecenterSpace() does the same thing.

1 Like

Thanks. But it doesn’t help me ;( I made another solution.
After start APP i just call and it work good in Quest and Pico:

XROrigin.MoveCameraToWorldLocation(Vector3.zero);
XROrigin.MatchOriginUpCameraForward(Vector3.up, Vector3.forward);
1 Like