沉浸式 Web 是指通过浏览器托管的虚拟世界体验。整个虚拟现实体验会显示在浏览器或支持 VR 的头戴设备中。
沉浸式 Web 是指通过浏览器托管的虚拟世界体验。这涵盖在浏览器中或支持 VR 的头戴设备(例如 Google Daydream、Oculus Rift、三星 Gear VR、HTC Vive 和 Windows Mixed Reality 头戴设备)中呈现的整个虚拟现实 (VR) 体验,以及为支持 AR 的移动设备开发的增强现实体验。
虽然我们使用这两个术语来描述沉浸式体验,但它们应该被视为一个从完全现实到完全沉浸式 VR 环境的光谱,中间有各种级别的 AR。
沉浸式体验的示例包括:
- 沉浸式 360 度视频
- 在沉浸式环境中呈现的传统 2D(或 3D)视频
- 数据可视化图表
- 在家购物
- 艺术
- 别人还没有想到的酷炫内容
去那里该怎么走?
沉浸式 Web 已经推出将近一年,但仍处于起步阶段。 这项功能是通过 WebVR 1.1 API 实现的,该 API 自 Chrome 62 起就已在源试用中提供。Firefox 和 Edge 也支持该 API,Safari 则支持该 API 的 polyfill。
但现在是时候继续前进了。
源试用已于 2018 年 7 月 24 日结束,该规范已被 WebXR Device API 和新的源试用取代。
WebVR 1.1 怎么了?
我们从 WebVR 1.1 中学到了很多,但随着时间的推移,我们发现需要进行一些重大变更,才能支持开发者想要构建的应用类型。我们从中总结出的经验教训太多,无法在此一一列出,但其中包括 API 明确绑定到主 JavaScript 线程、开发者有太多机会设置明显错误的配置,以及“魔术窗口”等常见用法是副作用而非有意设计的功能等问题。(魔术窗口是一种无需头戴设备即可观看沉浸式内容的技术,其中应用会根据设备的方向传感器渲染单个视图。)
新设计有助于简化实现流程并大幅提升性能。与此同时,AR 和其他用例也开始出现,因此,API 必须具有可扩展性,以便在未来支持这些用例。
WebXR Device API 的设计和命名是考虑到这些扩展的用例,并提供了更好的未来发展方向。WebVR 的实现者已承诺迁移到 WebXR Device API。
什么是 WebXR Device API?
与之前的 WebVR 规范一样,WebXR Device API 也是 Immersive Web Community Group 的产品,该组织的贡献者来自 Google、Microsoft、Mozilla 等公司。“XR 中的 X”旨在作为一种代数变量,代表沉浸式体验范围内的任何内容。您可以在前面提到的源试用中使用它,也可以通过polyfill 使用它。
本文最初发布于 Chrome 67 Beta 版期间,当时仅启用了 VR 功能。Chrome 69 引入了增强现实功能。如需了解详情,请参阅适用于 Web 的增强现实。
这篇文章无法详尽介绍这个新 API 的所有功能。我希望向您提供足够的信息,让您能够开始了解 WebXR 示例。您可以参阅原始说明文档和沉浸式 Web 抢先体验者指南,了解更多信息。随着源代码试用期的推进,我会逐步扩展后者。欢迎随时打开问题或提交拉取请求。
在本文中,我将介绍如何启动、停止和运行 XR 会话,以及一些有关处理输入的基本知识。
我不会介绍如何将 AR/VR 内容绘制到屏幕上。WebXR Device API 不提供图片渲染功能。决定权在您手上。绘制是使用 WebGL API 完成的。如果您有雄心壮志,可以这样做。不过,我们建议您使用框架。沉浸式 Web 示例使用专为演示而创建的 Cottontail。Three.js 自 5 月起就支持 WebXR。我还没有听说过 A-Frame。
启动和运行应用
基本流程如下:
- 申请 XR 设备。
- 请求 XR 会话(如果可用)。如果您希望用户将手机放入头戴式设备,则称为沉浸式会话,需要用户执行手势才能进入。
- 使用会话运行渲染循环,以每秒 60 帧的速率提供图片帧。在每个帧中向屏幕绘制适当的内容。
- 运行渲染循环,直到用户决定退出。
- 结束 XR 会话。
我们来详细了解一下,并添加一些代码。您将无法通过我即将向您展示的内容运行应用。但再次强调,这只是为了让您大致了解一下。
申请 XR 设备
您会在此处看到标准的特征检测代码。您可以将其封装在一个名为 checkForXR()
的函数中。
如果您不使用沉浸式会话,则可以跳过宣传该功能和获取用户手势的步骤,直接请求会话。沉浸式会话是指需要使用头盔的会话。非沉浸式会话只是在设备屏幕上显示内容。当您提到虚拟现实或增强现实时,大多数人想到的都是前者。后者有时称为“魔法窗口”。
if (navigator.xr) {
navigator.xr.requestDevice()
.then(xrDevice => {
// Advertise the AR/VR functionality to get a user gesture.
})
.catch(err => {
if (err.name === 'NotFoundError') {
// No XRDevices available.
console.error('No XR devices available:', err);
} else {
// An error occurred while requesting an XRDevice.
console.error('Requesting XR device failed:', err);
}
})
} else{
console.log("This browser does not support the WebXR API.");
}
请求 XR 会话
现在,我们已经有了设备和用户手势,接下来可以获取会话了。 如需创建会话,浏览器需要一个用来绘图的画布。
xrPresentationContext = htmlCanvasElement.getContext('xrpresent');
let sessionOptions = {
// The immersive option is optional for non-immersive sessions; the value
// defaults to false.
immersive: false,
outputContext: xrPresentationContext
}
xrDevice.requestSession(sessionOptions)
.then(xrSession => {
// Use a WebGL context as a base layer.
xrSession.baseLayer = new XRWebGLLayer(session, gl);
// Start the render loop
})
运行渲染循环
此步骤的代码需要进行一些整理。为了解决这个问题,我要向您抛出一些字词。如果您想先睹最终代码,请跳转前面快速浏览,然后再返回查看完整说明。您可能无法推断出很多信息。
渲染循环的基本流程如下:
- 请求动画帧。
- 查询设备的位置。
- 根据设备的位置,将内容绘制到设备的位置。
- 执行输入设备所需的工作。
- 每秒重复 60 次,直到用户决定退出。
请求呈现帧
“帧”一词在 Web XR 上下文中有多个含义。第一个是参考框架,用于定义坐标系的原点是从何处计算的,以及当设备移动时该原点会发生什么变化。(当用户移动时,视图是否保持不变,还是会像现实生活中一样发生偏移?)
第二种类型的帧是呈现帧,由 XRFrame
对象表示。此对象包含向设备渲染 AR/VR 场景单个帧所需的信息。这有点令人困惑,因为呈现帧是通过调用 requestAnimationFrame()
检索的。这样一来,它就与 window.requestAnimationFrame()
兼容了。
在继续介绍其他内容之前,我先提供一些代码。以下示例展示了如何启动和维护渲染循环。请注意“框架”一词的双重用法。请注意对 requestAnimationFrame()
的递归调用。系统会每秒调用此函数 60 次。
xrSession.requestFrameOfReference('eye-level')
.then(xrFrameOfRef => {
xrSession.requestAnimationFrame(onFrame(time, xrFrame) {
// The time argument is for future use and not implemented at this time.
// Process the frame.
xrFrame.session.requestAnimationFrame(onFrame);
}
});
姿势
在向屏幕绘制任何内容之前,您需要知道显示屏设备指向的位置,并且需要访问屏幕。一般来说,AR/VR 中物体的方位和方向称为姿势。观看者和输入设备都有姿势。(我稍后会介绍输入设备。)观看器和输入设备姿势均定义为 4 x 4 矩阵,以列主序存储在 Float32Array
中。您可以通过对当前动画帧对象调用 XRFrame.getDevicePose()
来获取观看者的姿势。请务必进行测试,看看是否收到了相应姿势。如果出现问题,您不希望绘制到屏幕上。
let pose = xrFrame.getDevicePose(xrFrameOfRef);
if (pose) {
// Draw something to the screen.
}
观看次数
检查好姿势后,接下来就该画点什么了。您绘制到的对象称为视图 (XRView
)。这时,会话类型就变得非常重要。视图会以数组的形式从 XRFrame
对象检索出来。如果您处于非沉浸式会话中,该数组将包含一个视图。如果您处于沉浸式会话中,则该数组包含两个,每个眼睛各对应一个。
for (let view of xrFrame.views) {
// Draw something to the screen.
}
这是 WebXR 与其他沉浸式系统之间的一个重要区别。虽然迭代一个视图似乎毫无意义,但这样做可以让您为各种设备使用单个渲染路径。
整个渲染循环
如果将所有这些代码组合在一起,我会得到以下代码。我为输入设备保留了一个占位符,稍后会对其进行介绍。
xrSession.requestFrameOfReference('eye-level')
.then(xrFrameOfRef => {
xrSession.requestAnimationFrame(onFrame(time, xrFrame) {
// The time argument is for future use and not implemented at this time.
let pose = xrFrame.getDevicePose(xrFrameOfRef);
if (pose) {
for (let view of xrFrame.views) {
// Draw something to the screen.
}
}
// Input device code will go here.
frame.session.requestAnimationFrame(onFrame);
}
}
结束 XR 会话
XR 会话可能会因多种原因而结束,包括通过调用 XRSession.end()
由您自己的代码结束。其他原因包括耳机未连接或其他应用控制了耳机。因此,行为良好的应用应监控结束事件,并在该事件发生时丢弃会话和渲染程序对象。XR 会话一旦结束,便无法恢复。
xrDevice.requestSession(sessionOptions)
.then(xrSession => {
// Create a WebGL layer and initialize the render loop.
xrSession.addEventListener('end', onSessionEnd);
});
// Restore the page to normal after immersive access has been released.
function onSessionEnd() {
xrSession = null;
// Ending the session stops executing callbacks passed to the XRSession's
// requestAnimationFrame(). To continue rendering, use the window's
// requestAnimationFrame() function.
window.requestAnimationFrame(onDrawFrame);
}
互动功能的运作方式
与应用生命周期一样,我只会向您介绍如何在 AR 或 VR 中与对象互动。
WebXR Device API 采用“指向和点击”方法来处理用户输入。采用这种方法时,每个输入源都有一个已定义的指针光线,用于指示输入设备指向的位置,以及用于指示何时选择了某个内容的事件。您的应用会绘制指针光线并显示指针所指的位置。当用户点击输入设备时,系统会触发事件,具体而言是 select
、selectStart
和 selectEnd
。您的应用会确定用户点击了什么,并做出适当的响应。
输入设备和指针光线
对于用户而言,指针光线只是控制器和用户所指向的对象之间的一条细线。但您的应用必须绘制它。这意味着,获取输入设备的姿势,并从其位置到 AR/VR 空间中的对象绘制一条线。该过程大致如下所示:
let inputSources = xrSession.getInputSources();
for (let xrInputSource of inputSources) {
let inputPose = frame.getInputPose(inputSource, xrFrameOfRef);
if (!inputPose) {
continue;
}
if (inputPose.gripMatrix) {
// Render a virtual version of the input device
// at the correct position and orientation.
}
if (inputPose.pointerMatrix) {
// Draw a ray from the gripMatrix to the pointerMatrix.
}
}
这是沉浸式 Web 社区群组的输入跟踪示例的简化版。与帧渲染一样,绘制指针光线和设备由您自行决定。如前所述,此代码必须作为渲染循环的一部分运行。
在虚拟空间中选择内容
仅仅在 AR/VR 中指向物品是没有用的。用户需要能够选择内容,才能执行任何有用操作。WebXR Device API 提供了三个事件来响应用户互动:select
、selectStart
和 selectEnd
。它们有一个我意料之外的怪癖:它们只会告诉您用户点击了输入设备。它们不会告诉您环境中被点击的项目。事件处理脚本会添加到 XRSession
对象中,并且应在可用时立即添加。
xrDevice.requestSession(sessionOptions)
.then(xrSession => {
// Create a WebGL layer and initialize the render loop.
xrSession.addEventListener('selectstart', onSelectStart);
xrSession.addEventListener('selectend', onSelectEnd);
xrSession.addEventListener('select', onSelect);
});
此代码基于输入选择示例,以便您了解更多背景信息。
如需了解用户点击了什么,您可以使用姿势。(您会感到惊讶吗?我不这么认为。) 具体详情因您的应用或所使用的框架而异,因此超出了本文的讨论范围。Cottontail 的方法在“输入选择”示例中。
function onSelect(ev) {
let inputPose = ev.frame.getInputPose(ev.inputSource, xrFrameOfRef);
if (!inputPose) {
return;
}
if (inputPose.pointerMatrix) {
// Figure out what was clicked and respond.
}
}
总结:展望未来
正如我之前所说,增强现实功能预计将在 Chrome 69(2018 年 6 月某个时间的 Canary 版)中推出。不过,我们还是建议您试用我们目前提供的功能。我们需要您的反馈来改进此功能。如需了解进展,请访问 ChromeStatus.com 查看 WebXR 点击测试。您还可以遵循 WebXR 锚点,以改进姿态跟踪。