使用 WebXR Device API 构建增强现实 (AR) 应用

1. 准备工作

本 Codelab 展示了构建 AR Web 应用的示例。它使用 JavaScript 渲染 3D 模型,这些模型就好像存在于现实世界中一样。

您将使用结合了 AR 和虚拟现实 (VR) 功能的 WebXR Device API。您只需专注于 WebXR Device API 的 AR 扩展程序,即可创建在互动式网页上运行的简单 AR 应用。

什么是 AR?

AR 是一个术语,通常用于描述计算机生成的图形与现实世界的混合。对于基于手机的 AR,这意味着令人信服地将计算机图形放置在实时的摄像头画面上。要想使该效果在手机移动时保持真实性,支持 AR 的设备需要了解其所处的环境,并确定其在 3D 空间中的姿势(位置和方向)。这可能包括检测表面以及估计环境照明情况。

在 Google 的 ARCore 和 Apple 的 ARKit 发布后,AR 已广泛用于各种应用,无论是用于自拍照滤镜,还是用于基于 AR 的游戏。

构建内容

在本 Codelab 中,您将构建一个 Web 应用,该应用使用增强现实将模型置于现实世界中。您的应用将:

  1. 使用目标设备的传感器确定和跟踪其在现实世界中的位置和方向
  2. 在实时的摄像头视图上渲染 3D 模型
  3. 执行点击测试以将对象放置在现实世界中探索到的表面上

学习内容

  • 如何使用 WebXR Device API
  • 如何配置基本 AR 场景
  • 如何使用 AR 点击测试查找表面
  • 如何加载和渲染与现实世界的摄像头画面同步的 3D 模型
  • 如何基于 3D 模型渲染阴影

本 Codelab 主要介绍 AR API。不相关的概念和代码块将一带而过,并在相应的代码库代码中为您提供。

所需条件

在 AR 设备上点击试试看,尝试执行本 Codelab 的第一步。如果您看到显示“Your browser does not have AR features”(您的浏览器不具备 AR 功能)这一消息的页面,请检查您的 Android 设备是否安装了面向 AR 的 Google Play 服务。

2. 设置您的开发环境

下载代码

  1. 点击下面的链接可在您的工作站上下载本 Codelab 的所有代码:

  1. 解压下载的 ZIP 文件。这将解压根文件夹 (ar-with-webxr-master),其中包含本 Codelab 的几个步骤的目录,以及您需要的所有资源。

step-03step-04 文件夹包含本 Codelab 的第三步和第四步的预期结束状态,以及 final 结果。这些内容可供参考。

您将在 work 目录中进行所有编码工作。

安装网络服务器

  1. 您可以随意使用自己的网络服务器。如果您还没有设置 Web Server for Chrome,请参阅此部分,详细了解如何进行设置。
    如果您尚未在工作站上安装该应用,则可以通过 Chrome 网上应用店安装。

  1. 安装 Web Server for Chrome 应用后,请前往 chrome://apps,然后点击“Web Server”图标:

“Web Server”图标

接下来,您将看到以下对话框,该对话框可让您配置本地网络服务器:

配置 Chrome 网络服务器

  1. 点击 CHOOSE FOLDER(选择文件夹),然后选择 ar-with-webxr-master 文件夹。这样您就可以通过网络服务器对话框中(Web Server URL(s) [网络服务器网址] 部分)突出显示的网址处理正在进行的工作。
  2. Options (needs restart)(选项 [需要重启])下,选中 Automatically show index.html(自动显示 index.html)复选框。
  3. Web Server(网络服务器)切换为 STOP(停止),然后切换回 STARTED(已启动)。重启 Chrome 网络服务器
  4. 验证是否至少出现一个网络服务器网址:http://127.0.0.1:8887 - 默认的 localhost 网址。

设置端口转发

