安全上下文
此特征只可用于
安全上下文
(HTTPS),在某些或所有
支持浏览器
.
Assuming you're already familiar with 3D graphics in general and WebGL in particular, taking that next bold step into mixed reality—the idea of presenting artificial scenery or objects in addition to or in place of the real world—is not overly complicated. Before you can begin to render your augmented or virtual reality scenario, you need to create and set up the WebXR session, and you should know how to shut it down properly as well. You will learn how to do these things in this article.
Your app's access to the WebXR API begins with the
XRSystem
object. This object represents the overall WebXR device suite available to you through the hardware and drivers available on the user's equipment. There is a global
XRSystem
object available for use by your document through the the
Navigator
property
xr
,其返回
XRSystem
object if suitable XR hardware is available for your use given the hardware available and your document's environment.
Thus the simplest code that fetches the
XRSystem
object is:
const xr = navigator.xr;
值
xr
将是
null
or
undefined
if WebXR isn't available.
As a new and still in development API, WebXR support is limited to specific devices and browsers; and even on those, it may not be enabled by default. There may be options available to allow you to experiment with WebXR even if you don't have a compatible system, however.
The team designing the WebXR specification has published a WebXR polyfill which you can use to simulate WebXR on browsers which don't have support for the WebXR APIs. If the browser supports the older WebVR API , that is used. Otherwise, the polyfill falls back to an implementation which uses Google's Cardboard VR API.
The polyfill is maintained alongside the specification, and is kept up to date with the specification. Additionally, it is updated to maintain compatibility with browsers as their support for WebXR and other technologies related to it and to the implementation of the polyfill change over time.
Be sure to read the readme carefully; the polyfill comes in several versions depending on what degree of compatibility with newer JavaScript features your target browsers include.
Mozilla WebXR team has created a WebXR API Emulator browser extension, compatible with both Firefox and Chrome, which emulates the WebXR API, simulating a variety of compatible devices such as the HTC Vive, the Oculus Go and Oculus Quest, Samsung Gear, and Google Cardboard. With the extension in place, you can open up a developer tools panel that lets you control the position and orientation of the headset and any hand controllers, as well as button presses on the controllers.
While somewhat awkward compared to using an actual headset, this makes it possible to experiment with and developer WebXR code on a desktop computer, where WebXR isn't normally available. It also lets you perform some basic testing before taking your code to a real device. Be aware, however, that the emulator does not yet completely emulate all of the WebXR API, so you may run into problems you're not expecting. Again, carefully read the readme file and make sure you're aware of the limitations before you begin.
重要: You should always test your code on actual AR and/or VR hardware before releasing or shipping a product! Emulated, simulated, or polyfilled environments are not an adequate substitute for actual testing on physical devices.
Download the WebXR API Emulator for your supported browser below:
source code for the extension is also available on GitHub.
While this isn't the place for a full article about the extension, there are some specific things worth mentioning.
Version 0.4.0 of the extension was announced on March 26, 2020. It introduced support for augmented reality (AR) through the WebXR AR Module , which has is approaching a stable state. Documentation for AR is forthcoming shortly here on MDN.
Other improvements include updating the emulator to rename the
XR
interface to
XRSystem
, introduce support for squeeze (grip) input sources, and add support for the
XRInputSource
property
profiles
.
A WebXR compatible environment starts with a securely-loaded document. Your document needs to either have been loaded from the local drive (such as by using an URL such as
http://localhost/...
), or using
HTTPS
when loading the page. The JavaScript code must, likewise, have been loaded securely.
If the document wasn't loaded securely, you won't get very far. The
navigator.xr
property doesn't even exist if the document wasn't loaded securely. This may also be the case if there simply is no compatible XR hardware available. Either way, you need to be prepared for the lack of an
xr
property and either gracefully handle the error or provide some form of fallback.
One fallback option is the WebXR polyfill , provided by the Immersive Web Working Group that's in charge of the WebXR standardization process. The polyfill brings support for WebXR to browsers that don't have native WebXR support, and smooths out the inconsistencies among implementations in the browsers that do have it, so it can sometimes also be useful even if WebXR is natively available.
Here we define a
getXR()
function, which returns the
XRSystem
object after optionally installing the polyfill, assuming that the polyfill has been included or loaded using a prior
<script>
标签。
let webxrPolyfill = null;
function getXR(usePolyfill) {
let tempXR;
let tempPoly;
switch(usePolyfill) {
case "if-needed":
if (!navigator.xr) {
webxrPolyfill = new WebXRPolyfill();
}
tempXR = navigator.xr;
break;
case "yes":
webxrPolyfill = new WebXRPolyfill();
tempXR = navigator.xr;
break;
case "no":
默认:
tempXR = navigator.xr;
break;
}
return tempXR;
}
const xr = getXR("no"); // Get the native XRSystem object
const xr = getXR("yes"); // Always returns an XRSystem from the polyfill
const xr = getXR("if-needed"); // Use the polyfill only if navigator.xr missing
返回的
XRSystem
object can then be used according to the documentation provided here on MDN. The global variable
webxrPolyfill
is used only to retain a reference to the polyfill in order to ensure that it remains available until you no longer need it. Setting it to
null
signals that the polyfill can be garbage collected when no objects depending on it are using it anymore.
Of course, you can simplify this depending on your needs; since your app is probably not going to go back and forth much on whether or not to use the polyfill, you can simplify this to just the specific case you need.
There are a number of security measures in place revolving around WebXR. First among these is that use of
immersive-vr
mode—which entirely replaces the user's view of the world—requires that the
xr-spatial-tracking
feature policy
be in place. On top of that, the document needs to be secure and currently focused. Finally, you must call
requestSession()
from a user event handler, such as the handler for the
click
事件。
For more specifics about securing WebXR actitvities and usage, see the article Permissions and security for WebXR .
Before trying to create a new WebXR session, it's often wise to first check to see if the user's hardware and software support the presentation mode you wish to use. This can also be used to determine whether to use an immersive or an inline presentation, for example.
To find out if a given mode is supported, simply call the
XRSystem
方法
isSessionSupported()
. This returns a promise which resolves to
true
if the given type of session is available for use or
false
否则。
const immersiveOK = await navigator.xr.isSessionSupported("immersive-vr");
if (immersiveOK) {
// Create and use an immersive VR session
} else {
// Create an inline session instead, or tell the user about the
// incompatibility if inline is required
}
A WebXR session is represented by an
XRSession
object. To obtain an
XRSession
, you call your
XRSystem
's
requestSession()
method, which returns a promise that resolves with an
XRSession
if it's able to establish one successfully. Fundamentally, that looks like this:
xr.requestSession("immersive-vr").then((session) => {
xrSession = session;
/* continue to set up the session */
});
Note the parameter passed into
requestSession()
in this code snippet:
immersive-vr
. This string specifies the type of WebXR session you want to establish—in this case, a fully-immersive virtual reality experience. There are three options:
immersive-vr
A fully-immersive virtual reality session using a headset or similar device that fully replaces the world around the user with the images you present.
immersive-ar
inline
An on-screen presentation of the XR imagery within the context of the document window.
If the session couldn't be created for some reason—such as feature policy disallowing its use or the user declining to grant permission to use the headset—the promise gets rejected. So a more complete function that starts up and returns a WebXR session could look like this:
async function createImmersiveSession(xr) {
try {
session = await xr.requestSession("immersive-vr");
return session;
} catch(error) {
throw error;
}
}
This function returns the new
XRSession
or throws an exception if an error occurs while creating the session.
In addition to the display mode, the
requestSession()
method can take an optional object with initialization parameters to customize the session. Currently, the only configurable aspect of the session is which of the reference spaces should be used to represent the world's coordinate system. You can specify either required or optional reference spaces in order to obtain a session compatible with the reference spaces you need or prefer to use.
For example, if you need an
unbounded
reference space, you can specify that as a required feature in order to ensure that the session you get can use unbounded spaces:
async function createImmersiveSession(xr) {
try {
session = await xr.requestSession("immersive-vr", {
requiredFeatures: [ "unbounded" ]
});
return session;
} catch(error) {
throw error;
}
}
On the other hand, if you need an
inline
session and would prefer a
local
reference space, you can do this:
async function createInlineSession(xr) {
try {
session = await xr.requestSession("inline", {
optionalFeatures: [ "local" ]
});
return session;
} catch(error) {
throw error;
}
}
This
createInlineSession()
function will try to create an inline session that's compatible with the
local
reference space. When you're ready to create your reference space, you can try for a local space, and if that fails, fall back to a
viewer
reference space, which all devices are required to support.
Once the
requestSession()
method's returned promise successfully resolves, you know you have a usable WebXR session in hand. You can then proceed to prepare the session for use and begin your animations.
The key things you need (or may need) to do in order to finish the configuration of your session include:
end
at a minimum, so you can detect when the session is over.
inputsourceschange
event to detect the addition or removal of XR input controllers, and the various
select and squeeze action events
.
XRSystem
event
devicechange
so you can be advised when the set of available immersive devices changes.
HTMLCanvasElement
方法
getContext()
on the target context.
XRWebGLLayer
and passing set the value of the session's
renderState
property
baseLayer
.
In basic form, code to do this final setup might look something like this:
async function runSession(session) {
let worldData;
session.addEventListener("end", onSessionEnd);
let canvas = document.querySelector("canvas");
gl = canvas.getContext("webgl", { xrCompatible: true });
// Set up WebGL data and such
worldData = loadGLPrograms(session, "worlddata.xml");
if (!worldData) {
return NULL;
}
// Finish configuring WebGL
worldData.session.updateRenderState({
baseLayer: new XRWebGLLayer(worldData.session, gl)
});
// Start rendering the scene
referenceSpace = await worldData.session.requestReferenceSpace("unbounded");
worldData.referenceSpace = referenceSpace.getOffsetReferenceSpace(
new XRRigidTransform(worldData.playerSpawnPosition, worldData.playerSpawnOrientation));
worldData.animationFrameRequestID = worldData.session.requestAnimationFrame(onDrawFrame);
return worldData;
}
For the purposes of this example, an object named
worldData
gets created to encapsulate data about the world and rendering environment. This includes the
XRSession
itself, all of the data used to render the scene in WebGL, the world reference space, and the ID returned by
requestAnimationFrame()
.
First, a handler for the
end
event is set up. Then the rendering canvas is obtained and a reference to its WebGL context is retrieved, specifying the
xrCompatible
option when calling
getContext()
.
Next, any data and setup needed for the WebGL renderer is performed before then configuring WebGL to use the framebuffer of the WebGL context as its own framebuffer. This is done using the
XRSession
方法
updateRenderState()
to set the render state's
baseLayer
to a newly-created
XRWebGLLayer
encapsulating the WebGL context.
At this point, the
XRSession
itself has been fully configured, so we can begin rendering. First, we need a reference space within which coordinates for the world will be stated. We can get the initial reference space for the session by calling the
XRSession
's
requestReferenceSpace()
method. We specify when calling
requestReferenceSpace()
the name of the type of reference space we want; in this case,
unbounded
. You might just as easily specify
local
or
viewer
, depending on your needs.
To understand how to select the right reference space for your needs, see Selecting the reference space type in Geometry and reference spaces in WebXR .
The reference space returned by
requestReferenceSpace()
places the origin (0, 0, 0) in the center of the space. This is great—if your player's viewpoint starts in the exact center of the world. But most likely, that's not the case at all. If that's so, you call
getOffsetReferenceSpace()
on the initial reference space to create a
new
reference space
which offsets the coordinate system
so that (0, 0, 0) is located at the position of the viewer, with the orientation likewise shifted to face in the desired direction. The input value into
getOffsetReferenceSpace()
是
XRRigidTransform
encapsulating the player's position and orientation as specified in the default world coordinates.
With the new reference space in hand and stored into the
worldData
object for safe-keeping, we call the session's
requestAnimationFrame()
method to schedule a callback to be executed when it's time to render the next frame of animation for the WebXR session. The returned value is an ID we can use later to cancel the request if need be, so we save that into
worldData
还。
In the end, the
worldData
object is returned to the caller to allow the main code to reference the data it needs later. At this point, the setup process is complete and we've entered the rendering stage of our application. To learn more about rendering, see the article
Rendering and the WebXR frame animation callback
.
Obviously, this was a just an example. You don't need a
worldData
object to store everything; you can store the information you need to maintain any way you want to. You may need different information or have different specific requirements that cause you to do things differently, or in a different order.
Similarly, the specific methodology you use for loading models and other information and setting up your WebGL data—textures, vertex buffers, shaders, and so on—will vary a great deal depending on your needs, what if any frameworks you're using, and the like.
Over the course of your WebXR session, you may receive any of a number of events which indicate changes to the state of the session, or which let you know about things you need to do to keep the session operating properly.
When the state of the
XRSession
's visibility changes—such as when the session is hidden or displayed, or when the user has focused another context—the session receives an event
visibilitychange
事件。
session.onvisibilitychange = (event) => {
switch(event.session.visibilityState) {
case "hidden":
myFrameRate = 10;
break;
case "blurred-visible":
myFrameRate = 30;
break;
case "visible":
默认:
myFrameRate = 60;
break;
}
};
This example changes a variable
myFrameRate
depending on the visibility state as it changes. Presumably the renderer uses this value to compute how often to render new frames as the animation loop progresses, thus rendering less frequently the more "blurred" the scene becomes.
Occasionally, discontinuities or jumps in the native origin may occur while tracking the user's position in the world. The most common scenarios in which this happens are when the user requests a recalibration of their XR device or when a hiccup or glitch occurs in the flow of tracking data received from the XR hardware. These situations cause the native origin to jump abruptly by the distance and directional angle necessary to bring the native origin back into alignment with the user's position and facing direction.
When this happens, a
reset
event is sent to the session's
XRReferenceSpace
. The event's
transform
property is an
XRRigidTransform
detailing the transform needed to realign the native origin.
注意,
reset
event is fired at the
XRReferenceSpace
, not the
XRSession
!
Another common cause for
reset
events is when a bounded reference space (a reference space whose
XRReferenceSpaceType
is
bounded-floor
) has its geometry as specified by the
XRBoundedReferenceSpace
's property
boundsGeometry
change.
For more common causes of reference space resets and more details and sample code, see the documentation for the
reset
事件。
WebXR maintains a list of input controls which is specific to the WebXR system. These devices include things such as the handheld controllers, motion-sensing cameras, motion-sensitive gloves and other feedback devices. When the user connects or disconnects a WebXR controller device, the
inputsourceschange
event is dispatched to the
XRSession
. This is an opportunity to notify the user of the device's availability, begin to monitor it for inputs, offer configuration options, or whatever you need to do with it.
When the user's VR or AR session draws to a close, the session ends. The shutdown of an
XRSession
can happen either due to the session itself deciding it's time to shut down (such as if the user turns off their XR device), because the user has clicked a button to end the session, or some other situation as appropriate for your application.
Here we discuss both how to request a shutdown of the WebXR session and how to detect when the session has ended, whether by your request or otherwise.
To cleanly shut down the WebXR session when you're done with it, you should call the session's
end()
method. This returns a
promise
you can use to know when the shutdown is complete.
async function shutdownXR(session) {
if (session) {
await session.end();
/* At this point, WebXR is fully shut down */
}
}
当
shutdownXR()
returns to its caller, the WebXR session is fully and safely shut down.
If you have work that must be done when the session ends, such as releasing resources and the like, you should do that work in your
end
event handler rather than in your main code body. That way, you handle the cleanup regardless of whether the shutdown was automatically or manually triggered.
As previously established, you can detect when the WebXR session has ended—whether because you've called its
end()
method, the user turned off their headset, or some sort of irresolvable error occurred in the XR system—by watching for the
end
event to be sent to the
XRSession
.
session.onend = (event) => {
/* the session has shut down */
freeResources();
};
Here, when the session has ended and the
end
event is received, a
freeResources()
function is called to release the resources previously allocated and/or loaded to handle the XR presentation. By calling
freeResources()
在
end
event handler, we call it both when the user clicks a button that triggers a shutdown such as by calling the
shutdownXR()
function shown above
and
when the session ends automatically, whether due to an error or some other reason.