了解以下基本知识,为各种沉浸式体验做好准备:虚拟现实、增强现实以及介于二者之间的所有内容。
Chrome 79 为网页带来了沉浸式体验。WebXR Device API 带来了虚拟现实,而 Chrome 81 则支持增强现实。而 GamePad API 的更新则将控件的高级用法扩展到了 VR 领域。其他浏览器很快也将支持这些规范,包括 Firefox Reality、Oculus Browser、Edge 和 Magic Leap 的 Helio 浏览器等。
本文是关于沉浸式 Web 的系列文章的第一篇。本分集中将介绍如何设置基本 WebXR 应用,以及如何进入和退出 XR 会话。后续文章将介绍帧循环(WebXR 体验的核心),增强现实的具体细节,以及 WebXR Hit Test API(用于检测 AR 会话中的 Surface)。除非另有说明,否则我在本篇和后续文章中介绍的所有内容都同样适用于 AR 和 VR。
什么是沉浸式 Web?
虽然我们使用两种术语来描述沉浸式体验(增强现实和虚拟现实),但许多人认为它们是从完全真实到完全虚拟的连续谱系,中间有不同的沉浸度。XR 中的“X”旨在反映这种思维,它是一种代数变量,代表沉浸式体验范围内的任何内容。
沉浸式体验的示例包括:
- 游戏
- 360° 全景视频
- 在沉浸式环境中呈现的传统 2D(或 3D)视频
- 买房
- 在购买前查看产品在家中的摆放效果
- 沉浸式艺术
- 别人还没有想到的酷炫内容
概念和用法
我将介绍使用 WebXR Device API 的一些基本知识。如果您需要比我提供的更深入的信息,请查看沉浸式 Web 工作组的 WebXR 示例或 MDN 不断扩充的参考资料。如果您熟悉早期版本的 WebXR Device API,则应浏览本文档中的所有内容。发生了变化。
本文中的代码基于沉浸式 Web 工作组的基本示例(演示版、源代码),但经过编辑以使其更加清晰简单。
在制定 WebXR 规范的过程中,我们一直在完善安全和隐私保护措施,以保护用户。因此,实现必须遵循特定要求。网页或应用必须处于活跃状态且处于聚焦状态,才能向观看者请求任何敏感信息。网页或应用必须通过 HTTPS 提供。该 API 本身旨在保护从传感器和摄像头获取的信息,这些信息是其正常运行所必需的。
请求会话
进入 XR 会话需要用户手势。为此,请使用特征检测来测试 XRSystem
(通过 navigator.xr
),并调用 XRSystem.isSessionSupported()
。请注意,在 Chrome 79 和 80 版本中,XRSystem
对象称为 XR
。
在以下示例中,我已指明希望使用 'immersive-vr'
会话类型的虚拟现实会话。其他会话类型包括 'immersive-ar'
和 'inline'
。内嵌会话用于在 HTML 中呈现内容,主要用于预告片内容。沉浸式 AR 会话示例演示了这一点。我会在后续文章中介绍这一点。
知道支持虚拟现实会话后,我会启用一个按钮,以便获取用户手势。
if (navigator.xr) {
const supported = await navigator.xr.isSessionSupported('immersive-vr');
if (supported) {
xrButton.addEventListener('click', onButtonClicked);
xrButton.textContent = 'Enter VR';
xrButton.enabled = supported; // supported is Boolean
}
}
启用该按钮后,我会等待点击事件,然后请求会话。
let xrSession = null;
function onButtonClicked() {
if (!xrSession) {
navigator.xr.requestSession('immersive-vr')
.then((session) => {
xrSession = session;
xrButton.textContent = 'Exit XR';
onSessionStarted(xrSession);
});
} else {
xrSession.end();
}
}
请注意此代码中的对象层次结构。它会从 navigator
移至 xr
,然后移至 XRSession
实例。在早期版本的 API 中,脚本必须先请求设备,然后才能请求会话。现在,系统会隐式获取设备。
进入会话
获取会话后,我需要启动会话并进入该会话。不过,我需要先设置一些内容。会话需要 onend
事件处理脚本,以便在用户退出时重置应用或网页。
我还需要一个 <canvas>
元素来绘制场景。它需要是与 XR 兼容的 WebGLRenderingContext 或 WebGL2RenderingContext。所有绘制操作都是使用它们或基于 WebGL 的框架(例如 Three.js)完成的。
现在,我已经有了画图的位置,接下来需要找到画图内容的来源。为此,我创建了一个 XRWebGLLayer
实例。我通过调用 XRSession.updateRenderState()
将其与画布相关联。
进入会话后,我需要一种方法来确定虚拟现实中各个内容的位置。我需要一个参考空间。'local-floor'
参照空间的原点位于观看者附近,y 轴在底板级别为 0,并且预计不会移动。还有其他类型的参考空间,但这个主题比较复杂,我无法在此详细介绍。我将引用空间保存到变量中,因为我需要在绘制到屏幕时使用它。
function onSessionStarted(xrSession) {
xrSession.addEventListener('end', onSessionEnded);
let canvas = document.createElement('canvas');
webGLRenContext = canvas.getContext('webgl', { xrCompatible: true });
xrSession.updateRenderState({
baseLayer: new XRWebGLLayer(xrSession, webGLRenContext)
});
xrSession.requestReferenceSpace('local-floor')
.then((refSpace) => {
xrRefSpace = refSpace;
xrSession.requestAnimationFrame(onXRFrame);
});
}
获取引用空间后,我调用 XRSession.requestAnimationFrame()
。这是开始呈现虚拟内容,此操作在帧循环中完成。
运行帧循环
帧循环是由用户代理控制的无限循环,其中会将内容重复绘制到屏幕上。内容以称为帧的离散块绘制。连续的帧会产生动画的错觉。对于 VR 应用,每秒帧数可以介于 60 到 144 之间。Android 版 AR 的运行速度为每秒 30 帧。您的代码不应假定任何特定帧速率。
帧循环的基本流程如下:
- 调用
XRSession.requestAnimationFrame()
。作为响应,用户代理会调用由您定义的XRFrameRequestCallback
。 - 在回调函数内:
- 再次调用
XRSession.requestAnimationFrame()
。 - 获取观看者的姿势。
- 将
WebGLFramebuffer
从XRWebGLLayer
传递(“绑定”)给WebGLRenderingContext
。 - 迭代每个
XRView
对象,从XRWebGLLayer
检索其XRViewport
并将其传递给WebGLRenderingContext
。 - 向帧缓冲区绘制内容。
- 再次调用
本文的其余部分介绍了第 1 步和第 2 步的一部分,即设置和调用 XRFrameRequestCallback
。第 2 步的其余内容将在第 2 部分中介绍。
XRFrameRequestCallback
XRFrameRequestCallback
由您定义。它接受两个参数:DOMHighResTimeStamp
和 XRFrame
实例。XRFrame
对象提供向显示屏渲染单个帧所需的信息。DOMHighResTimeStamp
参数供日后使用。
在执行任何其他操作之前,我将请求下一个动画帧。如前所述,帧的计时由用户代理根据底层硬件确定。先请求下一帧可确保在回调期间发生错误时帧循环会继续。
function onXRFrame(hrTime, xrFrame) {
let xrSession = xrFrame.session;
xrSession.requestAnimationFrame(onXRFrame);
// Render a frame.
}
现在,是时候为观看者画点什么了。我们将在第 2 部分进行讨论。在开始之前,我先向您展示一下如何结束会话。
结束会话
沉浸式会话可能会因多种原因而结束,包括通过调用 XRSession.end()
由您自己的代码结束。其他原因包括耳机未连接或其他应用控制了耳机。因此,行为良好的应用应监控 end
事件。发生这种情况时,请舍弃会话及其相关的渲染对象。已结束的沉浸式会话无法恢复。如需重新进入沉浸式体验,我的应用需要启动新会话。
回想一下进入会话部分,我曾在设置过程中添加了 onend
事件处理程序。
function onSessionStarted(xrSession) {
xrSession.addEventListener('end', onSessionEnded);
// More setup…
}
在事件处理脚本中,恢复用户进入会话之前应用的状态。
function onSessionEnded(event) {
xrSession = null;
xrButton.textContent = 'Enter VR';
}
总结
我并未介绍编写 Web XR 或 AR 应用所需的所有知识。 希望我提供的信息足以让您开始自行理解代码,并开始进行实验。在下一篇文章中,我将介绍帧循环,这是将内容绘制到屏幕上的过程。
照片由 JESHOOTS.COM 在 Unsplash 上发布