推荐:使用 NSDT场景设计器 快速搭建 3D场景。

在过去几年中,增强现实已成为许多移动应用程序中的常见功能。 从游戏和社交网络到导航和商业应用程序——我们已经看到许多主要参与者以各种形式尝试增强现实。 微软、Meta 和苹果等行业巨头正在该领域投入巨资,这项技术仍有大量令人兴奋的应用等待被发现。 如果你是 Web 开发人员,现在是开始开发 AR 应用程序的最佳时机!

在为 Web 创建增强现实 (AR) 和虚拟现实 (VR) 体验方面,WebXR Device API 的引入代表了重大飞跃。 事实证明,该 API 可为开发人员提供创建可在浏览器中运行的真正身临其境的 AR 和 VR 应用程序所需的性能和功能。

在编写用于移动 AR 的 WebXR 时,Android 上的 Chrome 和三星互联网浏览器支持最好,而 Oculus 浏览器和 Wolvic 可能是你在 VR 方面的最佳选择。 iOS 上的 Webkit 对 WebXR 的支持还不存在,但希望它指日可待。

然而,这确实意味着在此项目中创建的应用程序暂时不会在 iOS 上运行,相信这一点很快会改变!

1、关于这个项目

在这篇文章中,我们将创建一个简单的增强现实 Web 应用程序,可以将 3D 模型放置在你的环境中。 我在这个项目中的目标是向你介绍使用 WebXR API 创建基本增强现实应用程序所需的主要概念,同时让您对在 Web 上使用 AR 充满热情! ⚡

在这两部分中,您将学习如何设置基本的 WebXR 应用程序,如何使用 Three.js 库在增强现实空间中渲染模型,以及如何响应来自用户设备的触摸事件。

您还将了解一些巧妙的提示和技巧,以设置您的 WebXR 开发环境。 虽然我们在这个项目中使用了许多熟悉的 Web 技术,但与传统的 Web 开发相比,WebXR 空间仍然有些新生,并且它有自己的一系列挑战。

正如您可能已经注意到的那样,这篇文章相当冗长,但这是有充分理由的。 我们现在同时研究 3D 开发和 WebXR。 如果不能很好地掌握基本概念,这个项目可能很快就会变得混乱和令人沮丧。 因此,我将尝试在整个过程中提供一定程度的细节,以便您熟悉创建 AR 体验的所有活动部分。

如果您对最终结果感到好奇,可以通过在 Android 手机上打开下面的链接来试用该应用程序的现场演示。

项目应用演示:https://webxr-koalas.vercel.app/

2、前置条件

要构建和运行项目应用程序,需要满足以下前置条件:

  • 安装了 node.js 和 npm 的计算机。 node.js应该是 12.13.0 或更新版本。
  • 熟悉 TypeScript(或 JavaScript)。 我们将在本指南中使用 TypeScript,但如果你了解一点 JavaScript,也应该能够跟进。
  • 装有最新版本 Chrome 的 Android 设备。 由于 WebXR 尚未在 WebKit 浏览器引擎中实现,因此该应用程序暂时无法在 iOS 上运行。
  • 如果你手边没有 Android 设备,可以尝试在 Firefox 或 Chrome 上使用 WebXR Emulator。 在模拟器上运行应用程序无法与在真实设备上运行相提并论,但能够测试代码并在进行过程中在模拟器中进行尝试。 模拟器也可以非常方便地进行调试。

这将我们带到另一个重要的点。 由于该应用程序针对移动设备,你可能会发现很难在桌面浏览器控制台中以通常的方式进行调试。 模拟器有帮助,但一个好的解决方案是使用 Chrome 中的远程调试工具,这将允许你检查移动浏览器并查看那里输出的任何控制台消息。有关chrome远程调试工具的详细信息可以参看这里

在克隆代码存储库之前,让我们快速浏览一下构成该应用程序的技术,从 WebXR 本身开始。

3、WebXR Device API

WebXR Device API 是一种原生浏览器 API,使用户能够完全在浏览器内运行 AR 和 VR 体验,而无需依赖下载专用的原生应用程序。

