空間和參考空間:WebXR 中的空間跟蹤
用於實現增強現實和虛擬現實的 WebXR API 專門設計用於提供將人插入虛擬環境的能力。為此,軟體不僅需要能夠跟蹤虛擬世界中物體的位置、方向和運動,還需要能夠跟蹤使用者的位置、方向和運動。但 WebXR 不僅如此,它還增加了跟蹤輸入裝置的位置、方向和運動的能力,這些裝置生成的資料用於確定觀看者身體各個部分的姿態和運動(透過適當的裝置)。
使用者頭戴裝置的位置和運動代表了他們在虛擬環境中的頭部姿態和方向。手部控制器以同樣的方式代表他們的手。其他硬體元素也可以類似地用於代表身體的其他部分,在模擬使用者在環境中的動作時提供額外的資料。
在本指南中,我們將探討 WebXR 如何使用空間,更具體地說,是參考空間,來跟蹤虛擬世界中物體和使用者身體的位置、方向和運動。
注意:本文假定您熟悉WebXR 中的幾何和參考空間中介紹的概念:即 3D 座標系統的基礎知識,以及 WebXR 空間、參考空間,以及如何使用參考空間為場景中的單個物件或可移動元件建立區域性座標系統。
使用參考空間表示位置
如使用參考空間定義空間關係中所述,參考空間建立一個區域性座標系,該座標系從另一個本身由某個其他空間定義的座標系偏移。因此,參考空間可用於定義一個點的位置和方向,並以此推及該點作為原點的整個物件。雖然這對於場景中的每個物件來說有點大材小用,但對於少數特定物件以這種方式擁有自己的座標系來說非常有用。
- 世界空間;這個空間的原點是整個 3D 畫布底層的 WebGL 座標系統的原點。
- 玩家、頭像或相機;這個空間的原點用作渲染要顯示給使用者的場景的相機位置。
- 手和/或手部控制器;每個都代表使用者的一隻手,可以是手本身或控制器(或兩者)。原點通常是使用者握拳手的中心。
- 目標射線;每個控制器或其他手持裝置都可能有一個與之關聯的瞄準射線,它由一個空間表示,該空間的原點位於控制器上發射射線的位置,並定向使得 -Z 沿其指向的目標方向延伸。
因為這些都以參考空間為基礎進行定義,所以 WebXR Device API 可以輕鬆地在座標系之間進行轉換,執行影響這些空間及其相應物件的操作,等等。
描述相對於空間的位置
在兩種情況下,您可能需要描述相對於空間的位置和/或方向。第一種情況如上文所述:將參考空間應用於偏移(反之亦然,因為結果相同)以確定表示該空間座標系中結果位置的變換矩陣。
姿態
一旦為場景中的各種關鍵物件建立了參考空間,有時您需要描述相對於特定參考空間原點的另一個位置。這透過姿態來完成。簡而言之,姿態描述了相對於建立它的參考空間原點的位置和方向。
在 WebXR 中,姿態由一個 XRPose 物件表示,其 transform 屬性是一個 XRRigidTransform,定義了應用到原始空間中的任何座標、向量或矩陣時,將其轉換為目標空間的變換矩陣。因此,姿態不僅可以用於轉換和確定位置,還可以用於旋轉資訊。
建立 XRPose 的唯一方法是使用動畫幀上的 getPose() 方法,該方法透過 XRFrame 物件提供。這意味著最常見的情況是,您將在幀渲染程式碼中使用姿態,該程式碼作為 XRFrame 方法 requestAnimationFrame() 的回撥執行。
getPose() 計算 XRReferenceSpace 相對於指定 XRSpace 原點的位置,然後建立表示結果位置和方向的姿態。
例如,如果您希望使用控制器的 gripSpace 繪製手部控制器的表示,您可以像這樣獲取所需的姿態:
let controlPose = frame.getPose(inputSource.gripSpace, worldRefSpace);
這會將輸入手柄空間的位置和方向轉換為使用世界座標系,然後生成相應的 XRPose,並將其儲存在 controlPose 中。然後,您可以將 controlPose 的 transform 應用於表示控制器的物件模型中的頂點,以計算在渲染控制器表示到幀緩衝區時要使用的 WebGL 座標。
檢視器姿態
一種特殊的姿態型別,檢視器姿態,表示場景觀看者的視角。檢視器姿態由 WebXR 的 XRViewerPose 介面表示。渲染幀時,您將使用檢視器姿態來確定觀看者的位置和朝向方向,以便放置虛擬相機並渲染場景。
獲取將位置資訊從一個空間適應到另一個空間的姿態的唯一方法是透過您在呼叫 XRSession 方法 requestAnimationFrame() 時指定的幀渲染回撥函式接收到的 XRFrame 物件。
例如,給定一個參考空間為 worldRefSpace 的 XRSession,以下程式碼行將請求排程第一個動畫幀:
animationFrameRequestID = xrSession.requestAnimationFrame(myDrawFrame);
然後,myDrawFrame() 函式——在需要繪製幀時執行的回撥——可能類似於這樣:
function myDrawFrame(currentFrameTime, frame) {
let session = frame.session;
let viewerPose = frame.getViewerPose(viewerRefSpace);
animationFrameRequestID = session.requestAnimationFrame(myDrawFrame);
if (viewerPose) {
// render the frame
}
}
frame 引數是表示 WebXR 提供的動畫幀資訊的 XRFrame。呼叫時,此函式首先從幀物件獲取 XRSession,然後使用幀的 getViewerPose() 方法計算給定 viewerRefSpace 的 XRViewerPose,該引數描述了檢視器的當前朝向方向和位置。
返回的檢視器姿態 viewerPose 反過來可用於計算適當渲染場景中物件的位置和方向,具體取決於使用者的視角。
偏移或移動參考空間
雖然您無法更改參考空間,因為 XRReferenceSpace 和 XRBoundedReferenceSpace 都是隻讀的,但您可以透過對它們應用偏移變換來輕鬆建立新的參考空間。這透過呼叫參考空間的 getOffsetReferenceSpace() 方法來完成。
在參考空間內偏移位置
使用 getOffsetReferenceSpace() 最簡單的情況是在同一空間的上下文中轉換點或矩陣。例如,要建立一個新的參考空間,將參考空間 aRefSpace 在每個方向上移動半米,您可以這樣做:
let halfMeterTransform = new XRRigidTransform({
x: 0.5,
y: 0.5,
z: 0.5,
w: 1.0,
});
aRefSpace = aRefSpace.getOffsetReferenceSpace(halfMeterTransform);
這會將現有參考空間 aRefSpace 替換為一個其座標和方向已應用變換 halfMeterTransform 的參考空間。由於變換中不包含方向資料,因此 aRefSpace 的方向不受影響。
在 WebXR 會話型別之間轉換
您可能需要將位置資訊從一個參考空間轉換到另一個參考空間的另一個常見原因是,當需要將會話型別從 inline 更改為 immersive-vr 或反向更改時。這通常發生在您的使用者介面提供了一種在網頁上下文中預覽場景的方式,並帶有按鈕或其他控制元件來切換到沉浸模式。
由於大多數使用者希望在執行此轉換時保持相同的檢視器位置和朝向方向,因此您可以使用 XRFrame 方法 getViewerPose() 來獲取當前的 XRViewerPose,切換會話,然後使用儲存的檢視器姿態來恢復檢視器的位置和朝向。
let viewerPose = frame.getViewerPose(worldReferenceSpace);
let newSession = navigator.xr.requestSession("immersive-vr", {
requiredFeatures: "unbounded",
});
worldReferenceSpace = await newSession.requestReferenceSpace("unbounded");
viewerPose = worldReferenceSpace.getOffsetReferenceSpace(viewerPose.transform);
在這裡,獲取檢視器姿態,其變換相對於當前會話的全域性參考空間 worldReferenceSpace 定義。然後建立一個新的會話並建立一個參考空間作為新的世界參考空間。
最後,透過呼叫新參考系統的 getOffsetReferenceSpace() 方法,將儲存的 viewerPose 轉換為新世界空間的座標系。有了這個,我們可以像往常一樣恢復場景渲染,檢視器的視角不受影響。
在有界空間和無界空間之間轉換
有時當您的主要體驗使用無界空間時,您可能需要暫時將使用者的體驗過渡到有界空間。例如,透過切換到代表單個房間的有界空間,可能更容易實現在房屋單個房間中與物件的互動。這可能使得更容易實現定製牆壁、在地板上放置傢俱等功能。
在這種情況下,當您需要開始使用與您一直使用的不同的參考空間進行跟蹤時,您可以結合使用 getViewerPose() 和一些矩陣計算來將場景中的所有內容都基於新的參考幀原點。
由於 getViewerPose() 僅在 XRFrame 中可用,您需要在渲染回撥中開始此過程,可能使用 worker 進行計算以減少掉幀。
let previousViewerPose = null;
function myDrawFrame(currentFrameTime, frame) {
let session = frame.session;
let viewerPose = frame.getViewerPose(viewerRefSpace);
animationFrameRequestID = session.requestAnimationFrame(myDrawFrame);
if (viewerPose) {
previousViewerPose ??= viewerPose;
let offsetMatrix = mat4.create();
mat4.sub(
offsetMatrix,
previousViewerPose.transform.matrix,
viewerPose.transform.matrix,
);
previousViewerPose = viewerPose;
}
}
跟蹤丟失後的連續性和恢復
有時,當用戶正在積極地將他們的 XR 硬體與您的應用程式一起使用時,包含使用者位置和方向更新的資料流可能會丟失一段時間。您的應用程式不僅需要確定在此期間向用戶顯示什麼,還需要在跟蹤恢復時乾淨地恢復。
一旦 XR 硬體開始提供跟蹤資訊,它將繼續這樣做直到 XR 會話關閉。此資料在每幀中透過呼叫 XRFrame 方法 getViewerPose() 來獲取檢視器的位置和朝向方向(定義使用者應該看到的內容),以及 getPose() 來獲取任何其他姿態,例如手部控制器和 XR 系統的任何其他部分的位置。
檢測和跟蹤丟失後的功能
如果跟蹤失敗,例如由於頭戴裝置和使用者裝置之間的連線暫時丟失,XR 層將繼續返回姿態,但這些姿態的 emulatedPosition 屬性將為 true,表明姿態的計算是基於對使用者當前位置的猜測。
一些 XR 硬體使用演算法根據當前正在進行的運動計算使用者的估計位置,而另一些硬體則報告根本沒有運動,但 emulatedPosition 設定為 true。在這兩種情況下,您可能希望根據您的具體需求調整渲染以彌補損失。
當跟蹤恢復時
當用戶位置跳躍,同時 emulatedPosition 的值從 true 變為 false 時,您可以檢測到跟蹤在丟失後已恢復。如何處理取決於您的應用程式。如果您的應用程式提供了一種讓使用者在不實際移動的情況下在虛擬世界中移動的方式(即所謂的瞬移機制),您可以接受新位置並繼續,允許從您先前假設的位置跳躍立即透過新位置進行校正。
另一方面,如果您的應用程式涉及使用者在真實空間中物理移動以在您的虛擬世界中移動,則採用新的跟蹤位置並跳躍到那裡可能會讓使用者感到不安,如果可能應避免這種情況。相反,使用當前位置和新跟蹤位置之間的差異來計算新的瞬移偏移;也就是說,應用於所有內容的變換,以將 WebXR 的位置和方向資料適應您的需求。
您可以透過建立一個新的參考空間來做到這一點,該空間將檢視器位置自上一幀以來跳躍的距離納入其有效原點,使用 XRReferenceSpace 方法 getOffsetReferenceSpace()。
重置事件
當參考空間的本地或有效原點發生不連續或中斷時,使用者代理將向 XRReferenceSpace 傳送一個 reset 事件。此事件表明原點位置相對於使用者環境發生了重大變化。
reset 可能發生是因為 XR 硬體連線丟失了一段時間,導致使用者的移動一段時間未被正確跟蹤。在跟蹤恢復後,reset 意味著跟蹤已恢復,新的位置資訊代表了 XR 硬體提供的實際位置資訊,而不是快取或“最佳猜測”資料。
傳送 reset 的另一個原因是使用者已退出參考空間的邊界並進入另一個參考空間,或者使用者已透過程式設計從一個參考空間過渡到另一個參考空間。每當使用者空間邊界幾何形狀發生變化時,都會發送 reset。
reset 僅用於重大的跳躍或過渡;小問題將自動吸收。該事件總是傳送到每個受影響的參考空間,包括使用 getOffsetReferenceSpace() 建立的那些,因此如果您監聽 reset 事件,您需要確保保留對您仍在使用的任何空間的強引用。