欢迎使用沉浸式 Web 应用

沉浸式网络意味着通过浏览器托管的虚拟世界体验。这种完整的虚拟现实体验都可以在浏览器或支持 VR 的头戴式设备上获得。

Joe Medley
Joe Medley

沉浸式网络意味着通过浏览器托管的虚拟世界体验。这包括在浏览器或支持 VR 的头戴设备(如 Google 的 Daydream、Oculus Rift、Samsung Gear VR、HTC Vive 和 Windows 混合现实头戴设备)中显示的完整虚拟现实 (VR) 体验,以及为支持 AR 的移动设备开发的增强现实体验。

虽然我们使用两个术语来描述沉浸式体验,但应将其视为从完全现实到完全沉浸式 VR 环境的一种范围,两者之间具有各种级别的 AR。

沉浸式体验的示例包括:

  • 沉浸式 360 度全景视频
  • 在沉浸式环境中呈现的传统 2D(或 3D)视频
  • 数据可视化图表
  • 居家购物
  • 艺术
  • 还没有人想到的酷炫功能

去那里该怎么走?

沉浸式 Web 应用已经推出近一年,仍处于萌芽阶段。这是通过 WebVR 1.1 API 完成的,该 API 自 Chrome 62 起已在源试用中提供。Firefox 和 Edge 以及 Safari 的 polyfill 也支持该 API。

不过,是时候继续行动了。

源试用已于 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 次,直到用户决定退出。

请求呈现帧

“frame”一词在 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 中物体的位置和方向称为姿势。观看器和输入设备都需要有相应的姿势。(稍后我会介绍输入设备。)查看器和输入设备姿势都定义为按列主要顺序存储在 Float32Array 中的 4x4 矩阵。您可以通过对当前动画帧对象调用 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.
    }
}

这是 Immersive 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(Canary 版,2018 年 6 月的某个时间)预计会推出增强现实功能。尽管如此,我还是建议您试试我们到目前为止的成果。我们需要反馈来对其进行改进。您可以查看 ChromeStatus.com 中的 WebXR Hit Test 来了解其进度。您还可以关注 WebXR 锚点,这将有助于改进姿势跟踪。