简而言之,WebXR 设备 API 使你的浏览器能够与手机或头戴式显示器中的所有传感器、摄像头和其他组件进行通信,以便设备了解你的位置、动作和周围环境。

描述 API 的 Web 标准文档仍在积极开发中,但它已经开始稳定一段时间了,主要的浏览器引擎早已开始实施 WebXR API 的最重要部分。

将此 API 与 3D 渲染库相结合,你便拥有了在浏览器中打造复杂的 AR 或 VR 体验所需的一切。

说到 web 的 3D 库,我们也来看看 Three.js。

4、Three.js 简介

如果你曾经在网络上玩过 3D,可能已经熟悉 Three.js 这个名字。 10 多年前首次发布,这个开源库仍然一如既往地强大,每个月都会发布新版本。 由于其用户友好的 API、广泛的功能以及庞大的在线用户社区提供支持、示例代码和令人惊叹的 3D 体验以供探索和学习,它受到许多人的喜爱。

好奇 Three.js 在经验丰富的开发人员手中能做什么? 以下是网络上我最喜欢的一些示例:

除了是 web 上 3D 的可靠全能库之外,它也恰好是开始使用 WebXR 的最简单方法之一! 🙌

如果你以前从未进行过任何 3D 开发,那么应该熟悉一些关键术语和概念,以便更好地理解典型的 Three.js 项目中发生的事情。

让我们来看看其中的一些。

  • 场景

在 Three.js 和许多其他 3D 库的上下文中,场景代表应用程序中的主要 3D 环境。 这是放置所有模型、灯光和相机的地方,并将它们排列在一起以获得连贯的体验。

  • 渲染器

渲染器是负责在屏幕上实际绘制场景的部分。 对于游戏、动画或任何其他交互式应用程序,我们通常会创建一个渲染循环,该函数负责以给定的间隔(例如每秒 60 次)将我们的场景重复绘制到屏幕上。 这是必要的,因为每次我们调用渲染函数就像拍摄场景当前状态的快照。 然后我们可以对场景执行更新并四处移动我们的模型、相机和灯光,这些更改将反映在下一次渲染调用中。

  • 相机

相机代表用户对场景的看法。 Three.js 中有几种不同的相机类型,具体取决于你想要创建的体验类型。 我们将要使用的是透视相机,它旨在复制人眼如何看待事物。

任何 3D 体验的另一个关键组成部分是光。 在没有光源的情况下,渲染到画布上时,场景将看起来像一片漆黑的虚空。 即使我们将在增强现实的背景下工作——以现实世界作为我们的环境——照明仍然很重要,它可以为我们的场景提供真实感,并使我们的模型看起来最好。 😎

  • 模型和网格

Three.js 提供了加载预制 3D 模型以及从头开始创建 3D 对象的可能性。 我们将两者兼顾。 但是,通过指定形状的几何形状并为其应用材质,然后从这两个部分创建网格,完全可以在 Three.js 中制作你自己的 3D 对象。 然而,这很快就会变得有点复杂,如果要创建的东西有点复杂,最好使用像 Blender 这样的专用 3D 建模工具来创建你的模型,然后再将其导入 Three.js。

在此项目中,我们将使用为 Google Poly 制作的预制考拉熊模型。

上面的部分只涵盖了我们在开始之前应该知道的最起码的内容,而 Three.js 库本身包含如此多的功能,你可能需要花费数年时间才能全部了解。 然而,我们现在将保持简单,我将解释我们在此过程中需要的相关概念。

5、为开发做好准备

使用 webpack 等工具建立一个新项目有时是一件苦差事。 为了最大程度地减少花在工具和项目设置上的时间,我因此创建了一个小型入门存储库以供使用。

该项目包括我们在整个过程中需要的所有依赖项。 我还在整个过程中定义了一些空函数,并声明了我们将在代码中使用的每个导入。 这只是为了在我们开始编写应用程序代码时让一切变得更简单,并确保你可以专注于代码而不是修复构建问题。

最后,我还在项目文件中添加了一个经过缩放和处理的考拉熊模型,所以它可以在我们的应用程序中使用了。 🐨

访问下面的链接,并将存储库克隆到您的本地计算机:

https://github.com/sopra-steria-norge/get-started-with-ar-web/

