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

如果您已经使用过 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 上时的尺寸,并允许用户在所选 Surface 上移动,移动距离更近或更远。与二维图像相比,这种方法能让观看者更深入地了解对象。

我比自己快点了。要实际执行我的描述,您需要 AR 功能以及一些检测表面的方法。本文介绍前者。WebXR Hit Test API 的附带文章(链接至上文)介绍了后者。

申请会话

请求会话与您之前看到的情况非常相似。首先,通过调用 xr.isSessionSupported() 来确认您想要的会话类型在当前设备上是否可用。而不是像以前一样请求 'immersive-vr',而是请求 'immersive-ar'

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 会话示例,就会发现最初的场景是静态的,完全没有增强现实效果。您可以使用手指拖动和滑动以在场景中四处移动。如果您点击“START 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.
  }
}

总结

本系列文章仅介绍了在网络上实现沉浸式内容的基础知识。Immersive Web 工作组的 WebXR Device API 示例介绍了更多功能和用例。我们还刚刚发布了一篇点击测试文章,其中介绍了一个用于检测 Surface 并在真实相机视图中放置虚拟内容的 API。敬请查看并观看 web.dev 博客,了解明年的更多文章。

照片由 David Grandmougin 提供,来自 Unsplash 网站