配置 AR 设备,使其在您访问工作站上的 localhost:8887 时访问工作站上的同一端口。

  1. 在开发工作站上,前往 chrome://inspect,然后点击 Port forwarding…(端口转发…):chrome://inspect
  2. 使用 Port forwarding settings(端口转发设置)对话框将端口 8887 转发到 localhost:8887。
  3. 选中 Enable port forwarding(启用端口转发)复选框:

配置端口转发

验证您的设置

测试连接:

  1. 使用 USB 线将 AR 设备连接到工作站。
  2. 在 AR 设备上的 Chrome 地址栏中,输入 http://localhost:8887。您的 AR 设备应将此请求转发给开发工作站的网络服务器。您应该会看到一个文件目录。
  3. 在 AR 设备上,点击 step-03 在浏览器中加载 step-03/index.html 文件。

您应该会看到一个包含 Start augmented reality(启动增强现实)按钮的页面

不过,如果您看到 Unsupported browser(浏览器不受支持)错误页面,则说明您的设备可能不兼容。

支持 ARCore

不支持 ARCore

现在,您的 AR 设备应该能够与您的网络服务器建立连接。

  1. 点击 Start augmented reality(启动增强现实)。系统可能会提示您安装 ARCore。

安装 ARCore 提示

第一次运行 AR 应用时,您会看到相机权限提示。

Chrome 请求获得相机权限“权限”对话框

一切就绪后,您应该会看到一个立方体场景叠加在摄像头画面上。摄像头解析的世界越多,场景理解能力就越强,因此四处移动有助于稳定事物。

3. 配置 WebXR

在此步骤中,您将学习如何设置 WebXR 现场录像和基本的 AR 场景。HTML 页面带有 CSS 样式和 JavaScript,用于启用基本的 AR 功能。这样可以加快设置过程的速度,让 Codelab 能够专注于 AR 功能。

HTML 页面

您可以使用现有的网络技术将 AR 体验融入传统网页中。在此体验中,您可以使用全屏渲染画布,因此 HTML 文件无需过于复杂。

AR 功能需要用户通过手势启动,因此一些 Material Design 组件可显示 Start AR(启动 AR)按钮和“Unsupported browser”(浏览器不受支持)消息。

work 目录中已有的 index.html 文件应如下所示。这是实际内容的子集;请勿将此代码复制到您的文件中!

<!-- Don't copy this code into your file! -->
<html>
  <head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Building an augmented reality application with the WebXR Device API</title>
    <link rel="stylesheet" href="https://unpkg.com/material-components-web@latest/dist/material-components-web.min.css">
    <script src="https://unpkg.com/material-components-web@latest/dist/material-components-web.min.js"></script>

    <!-- three.js -->
    <script src="https://unpkg.com/three@0.123.0/build/three.js"></script>
    <script src="https://unpkg.com/three@0.123.0/examples/js/loaders/GLTFLoader.js"></script>

    <script src="../shared/utils.js"></script>
    <script src="app.js"></script>
  </head>
  <body>
  <!-- Information about AR removed for brevity. -->

  <!-- Starting an immersive WebXR session requires user interaction. Start the WebXR experience with a simple button. -->
  <a onclick="activateXR()" class="mdc-button mdc-button--raised mdc-button--accent">
    Start augmented reality
  </a>

</body>
</html>

打开关键的 JavaScript 代码

您的应用的出发点位于 app.js 中。此文件提供了一些用于设置 AR 体验的样板。

您的工作目录也已包含应用代码 (app.js)。

检查 WebXR 和 AR 支持

在用户使用 AR 之前,请先检查是否存在 navigator.xr 和必要的 XR 功能。navigator.xr 对象是 WebXR Device API 的入口点,因此,如果设备兼容,则该对象应该存在。此外,请检查 "immersive-ar" 现场录像模式是否受支持。

如果一切正常,则点击 Enter augmented reality(进入增强现实)按钮会尝试创建 XR 现场录像。否则,系统将调用(shared/utils.js 中的)onNoXRDevice(),用于显示一条指示缺乏 AR 支持的消息。

