在實際檢視畫面中放置虛擬物件

Hit Test API 可讓您在實際檢視畫面中放置虛擬商品,

喬梅利
Joe Medley

WebXR Device API 於 Chrome 79 去年上市。如前所述,Chrome 的 API 實作仍在開發階段。Chrome 很高興在此宣布 部分工作已完成Chrome 第 81 版推出了兩項新功能:

本文將介紹 WebXR Hit Test API,也就是將虛擬物件放在實際的相機檢視畫面中。

在本文中,我們假設您已瞭解如何建立擴增實境工作階段,也知道如何執行影格迴圈。如果您不熟悉這些概念,請參閱本系列先前的文章。

沉浸式 AR 工作階段範例

本文中的程式碼是以沉浸式網路工作團體命中測試範例 (示範來源) 中的為基礎,但並非完全相同。這個範例能讓您將虛擬向日葵放在現實世界的表面上。

首次開啟應用程式時,您會看到中間有一個圓點的藍色圓圈。 圓點是指虛線從裝置到環境點之間的交集。並隨著裝置移動而移動。找到交集點時,它看起來就能貼合平面,例如樓層、桌面和牆壁。這是因為命中測試會提供交集點的位置和方向,但沒有與介面本身有關的位置和方向。

這個圓形稱為「路徑」,這是臨時圖像,有助於在擴增實境中放置物件。如果輕觸螢幕,無論輕觸螢幕的位置為何,太陽花都會放在表面位置和位置方向的向日葵。回應會持續隨著裝置移動。

展示在牆上、Lax 或 Strict 上的搞笑故事 (視情境而定)
使用中的臨時圖片有助於在擴增實境中放置物件。

建立朗誦

您必須自行建立 Recap 映像檔,因為該映像檔並非由瀏覽器或 API 提供。載入及繪圖的方法因架構而異。如果您不是使用 WebGL 或 WebGL2 直接繪製繪圖,請參閱架構說明文件。因此,我不會詳細說明樣本中繪製週期的方式。我在下方只顯示這一行原因之一:這樣一來,在之後的程式碼範例中,您就能瞭解使用 reticle 變數時指的是什麼。

let reticle = new Gltf2Node({url: 'media/gltf/reticle/reticle.gltf'});

要求課程

要求工作階段時,您必須在 requiredFeatures 陣列中要求 'hit-test',如下所示。

navigator.xr.requestSession('immersive-ar', {
  requiredFeatures: ['local', 'hit-test']
})
.then((session) => {
  // Do something with the session
});

進入工作階段

我在先前的文章中提到了用來進入 XR 工作階段的程式碼,我在下方示範了這個版本,還有一些新增項目首先,我已新增 select 事件監聽器。使用者輕觸螢幕後,系統會根據位置的姿勢,在攝影機檢視畫面中放置一朵花。我稍後會說明該事件監聽器。

function onSessionStarted(xrSession) {
  xrSession.addEventListener('end', onSessionEnded);
  xrSession.addEventListener('select', onSelect);

  let canvas = document.createElement('canvas');
  gl = canvas.getContext('webgl', { xrCompatible: true });

  xrSession.updateRenderState({
    baseLayer: new XRWebGLLayer(session, gl)
  });

  xrSession.requestReferenceSpace('viewer').then((refSpace) => {
    xrViewerSpace = refSpace;
    xrSession.requestHitTestSource({ space: xrViewerSpace })
    .then((hitTestSource) => {
      xrHitTestSource = hitTestSource;
    });
  });

  xrSession.requestReferenceSpace('local').then((refSpace) => {
    xrRefSpace = refSpace;
    xrSession.requestAnimationFrame(onXRFrame);
  });
}

多個參考空間

請注意,醒目顯示的程式碼會呼叫 XRSession.requestReferenceSpace() 兩次。一開始我覺得很困惑。我詢問為何命中測試程式碼未要求動畫影格 (啟動影格迴圈),以及影格迴圈為何不涉及命中測試。誤解的起因是誤解參照空間。參照空間可用來表達來源和世界之間的關係。

如要瞭解此程式碼的運作情形,請假設您是使用獨立設備查看這個範例,同時擁有頭戴式裝置和控制器。如要測量與控制器的距離,應使用控制器置中的參照影格。不過,如要在螢幕上繪製內容,建議您使用以使用者為中心的座標。

在本範例中,檢視器和控制器是同一部裝置。但我遇到問題我繪製的東西必須根據環境保持穩定,但我繪製的「控制器」正在移動

繪製圖片時,我會使用 local 參考空間,這樣就能在環境方面提供穩定性。完成這項操作後,請呼叫 requestAnimationFrame() 來啟動影格迴圈。

針對命中測試,我使用 viewer 參考空間,此空間以在命中測試期間的裝置姿勢為依據。「viewer」標籤在這種情境中不太容易理解 因為我其實是說控制器你可以將控制器視為電子檢視器完成上述步驟後,我呼叫 xrSession.requestHitTestSource(),建立繪圖時要使用的命中測試資料來源。

執行影格迴圈

