共用方式為


案例研究 - 擴充 HoloLens 的空間對應功能

在為 Microsoft HoloLens 創建我們的第一個應用程序時,我們迫不及待地想看看我們可以在設備上將空間映射的界限推向多遠。 Microsoft Studios 的軟體工程師 Jeff Evertt 解釋了如何開發一項新技術,因為需要更好地控制全息影像在使用者現實世界中的放置方式。

注意事項

HoloLens 2 實作新的場景理解執行階段,為 Mixed Reality 開發人員提供結構化的高階環境表示法,旨在讓環境感知應用程式的開發變得直觀。

觀賞影片

超越空間映射

當我們開發 FragmentsYoung Conker 這兩款 HoloLens 的首批遊戲時,我們發現當我們在物理世界中對全像投影進行程序放置時,我們需要對使用者環境有更高層級的了解。 每個遊戲都有其特定的放置需求:例如,在《碎片》中,我們希望能夠區分不同的表面(例如地板或桌子),以便在相關位置放置線索。 我們還希望能夠識別真人大小的全息角色可以坐在的表面,例如沙發或椅子。 在《少年康克》中,我們希望康克和他的對手能夠使用玩家房間中的凸起表面作為平台。

我們這些遊戲的開發合作夥伴 Asobo Studios 直面了這個問題,並創建了一種擴展 HoloLens 空間映射功能的技術。 利用它,我們可以分析玩家的房間並識別牆壁、桌子、椅子和地板等表面。 它還使我們能夠根據一組條件約束進行優化,以確定全息圖形對象的最佳位置。

空間理解程式碼

我們採用了 Asobo 的原始程式碼並創建了一個封裝這項技術的函式庫。 Microsoft 和 Asobo 開源了這段程式碼,並在 MixedRealityToolkit 上提供它,供您在自己的專案中使用。 所有源代碼都包含在內,允許您根據需要對其進行自定義並與社區分享您的改進。 C++ 求解器的程式碼會包裝成 UWP DLL,並透過 MixedRealityToolkit 中包含的插入式預製件公開給 Unity。

Unity 範例中有許多有用的查詢,可讓您尋找牆壁上的空白空間、將物件放在天花板或地板上的大型空間上、識別角色的座位,以及無數其他空間理解查詢。

雖然 HoloLens 提供的空間對應解決方案設計為足夠通用,可以滿足整個問題空間領域的需求,但空間理解模塊是為支持兩個特定遊戲的需求而構建的。 因此,其解決方案是圍繞特定流程和一組假設構建的:

  • 固定大小的播放空間:使用者在init呼叫中指定最大播放空間大小。
  • 一次性掃描過程:該過程需要一個離散的掃描階段,用戶在該階段四處走動,定義遊戲空間。 查詢函式在掃描完成之前不會運作。
  • 使用者驅動的遊戲空間「繪畫」:在掃描階段,使用者移動並環顧遊戲空間,有效地繪製應包含的區域。 產生的網格對於在此階段提供使用者回饋非常重要。
  • 室內家庭或辦公室設定:查詢功能是圍繞平面和牆壁以直角設計。 這是一個軟限制。 然而,在掃描階段,會完成主軸分析,以最佳化沿長軸和短軸的網格鑲嵌。

房間掃描流程

當您載入空間理解模組時,首先要做的是掃描您的空間,以便識別並標示所有可用的表面 (例如地板、天花板和牆壁)。 在掃描過程中,您會環顧房間並「繪製」掃描中應包含的區域。

在此階段看到的網格是視覺回饋的重要部分,可讓使用者知道正在掃描房間的哪些部分。 空間理解模組的 DLL 會在內部將遊戲空間儲存為 8 公分大小的體素立方體網格。 在掃描的初始部分,完成主要組件分析以確定房間的軸線。 在內部,它儲存與這些軸對齊的體素空間。 透過從體素體積中提取等值面,大約每秒產生一個網格。

白色空間映射網格和綠色理解遊戲空間網格

白色空間映射網格和綠色理解遊戲空間網格

