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

1. 事前準備

本程式碼研究室將介紹建構 AR 網頁應用程式的範例,使用 JavaScript 算繪 3D 模型,讓模型看起來就像存在於現實世界中。

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

什麼是 AR?

AR 一詞通常用來描述電腦產生的圖像與現實世界的混合。以手機為基礎的擴增實境為例,這表示將電腦繪圖放置在即時攝影機畫面上。為了讓此效果在手機移動時保持真實,支援 AR 的裝置需要瞭解移動中的世界,並判斷其在 3D 空間中的姿勢 (位置和方向)。這可能包括偵測表面和估算環境照明。

自 Google 推出 ARCore 和 Apple 推出 ARKit 後,無論是自拍濾鏡或 AR 遊戲,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 的 Web Server。
    如果工作站上沒有安裝該應用程式,請前往 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 forwarding...」chrome://inspect
  2. 使用「Port forwarding settings」對話方塊,將通訊埠 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 支援情形

使用者在使用 AR 前,請先檢查 navigator.xr 是否存在,以及是否有必要的 XR 功能。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)

當您點選「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

在每個影格中,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。你應該會看到攝影機畫面,其中有浮在空間中的立方體,當你移動裝置時,立方體的視角也會改變。移動次數越多,追蹤功能就會越準確,因此請探索最適合你和裝置的追蹤方式。

如果您在執行應用程式時遇到問題,請參閱「簡介」和「設定開發環境」部分。

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 中載入十字準星模型的作業。
  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. 其他資源

恭喜!您已完成本程式碼研究室的 AR 相關內容。

瞭解詳情