以下代码已存在于 app.js 中,因此无需进行任何更改。

(async function() {
  if (navigator.xr && await navigator.xr.isSessionSupported("immersive-ar")) {
    document.getElementById("enter-ar").addEventListener("click", activateXR)
  } else {
    onNoXRDevice();
  }
})();

请求 XRSession

当您点击 Enter augmented Reality(进入增强现实)时,代码会调用 activateXR()。这会启动 AR 体验。

  1. app.js 中查找 activateXR() 函数。某些代码已省略:
activateXR = async () => {
  // Initialize a WebXR session using "immersive-ar".
  this.xrSession = /* TODO */;

  // Omitted for brevity
}

WebXR 的入口点是通过 XRSystem.requestSession() 实现的。使用 immersive-ar 模式允许在现实世界环境中查看渲染的内容。

  1. 使用 "immersive-ar" 模式初始化 this.xrSession
activateXR = async () => {
  // Initialize a WebXR session using "immersive-ar".
  this.xrSession = await navigator.xr.requestSession("immersive-ar");

  // ...
}

初始化 XRReferenceSpace

XRReferenceSpace 描述了用于虚拟世界中对象的坐标系。'local' 模式很适合 AR 体验,其参考空间的原点靠近观看器,并且可以进行稳定的跟踪。

使用以下代码在 onSessionStarted() 中初始化 this.localReferenceSpace

this.localReferenceSpace = await this.xrSession.requestReferenceSpace("local");

定义动画循环

  1. 使用 XRSessionrequestAnimationFrame 启动渲染循环,类似于 window.requestAnimationFrame

在每一帧上,系统使用时间戳和 XRFrame 调用 onXRFrame

  1. 完成 onXRFrame 的实现。绘制帧时,添加以下代码,将下一个请求加入队列:
// Queue up the next draw request.
this.xrSession.requestAnimationFrame(this.onXRFrame);
  1. 添加代码以设置图形环境。添加到 onXRFrame 的底部:
// Bind the graphics framebuffer to the baseLayer's framebuffer.
const framebuffer = this.xrSession.renderState.baseLayer.framebuffer;
this.gl.bindFramebuffer(this.gl.FRAMEBUFFER, framebuffer);
this.renderer.setFramebuffer(framebuffer);
  1. 如需确定观看器姿势,请使用 XRFrame.getViewerPose()。此 XRViewerPose 描述设备在空间中的位置和方向。它还包含 XRView 数组,用于描述应渲染场景的每个视角,以便在当前设备上正确显示。立体 VR 有两个视图(每只眼睛各对应一个视图),而 AR 设备只有一个视图。
    pose.views 中的信息常用于配置虚拟摄像头的视图矩阵和投影矩阵。这会影响场景在 3D 中的布局。配置摄像头后,就可以渲染场景。
  2. 添加到 onXRFrame 的底部:
// Retrieve the pose of the device.
// XRFrame.getViewerPose can return null while the session attempts to establish tracking.
const pose = frame.getViewerPose(this.localReferenceSpace);
if (pose) {
  // In mobile AR, we only have one view.
  const view = pose.views[0];

  const viewport = this.xrSession.renderState.baseLayer.getViewport(view);
  this.renderer.setSize(viewport.width, viewport.height);

  // Use the view's transform matrix and projection matrix to configure the THREE.camera.
  this.camera.matrix.fromArray(view.transform.matrix);
  this.camera.projectionMatrix.fromArray(view.projectionMatrix);
  this.camera.updateMatrixWorld(true);

  // Render the scene with THREE.WebGLRenderer.
  this.renderer.render(this.scene, this.camera);
}

测试

运行应用;在开发设备上,访问 work/index.html。您应该会看到摄像头画面,其中包含悬浮在空间中的立方体;当您移动设备时,这些立方体的视角会发生变化。跟踪效果会随着您的四处移动而改善,因此您不妨探索一下适合您和设备的移动方式。

