讓你的全息影像保持原位、跟著你移動,或在某些情況下能相對於其他全息影像定位,是創造 Mixed Reality 應用程式的重要部分。 本文將帶你了解我們推薦的解決方案,使用世界鎖定工具,同時也涵蓋如何在 Unity 專案中手動設定空間錨點。 在我們進入任何程式碼之前,了解 Unity 如何在自身引擎中處理座標空間和錨點非常重要。
世界尺度座標系統
如今,在撰寫遊戲、資料視覺化應用程式或虛擬實境應用程式時,典型的做法是建立一個絕對的世界 座標系統 ,讓所有其他座標都能可靠地映射回去。 在那種環境下,你總能找到一個穩定的轉換,定義該世界中任兩個物體之間的關係。 如果你不移動那些物件,它們的相對轉換永遠不會改變。 這種全域座標系統在渲染純虛擬世界時很容易做到,因為你事先知道所有幾何形狀。 現今的房間尺度 VR 應用程式通常建立這種絕對的房間尺度座標系統,起點位於地面。
相較之下,像 HoloLens 這類無繩混合實境裝置,擁有動態感測器驅動的世界感知,隨著使用者在整棟建築的一層樓內行走數公尺,持續調整對周遭環境的認知。 在世界尺度的體驗中,如果你把所有全息影像放在一個天真的剛性座標系裡,這些全息影像會隨時間漂移,不論是根據世界還是彼此相對於。
例如,頭戴裝置目前可能認為世界上兩個地點相距4公尺,之後再修正這個理解,發現它們其實相距3.9公尺。 如果這些全息影像最初相距4公尺,放在單一剛性座標系中,其中一個就會總是偏離現實世界0.1公尺。
你可以在 Unity 中手動放置 空間錨點 ,以維持全息影像在實體世界中的位置,當使用者行動時。 然而,這會犧牲虛擬世界中的自洽性。 不同的錨點彼此相對不斷移動,並且也在全域座標空間中移動。 在這種情況下,像排版這類簡單的任務變得困難。 物理模擬也可能帶來問題。
世界鎖定工具 (WLT) 讓你兼顧兩者優點,利用內部空間錨點穩定單一剛性座標系,這些錨點分布在虛擬場景中,隨著使用者移動而持續運作。 WLT 每幀都會分析攝影機的座標及這些空間錨點。 WLT 不會改變世界上所有事物的座標來補償使用者頭部的座標修正,而是直接固定頭部的座標。
選擇你的世界鎖定方式
如果可能,使用 世界鎖定工具 來定位全息影像。
世界鎖定工具 提供穩定的座標系統,將虛擬世界與現實世界標記間的可見不一致降到最低。 世界鎖定工具會用共用的錨點池來鎖定整個場景,而不是用每組物件各自的錨點來鎖定。
世界鎖定工具自動處理空間錨點的內部建立與管理。 你不需要與ARAnchorManager或WorldAnchor互動來保持全息影像的世界鎖定。
- 使用 OpenXR 或 Windows XR 外掛的 Unity 2019/2020,請使用 ARAnchorManager。
- 對於較舊的 Unity 版本或 WSA 專案,請使用 WorldAnchor。
設定世界鎖定
要開始使用世界鎖定工具,請下載 Mixed Reality 功能工具。 想了解更多基礎知識,請參閱世界鎖定工具主文件頁面,裡面有概述、快速入門及其他實用主題的連結。
自動設定
當你的專案準備好時,執行 Mixed Reality > 世界鎖定工具中的配置場景工具:
重要事項
配置場景工具可隨時重執行。 例如,如果 AR 目標從 Legacy 改成 XR SDK,應該重新執行。 如果場景已經正確設定好,執行工具不會有效果。
視覺化器
在早期開發階段,加入視覺化工具有助於確保 WLT 的設定與運作正常。 為了生產效能,可以移除它們,或因任何原因不再需要,使用 Remove visualizers 工具。 關於視覺化器的更多細節,請參閱 工具文件。
命名空間:UnityEngine.XR.WSA
類型:世界錨
一項關鍵技術是創造空間 錨點 ,將一群全息影像精確鎖定在物理世界中,無論使用者走多遠,然後 在後續的遊戲中再次尋找這些全息影像。
在較舊的 Unity 版本中,你要透過將 WorldAnchor Unity 元件加入 GameObject 來建立空間錨點。
新增世界錨點
要新增世界錨點,請呼叫 AddComponent<WorldAnchor>() 你想在現實世界中錨定的遊戲物件。
WorldAnchor anchor = gameObject.AddComponent<WorldAnchor>();
這個遊戲物件現在被錨定在其在物理世界中的位置。 你可能會看到它的 Unity 世界座標隨時間稍微調整,以確保物理上的對齊。 請參考 載入世界錨點 ,在未來的應用程式會話中再次找到這個錨定位置。
移除世界錨點
如果你不想再鎖定在 GameObject 物理世界位置,也不打算這幀移動它,可以呼叫 Destroy World Anchor 元件。
Destroy(gameObject.GetComponent<WorldAnchor>());
如果你想移動 GameObject 這個框架,請打電話 DestroyImmediate 。
DestroyImmediate(gameObject.GetComponent<WorldAnchor>());
移動一個世界錨定的遊戲物件
你不能動一會 GameObject 兒,因為世界錨正在上面。 如果你需要移動 GameObject 這個框架,你需要:
-
DestroyImmediate 世界錨(World Anchor)組成部分。
- 移動
GameObject.
- 新增一個 World Anchor 元件到
GameObject.
DestroyImmediate(gameObject.GetComponent<WorldAnchor>());
gameObject.transform.position = new Vector3(0, 0, 2);
WorldAnchor anchor = gameObject.AddComponent<WorldAnchor>();
處理可定位性變更
世界錨點在某個時間點可能無法定位於物理世界中。 Unity 則不會更新錨定物件的轉換。 這種情況也可能在應用程式執行時發生。 未能處理定位變化會導致物體無法出現在世界上正確的物理位置。
欲通知可定位性變更:
訂閱本次 OnTrackingChanged 活動。
OnTrackingChanged當底層空間錨點在可定位與不可定位狀態之間變化時,該事件即被呼叫。
anchor.OnTrackingChanged += Anchor_OnTrackingChanged;
處理這個活動。
private void Anchor_OnTrackingChanged(WorldAnchor self, bool located)
{
// This simply activates/deactivates this object and all children when tracking changes
self.gameObject.SetActiveRecursively(located);
}
如果錨點被立即定位,錨 isLocated 點的屬性會設定為 true 當 AddComponent<WorldAnchor>() 返回時。 因此,事件 OnTrackingChanged 不會被觸發。 更乾淨的模式是在附加錨點後,以初始IsLocated狀態呼叫OnTrackingChanged處理器。
Anchor_OnTrackingChanged(anchor, anchor.isLocated);
持續世界鎖定
空間錨點在應用會話間的真實空間中保存全息影像。 一旦儲存在 HoloLens 錨點商店,空間錨點可以在不同的工作階段找到並載入,是沒有網路連線時理想的備用工具。
預設情況下,世界鎖定工具會在支援持續存在的本地空間錨點裝置上,恢復 Unity 相對於物理世界的座標系統。 要讓全息影像在退出並重啟應用程式後出現在物理世界中的同一位置,應用程式只需將相同姿勢還原到全息影像上即可。
如果應用程式需要更細緻的控制,可以在檢查器中關閉 自動儲存 和 自動載入 ,並透過腳本管理持久化。 欲了解更多資訊,請參閱 「持續存在空間座標系」。
世界鎖定工具僅支援 HoloLens 裝置上的本地錨點持久化。
一個名為 的 XRAnchorStore API 允許錨點在會話間持久化。 是 XRAnchorStore 裝置上儲存錨點的表示。 你可以在 Unity 場景中持久化錨點 ARAnchors ,將儲存的錨點載入新設 ARAnchors,或刪除儲存中的錨點。
注意事項
你要把這些錨點儲存並載入在同一台裝置上。
命名空間
針對 Unity 2020 與 OpenXR:
using Microsoft.MixedReality.ARSubsystems.XRAnchorStore
或 Unity 2019/2020 + Windows XR 外掛:
using UnityEngine.XR.WindowsMR.XRAnchorStore
公共方法
{
// A list of all persisted anchors, which can be loaded.
public IReadOnlyList<string> PersistedAnchorNames { get; }
// Clear all persisted anchors
public void Clear();
// Load a single persisted anchor by name. The ARAnchorManager will create this new anchor and report it in
// the ARAnchorManager.anchorsChanged event. The TrackableId returned here is the same TrackableId the
// ARAnchor will have when it is instantiated.
public TrackableId LoadAnchor(string name);
// Attempts to persist an existing ARAnchor with the given TrackableId to the local store. Returns true if
// the storage is successful, false otherwise.
public bool TryPersistAnchor(TrackableId id, string name);
// Removes a single persisted anchor from the anchor store. This will not affect any ARAnchors in the Unity
// scene, only the anchors in storage.
public void UnpersistAnchor(string name);
}
取得主力店推薦信
要載入 XRAnchorStore 並搭載 Unity 2020 和 OpenXR,請使用 XRAnchorSubsystem(ARAnchorManager 子系統)上的擴充方法:
public static Task<XRAnchorStore> LoadAnchorStoreAsync(this XRAnchorSubsystem anchorSubsystem)
要載入 XRAnchorStore 與 Unity 2019/2020 及 Windows XR 外掛,請使用 XRReferencePointSubsystem (Unity 2019) 或 XRAnchorSubsystem (Unity 2020) 上的擴充功能,後者是 ARReferencePointManager/ARAnchorManager 的子系統:
// Unity 2019 + Windows XR Plugin
public static Task<XRAnchorStore> TryGetAnchorStoreAsync(this XRReferencePointSubsystem anchorSubsystem);
// Unity 2020 + Windows XR Plugin
public static Task<XRAnchorStore> TryGetAnchorStoreAsync(this XRAnchorSubsystem anchorSubsystem);
裝載一個主力店
要在 Unity 2020 和 OpenXR 中載入錨點儲存,請從 ARAnchorManager 的子系統依以下方式存取:
ARAnchorManager arAnchorManager = GetComponent<ARAnchorManager>();
XRAnchorStore anchorStore = await arAnchorManager.subsystem.LoadAnchorStoreAsync();
或是 Unity 2019/2020 與 Windows XR 外掛:
// Unity 2019
ARReferencePointManager arReferencePointManager = GetComponent<ARReferencePointManager>();
XRAnchorStore anchorStore = await arReferencePointManager.subsystem.TryGetAnchorStoreAsync();
// Unity 2020
ARAnchorManager arAnchorManager = GetComponent<ARAnchorManager>();
XRAnchorStore anchorStore = await arAnchorManager.subsystem.TryGetAnchorStoreAsync();
想完整查看持久/非持續錨點的範例,請參考 [Mixed Reality OpenXR 插件範例場景]AnchorsSample.cs ( (https://github.com/microsoft/OpenXR-Unity-MixedReality-Samples) 中的 Anchors> - Anchors 範例 GameObject 與腳本:
在舊版 Unity 或 WSA 專案中,若想維持全息影像,請使用 WorldAnchor。
命名空間:UnityEngine.XR.WSA.Persistence
類別:WorldAnchorStore
WorldAnchorStore 創造全息體驗,讓全息影像在應用程式的實例中停留在特定現實位置。 使用者可以將個別全息影像釘選在任意位置,並在應用程式會話中在同一地點找到它們。
這 WorldAnchorStore 讓你能在不同會話中持續保存世界錨點的位置。 要在多個會話間持續存在全息影像,請使用特定的世界錨點來獨立追蹤 GameObjects 。 你可以用世界錨點建立 GameObject 根,並用它來錨定子全息影像,並設定局部位置偏移。
要載入之前遊戲的全息影像:
- 取得
WorldAnchorStore.
- 載入世界錨點應用程式的資料,這會給你世界錨點的ID。
- 依照 ID 載入世界錨點。
為了保存全息影像以備未來遊戲使用:
- 取得
WorldAnchorStore.
- 儲存一個世界錨點,指定一個 ID。
- 將與世界錨點相關的應用程式資料與 ID 一同儲存。
訂閱WorldAnchorStore
請保留參考 WorldAnchorStore,這樣你就知道它什麼時候準備好執行操作。 由於此通話是非同步的,應用程式一啟動即可撥打:
WorldAnchorStore.GetAsync(StoreLoaded);
StoreLoaded 是裝載結束時 WorldAnchorStore 的處理者:
private void StoreLoaded(WorldAnchorStore store)
{
this.store = store;
}
你現在有一個參考 , WorldAnchorStore可以用來儲存和載入特定的世界錨點。
拯救世界錨
要拯救世界錨點,請先命名世界錨點並將其傳入 WorldAnchorStore 你之前的版本。 如果你嘗試將兩個錨點儲存到同一字串,則 store.Save 回傳 false。 在存新存檔之前,先刪除之前的存檔。
private void SaveGame()
{
// Save data about holograms that this world anchor positions
if (!this.savedRoot) // Only save the root once
{
this.savedRoot = this.store.Save("rootGameObject", anchor);
Assert(this.savedRoot);
}
}
載入一個世界錨點
載入世界錨點:
private void LoadGame()
{
// Saved data about holograms that this world anchor positions:
this.savedRoot = this.store.Load("rootGameObject", rootGameObject);
if (!this.savedRoot)
{
// Game root not saved. Re-place objects or start over.
}
}
你也可以用 store.Delete() 來移除之前儲存過的錨點,以及 store.Clear() 移除所有先前儲存的資料。
列舉現有錨點
若要列出儲存的錨點,請呼叫 GetAllIds。
string[] ids = this.store.GetAllIds();
for (int index = 0; index < ids.Length; index++)
{
Debug.Log(ids[index]);
}
後續步驟
分享一個世界鎖定座標空間:
了解空間映射:
返回 Unity 開發檢查點:
另請參閱