接下来,使用终端导航到存储库并运行以下命令以安装所有项目依赖项并以开发模式启动应用程序。

npm install             # Install dependencies
npm run start:live      # Start dev server and enable tunneling

上面的 start:live 命令将启动一个开发服务器,然后使用 localtunnel 包通过互联网启用到本地主机的隧道。

请注意——这会将你的本地开发服务器暴露在互联网上!

我已经这样设置了,因为我发现在物理移动设备上预览应用程序时,使用像 localtunnel 或 ngrok 这样的隧道服务而不是直接访问开发服务器可以大大减少开发过程中的摩擦。

这也有助于简化在 HTTPS 下运行所有内容,因为 WebXR 应用程序需要通过 HTTPS 提供服务才能运行,如果没有 localtunnel,我们将不得不为本地开发设置证书才能使用 WebXR 设备 API。

运行代码后检查你的终端。

如果 localtunnel 成功建立连接,它应该打印一个随机生成的 URL,现在可以从互联网访问开发服务器。

结果页面应该是空白的,我们的 AR 应用程序的空白画布。

确保没有错误打印到终端,并随意浏览并熟悉启动项目。

6、迈入 AR 的第一步

好了,聊够了——是时候开始开发我们的应用程序了。 ⚡

启动你的 IDE 并首先打开文件 index.ts ,这是我们应用程序的主要入口点。

找到名为 initializeXRApp 的函数并插入以下代码片段中的代码。 不要因为简单地复制和粘贴它而烦恼,在我们前进的过程中,我将始终遍历每个代码片段,以解释每一步中发生的事情。

function initializeXRApp() {
  const { devicePixelRatio, innerHeight, innerWidth } = window;
  
  // Create a new WebGL renderer and set the size + pixel ratio.
  const renderer = new WebGLRenderer({ antialias: true, alpha: true })
  renderer.setSize(innerWidth, innerHeight);
  renderer.setPixelRatio(devicePixelRatio);
  
  // Enable XR functionality on the renderer.
  renderer.xr.enabled = true;

  // Add it to the DOM.
  document.body.appendChild( renderer.domElement );

  // Create the AR button element, configure our XR session, and append it to the DOM.
  document.body.appendChild(ARButton.createButton(
    renderer,
    { requiredFeatures: ["hit-test"] },
  ));

  // Pass the renderer to the createScene-funtion.
  createScene(renderer);

  // Display a welcome message to the user.
  displayIntroductionMessage();
};

在上面的函数中,我们首先获取设备的像素比,以及窗口布局视口的高度和宽度。 然后我们使用 Three.js 提供的 WebGLRenderer 构造函数创建一个新的渲染器对象。 调用此构造函数时,我们传入两个参数:antialias 和 alpha。

通过启用抗锯齿(antialias),我们的模型在渲染到场景时看起来会更干净,锯齿更少。 启用 alpha 缓冲区将使我们能够在场景中渲染的任何内容上使用透明度。

接下来,我们使用从窗口对象中提取的值在渲染器上设置大小和像素比率。 这将有助于确保我们的应用程序以适合设备的正确分辨率呈现。 我们还必须在渲染器上专门启用 XR 功能,因为默认情况下它是关闭的。 然后,我们将渲染器添加到 DOM。

下一步是使用 Three.js 的内置辅助函数设置 AR 体验。 通过调用  ARButton.createButton() 并传入我们的配置对象,Three.js 将负责设置所有内容,甚至会为我们的页面提供一个漂亮的小按钮以进入 AR 体验。

我们传递给 createButton 函数的配置对象指定我们要求设备浏览器在允许我们的应用程序运行之前支持命中测试。 这意味着如果命中测试不可用,应用程序将不会启动。 这有助于确保用户不会意外体验到应用程序的损坏/不完整版本。

在底部附近,我们调用 createScene 函数并传入我们的渲染器。 这个函数是从 scene.ts 文件导入的,该文件目前是空的——但别担心,我们很快就会讲到这个。

我们还在最后调用了一个辅助函数。 它所做的只是显示一条简单的欢迎消息,以迎接任何打开我们应用程序的用户。

如果现在运行该应用程序,会发现什么都没有发生——因为我们实际上还没有调用 initializeXrApp 函数!