如果您在运行应用时遇到问题,请参阅简介以及设置您的开发环境部分。

4. 添加定位十字线

设置基本的 AR 场景后,就可以使用点击测试开始与现实世界互动了。在此部分中,您将编写一个点击测试程序,并使用该程序查找现实世界中的表面。

了解点击测试

点击测试是一种方法,通常用于从空间中的某个点向某个方向投射一条直线并确定该直线是否与任何相关物体相交。在此示例中,您将设备对准现实世界中的某个位置。设想有一条光线从您设备的摄像头射出并直接进入前方的物理世界。

WebXR Device API 可让您知道该光线是否与现实世界中的任何物体相交,具体取决于底层 AR 功能以及对世界的理解。

点击测试示意图

使用额外的功能请求 XRSession

为了执行点击测试,请求 XRSession 时需要额外的功能。

  1. app.js 中,找到 navigator.xr.requestSession
  2. "hit-test""dom-overlay" 功能添加为 requiredFeature,如下所示:
this.xrSession = await navigator.xr.requestSession("immersive-ar", {
  requiredFeatures: ["hit-test", "dom-overlay"]
});
  1. 配置 DOM 叠加层。如下所示,在 AR 摄像头视图上叠加 document.body 元素:
this.xrSession = await navigator.xr.requestSession("immersive-ar", {
  requiredFeatures: ["hit-test", "dom-overlay"],
  domOverlay: { root: document.body }
});

添加运动提示

充分理解环境后,ARCore 便可发挥出色的作用。这是通过一个称为同时定位和映射 (SLAM) 的过程实现的,其中视觉上不同的特征点用于计算位置和环境特征的变化。

使用上一步中的 "dom-overlay" 在摄像头信息流顶部显示运动提示。

<div>(ID 为 stabilization)添加到 index.html。此 <div> 会向用户显示一部表示稳定状态的动画,并提示他们拿着设备四处移动以增强 SLAM 过程。此动画会在用户进入 AR 模式时显示,在十字线发现表面后隐藏起来,具体由 <body> 类控制。

  <div id="stabilization"></div>

</body>
</html>

添加十字线

使用十字线来指示设备的视图所指向的位置。

  1. app.js 中,将 setupThreeJs() 中的 DemoUtils.createCubeScene() 调用替换为空的 Three.Scene()
setupThreeJs() {
  // ...

  // this.scene = DemoUtils.createCubeScene();
  this.scene = DemoUtils.createLitScene();
}
  1. 使用表示冲突点的对象填充新场景。提供的 Reticle 类负责在 shared/utils.js 中加载十字线模型。
  2. setupThreeJs() 中,将 Reticle 添加到场景:
setupThreeJs() {
  // ...

  // this.scene = DemoUtils.createCubeScene();
  this.scene = DemoUtils.createLitScene();
  this.reticle = new Reticle();
  this.scene.add(this.reticle);
}

如需执行点击测试,请使用新的 XRReferenceSpace。此参考空间从观看器的视角指示一个新的坐标系,以创建一条与观看方向对齐的光线。XRSession.requestHitTestSource()(可以计算点击测试)中会使用此坐标系。

  1. 将以下代码添加到 app.js 中的 onSessionStarted()
async onSessionStarted() {
  // ...

  // Setup an XRReferenceSpace using the "local" coordinate system.
  this.localReferenceSpace = await this.xrSession.requestReferenceSpace("local");

  // Add these lines:
  // Create another XRReferenceSpace that has the viewer as the origin.
  this.viewerSpace = await this.xrSession.requestReferenceSpace("viewer");
  // Perform hit testing using the viewer as origin.
  this.hitTestSource = await this.xrSession.requestHitTestSource({ space: this.viewerSpace });

  // ...
}
  1. 使用此 hitTestSource,每帧执行一次点击测试:
    • 如果点击测试没有结果,则说明 ARCore 没有足够的时间来理解环境。在这种情况下,请使用 stabilization <div> 提示用户移动设备。
    • 如果有结果,请将十字线移到该位置。
  2. 修改 onXRFrame 以移动十字线:
