沉浸式 Web 是指通过浏览器托管的虚拟世界体验。整个虚拟现实体验都会显示在浏览器或支持 VR 的头戴设备中。
沉浸式 Web 是指通过浏览器托管的虚拟世界体验。这涵盖在浏览器中或支持 VR 的头戴设备(例如 Google Daydream、Oculus Rift、三星 Gear VR、HTC Vive 和 Windows 混合现实头戴设备)中呈现的整个虚拟现实 (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 Anchors,这会改善姿势跟踪。