移动到开始功能,并添加以下代码。

async function start() {
  // Check if browser supports WebXR with "immersive-ar".
  const immersiveArSupported = await browserHasImmersiveArCompatibility();
  
  // Initialize app if supported.
  immersiveArSupported ?
    initializeXRApp() : 
    displayUnsupportedBrowserMessage();
};

在这里,我们在启动应用程序之前检查用户的浏览器是否支持 WebXR 的沉浸式 AR 会话,如果不支持则呈现消息。

为此,我们使用了项目中包含的两个辅助函数。 包括这些主要是为了缩小该项目的范围,让我们专注于实际的 AR 功能。

详细命名的 browserHasImmersiveArCompatibility 函数只是调用 navigator.xr 对象上的一个内置函数来检查 WebXR 支持,而 showUnsupportedBrowserMessage 函数只是将一些 HTML 渲染到 DOM,其中包含一条消息,告诉用户他们的浏览器不受支持 .

由于这是一个教程项目,我们当然可以跳过这一步,但最好让用户知道如果他们的设备还不受支持,他们将无法运行该应用程序,而不是只显示一个空白页面 . 在处理尚未被所有浏览器引擎完全采用的尖端 API 时,这一点尤其重要,可能会将一些用户排除在外。

添加上述代码后,可以在你的设备上打开该应用程序。 使用 localtunnel URL,并确保访问的是 HTTPS-URL。

现在应该看到一个带有文本“START AR”的按钮。

按下它,Three.js 中的 WebXR 功能将创建并呈现一个摄像头源。

所有这一切只需要编写几行代码——非常简洁,是吧? 👏

7、创建场景

让我们继续为我们的应用程序设置场景。

在上一步中,我们将渲染器对象传递给了 createScene 函数。 这个函数目前是空的,所以让我们开始下一步。

打开 scene.ts 文件并找到函数。 我们现在要添加一个场景、一个相机和一个渲染循环。

在顶部声明一个名为 scene 的变量,并通过调用构造函数创建一个新的场景对象。

export function createScene(renderer: WebGLRenderer) {
  const scene = new Scene(); 
  
  const camera = new PerspectiveCamera(
    70,
    window.innerWidth / window.innerHeight,
    0.02,
    20,
  )
  
  // Render loop
  function renderLoop(timestamp: number, frame?: XRFrame) {
    // Only render content if XR view is presenting.
    if (renderer.xr.isPresenting) {
      renderer.render(scene, camera);    
    }
  }
  
  renderer.setAnimationLoop(renderLoop);
}

我们首先调用 Scene的构造函数,它初始化一个新的空场景对象。 这个场景将容纳我们应用程序中的所有 3D 内容。

接下来,我们使用 Three.js 提供的构造函数创建一个 PerspectiveCamera。 虽然这个应用程序也使用我们从 ARButton 获得的实际设备摄像头,但我们仍然需要实例化一个摄像头,作为进入 Three.js 场景的视图。 我们传入的第一个参数是视野。 第二个是宽高比,我们通过将视口的宽度除以高度来计算。 最后,我们为近平面提供一个值,然后为远平面提供另一个值。 任何比近平面更近或比远平面更远的物体都不会被渲染,所以这里我们实际上是在定义我们允许用户离任何物体有多近或多远的边界。

在本例中,我们传入值 0.02 和 20,乍一看它们可能是任意数字。 然而,Three.js 一般对任何物理量都使用 SI 单位,对距离单位使用米。 这非常适合在 AR 中工作,因此这意味着我们将渲染场景中 0.02 到 20 米范围内的对象。

最后,我们为应用程序设置主渲染循环,然后将其传递给渲染器上的 setAnimationLoop 函数。 这将确保我们的 renderLoop 函数在每一帧都被调用,从而渲染我们的场景。

如果你熟悉浏览器中的 window.requestAnimationFrame 函数, setAnimationLoop 函数非常相似。 然而,Three.js 强烈建议在处理 WebXR 时使用后一个函数,所以这就是我们要做的。

我们现在可以在我们的设备上打开应用程序。 如果一切正常,应该仍会看到摄像头画面呈现在屏幕上。

