使用 WebXR Device API 建構擴增實境 (AR) 應用程式

1. 事前準備

本程式碼研究室將逐步說明如何建構 AR 網頁應用程式。它使用 JavaScript 來呈現 3D 模型,彷彿它們出現在真實世界中。

您將使用結合 AR 和虛擬實境 (VR) 功能的 WebXR Device API。您著重於 WebXR Device API 的 AR 擴充功能,建構在互動式網路上執行的簡易 AR 應用程式。

什麼是 AR?

AR 這個術語通常用於描述電腦合成圖像與現實世界間的融合。以手機為基礎的 AR 而言,就像是將電腦圖形放在即時鏡頭畫面上方,令人信服。為了讓這種效果在手機隨著世界移動時保持逼真的效果,支援 AR 的裝置必須瞭解這行蹤的世界,並判斷其在 3D 空間中的姿勢 (位置和方向)。包括偵測表面及預估環境照明。

無論是自拍濾鏡還是 AR 遊戲,Google 推出 ARCore 和 Apple 的 ARKit 後,目前已廣受應用程式的應用方式使用 AR。

建構項目

在本程式碼研究室中,您會建構一個網頁應用程式,透過擴增實境技術將模型放在實際環境中。您的應用程式將會:

  1. 使用目標裝置的感應器判斷並追蹤裝置在世界中的位置和方向
  2. 在即時鏡頭畫面中算繪合成的 3D 模型
  3. 執行命中測試,將物件放在真實世界中發現的表面上

課程內容

  • 如何使用 WebXR Device API
  • 如何設定基本 AR 場景
  • 如何使用 AR 命中測試找出途徑
  • 如何載入與算繪與真實鏡頭畫面同步的 3D 模型
  • 如何根據 3D 模型算繪陰影

本程式碼研究室著重於 AR API,我們不會對與本主題無關的概念和程式碼區塊介紹,您也會在對應的存放區程式碼中取得。

軟硬體需求

請按一下 AR 裝置上的「試試看」,試試本程式碼研究室的第一步。如果頁面顯示「您的瀏覽器沒有 AR 功能」訊息,請檢查 Android 裝置是否已安裝 Google Play 服務 - AR 適用。

2. 設定開發環境

下載程式碼

  1. 點選下方連結,即可在工作站上下載這個程式碼研究室的所有程式碼:

  1. 將下載的 ZIP 檔案解壓縮。這會將根資料夾 (ar-with-webxr-master) 解壓縮,根資料夾包含了本程式碼研究室數個步驟的目錄,以及所有必要資源。

step-03step-04 資料夾包含本程式碼研究室第三和第四步驟所需的結束狀態,以及 final 結果。這些資訊僅供參考。

您可以在 work 目錄中執行所有編碼工作。

安裝網路伺服器

  1. 您可以自由使用自己的網路伺服器。如果您尚未設定網路伺服器,請參閱本節,進一步瞭解如何設定 Chrome 的網路伺服器。
    如果工作站上沒有安裝該應用程式,請前往 Chrome 線上應用程式商店進行安裝。

  1. 安裝 Chrome 專用網路伺服器後,請前往 chrome://apps 並按一下「網路伺服器」圖示:

「網路伺服器」圖示

您接下來會看到這個對話方塊,可讓您設定本機網路伺服器:

設定 Chrome 網路伺服器

  1. 按一下「選擇資料夾」,然後選取 ar-with-webxr-master 資料夾。這樣一來,您就能透過網路伺服器對話方塊中 (位於「網路伺服器網址」部分) 中醒目顯示的網址,提供處理中的工作。
  2. 在「選項 (需要重新啟動)」下方,勾選「自動顯示 index.html」核取方塊。
  3. 將「網路伺服器」切換為「停止」,然後再返回「啟動」重新啟動 Chrome 網路伺服器
  4. 確認出現至少一個網路伺服器網址:http://127.0.0.1:8887(預設的 localhost 網址)。

設定通訊埠轉送

設定您的 AR 裝置,讓裝置在您造訪 localhost:8887 時,存取工作站上的同一個通訊埠。

  1. 在開發工作站中,前往 chrome://inspect,然後按一下「Port forward...」(通訊埠轉送...)chrome://inspect
  2. 使用「通訊埠轉送設定」對話方塊,將通訊埠 8887 轉送至 localhost:8887。
  3. 選取「啟用通訊埠轉送」核取方塊:

