虚拟现实即将登陆网络

了解以下基本知识,为各种沉浸式体验做好准备:虚拟现实、增强现实以及介于二者之间的所有内容。

Joe Medley
Joe Medley

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 会话中检测表面的方法)。除非另有说明,否则我在本篇和后续文章中介绍的所有内容都同样适用于 AR 和 VR。

什么是沉浸式 Web?

虽然我们使用两种术语来描述沉浸式体验(增强现实和虚拟现实),但许多人认为它们是从完全真实到完全虚拟的连续谱系,中间有不同的沉浸度。XR 中的“X”旨在反映这种思维,它是一种代数变量,代表沉浸式体验范围内的任何内容。

一张图表,展示了从完全真实到完全沉浸的视觉体验谱系。
丰富多样的沉浸式体验

沉浸式体验的示例包括:

  • 游戏
  • 360° 全景视频
  • 在沉浸式环境中呈现传统 2D(或 3D)视频
  • 买房
  • 在购买前查看产品在家中的摆放效果
  • 沉浸式艺术
  • 别人还没有想到的酷炫内容

概念和用法

我将介绍使用 WebXR Device API 的一些基础知识。如果您需要的深度超出了我所提供的范围,请查看 Immersive Web Work Group 的 WebXR 示例MDN 不断增加的参考资料。如果您熟悉早期版本的 WebXR Device API,则应浏览所有这些资料。发生了变化。

本文中的代码基于沉浸式 Web 工作组的基本示例(演示版源代码),但经过编辑,以便清晰简洁。

在制定 WebXR 规范的过程中,我们一直在完善安全和隐私保护措施,以保护用户。因此,实现必须遵循特定要求。网页或应用必须处于活跃状态并处于聚焦状态,才能向查看者发出任何敏感内容请求。网页或应用必须通过 HTTPS 提供。该 API 本身旨在保护从传感器和摄像头获取的信息,这些信息是其正常运行所必需的。

申请会话

进入 XR 会话需要用户手势。为此,请使用特征检测(通过 navigator.xr)测试 XRSystem,并调用 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 兼容的 WebGLRenderingContextWebGL2RenderingContext。所有绘制操作都是使用它们或基于 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 帧。您的代码不应假定任何特定帧速率。

帧循环的基本流程如下:

  1. 调用 XRSession.requestAnimationFrame()。作为响应,用户代理会调用由您定义的 XRFrameRequestCallback
  2. 在回调函数内:
    1. 再次调用 XRSession.requestAnimationFrame()
    2. 获取观看者的姿势。
    3. WebGLFramebufferXRWebGLLayer 传递(“bind”)到 WebGLRenderingContext
    4. 迭代每个 XRView 对象,从 XRWebGLLayer 检索其 XRViewport 并将其传递给 WebGLRenderingContext
    5. 向帧缓冲区绘制内容。

本文的其余部分将介绍第 1 步和第 2 步的一部分,即设置和调用 XRFrameRequestCallback。第 2 步的其余内容将在第 II 部分中介绍。

XRFrameRequestCallback

XRFrameRequestCallback 由您定义。它接受两个参数:DOMHighResTimeStampXRFrame 实例。XRFrame 对象提供向显示屏渲染单个帧所需的信息。DOMHighResTimeStamp 参数供将来使用。

在执行任何其他操作之前,我将请求下一个动画帧。如前所述,帧的计时由用户代理根据底层硬件确定。先请求下一帧可确保在回调期间发生错误时帧循环会继续。

function onXRFrame(hrTime, xrFrame) {
  let xrSession = xrFrame.session;
  xrSession.requestAnimationFrame(onXRFrame);
  // Render a frame.
}

现在,是时候为观看者画点什么了。以上是第二部分讨论的内容在开始之前,我来为您介绍如何结束课程。

结束会话

沉浸式会话可能会因多种原因而结束,包括通过调用 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 提供,由 Un 创立