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

如果您已经使用过 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 的文章,或者至少熟悉其中介绍的主题。您应了解如何请求和进入会话,以及如何运行帧循环

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

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

它有什么用途?

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

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

我有点领先我自己。若要实际执行我所描述的操作,您需要 AR 功能和一些检测表面的方法。本文将介绍前者。WebXR Hit Test 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 工作组的 WebXR Device API 示例介绍了更多功能和用例。我们还刚刚发布了一篇点击测试文章,其中介绍了用于检测表面和在真实相机视图中放置虚拟项的 API。请查看这些文章,并关注 web.dev 博客,以便在未来一年内阅读更多文章。

照片由 David Grandmougin 拍摄,选自 Unsplash