隨附的SpatialUnderstanding.cs檔案會管理掃描階段程序。 它會呼叫下列函式:

  • SpatialUnderstanding_Init:一開始就叫了一次。
  • GeneratePlayspace_InitScan:表示掃描階段應該開始。
  • GeneratePlayspace_UpdateScan_DynamicScan:呼叫每個幀以更新掃描過程。 相機位置和方向會傳入,並用於遊戲空間繪製程序,如所述。
  • GeneratePlayspace_RequestFinish:呼叫以完成遊戲空間。 這會使用掃描階段「繪製」的區域來定義和鎖定遊戲空間。 應用程式可以在掃描階段查詢統計資料,並查詢自訂網格以提供使用者意見反應。
  • Import_UnderstandingMesh:在掃描過程中,模組提供並放置在理解預製件上的 SpatialUnderstandingCustomMesh 行為會定期查詢進程產生的自訂網格。 此外,掃描完成後會再次執行此操作。

SpatialUnderstanding 行為驅動的掃描流程會呼叫 InitScan,然後呼叫每個畫面的 UpdateScan 。 當統計資料查詢報告合理的涵蓋範圍時,使用者可以隔空點選以呼叫 RequestFinish 以指出掃描階段的結束。 UpdateScan 會繼續呼叫,直到其傳回值指出 DLL 完成處理為止。

查詢

掃描完成後,您可以在介面中存取三種不同類型的查詢:

  • 拓撲查詢:這些是基於掃描房間拓撲的快速查詢。
  • 形狀查詢:這些會利用拓撲查詢的結果來尋找與您定義的自訂形狀完美相符的水平表面。
  • 物件放置查詢:這些是更複雜的查詢,可根據物件的一組規則和限制來尋找最適合的位置。

除了三個主要查詢之外,還有一個光線投射介面,用於擷取標記的表面類型,並且可以複製自訂水密房間網格。

拓蹼查詢

在 DLL 內,拓撲管理員會處理環境的標籤。 如前所述,大部分數據都存儲在體素卷中。 此外, PlaySpaceInfos 結構用於存儲有關遊戲空間的信息,包括世界對齊方式 (有關此的更多詳細信息,包括下面的) 、地板和天花板高度。

啟發式用於確定地板、天花板和牆壁。 例如,表面積大於 1 m2 的最大和最低水平面被視為樓板。 請注意,掃描過程中的相機路徑也在此過程中使用。

拓撲管理員所公開的查詢子集會透過 DLL 公開。 公開的拓撲查詢如下:

  • QueryTopology_FindPositionsOnWalls
  • QueryTopology_FindLargePositionsOnWalls
  • QueryTopology_FindLargestWall
  • QueryTopology_FindPositionsOnFloor
  • QueryTopology_FindLargestPositionsOnFloor
  • QueryTopology_FindPositionsSittable

每個查詢都有一組參數,特定於查詢類型。 在下列範例中,使用者指定所需體積的最小高度 & 寬度、地板上方的最小放置高度,以及體積前面的最小間隙量。 所有測量值均以米為單位。

EXTERN_C __declspec(dllexport) int QueryTopology_FindPositionsOnWalls(
          _In_ float minHeightOfWallSpace,
          _In_ float minWidthOfWallSpace,
          _In_ float minHeightAboveFloor,
          _In_ float minFacingClearance,
          _In_ int locationCount,
          _Inout_ Dll_Interface::TopologyResult* locationData)

每個查詢都會採用預先配置的 TopologyResult 結構陣列。 locationCount 參數會指定傳入陣列的長度。 傳回值會報告傳回位置的數目。 此數字永遠不會大於傳入的 locationCount 參數。

TopologyResult 包含傳回磁碟區的中心位置、面向方向 (,也就是一般) ,以及找到空間的尺寸。

struct TopologyResult
     {
          DirectX::XMFLOAT3 position;
          DirectX::XMFLOAT3 normal;
          float width;
          float length;
     };

在 Unity 範例中,每個查詢都會連結至虛擬 UI 面板中的按鈕。 範例會將每個查詢的參數硬式編碼為合理的值。 如需更多範例,請參閱範例程式碼中的 SpaceVisualizer.cs

圖形查詢

在 DLL 內部,圖形分析器 (ShapeAnalyzer_W) 使用拓撲分析器來比對使用者定義的自訂圖形。 Unity 範例有一組預先定義的圖形,這些圖形會顯示在 [圖形] 索引標籤上的查詢功能表中。

造型分析僅適用於水平曲面。 例如,沙發由平坦的座面和沙發靠背的平坦頂部定義。 形狀查詢會尋找特定大小、高度和縱橫向範圍的兩個表面,其中兩個表面已對齊並連接。 使用 API 術語,沙發座椅和沙發靠背的頂部是形狀組件,坐標系需求是形狀組件限制。

