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

建立第一個適用於 Microsoft HoloLens 的應用程式時,我們非常想要瞭解我們可以在裝置上推送空間對應的界限。 Jeff Evertt 是 Microsoft Studios 的軟體工程師,說明如何利用新技術來開發,以更充分掌控全像投影在用戶真實世界環境中的方式。

注意

HoloLens 2 實作新的 Scene Understanding 運行時間,為 Mixed Reality 開發人員提供結構化、高階的環境表示法,其設計目的是要讓開發以直覺的方式開發感知應用程式。

觀賞影片

超出空間對應

雖然我們正在處理 FragmentsYoung Conker,但 HoloLens 的第一個遊戲之兩個,我們發現當我們在實體世界中執行全像投影的程式性放置時,我們需要更高層級的了解用戶環境。 每個遊戲都有自己的特定位置需求:例如,在片段中,我們想要能夠區分不同的表面,例如樓層或表格,以將線索放在相關位置。 我們也想要能夠識別生命大小的全像攝影字元可以放在一起的表面,例如同事或椅子。 在Young Conker中,我們希望 Conker 和他的氣力能夠使用玩家會議室中引發的表面作為平臺。

Asobo Studios 是這些遊戲的開發合作夥伴,面臨此問題,並建立了一種技術來擴充 HoloLens 的空間對應功能。 使用此方式,我們可以分析玩家的會議室,並識別牆、表格、椅子和樓層等表面。 它也讓我們能夠針對一組條件約束進行優化,以判斷全像攝影物件的最佳位置。

空間瞭解程序代碼

我們採用 Asobo 的原始程式代碼,並建立了封裝這項技術的連結庫。 Microsoft 和 Asobo 現在已開放原始碼此程式碼,並可讓您在 MixedRealityToolkit 上使用自己的專案。 所有原始程式碼都包含在內,可讓您根據需求加以自定義,並與社群分享您的改善。 C++ 規劃求解的程式代碼已包裝成 UWP DLL,並公開至 Unity,其中包含 MixedRealityToolkit 內含的下拉式預製物件

Unity 範例中包含許多有用的查詢,可讓您在牆上尋找空白空間、將物件放在上限或大空間上、識別要放置字元的位置,以及大量其他空間理解查詢。

雖然 HoloLens 所提供的空間對應解決方案設計成足以符合整個問題空間之需求的泛型,但空間理解模組是建置來支援兩個特定遊戲的需求。 因此,其解決方案是以特定程式和一組假設為結構:

  • 固定大小播放空間:使用者指定 init 呼叫中的播放空間大小上限。
  • 單次掃描程式:此程式需要離散掃描階段,使用者會在其中逐步解說,定義播放空間。 查詢函式在掃描完成之後才會運作。
  • 用戶驅動劇本「繪製」:在掃描階段,用戶移動並查看播放空間,有效地繪製應該包含的區域。 在此階段,產生的網格對於提供使用者意見反應很重要。
  • 室內家庭或辦公室設定:查詢函式是針對平面和牆設計,以直角呈現。 這是軟性限制。 不過,在掃描階段,主要軸分析已完成,以優化主要和次要軸上的網格鑲嵌。

會議室掃描程式

當您載入空間瞭解模組時,第一件事就是掃描空間,因此會識別並標示所有可用的表面,例如樓層、上限和牆。 在掃描過程中,您會查看房間並「繪製」掃描中應包含的區域。

此階段所見的網格是一個重要視覺意見反應片段,可讓使用者知道正在掃描會議室的哪些部分。 空間理解模組的 DLL 會在內部將播放空間儲存為 8cm 大小的體素 Cube 網格線。 在掃描的初始部分期間,主要元件分析已完成,以判斷會議室的軸。 在內部,它會儲存其對齊這些軸的體素空間。 藉由從體素磁碟區擷取 isosurface,大約每秒會產生網格。

白色的空間對應網格,並以綠色了解劇本網格