但是你可能会注意到,场景中还没有渲染任何内容。 👀

我们现在就做点什么!

8、在场景中渲染3D网格

我答应过渲染一个考拉,我打算信守诺言。 但在我们对有袋动物着迷之前,让我们先从更简单的事情开始。

我们将在场景中渲染一个简单的立方体。 渲染立方体几乎就像 Threejs 的“Hello world”(以及一般的 3D 开发),因此你可以将其视为正式的成人礼! 🎉

更新 createScene 函数以反映以下代码:

export function createScene(renderer: WebGLRenderer) {
  const scene = new Scene()

  const camera = new PerspectiveCamera(
    70,
    window.innerWidth / window.innerHeight,
    0.02,
    20,
  )

  const boxGeometry = new BoxBufferGeometry(1, 1, 1);
  const boxMaterial = new MeshBasicMaterial({ color: 0xff0000 });
  const box = new Mesh(boxGeometry, boxMaterial);
  box.position.z = -3;

  scene.add(box);

  const renderLoop = (timestamp: number, frame?: XRFrame) => {
    // Rotate box
    box.rotation.y += 0.01;
    box.rotation.x += 0.01;

    if (renderer.xr.isPresenting) {
      renderer.render(scene, camera);
    }
  }
  
  renderer.setAnimationLoop(renderLoop);
};

我们做的第一件事是创建一个 BoxGeometry。 这是 Threejs 的预定义形状,它为我们提供了一个形状像立方体的几何对象,我们可以在创建网格时使用它。 我们传入的三个参数将按顺序定义该立方体的宽度、高度和深度。

接下来,我们为我们的立方体创建一个 MeshBasicMaterial。 我们需要一种材质,以便将我们的立方体绘制到场景中。 Threejs 提供了多种类型的材料可供使用,但 MeshBasicMaterial 不需要任何灯光让我们看到它,这让我们暂时保持简单。 构造函数接受一个配置对象来设置材质的各种属性,但你可能会注意到我们只使用了颜色属性。

此外,Three.js一般推荐颜色使用十六进制值,所以我们照做。 我们传入值 0xff0000,它为我们提供了红色。

最后,我们可以通过将几何体和材质传递给 Mesh 构造函数来创建盒子网格本身。 这将返回一个漂亮的红色立方体,我们可以将其渲染到我们的 AR 视图中。

然后我们使用场景对象上的 .add() 方法将它添加到场景中。

在使用 Three.js 时,你会经常调用此添加函数,但这也是最容易忘记的事情之一。 如果想知道为什么场景中缺少你的对象,请始终检查是否调用了 .add() 函数。

为了给这个原本简单的演示添加一点天赋,我们还将使立方体旋转。 为此,我们通过不断添加对象旋转的 x 和 y 值来在渲染方法中操纵立方体的旋转。

这意味着对于 Three.js 渲染的每一帧,立方体将在每个轴上旋转 0.01 弧度。 您可以使用此值来增加或减少旋转速度。

如果你在设备上打开该应用程序,应该会看到如下内容:

看看那个立方体! 🟥

无忧无虑地在AR空间中旋转。

想看它转得更快吗? 尝试在渲染函数中将旋转设置为 1,然后看着立方体变成香蕉! 💫

本项目的第一部分到此结束。 做得很好!
希望我已经成功激发了你对 WebXR 开发的兴趣,并让你对使用 Three.js 感到有些自在。 不要害怕自己尝试代码。 尝试改变立方体的大小,或者在场景中添加另一个立方体!
我发现这种开发是通过代码发挥创意的好方法,而且正如你现在看到的那样,它真的不必那么复杂。 一旦掌握了基础知识,就会感觉世界充满了可能性。

9、命中测试

要向应用程序添加一些交互性,我们需要实施一些基本的命中测试(Hit Test)。 对于不熟悉图形编程的人来说,这个概念可能比较陌生,那么我们先来看看hit-testing到底是什么:

“在计算机图形编程中,命中测试是确定用户控制的光标(例如鼠标光标或触摸屏界面上的触摸点)是否与屏幕上绘制的给定形状、直线或曲线相交的过程 . 这可以针对指针或基础形状的移动来完成,或者仅限于用户启动的选择,例如鼠标单击。“  — W3C 维基

