增强现实:您可能已经知道了

如果您已经使用过 WebXR Device API,那么您已经完成了大部分工作。

Joe Medley
Joe Medley

WebXR Device API 于去年秋季随 Chrome 79 一起发布。正如当时所述,Chrome 对该 API 的实现仍在开发中。Chrome 很高兴地宣布,部分工作已完成。Chrome 81 中新增了以下两个功能:

本文将介绍增强现实。如果您已经使用过 WebXR Device API,那么您会很高兴地知道,您需要学习的新内容非常少。进入 WebXR 会话的方式基本相同。运行帧循环基本相同。两者之间的区别在于配置,这些配置可让内容以适合增强现实的方式显示。如果您不熟悉 WebXR 的基本概念,请阅读我之前关于 WebXR Device API 的文章,或者至少熟悉其中介绍的主题。您应了解如何请求和进入会话,以及如何运行帧循环

如需了解点击测试,请参阅随附文章在真实视图中放置虚拟对象。本文中的代码基于沉浸式 Web 工作组的 WebXR Device API 示例中的沉浸式 AR 会话示例(演示版 源代码)。

在深入了解代码之前,您应至少使用一次沉浸式 AR 会话示例。您需要一部搭载 Chrome 81 或更高版本的新型 Android 手机。

它有什么用途?

增强现实技术将成为许多现有或新网页的宝贵补充,让它们无需离开浏览器即可实现 AR 用例。例如,它可以帮助用户在教育网站上学习,让潜在买家在购物时直观地看到家中的物品。

考虑第二种使用情形。假设您要模拟将虚拟对象的等身大小表示法放置在真实场景中。放置后,图片会保留在所选 Surface 上,显示的尺寸与实际内容在该 Surface 上的尺寸相同,并且允许用户围绕图片移动,以及向图片靠近或远离图片。与二维图片相比,这让观看者能够更深入地了解对象。

我有点超前了。若要实际执行我所描述的操作,您需要 AR 功能和一些检测表面的方法。本文将介绍前者。关于 WebXR 点击测试 API 的随附文章(上面提供了链接)介绍了后者。

请求时段

请求会话与您之前看到的非常相似。首先,通过调用 xr.isSessionSupported() 确定当前设备上是否支持所需的会话类型。请改为请求 'immersive-ar',而不是像之前那样请求 'immersive-vr'

if (navigator.xr) {
  const supported = await navigator.xr.isSessionSupported('immersive-ar');
  if (supported) {
    xrButton.addEventListener('click', onButtonClicked);
    xrButton.textContent = 'Enter AR';
    xrButton.enabled = supported; // supported is Boolean
  }
}

与之前一样,这会启用“进入 AR”按钮。当用户点击该按钮时,调用 xr.requestSession(),同时传递 'immersive-ar'

let xrSession = null;
function onButtonClicked() {
  if (!xrSession) {
    navigator.xr.requestSession('immersive-ar')
    .then((session) => {
      xrSession = session;
      xrSession.isImmersive = true;
      xrButton.textContent = 'Exit AR';
      onSessionStarted(xrSession);
    });
  } else {
    xrSession.end();
  }
}

一个便捷属性

您可能已经注意到,我在上一个代码示例中突出显示了两行代码。 XRSession 对象似乎有一个名为 isImmersive 的属性。这是我自己创建的一个便捷属性,并非规范的一部分。我稍后会使用它来决定向观看者显示什么内容。为什么此属性不是 API 的一部分?由于您的应用可能需要以不同的方式跟踪此属性,因此规范作者决定让 API 保持简洁。

进入会话

回想一下我在上一篇文章中介绍的 onSessionStarted() 的样子:

function onSessionStarted(xrSession) {
  xrSession.addEventListener('end', onSessionEnded);

  let canvas = document.createElement('canvas');
  gl = canvas.getContext('webgl', { xrCompatible: true });

  xrSession.updateRenderState({
    baseLayer: new XRWebGLLayer(session, gl)
  });

  xrSession.requestReferenceSpace('local-floor')
  .then((refSpace) => {
    xrRefSpace = refSpace;
    xrSession.requestAnimationFrame(onXRFrame);
  });
}

我需要添加一些内容来处理增强现实渲染。关闭背景。首先,我要确定是否需要背景。这是我将首次使用便捷属性的地方。