onXRFrame = (time, frame) => {
  // ... some code omitted ...
  this.camera.updateMatrixWorld(true);

  // Add the following:
  const hitTestResults = frame.getHitTestResults(this.hitTestSource);

  if (!this.stabilized && hitTestResults.length > 0) {
    this.stabilized = true;
    document.body.classList.add("stabilized");
  }
  if (hitTestResults.length > 0) {
    const hitPose = hitTestResults[0].getPose(this.localReferenceSpace);

    // update the reticle position
    this.reticle.visible = true;
    this.reticle.position.set(hitPose.transform.position.x, hitPose.transform.position.y, hitPose.transform.position.z)
    this.reticle.updateMatrixWorld(true);
  }
  // More code omitted.
}

点按屏幕时添加行为

XRSession 可以通过 select 事件(表示主要操作)根据用户互动发出事件。在移动设备上的 WebXR 中,主要操作是点按屏幕。

  1. onSessionStarted 底部添加 select 事件监听器:
this.xrSession.addEventListener("select", this.onSelect);

在此示例中,点按屏幕后,系统会在十字线处放置一朵向日葵。

  1. App 类中为 onSelect 创建一个实现:
onSelect = () => {
  if (window.sunflower) {
    const clone = window.sunflower.clone();
    clone.position.copy(this.reticle.position);
    this.scene.add(clone);
  }
}

测试应用

您创建了一条十字线,可以使用设备通过点击测试对准该十字线。点按屏幕时,您应该能够将向日葵放在十字线指定的位置。

  1. 运行应用时,您应该能够看到一条十字线在跟踪地面。如果没有看到,请尝试使用手机缓慢地环顾四周。
  2. 看到十字线后,请点按它。它上面应该会放置一朵向日葵。您可能必须稍微移动一下,以便底层 AR 平台能够更好地检测现实世界中的表面。较差的光线和没有特征的表面会导致场景理解质量降低,找不到点击的可能性也会增加。如果您遇到任何问题,请检验 step-04/app.js 代码以查看此步骤的有效示例。

5. 添加阴影

创建逼真的场景涉及数字对象上适当的照明和阴影等元素,这些元素可以增加场景的真实感和沉浸感。

照明和阴影是由 three.js 处理的。您可以指定哪些光线应投射阴影、哪些材料应接收和渲染这些阴影、哪些网格可以投射阴影。此应用的场景包含投射阴影的光线,以及仅用于渲染阴影的平坦表面。

  1. three.js WebGLRenderer 上启用阴影。创建渲染程序后,在其 shadowMap 上设置以下值:
setupThreeJs() {
  ...
  this.renderer = new THREE.WebGLRenderer(...);
  ...
  this.renderer.shadowMap.enabled = true;
  this.renderer.shadowMap.type = THREE.PCFSoftShadowMap;
  ...
}

DemoUtils.createLitScene() 中创建的示例场景包含一个名为 shadowMesh 的对象,这是一个仅渲染阴影的水平表面。此表面最初的 Y 位置为 10,000 个单位。放置向日葵后,将 shadowMesh 移动到与真实表面相同的高度,从而将花朵的阴影渲染在真实地面上。

  1. onSelect 中,向场景添加 clone 后,添加代码以调整阴影平面的位置:
onSelect = () => {
  if (window.sunflower) {
    const clone = window.sunflower.clone();
    clone.position.copy(this.reticle.position);
    this.scene.add(clone);

    const shadowMesh = this.scene.children.find(c => c.name === "shadowMesh");
    shadowMesh.position.y = clone.position.y;
  }
}

测试

放置向日葵时,您应该能够看到它在投射阴影。如果您遇到任何问题,请检验 final/app.js 代码以查看此步骤的有效示例。

6. 其他资源

恭喜!您已使用 WebXR 完成有关 AR 的本 Codelab。

了解详情