設定通訊埠轉送

驗證設定

測試連線:

  1. 使用 USB 傳輸線將 AR 裝置連接至工作站。
  2. 在 Chrome 的 AR 裝置上,於網址列輸入 http://localhost:8887。您的 AR 裝置應將這項要求轉送至開發工作站的網路伺服器。畫面上應會顯示檔案目錄。
  3. 在 AR 裝置上按一下 step-03,即可在瀏覽器中載入 step-03/index.html 檔案。

您應該會看到含有「啟動擴增實境」按鈕的頁面

不過,如果顯示「不支援的瀏覽器」錯誤頁面,表示您的裝置可能不相容。

支援 ARCore

不支援 ARCore

您的網路伺服器連線現在應該會與 AR 裝置搭配運作。

  1. 按一下「啟動擴增實境」。系統可能會提示您安裝 ARCore。

安裝 ARCore 提示

首次執行 AR 應用程式時,您會看到相機權限提示。

Chrome 要求相機權限權限對話方塊

一切都準備就緒後,攝影機畫面上方就會顯示方塊的場景。隨著攝影機所剖析的世界越來越多,場景理解能力將能提升,因此移動時有助於讓物體穩定下來。

3. 設定 WebXR

在這個步驟中,您將瞭解如何設定 WebXR 工作階段和基本的 AR 場景。HTML 網頁包含 CSS 樣式和 JavaScript,可用來啟用基本 AR 功能。這可以加快設定程序,讓程式碼研究室能專注於 AR 功能。

HTML 網頁

您可以在傳統網頁中運用現有的網路技術,打造 AR 體驗。在這個體驗中,您使用的是全螢幕算繪畫布,因此 HTML 檔案可以不用過於複雜。

AR 功能必須透過使用者手勢啟動,因此有些 Material Design 元件可顯示「Start AR」按鈕和不支援的瀏覽器訊息,

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 支援情形

使用者必須先確認 navigator.xr 是否存在,以及必要的 XR 功能,才能開始使用 AR。navigator.xr 物件是 WebXR Device API 的進入點,因此如果裝置相容,則應存在。此外,請確認支援 "immersive-ar" 工作階段模式。

如果一切順利,請按一下「進入擴增實境」按鈕,嘗試建立 XR 工作階段。否則,系統會呼叫 onNoXRDevice() (在 shared/utils.js 中),並顯示訊息指出缺少 AR 支援。

這個程式碼已經在 app.js 中,因此不需要修改。

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

要求 XRSession

當您點選「進入擴增實境」時,程式碼會呼叫 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

在每個影格,onXRFrame 都會使用時間戳記和 XRFrame 呼叫。

  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。你應該在攝影機畫面中看到方塊飄在太空中,而且視角會隨著你移動裝置而改變。追蹤功能可改善你四處移動的過程,協助你找出適合自己的和裝置。

如果無法順利執行應用程式,請參閱「Introduction」和「設定開發環境」章節。

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 疊加層。將 document.body 元素疊加在 AR 相機檢視畫面上,如下所示:
this.xrSession = await navigator.xr.requestSession("immersive-ar", {
  requiredFeatures: ["hit-test", "dom-overlay"],
  domOverlay: { root: document.body }
});

新增動態提示

ARCore 在建構對環境有充分的瞭解時效果最佳。這個過程是透過同時本地化和對應 (SLAM) 的程序達成,而這個程序會使用明顯不同的特徵點,計算位置和環境特徵的變動。

使用上一個步驟中的 "dom-overlay",在攝影機串流畫面頂端顯示動作提示。

<div> 新增至 ID 為 stabilizationindex.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 中載入 Reticle 模型。
  2. Reticle 新增至 setupThreeJs() 中的場景:
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 沒有足夠時間深入瞭解環境。在這種情況下,請使用穩定器 <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 的物件,這是一種僅算繪陰影的平面水平表面。這個介面一開始會有 10,000 個單位的 Y 位置。放置了向日葵後,請將 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 作業。

瞭解詳情