Unity 範例 (ShapeDefinition.cs) 中定義的範例查詢,適用於「可坐」物件,如下所示:

shapeComponents = new List<ShapeComponent>()
     {
          new ShapeComponent(
               new List<ShapeComponentConstraint>()
               {
                    ShapeComponentConstraint.Create_SurfaceHeight_Between(0.2f, 0.6f),
                    ShapeComponentConstraint.Create_SurfaceCount_Min(1),
                    ShapeComponentConstraint.Create_SurfaceArea_Min(0.035f),
               }),
     };
     AddShape("Sittable", shapeComponents);

每個圖形查詢都由一組圖形元件定義,每個元件都有一組元件條件約束,以及一組列出元件之間相依關係的圖形條件約束。 此範例在單一元件定義中包含三個條件約束,且元件之間沒有形狀條件約束 (因為只有一個元件) 。

相反,沙發形狀有兩個形狀組件和四個形狀約束。 元件是由使用者元件清單中的索引識別,在此範例) 中 (0 和 1。

shapeConstraints = new List<ShapeConstraint>()
        {
              ShapeConstraint.Create_RectanglesSameLength(0, 1, 0.6f),
              ShapeConstraint.Create_RectanglesParallel(0, 1),
              ShapeConstraint.Create_RectanglesAligned(0, 1, 0.3f),
              ShapeConstraint.Create_AtBackOf(1, 0),
        };

Unity 模組中提供了包裝函式,以便輕鬆建立自訂形狀定義。 元件和圖形條件約束的完整清單可以在 ShapeComponentConstraintShapeConstraint 結構內的SpatialUnderstandingDll.cs中找到。

藍色矩形會醒目提示椅子形狀查詢的結果。

藍色矩形會醒目提示椅子形狀查詢的結果。

物件放置求解器

物件放置查詢可用來識別實體房間中放置物件的理想位置。 求解器會在給定物件規則和約束的情況下找到最佳擬合位置。 此外,物件查詢會持續存在,直到透過 Solver_RemoveObjectSolver_RemoveAllObjects 呼叫移除物件為止,從而允許限制多物件放置。

物件放置查詢由三個部分組成:具有參數的放置類型、規則清單和限制清單。 若要執行查詢,請使用下列 API:

public static int Solver_PlaceObject(
                [In] string objectName,
                [In] IntPtr placementDefinition,	// ObjectPlacementDefinition
                [In] int placementRuleCount,
                [In] IntPtr placementRules,     	// ObjectPlacementRule
                [In] int constraintCount,
                [In] IntPtr placementConstraints,	// ObjectPlacementConstraint
                [Out] IntPtr placementResult)

此函數採用物件名稱、放置定義以及規則和約束清單。 C# 包裝函式提供建構協助程式函式,讓規則和條件約束建構變得容易。 放置定義包含查詢類型,也就是下列其中一項:

public enum PlacementType
                {
                    Place_OnFloor,
                    Place_OnWall,
                    Place_OnCeiling,
                    Place_OnShape,
                    Place_OnEdge,
                    Place_OnFloorAndCeiling,
                    Place_RandomInAir,
                    Place_InMidAir,
                    Place_UnderFurnitureEdge,
                };

每個放置型別都有一組類型特有的引數。 ObjectPlacementDefinition 結構包含一組靜態協助程式函式,用於建立這些定義。 例如,要找到一個將物體放在地板上的地方,您可以使用以下函數:

public static ObjectPlacementDefinition Create_OnFloor(Vector3 halfDims)

除了放置類型之外,您還可以提供一組規則和條件約束。 不能違反規則。 然後,會針對一組條件約束最佳化滿足類型與規則的可能放置位置,以選取最佳放置位置。 每個規則和限制都可以由提供的靜態建立函數建立。 下面提供了一個示例規則和約束構建函數。

public static ObjectPlacementRule Create_AwayFromPosition(
                    Vector3 position, float minDistance)
               public static ObjectPlacementConstraint Create_NearPoint(
                    Vector3 position, float minDistance = 0.0f, float maxDistance = 0.0f)

物件放置查詢會尋找一個位置,以將半公尺立方體放在表面的邊緣,遠離其他先前放置的物件,並靠近房間的中心。

List<ObjectPlacementRule> rules = 
          new List<ObjectPlacementRule>() {
               ObjectPlacementRule.Create_AwayFromOtherObjects(1.0f),
          };

     List<ObjectPlacementConstraint> constraints = 
          new List<ObjectPlacementConstraint> {
               ObjectPlacementConstraint.Create_NearCenter(),
          };

     Solver_PlaceObject(
          “MyCustomObject”,
          new ObjectPlacementDefinition.Create_OnEdge(
          new Vector3(0.25f, 0.25f, 0.25f), 
          new Vector3(0.25f, 0.25f, 0.25f)),
          rules.Count,
          UnderstandingDLL.PinObject(rules.ToArray()),
          constraints.Count,
          UnderstandingDLL.PinObject(constraints.ToArray()),
          UnderstandingDLL.GetStaticObjectPlacementResultPtr());

如果成功,則會傳回包含放置位置、維度和方向的 ObjectPlacementResult 結構。 此外,放置會新增至 DLL 的內部放置物件清單。 後續放置查詢會考慮此物件。 Unity 範例中的 LevelSolver.cs 檔案包含更多範例查詢。

藍色方塊顯示三個具有「遠離攝影機位置」規則的「放置在地板上」查詢的結果。

藍色方塊顯示三個具有「遠離攝影機位置」規則的「放置在地板上」查詢的結果。

小貼士:

  • 在求解一個關卡或應用場景所需的多個物件的放置位置時,首先求解不可或缺的大型物件,以最大化找到一個空間的機率。
  • 放置順序很重要。 如果找不到物件放置,請嘗試較少限制的組態。 擁有一組後援組態對於支援許多會議室組態的功能至關重要。

光線投射

除了三個主要查詢之外,光線投射介面還可用於檢索標記的表面類型,並且可以複製自訂水密遊樂空間網格掃描並最終確定房間後,會在內部為地板、天花板和牆壁等表面產生標籤。 PlayspaceRaycast 函式會取得光線,並在光線與已知表面碰撞時傳回,如果是,則會傳回 RaycastResult 形式的該表面相關資訊。

struct RaycastResult
     {
          enum SurfaceTypes
          {
               Invalid,	// No intersection
               Other,
               Floor,
               FloorLike,         // Not part of the floor topology, 
                                  //     but close to the floor and looks like the floor
               Platform,          // Horizontal platform between the ground and 
                                  //     the ceiling
               Ceiling,
               WallExternal,
               WallLike,          // Not part of the external wall surface, 
                                  //     but vertical surface that looks like a 
                                  //	wall structure
               };
               SurfaceTypes SurfaceType;
               float SurfaceArea;	// Zero if unknown 
                                        //	(i.e. if not part of the topology analysis)
               DirectX::XMFLOAT3 IntersectPoint;
               DirectX::XMFLOAT3 IntersectNormal;
     };

在內部,光線投射是根據計算出的遊戲空間的 8 厘米立方體體素表示法計算的。 每個體素都包含一組表面元素,其中包含經過處理的拓撲資料 (也稱為表面) 。 比較相交體素單元中包含的表面,並使用最佳匹配來查找拓撲信息。 此拓蹼資料包含以 SurfaceTypes 列舉形式傳回的標籤,以及相交曲面的表面積。

在 Unity 範例中,游標會在每個畫面中投射光線。 首先,對抗 Unity 的對撞機;第二,反對理解模組的世界表示;最後,針對 UI 元素。 在此應用程式中,UI 獲得優先權,然後是理解結果,最後是 Unity 的碰撞器。 SurfaceType 會報告為游標旁邊的文字。

光線投射結果報告與地板的交點。

光線投射結果報告與地板的交點。

Get the code

開放原始碼程式碼可在 MixedRealityToolkit 中使用。 如果您在專案中使用程式碼,請在 HoloLens 開發人員論壇 上告訴我們。 我們迫不及待地想看看你用它做什麼!

關於作者

Jeff Evertt,Microsoft 軟體工程主管 Jeff Evertt 是一名軟體工程主管,他從早期就致力於 HoloLens,從孵化到體驗開發。 在 HoloLens 之前,他曾在 Xbox Kinect 和遊戲行業的各種平台和遊戲中工作。 傑夫對機器人技術、圖形和帶有蜂鳴聲的華麗燈光的事物充滿熱情。 他喜歡學習新事物並從事軟件、硬件方面的工作,尤其是在兩者相交的領域。

另請參閱