虚拟现实即将登陆网络

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

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

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

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

沉浸式体验的示例包括:

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

概念和用法

我将介绍使用 WebXR Device API 的一些基本知识。如果您需要比我提供的更深入的信息,请查看沉浸式 Web 工作组的 WebXR 示例MDN 不断扩充的参考资料。如果您熟悉早期版本的 WebXR Device API,则应浏览本文档中的所有内容。发生了变化。

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

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

请求会话

进入 XR 会话需要用户手势。为此,请使用特征检测来测试 XRSystem(通过 navigator.xr),并调用 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 传递(“绑定”)给 WebGLRenderingContext
    4. 迭代每个 XRView 对象,从 XRWebGLLayer 检索其 XRViewport 并将其传递给 WebGLRenderingContext
    5. 向帧缓冲区绘制内容。

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

XRFrameRequestCallback

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

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

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

现在,是时候为观看者画点什么了。我们将在第 2 部分进行讨论。在开始之前,我先向您展示一下如何结束会话。

结束会话

沉浸式会话可能会因多种原因而结束,包括通过调用 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.COMUnsplash 上发布