requestAnimationFrame() 回呼也會取得新程式碼,用來處理命中測試。

移動裝置時,裝置在嘗試尋找錶面時,必須跟著裝置移動。為了營造動作的幻覺,請在每幅畫面中重新畫出奶子。 但如果命中測試失敗,請不要顯示謎題。針對我先前建立的程式碼,並將其 visible 屬性設為 false

function onXRFrame(hrTime, xrFrame) {
  let xrSession = xrFrame.session;
  xrSession.requestAnimationFrame(onXRFrame);
  let xrViewerPose = xrFrame.getViewerPose(xrRefSpace);

  reticle.visible = false;

  // Reminder: the hitTestSource was acquired during onSessionStart()
  if (xrHitTestSource && xrViewerPose) {
    let hitTestResults = xrFrame.getHitTestResults(xrHitTestSource);
    if (hitTestResults.length > 0) {
      let pose = hitTestResults[0].getPose(xrRefSpace);
      reticle.visible = true;
      reticle.matrix = pose.transform.matrix;
    }
  }

  // Draw to the screen
}

如要在 AR 中繪製任何內容,我必須知道檢視器的位置和所處位置。因此,我測試了 hitTestSourcexrViewerPose 是否仍然有效。

function onXRFrame(hrTime, xrFrame) {
  let xrSession = xrFrame.session;
  xrSession.requestAnimationFrame(onXRFrame);
  let xrViewerPose = xrFrame.getViewerPose(xrRefSpace);

  reticle.visible = false;

  // Reminder: the hitTestSource was acquired during onSessionStart()
  if (xrHitTestSource && xrViewerPose) {
    let hitTestResults = xrFrame.getHitTestResults(xrHitTestSource);
    if (hitTestResults.length > 0) {
      let pose = hitTestResults[0].getPose(xrRefSpace);
      reticle.visible = true;
      reticle.matrix = pose.transform.matrix;
    }
  }

  // Draw to the screen
}

現在我呼叫了 getHitTestResults()。它會將 hitTestSource 做為引數,並傳回 HitTestResult 例項的陣列。命中測試可能會找到多個途徑。陣列中的第一個函式是最接近相機的那一個。大部分時間都會使用,但系統會針對進階用途傳回陣列。舉例來說,假設攝影機目前指向地板上桌子上的盒子。命中測試可能會傳回陣列中的所有三個途徑。在大多數情況下,就是我最關注的包裝盒。如果傳回的陣列長度為 0,換句話說,如果未傳回命中測試,請繼續。請在下一個畫面中再試一次。

function onXRFrame(hrTime, xrFrame) {
  let xrSession = xrFrame.session;
  xrSession.requestAnimationFrame(onXRFrame);
  let xrViewerPose = xrFrame.getViewerPose(xrRefSpace);

  reticle.visible = false;

  // Reminder: the hitTestSource was acquired during onSessionStart()
  if (xrHitTestSource && xrViewerPose) {
    let hitTestResults = xrFrame.getHitTestResults(xrHitTestSource);
    if (hitTestResults.length > 0) {
      let pose = hitTestResults[0].getPose(xrRefSpace);
      reticle.visible = true;
      reticle.matrix = pose.transform.matrix;
    }
  }

  // Draw to the screen
}

最後,我需要處理命中測試結果。基本流程從命中測試結果取得姿勢,將可組合項圖片轉換 (移動) 至命中測試位置,然後將其 visible 屬性設為 true。這個姿勢代表表面上某個點的姿勢。

function onXRFrame(hrTime, xrFrame) {
  let xrSession = xrFrame.session;
  xrSession.requestAnimationFrame(onXRFrame);
  let xrViewerPose = xrFrame.getViewerPose(xrRefSpace);

  reticle.visible = false;

  // Reminder: the hitTestSource was acquired during onSessionStart()
  if (xrHitTestSource && xrViewerPose) {
    let hitTestResults = xrFrame.getHitTestResults(xrHitTestSource);
    if (hitTestResults.length > 0) {
      let pose = hitTestResults[0].getPose(xrRefSpace);
      reticle.matrix = pose.transform.matrix;
      reticle.visible = true;

    }
  }

  // Draw to the screen
}

設定物件

使用者輕觸螢幕時,物件會放置在 AR 中。我已將 select 事件處理常式新增至工作階段。(請參閱上方。)

此步驟的重點,就是知道要在哪裡放置這個模型。由於動態項目可讓您穩定執行命中測試,因此放置物件最簡單的方法就是在上次命中測試的組合位置繪製物件。

function onSelect(event) {
  if (reticle.visible) {
    // The reticle should already be positioned at the latest hit point,
    // so we can just use its matrix to save an unnecessary call to
    // event.frame.getHitTestResults.
    addARObjectAt(reticle.matrix);
  }
}

結語

如要取得處理方式,最佳做法是逐步執行程式碼範例,或是試用程式碼研究室。希望你有足夠的背景資訊能理解。

我們目前在建構沉浸式網路 API 的過程中,並非漫無目的。我們也會同步在此處發布新的文章。

相片來源:Daniel FrankUnsplash 網站上提供的相片