在这个项目的上下文中,点击测试将使我们能够检测到一旦用户触摸屏幕,在 AR 空间中放置对象的位置。 这是迄今为止该应用程序中最复杂的部分,但我们将逐步完成该过程,以便我们可以很好地了解幕后发生的事情。

还值得一提的是,命中测试操作将在每次渲染时执行,因此我将相关代码分离到一个单独的文件中,以避免主渲染循环因太多逻辑而变得混乱。

首先打开项目文件夹中的文件 src/utils/hitTest.ts

这应该包含一个名为 handleXRHitTest 的空函数。

将下面的代码添加到函数中,让我们看看这里发生了什么。

注意:以下代码改编自 WebXR 的 Three.js官方提供的命中测试示例。 感谢!👏

export function handleXRHitTest(
  renderer: WebGLRenderer,
  frame: XRFrame,
  onHitTestResultReady: (hitPoseMatrix: Float32Array) => void,
  onHitTestResultEmpty: () => void,
) {
  const referenceSpace = renderer.xr.getReferenceSpace();
  const session = renderer.xr.getSession();

  let xrHitPoseMatrix: Float32Array | null | undefined;

  if (session && hitTestSourceRequested === false) {
    session.requestReferenceSpace("viewer").then((referenceSpace) => {
      if (session) {
        session
          .requestHitTestSource({ space: referenceSpace })
          .then((source) => {
            hitTestSource = source;
          });
      }
    });

    hitTestSourceRequested = true;
  }

  if (hitTestSource) {
    const hitTestResults = frame.getHitTestResults(hitTestSource);

    if (hitTestResults.length) {
      const hit = hitTestResults[0];

      if (hit && hit !== null && referenceSpace) {
        const xrHitPose = hit.getPose(referenceSpace);

        if (xrHitPose) {
          xrHitPoseMatrix = xrHitPose.transform.matrix;
          onHitTestResultReady(xrHitPoseMatrix);
        }
      }
    } else {
      onHitTestResultEmpty();
    }
  }
};

我们首先从渲染器上的 xrobject 获取当前参考空间和会话,然后声明一个变量来保存我们命中姿势的值。

参考空间的概念是 WebXR 中的一个重要(并且有些复杂)的概念。 幸运的是,我们可以通过表面理解来在这里使用它们。 在此应用程序的上下文中,我们可以将参考空间视为 AR 空间的局部坐标系。 设备需要了解其周围空间,以便在 Three.js 场景的坐标空间与现实世界之间进行转换。 这确保了对象在 AR 中相对于物理设备位置的正确位置呈现。

session 变量只是用来保持对当前运行的 XR 会话的方便引用,这将让我们在任何给定时刻询问用户设备的位置和方向等信息。

我们还将最后一次命中测试结果保存在变量 xrHitPoseTransformed 中,每次新的命中测试结果准备就绪时我们都会更新它。

在下一步中,我们首先检查会话是否存在以及是否已请求命中测试源。 如果这是对命中测试源的初始请求,我们会从 XR 会话中请求一个类型为“查看器”的参考空间。 查看器参考空间适用于内联体验或简单的 AR 查看器应用程序,这应该非常适合我们在这里所做的事情。

如果我们的参考空间请求成功,我们将继续从会话中请求命中测试源,并传入我们获得的参考空间。 如果成功,我们最终将命中测试源分配给变量 hitTestSource,并将 hitTestSourceRequestedflag 切换为 true。

现在,开始执行实际的命中测试! 💥

在下一步中,我们在继续之前验证是否已设置命中测试源。 如果我们有源,我们会从当前帧获取命中测试结果,并将其作为参数传递给函数。 对 getHitTestResults 的调用返回一个数组,所以我们通过查看 .length 属性来检查里面是否有任何东西。 如果此检查的计算结果为真,我们将从此数组中获得实际命中,这里它始终位于第一个位置。

下一步是另一个检查以验证命中对象不为空,并确保我们的参考空间设置正确。 整个函数中的空值检查对某些人来说似乎有点过分,但实际上这是非常必要的,因为如果应用程序与任何 XR 系统失去连接,一些 API 函数可能会突然返回空值。 我们最不想要的就是崩溃,所以这是一个更可持续的选择。