function onSessionStarted(xrSession) {
  xrSession.addEventListener('end', onSessionEnded);

  if (session.isImmersive) {
    removeBackground();
  }

  let canvas = document.createElement('canvas');
  gl = canvas.getContext('webgl', { xrCompatible: true });

  xrSession.updateRenderState({
    baseLayer: new XRWebGLLayer(session, gl)
  });

  refSpaceType = xrSession.isImmersive ? 'local' : 'viewer';
  xrSession.requestReferenceSpace(refSpaceType).then((refSpace) => {
    xrSession.requestAnimationFrame(onXRFrame);
  });

}

参考空间

我之前的文章仅简要介绍了参考空间。我要介绍的示例使用了其中两个,因此现在需要修正这一遗漏。

参照空间描述了虚拟世界与用户的物理环境之间的关系。具体方法如下:

  • 指定用于表示虚拟世界中位置的坐标系的原点。
  • 指定用户是否应在该坐标系内移动。
  • 该坐标系是否具有预先建立的边界。(此处显示的示例不使用预先建立边界的坐标系。)

对于所有参考空间,X 坐标表示左右,Y 坐标表示上下,Z 坐标表示前后。正值分别表示向右、向上和向后。

XRFrame.getViewerPose() 返回的坐标取决于请求的参考空间类型。我们在讲解帧循环时会详细介绍这一点。现在,我们需要选择适合增强现实的参考类型。同样,这使用了我的便捷属性。

let refSpaceType
function onSessionStarted(xrSession) {
  xrSession.addEventListener('end', onSessionEnded);

  if (session.isImmersive) {
    removeBackground();
  }

  let canvas = document.createElement('canvas');
  gl = canvas.getContext('webgl', { xrCompatible: true });

  xrSession.updateRenderState({
    baseLayer: new XRWebGLLayer(session, gl)
  });

  refSpaceType = xrSession.isImmersive ? 'local' : 'viewer';
  xrSession.requestReferenceSpace(refSpaceType).then((refSpace) => {
    xrSession.requestAnimationFrame(onXRFrame);
  });
}

如果您查看过沉浸式 AR 会话示例,就会发现场景最初是静态的,完全不是增强现实。您可以用手指拖动和滑动来在场景中移动。如果您点击“开始 AR”,背景就会消失,您可以通过移动设备在场景中移动。这些模式使用不同的参考空间类型。上面突出显示的文字展示了如何进行选择。它使用以下引用类型:

local - 原点位于会话创建时观看者的位置。这意味着,体验不一定有明确定义的底价,并且起源的确切位置可能会因平台而异。虽然聊天室没有预先设定的边界,但预计用户在观看内容时除了旋转之外不会有其他移动。如您在我们自己的 AR 示例中看到的,您或许可以在空间内进行一些移动。

viewer - 最常用于在页面中内嵌显示的内容,此间距会随观看设备而变化。传递给 getViewerPose 时,它不会提供任何跟踪,因此始终会报告原点处的姿势,除非应用使用 XRReferenceSpace.getOffsetReferenceSpace() 对其进行修改。该示例使用此方法来实现基于触摸的摄像头平移。

运行帧循环

从概念上讲,我之前在 VR 会话中所做的一切都没有改变。将引用空间类型传递给 XRFrame.getViewerPose()。 返回的 XRViewerPose 将适用于当前的参考空间类型。使用 viewer 作为默认值可让网页在请求用户同意使用 AR 或 VR 内容之前显示内容预览。这说明了一点:内嵌内容使用与沉浸式内容相同的帧循环,从而减少了需要维护的代码量。

function onXRFrame(hrTime, xrFrame) {
  let xrSession = xrFrame.session;
  xrSession.requestAnimationFrame(onXRFrame);
  let xrViewerPose = xrFrame.getViewerPose(refSpaceType);
  if (xrViewerPose) {
    // Render based on the pose.
  }
}

总结

本系列文章仅介绍了在 Web 上实现沉浸式内容的基础知识。沉浸式 Web 工作组的 WebXR Device API 示例介绍了更多功能和用例。我们还刚刚发布了一篇点击测试文章,其中介绍了用于检测表面和在真实相机视图中放置虚拟项的 API。请查看这些文章,并关注 web.dev 博客,以便在未来一年内阅读更多文章。

照片由 David Grandmougin 拍摄,选自 Unsplash