欢迎使用沉浸式 Web 应用

沉浸式 Web 是指通过浏览器托管的虚拟世界体验。整个虚拟现实体验会显示在浏览器或支持 VR 的头戴设备中。

Joe Medley
Joe Medley

沉浸式 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。

启动和运行应用

基本流程如下:

  1. 申请 XR 设备。
  2. 请求 XR 会话(如果可用)。如果您希望用户将手机放入头戴式设备,则称为沉浸式会话,需要用户执行手势才能进入。
  3. 使用会话运行渲染循环,以每秒 60 帧的速率提供图片帧。在每个帧中向屏幕绘制适当的内容。
  4. 运行渲染循环,直到用户决定退出。
  5. 结束 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
})

运行渲染循环

此步骤的代码需要进行一些整理。为了解决这个问题,我要向您抛出一些字词。如果您想先睹最终代码,请跳转前面快速浏览,然后再返回查看完整说明。您可能无法推断出很多信息。

渲染循环的基本流程如下:

  1. 请求动画帧。
  2. 查询设备的位置。
  3. 根据设备的位置,将内容绘制到设备的位置。
  4. 执行输入设备所需的工作。
  5. 每秒重复 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 采用“指向和点击”方法来处理用户输入。采用这种方法时,每个输入源都有一个已定义的指针光线,用于指示输入设备指向的位置,以及用于指示何时选择了某个内容的事件。您的应用会绘制指针光线并显示指针所指的位置。当用户点击输入设备时,系统会触发事件,具体而言是 selectselectStartselectEnd。您的应用会确定用户点击了什么,并做出适当的响应。

输入设备和指针光线

对于用户而言,指针光线只是控制器和用户所指向的对象之间的一条细线。但您的应用必须绘制它。这意味着,获取输入设备的姿势,并从其位置到 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 提供了三个事件来响应用户互动:selectselectStartselectEnd。它们有一个我意料之外的怪癖:它们只会告诉您用户点击了输入设备。它们不会告诉您环境中被点击的项目。事件处理脚本会添加到 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 锚点,以改进姿态跟踪。