如果一切正常,我们通过在命中对象上调用 getPose() 并传入我们的参考空间来获取姿势。 这将返回命中相对于设备的位置和方向。 然后我们得到变换矩阵,最后,我们通过 onHitTestResultReady 函数返回它,这样我们就可以用它来定位场景中的对象。

诚然,整个过程有点复杂——但你可以高枕无忧,因为我们现在拥有了对应用程序执行命中测试所需的一切。

如前所述,我们将在每次渲染时调用此函数。 所以你可能已经猜到了——我们将在渲染循环中使用 hitTestHelper 函数。 现在我们已经完成了设置命中测试的困难部分,我认为我们应该走捷径。

我在项目中包含了一个函数,它将创建一个简单的圆形标记网格,我们可以将其显示在通过命中测试发现的任何表面上。 如果您曾经使用过任何其他移动 AR 应用程序,这种用户体验可能会很熟悉。

创建标记的函数应该已经导入到 scene.ts 文件中。 更新你的 createScene 函数以包含以下代码段中的所有内容。

export function createScene(renderer: WebGLRenderer) {
  const scene = new Scene()

  const camera = new PerspectiveCamera(
    70,
    window.innerWidth / window.innerHeight,
    0.02,
    20,
  )

  const planeMarker = createPlaneMarker();

  scene.add(planeMarker);

  const renderLoop = (timestamp: number, frame?: XRFrame) => {   
    if (renderer.xr.isPresenting) {

      if (frame) {
        handleXRHitTest(renderer, frame, (hitPoseTransformed: Float32Array) => {
          if (hitPoseTransformed) {
            planeMarker.visible = true;
            planeMarker.matrix.fromArray(hitPoseTransformed);
          }
        }, () => {
          planeMarker.visible = false;
        })
      }
      renderer.render(scene, camera);
    }
  }
  
  renderer.setAnimationLoop(renderLoop);
};

你会注意到第 1 部分中与旋转 Box 网格相关的代码已被删除。 这是故意的,因为我们以后不需要它。

通过对我们的 renderLoop 函数进行上述更改,我们现在对渲染循环中的每个迭代执行命中测试。

handleXRHitTest 函数有两个回调参数,一个在命中测试结果就绪时调用,一个在结果为空时调用。 有了这个,我们现在可以在 XR 系统检测到的任何表面上显示或隐藏圆形标记,具体取决于最新的命中测试是否成功。

如果命中测试成功,我们将标记网格上的可见属性设置为 true,然后我们更新标记的位置以反映命中测试的结果。 这将使它看起来好像标记正在实时跟踪地面。

另一方面,如果命中测试为空,我们只需隐藏标记以向用户指示未找到任何表面。

再次运行该应用程序,你现在应该会看到在命中测试检测到的任何表面上都呈现了一个标记。

如果没有显示任何内容,请尝试在房间内稍微移动一下,同时以圆周运动方式移动你的设备以扫描它前面的表面。 重新加载页面并重新进入 AR 视图也可能有所帮助。

既然我们有办法识别 AR 空间中的表面,是时候放生考拉了!🐨

10、将模型放置在 AR 空间中

使用像 Three.js 这样的专用库的一个好处是加载和显示预制模型等常见任务非常简单。

Three.js 为各种模型格式(如 OBJ、3DS 和 gLTF)提供了一堆不同的加载器。 我们将为此应用程序使用 gLTF 格式的模型,因为这种格式具有良好的“质量与文件大小”比率,使其成为移动体验的合理选择。

如前所述,包含的模型文件已经导入到 scene.ts 文件中,可以在我们的代码中使用了。 我还在 Three.js 中导入了 gLTF 加载器,我们将使用它来加载我们的模型。 让我们从这样做开始吧。

createScene 函数中添加以下行。

let koalaModel: Object3D;
  
  const gltfLoader = new GLTFLoader();

  gltfLoader.load("../assets/models/koala.glb", (gltf: GLTF) => {
    koalaModel = gltf.scene.children[0];
  });

