Handling tracking loss is an important aspect of VR/MR app development.
In current version explainer, the tracking loss handling is only mentioned in Main Render Loop:
Unless the "headModel" VRFrameOfReference is being used, this function is not guaranteed to return a value. For example, the most common frame of reference, "eyeLevel", will fail to return a viewMatrix or a poseModelMatrix under tracking loss conditions. In that case, the page will need to decide how to respond. It may wish to re-render the scene using an older pose, fade the scene out to prevent disorientation, fall back to a "headModel" VRFrameOfReference, or simply not update. For more information on this see the Advanced functionality section.
There might be several potential improvements for tracking loss handling:
Even for "headModel" VRFrameOfReference, it is still possible to experience "tracking loss".
For example, on OpenVR/Vive, if headset moves out of the tracking area, OpenVR will stops updating mDeviceToAbsoluteTracking of TrackedDevicePose_t, sets bPoseIsValid to false and eTrackingResult to TrackingResult_Calibrating_OutOfRange. It means no any pose data update in this case. Similar for WinMR, app needs to be prepared for SpatialLocatability.Unavailable for no pose data. So the sentence "_Unless the "headModel" VRFrameOfReference is being used, this function is not guaranteed to return a value._ " might need to be changed.
It would be better to add code samples of tracking loss handling for different tracking experiences.
For example, in current API design, developer should know how to use a "headModel" VRFrameOfReference as fallback when "eyeLevel" or "stage" VRFrameOfReference fails to return pose data. e.g.
let pose = vrFrame.getDevicePose(eyeLevelFrameOfRef);
if (pose) {
// Head position and orientation available, render the seated-experience.
} else {
// Fail to get pose in eye level frame of reference, try head model frame of reference.
pose = vrFrame.getDevicePose(headModelFrameOfRef);
if (pose) {
// Fallback to render orientation-only experience.
} else {
// No tracking data is available
// Fade out content and render the head-locked warning message.
}
}
When tracking loss happening, it might be useful to expose the reason to app. So app is able to guide user to recover. For outside-in tracking, the reason might be "out-of-range". For inside-out tracking, the reason might be "vision-inhibited". Or "unavailable" for other hardware errors.
partial interface VRSession : EventTarget {
attribute EventHandler ontrackingloss;
};
enum VRTrackingLossReason {
"out-of-range",
"vision-inhibited",
"unavailable"
};
[Constructor(DOMString type, VRTrackingLossEventInit eventInitDict)]
interface VRTrackingLossEvent : Event {
readonly attribute VRTrackingLossReason reason;
};
dictionary VRTrackingLossEventInit : EventInit {
required VRTrackingLossReason reason;
};
Would love to hear community's thoughts on this. Thanks!
@toji , this is the issue we talked in today's implementers call.
@NellWaliczek , your summary in the call is accurate.
Please review and share your thoughts. Thanks!
No issues with points 1 and 2.
I'm not sure if your third point should be implemented the way that's described. On the call @NellWaliczek seemed to think that the events would be fired on individual frame of references rather than the session (which makes sense, I think). I also imagine we'd want the inverse (VRTrackingAcquiredEvent) if we're going to add a loss event.
I also wonder how necessary an event is in this case? Specifically, what does having an event to signify tracking loss/acquisition enable that's not as easily handled by an error state on getDevicePose? (Whether that's returning null or something else.) Specifically, most of the APIs that I've interacted with don't have equivalent events for tracking loss, but instead fail the pose query somehow, so a browser implementation would just be watching for a failed query and firing an event at that point. That's something that the web application can do easily, especially since we expect apps to be querying poses per-frame.
I'll put together a pull request for the first two items soon, and in the meantime could @huningxin or @NellWaliczek (or anyone else who would like to chime in) expand on the need for a tracking loss event?
No issues with points 1 and 2.
Are we able to implement points 1 and 2 for TAG review? Point 3 could be an enhancement.
For point 3:
Specifically, what does having an event to signify tracking loss/acquisition enable that's not as easily handled by an error state on getDevicePose?
An event might be useful to notify the low quality of tracking for app to prepare for a tracking loss besides the normal data channel.
(Whether that's returning null or something else.)
I agree that this can be done by returning some error states by getDevicePose. For example, for OpenVR, TrackingResult_Running_OutOfRange can be notified in pose.eTrackingResult while still return valid pose data. My initial purpose is to expose more tracking errors/warnings to enhance the app experience, surfacing through either an event or error status in VRDevicePose looks good to me.
As mentioned during the course of landing #409, we need to get a better understanding of the behavior when tracking is lost on the various supported tracking systems. Based on that, we can figure out if there is consistent behavior that WebXR can present to developers for each of the frame of reference types. This behavior, if it exists, will likely need to be communicated to developers when it is occurring. The answer may be an event, but we'd need to have a clear idea of what guidance we are giving developers about what they should or need to do in response. The same is true if we decide that simply failing a getDevicePose() call is sufficient.
This topic will be discussed at TPAC with the goal of getting closure on understanding the ecosystem. After TPAC, I'll write up the conclusions from the conversation in a PR for inclusion.
@thetuvix I think this is waiting on a writeup from you?
Indeed, apologies for the delay! In writing out all the details of our TPAC discussion, I uncovered some nuances that I've been sounding out with folks here now that they're back from the holidays. I'll post the full writeup by end of week.
_Update:_ Draft sent around internally for review. I'll post the final writeup here by EOD Monday.
At TPAC, I talked with @lincolnfrog about the tracking loss and tracking recovery behaviors that I've observed in use by VR and AR apps, engines and middleware. These behaviors have all been built (at varying developer effort) on top of the various native platform APIs that exist today.
The key balance to strike here in WebXR is enabling apps and engines to easily build out the tracking loss/recovery semantics their scenarios need, while minimizing the policy choices that need to be injected back down into the API. If WebXR consumers find it easy to build whatever behavior they need with a minimum of stateful "modes" in the API, that to me is the ideal.
For heads, controllers and anchors across the three top-level reference space types we've defined (stationary, bounded or unbounded), I've seen apps, engines and middleware implement one of three tracking loss behaviors, either to end users (for apps) or to their own app developers further up the stack (for engines/middleware):
emulatedPosition bool to allow apps to then optionally detect the tracking loss state and add any extra handling on top.emulatedPosition bool to tell apps when a controller is in this state.Given the above, here's my strawman for how we should spec WebXR implementations to behave for various tracked entities under different tracking states, enabling middleware to itself expose any of the models above without the need to choose a modal state:
| Entity being located /
tracking state | Is there a meaningful position?getPose(…) != null | Is the position emulated?pose.emulatedPosition |
| ------------- | ------------- | ------------- |
| 3DoF head
orientation tracked (neck model) | true | true |
| 6DoF head
never tracked yet (no aligned orientation) | false | |
| 6DoF head
tracking lost (neck model) | true | true |
| 6DoF head
nominal tracking | true | false |
| 3DoF controller
orientation tracked (shoot from the hip) | true | true |
| 6DoF controller
never tracked yet (no aligned orientation) | false | |
| 6DoF controller
tracking lost (shoot from the hip) | true | true |
| 6DoF controller
nominal tracking | true | false |
| Anchor
down dark hallway or far away | false | |
| Anchor
nominal tracking | true | false |
With this approach, apps that don't want the "freeze last head position" behavior can easily implement either alternate behavior without having to set a mode in the WebXR API:
emulatedPosition is true for this frame, and return null to the next layer up.emulatedPosition is true for this frame, and take whatever fallback action it wishes.While the choices above describe how components express the transition from tracking-nominal to tracking-lost, there is then a separate choice around how an app should adjust its world origin when head tracking is recovered again. While the obvious solution for most tracked objects is to just jump the object back to its "true" position relative to the current origin, for VR head tracking that's generally more jarring than just absorbing the drift, especially if there is an arbitrary teleportation offset being applied to the reference space anyway.
Here are the three world origin tracking recovery behaviors I've seen in apps/engines across stationary, bounded and unbounded spaces:
Given the above, here's my strawman for how WebXR reference spaces should handle their origin (and thus head poses) when head tracking is recovered:
| Reference space | WebXR reference space origin behavior upon head tracking recovery |
| ------------- | ------------- |
| stationary | Snap back into position for current origin (option 1) |
| bounded | Open design question: (perhaps an option when creating the bounded reference space?)
Snap back into position for current origin (option 1) or
Adjust origin to keep head in position (option 2)
For option 1, an app that wishes to absorb the drift into a teleportation offset instead (option 2) can observe the jump in head pose since the previous frame and adjust the bounded reference space's originOffset by that amount. Open design question: Would we then provide some viewerReset event to directly provide the viewer's pose discontinuity?
For option 2, a reset event would then fire for the bounded reference space, with transform representing the origin's discontinuity |
| unbounded
(recovery into same tracking fragment) | Adjust origin to keep head in position (option 2)
A reset event will then fire for the unbounded reference space, with transform representing the origin's discontinuity |
| unbounded
(recovery into different tracking fragment) | Adjust origin to keep head in position (option 2)
A reset event will then fire for the unbounded reference space, although we'd need to provide a null transform, as the UA does not know the relationship from the previous origin to the new origin |
The tracking loss and tracking recovery tables above represent my own strawman proposals for the following key questions:
There will certainly be much bikeshedding across each of the rows above :smile: However, what's more critical than any particular choice is that we do come to a consensus decision for each of these rows. For WebXR content to successfully span across the world's VR and AR devices, it's critical that we spec a common expectation for how all WebXR implementations "should" behave in each of these tracking loss and tracking recovery situations, across all XR platforms.
Let's drive this to consensus at the F2F!
Thanks @thetuvix for putting all this information together! What a great summary of a very complicated problem space.
One question for you.... Do you see this behavior as needing to be different for stationary and bounded reference spaces created on devices that have inside out tracking?
Do you see this behavior as needing to be different for stationary and bounded reference spaces created on devices that have inside out tracking?
The general design intention with our reference spaces is that the app selects its desired space and then that space behaves equivalently across devices. A key thing we should nail down is whether we see anything in the definition of our spaces that would lead to divergence across devices. If so, we should aim to tweak those definitions so that our UAs and devices can align.
@thetuvix Thanks for driving this at the Jan 19 F2F! Do you have an idea when you'll have the PR put together that covers what we all agreed on?
I’m aiming to write up a PR here this week.
Apologies for the delay! Initial tracking loss coverage for the spatial tracking explainer is in #559 - I'll be adding to this throughout the week.
Most helpful comment
At TPAC, I talked with @lincolnfrog about the tracking loss and tracking recovery behaviors that I've observed in use by VR and AR apps, engines and middleware. These behaviors have all been built (at varying developer effort) on top of the various native platform APIs that exist today.
The key balance to strike here in WebXR is enabling apps and engines to easily build out the tracking loss/recovery semantics their scenarios need, while minimizing the policy choices that need to be injected back down into the API. If WebXR consumers find it easy to build whatever behavior they need with a minimum of stateful "modes" in the API, that to me is the ideal.
Pose behavior during tracking loss
For heads, controllers and anchors across the three top-level reference space types we've defined (stationary, bounded or unbounded), I've seen apps, engines and middleware implement one of three tracking loss behaviors, either to end users (for apps) or to their own app developers further up the stack (for engines/middleware):
emulatedPositionbool to allow apps to then optionally detect the tracking loss state and add any extra handling on top.emulatedPositionbool to tell apps when a controller is in this state.Given the above, here's my strawman for how we should spec WebXR implementations to behave for various tracked entities under different tracking states, enabling middleware to itself expose any of the models above without the need to choose a modal state:
| Entity being located /
tracking state | Is there a meaningful position?
getPose(…) != null| Is the position emulated?pose.emulatedPosition|| ------------- | ------------- | ------------- |
| 3DoF head
orientation tracked (neck model) | true | true |
| 6DoF head
never tracked yet (no aligned orientation) | false | |
| 6DoF head
tracking lost (neck model) | true | true |
| 6DoF head
nominal tracking | true | false |
| 3DoF controller
orientation tracked (shoot from the hip) | true | true |
| 6DoF controller
never tracked yet (no aligned orientation) | false | |
| 6DoF controller
tracking lost (shoot from the hip) | true | true |
| 6DoF controller
nominal tracking | true | false |
| Anchor
down dark hallway or far away | false | |
| Anchor
nominal tracking | true | false |
With this approach, apps that don't want the "freeze last head position" behavior can easily implement either alternate behavior without having to set a mode in the WebXR API:
emulatedPositionis true for this frame, and return null to the next layer up.emulatedPositionis true for this frame, and take whatever fallback action it wishes.World origin behavior during head tracking recovery
While the choices above describe how components express the transition from tracking-nominal to tracking-lost, there is then a separate choice around how an app should adjust its world origin when head tracking is recovered again. While the obvious solution for most tracked objects is to just jump the object back to its "true" position relative to the current origin, for VR head tracking that's generally more jarring than just absorbing the drift, especially if there is an arbitrary teleportation offset being applied to the reference space anyway.
Here are the three world origin tracking recovery behaviors I've seen in apps/engines across stationary, bounded and unbounded spaces:
Given the above, here's my strawman for how WebXR reference spaces should handle their origin (and thus head poses) when head tracking is recovered:
| Reference space | WebXR reference space origin behavior upon head tracking recovery |
| ------------- | ------------- |
|
stationary| Snap back into position for current origin (option 1) ||
bounded| Open design question: (perhaps an option when creating the bounded reference space?)Snap back into position for current origin (option 1) or
Adjust origin to keep head in position (option 2)
For option 1, an app that wishes to absorb the drift into a teleportation offset instead (option 2) can observe the jump in head pose since the previous frame and adjust the bounded reference space's
originOffsetby that amount. Open design question: Would we then provide someviewerResetevent to directly provide the viewer's pose discontinuity?For option 2, a
resetevent would then fire for the bounded reference space, withtransformrepresenting the origin's discontinuity ||
unbounded(recovery into same tracking fragment) | Adjust origin to keep head in position (option 2)
A
resetevent will then fire for the unbounded reference space, withtransformrepresenting the origin's discontinuity ||
unbounded(recovery into different tracking fragment) | Adjust origin to keep head in position (option 2)
A
resetevent will then fire for the unbounded reference space, although we'd need to provide a nulltransform, as the UA does not know the relationship from the previous origin to the new origin |Next steps
The tracking loss and tracking recovery tables above represent my own strawman proposals for the following key questions:
There will certainly be much bikeshedding across each of the rows above :smile: However, what's more critical than any particular choice is that we do come to a consensus decision for each of these rows. For WebXR content to successfully span across the world's VR and AR devices, it's critical that we spec a common expectation for how all WebXR implementations "should" behave in each of these tracking loss and tracking recovery situations, across all XR platforms.
Let's drive this to consensus at the F2F!