白色的空間對應網格,並以綠色了解劇本網格

包含的 SpatialUnderunderunder.cs 檔案會管理掃描階段程式。 它會呼叫下列函式:

  • SpatialUnderstanding_Init:在啟動時呼叫一次。
  • GeneratePlayspace_InitScan:表示掃描階段應該開始。
  • GeneratePlayspace_UpdateScan_DynamicScan:呼叫每個畫面以更新掃描程式。 相機位置和方向會傳入,並用於播放空間繪製程式,如上所述。
  • GeneratePlayspace_RequestFinish:呼叫 以完成播放空間。 這會在掃描階段使用「繪製」區域來定義和鎖定播放空間。 應用程式可以在掃描階段期間查詢統計數據,以及查詢自定義網格以提供使用者意見反應。
  • Import_UnderstandingMesh:在掃描期間, 模組所提供的 SpatialUnder understandingCustomMesh 行為,並置於了解預製專案上,會定期查詢進程所產生的自定義網格。 此外,在掃描完成之後,就會再完成一次。

由 SpatialUnderunderunder 行為驅動的掃描流程會呼叫 InitScan,然後更新掃描每個畫面。 當統計數據查詢報告合理的涵蓋範圍時,使用者可以 airtap 呼叫 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) ,適用於 “sittable” 物件如下:

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);

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

相反地,Couch 圖形有兩個圖形元件和四個圖形條件約束。 請注意,元件是由使用者元件清單中的索引識別, (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 結構內的 SpatialUnderunderunderDll.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 檔案包含更多範例查詢。

藍色方塊會顯示三個位置 On Floor 查詢的結果,其中包含「離開相機位置」規則。

藍色方塊會顯示三個位置 On Floor 查詢的結果,其中包含「離開相機位置」規則。

祕訣:

  • 解決層級或應用程式案例所需的多個物件放置位置時,請先解決基底和大型物件,以最大化空間的機率。
  • 放置順序很重要。 如果找不到物件位置,請嘗試較少的限制組態。 擁有一組後援組態對於支持許多會議室設定的功能非常重要。

光線轉換

除了三個主要查詢之外,光線轉換介面還可以用來擷取標記的表面類型,而且可以在會議室掃描和完成之後,針對樓層、上限和牆等表面產生內部捲標。 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;
     };

在內部,光線廣播會根據播放空間的計算 8cm 立方體體素表示法來計算。 每個體素都包含一組表面元素,其中包含一組已處理的拓撲數據 (也稱為) 。 比較內含於交集體素單元格中的呈現方式,以及用來查閱拓撲資訊的最佳比對。 此拓撲數據包含 以 SurfaceTypes 列舉形式傳回的標籤,以及交集表面的介面區。

在 Unity 範例中,游標會轉換每個畫面格的光線。 首先,針對 Unity 的碰撞器;第二,根據瞭解模組的世界表示法;最後,針對UI元素。 在此應用程式中,UI 會取得優先順序,然後了解結果,最後是 Unity 的碰撞器。 SurfaceType 會回報為游標旁的文字。

光線廣播結果報告與樓層交集。

光線廣播結果報告與樓層交集。

取得程式碼

Open-source code 可在 MixedRealityToolkit 中使用。 如果您在專案中使用程式碼,請在 HoloLens 開發人員論壇 上告訴我們。 我們無法等候查看您使用的內容!

關於作者

Microsoft 的軟體工程主管 Jeff Evertt Jeff Evertt 是一位軟體工程負責人,自早期開始在 HoloLens 上工作,從開發到體驗開發。 在 HoloLens 之前,他任職於 Xbox Kinect,以及在各種平臺和遊戲的遊戲產業中工作。 Jeff 很愛機器人、圖形,以及具有閃爍燈聲的事物。 他喜歡學習新事物,並處理軟體、硬體,特別是在兩者交集的空間中。

另請參閱