擴增實境:你可能早就知道

如果您已使用 WebXR Device API,就已完成大部分工作。

Joe Medley
Joe Medley

WebXR Device API 已在去年秋季的 Chrome 79 版推出。如先前所述,Chrome 的 API 實作功能仍在開發中。部分 Chrome 作業已完成 Chrome 很高興在此宣布Chrome 第 81 版推出了兩項新功能:

本文將說明擴增實境。如果您已用過 WebXR Device API,知道沒什麼新知。進入 WebXR 工作階段的步驟大致相同。執行影格迴圈的做法大致相同。差別在於允許在擴增實境中正確顯示內容的設定。如果您不熟悉 WebXR 的基本概念,請參閱我先前發布的 WebXR Device API 相關文章,或至少熟悉其中的內容。您應該瞭解如何要求及進入工作階段,以及如何執行影格迴圈

如要進一步瞭解命中測試,請參閱相關文章「在實際檢視畫面中定位虛擬物件」。本文的程式碼是根據 Immersive Web Working Group 的 WebXR Device API 範例,採用沉浸式 AR 工作階段範例 (示範來源) 所編寫。

開始編寫程式碼前,請至少使用一次沉浸式 AR 工作階段範例。你必須使用搭載 Chrome 81 以上版本的新型 Android 手機。

這項功能有什麼用途?

擴增實境將成為許多現有或新網頁的寶貴附加功能,讓這些網頁不必離開瀏覽器就能實作 AR 用途。舉例來說,這項功能可協助使用者在教育網站上學習,也能讓潛在買家在購物時,將家中的物品以圖像呈現。

請考慮第二種用途。想像一下,在真實場景中模擬放置虛擬物件的真實大小。放置後,圖片會停留在所選表面上,並顯示實際物品在該表面上的大小,讓使用者可以移動圖片,以及靠近或遠離圖片。比起使用 2D 圖片,這樣可讓觀眾更深入瞭解物件。

我好像講得太快了。如要實際執行上述說明的操作,您需要 AR 功能和一些偵測表面的方式。本文將說明前者。關於 WebXR 命中測試 API 的隨附文章 (上方已提供連結) 將說明後者。

要求工作階段

要求工作階段的流程與您先前看到的非常相似。首先,請呼叫 xr.isSessionSupported(),瞭解目前裝置是否支援所需的工作階段類型。您應要求 'immersive-ar',而非像之前一樣要求 'immersive-vr'

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 Working Group 的 WebXR Device API 範例提供了更多功能和用途。我們也剛發布一篇命中測試文章,說明用於偵測表面並在實際相機檢視畫面中放置虛擬項目的 API。歡迎前往查看,並觀看 web.dev 網誌,瞭解今年還會提供哪些文章。

David Grandmougin 拍攝的 Unsplash 相片