在第一行,我们声明了一个变量来保存我们的考拉模型。

然后我们创建一个新的 GLTFLoader 实例,然后我们用它来加载我们的模型。 加载函数中的第一个参数是模型位置的 URL。 第二个参数是 onLoad 回调,模型准备就绪后调用的函数。 这里我们简单地创建一个匿名函数,它允许我们从加载的资源中获取数据。 在这个函数中,我们最终将模型分配给之前声明的变量。

在这个特定的 gLTF 资产中,我们将使用的模型存储为 scene.children 上的第一个孩子。 gLTF 文件可以包含一堆不同的数据,例如动画、相机和场景。 因此,了解你的资产是如何构成的很重要。 如果你使用的是下载的 gLTF 格式模型,可以使用 BabylonJS 中这个方便的沙箱应用程序来检查文件并弄清楚它是如何组合在一起的:

11、添加 WebXR 控制器

在我们对 AR 视图上执行的触摸事件做出反应之前,我们需要设置一个 WebXR 控制器并将一个侦听器附加到“选择”事件,每当用户在 AR 模式下点击屏幕时都会调用该事件。

首先在 createScene 函数中添加以下行:

  const controller = renderer.xr.getController(0);
  scene.add(controller);

然后,在刚刚创建的控制器上为“select”事件添加一个新的事件监听器,并像这样定义伴随的监听器函数:

 controller.addEventListener("select", onSelect);

  function onSelect() {
    if (planeMarker.visible) {
      const model = koalaModel.clone();

      model.position.setFromMatrixPosition(planeMarker.matrix);

      // Rotate the model randomly to give a bit of variation to the scene.
      model.rotation.y = Math.random() * (Math.PI * 2);
      model.visible = true;

      scene.add(model);
    }
  }

onSelect 函数中,我们首先检查标记当前是否正在显示。 如果是,我们可以假设最新的命中测试结果代表了放置我们模型的有效表面的位置。 然后我们创建一个我们之前加载的模型的克隆,并为其分配标记的位置。 我们还在 y 轴上随机旋转模型,这样如果我们放置多个模型,模型就不会全部面向同一个方向。 这是为场景提供一些变化的简单但有效的方法。

最后,我们确保模型在添加到场景之前是可见的。

现在可以尝试运行该应用程序,但你很快就会注意到我们缺少一个关键部分。 由于我们没有在场景中添加任何灯光,因此考拉呈现出一种漆黑的颜色!

那不行——我们需要一盏灯! 💡

幸运的是,这是一个简单的修复。 可以使用以下两行从 Three.js 添加一个简单的 AmbientLight 到场景中:

const ambientLight = new AmbientLight(0xffffff, 1.0);
scene.add(ambientLight);

AmbientLight 将为场景提供全局且均匀分布的照明。 我们将白色作为第一个参数传入,并将强度设置为 1。这将确保我们的模型在渲染时具有清晰明快的外观,并且我们的考拉应该最终展现出它们的光彩。

现在,如果运行该应用程序,应该能够扫描环境并将可爱的小考拉熊放在房间周围。 🐨

它应该看起来像这样:

12、结束语

如果你做到了这一点——恭喜你! 🎉

你现在已经完成了使用 WebXR API 和 Three.js 创建 AR 应用程序所需的步骤。

如果这个项目激起了你对 AR 和 WebXR 的兴趣,请随意使用这个项目作为创建你自己的 AR 体验的基础。 你可以从制作考拉动画开始! 或者让他们以某种方式互动!

你还可以轻松地将考拉模型换成完全不同的东西,只需加载不同的模型文件即可。 你宁愿养狗吗? 或者也许是一件家具,看看它在您客厅里的样子? 去野猪!

我还想提一下,这个小应用程序只展示了 Three.js 的一小部分功能。 该库能够创建强大而复杂的 3D 体验,如果你喜欢这种 3D 小尝试,我强烈建议你查看 Three.js 文档以进一步探索该库。 我将在下面留下一些指向其他材料的链接。

如果你想查看完整的项目代码,可以在项目存储库的complete分支上找到:


原文链接:Get started with Augmented Reality on the web using Three.js and WebXR

BimAnt翻译整理,转载请标明出处