您可以使用命中測試 API,將虛擬項目置於實際檢視畫面中。
WebXR Device API 預計於 Chrome 79 推出。如先前所述,Chrome 的 API 實作功能仍在開發中。Chrome 很高興宣布部分工作已完成。Chrome 81 推出了兩項新功能:
本文將介紹 WebXR Hit Test API,也就是如何在實際相機檢視畫面中放置虛擬物件。
在本文中,我假設您已瞭解如何建立擴增實境工作階段,也知道如何執行影格迴圈。如果您不熟悉這些概念,請閱讀本系列的先前文章。
沉浸式 AR 工作階段範例
本文中的程式碼是以 Immersive Web Working Group 命中測試範例 (示範、來源) 為基礎,但並非完全相同。這個範例可讓您在現實世界的表面上放置虛擬的向日葵。
首次開啟應用程式時,您會看到一個中間有圓點的藍色圓圈。圓點是從裝置到環境中某個點的假想線的交點。隨著裝置移動而移動。當它找到交會點時,就會對齊地板、桌面和牆壁等表面。這是因為命中測試會提供交叉點的位置和方向,但不會提供任何關於表面本身的資訊。
這個圓形稱為十字準線,是一種暫時圖像,可協助將物件放置在擴增實境中。無論您輕觸螢幕的哪個位置,都會在表面上放置一個太陽花,位置和方向皆與十字準星點相同。十字準線會隨著裝置移動。
創造故事
瀏覽器或 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
參照空間,該空間會根據觸發測試時的裝置姿勢。在這個情況下,使用「觀眾」這個標籤會造成混淆,因為我指的是控制器。您可以將控制器想像成電子檢視器,這是合理的做法。取得後,我呼叫 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 中繪製任何內容,我必須知道觀看者的位置和他們正在觀看的位置。因此,我測試 hitTestSource
和 xrViewerPose
是否仍有效。
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 Frank 在 Unsplash 網站上提供