場景瞭解 SDK 概觀

場景瞭解會轉換Mixed Reality裝置所擷取的非結構化環境感應器資料,並將其轉換成功能強大的抽象標記法。 SDK 可作為應用程式與 Scene Understanding 執行時間之間的通訊層。 其目標是模擬現有的標準建構,例如 3D 標記法的 3D 場景圖形,以及 2D 應用程式的 2D 矩形和麵板。 雖然建構 Scene Understanding 模擬會對應到具體架構,但一般 SceneUnder understanding 是架構無從驗證,允許與其互動之不同架構之間的互通性。 隨著 Scene Understanding 發展 SDK 的角色,可確保新的標記法和功能會繼續在統一架構中公開。 在本檔中,我們將先介紹高階概念,以協助您熟悉開發環境/使用方式,然後針對特定類別和建構提供更詳細的檔。

哪裡可以取得 SDK?

SceneUnderstanding SDK 可透過Mixed Reality 功能工具下載。

注意: 最新版本取決於預覽套件,您必須啟用發行前版本套件才能看到它。

針對 0.5.2022-rc 和更新版本,Scene Understanding 支援 C# 和 C++ 的語言投影,讓應用程式能夠開發 Win32 或 UWP 平臺的應用程式。 從這個版本開始,SceneUnderstanding 支援 Unity 編輯器內支援 Barring SceneObserver,其僅用於與 HoloLens2 通訊。

SceneUnderstanding 需要 Windows SDK 18362 版或更高版本。

概觀說明

場景

您的混合實境裝置會持續整合環境中所看到內容的相關資訊。 Scene Understanding 漏斗圖會包含所有這些資料來源,並產生一個單一的一致抽象概念。 Scene Understanding 會產生 Scenes,這是代表單一專案的實例 的 SceneObjects 組合 (,例如牆/ceiling/floor.) Scene Objects 本身是 [SceneComponents] 的組合,代表構成此 SceneObject 的更細微片段。 元件的範例包括四邊形和網格,但未來可能代表周框方塊、碰撞網格、中繼資料等。

將原始感應器資料轉換成場景的程式是一項可能耗費資源的作業,可能需要幾秒的時間,讓中型空間 (~10x10m) 到大型空間的分鐘數 (約 50x50m) ,因此它不是裝置在沒有應用程式要求的情況下計算的專案。 相反地,應用程式會視需要觸發場景產生。 SceneObserver 類別具有靜態方法,可計算或還原序列化場景,然後您可以列舉/與其互動。 「計算」動作會隨選執行,並在 CPU 上執行,但在個別的進程中 (Mixed Reality驅動程式) 。 不過,當我們在另一個進程中計算時,產生的場景資料會儲存和維護在 Scene 物件的應用程式中。

下圖說明此程式流程,並顯示兩個應用程式與 Scene Understanding 執行時間交集的範例。

流程圖

左側是混合實境執行時間的圖表,該執行時間一律在自己的進程中執行。 此執行時間負責執行 Scene Understanding 用來瞭解及推斷您周圍世界之裝置追蹤、空間對應和其他作業。 在圖表右側,我們顯示兩個使用 Scene Understanding 的理論應用程式。 第一個應用程式介面與 MRTK ,其會在內部使用 Scene Understanding SDK,第二個應用程式會計算並使用兩個不同的場景實例。 此圖表中的所有三個場景都會產生場景的不同實例,驅動程式不會追蹤在某個場景中的應用程式與場景物件之間共用的全域狀態。 Scene Understanding 確實會提供一個機制來追蹤一段時間,但這是使用 SDK 完成的。 追蹤程式碼已在您應用程式的進程中的 SDK 中執行。

由於每個場景都會將其資料儲存在應用程式的記憶體空間中,因此您可以假設您應用程式進程一律會執行 Scene 物件或其內部資料的所有功能。

Layout

若要使用 Scene Understanding,瞭解執行時間如何以邏輯方式和實體方式呈現元件,可能十分有用。 場景代表具有特定版面配置的資料,其選擇簡單,同時維護可靠以符合未來需求的基礎結構,而不需要主要修訂。 場景會藉由將所有元件 (所有 Scene Objects 建置組塊儲存在一般清單中) ,並透過特定元件參考其他人的參考來定義階層和組合,來執行此動作。

在下方,我們會以其一般和邏輯形式呈現 結構的範例。

邏輯配置實體配置
    場景
    • SceneObject_1
      • SceneMesh_1
      • SceneQuad_1
      • SceneQuad_2
    • SceneObject_2
      • SceneQuad_1
      • SceneQuad_3
    • SceneObject_3
      • SceneMesh_3
  • SceneObject_1
  • SceneObject_2
  • SceneObject_3
  • SceneQuad_1
  • SceneQuad_2
  • SceneQuad_3
  • SceneMesh_1
  • SceneMesh_2

此圖醒目提示場景的實體和邏輯配置之間的差異。 在左側,我們會看到應用程式在列舉場景時所看到資料的階層式配置。 在右側,我們看到場景是由 12 個不同的元件組成,必要時可個別存取。 處理新的場景時,我們預期應用程式會以邏輯方式逐步執行此階層,不過在場景更新之間追蹤時,某些應用程式可能只想要鎖定兩個場景之間共用的特定元件。

API 概觀

下一節提供 Scene Understanding 中建構的高階概觀。 閱讀本節可讓您瞭解場景的呈現方式,以及各種元件的用途。 下一節會提供具體程式碼範例,以及本概觀中所詳述的其他詳細資料。

以下所述的所有類型都位於 命名空間中 Microsoft.MixedReality.SceneUnderstanding

SceneComponents

既然您已瞭解場景的邏輯配置,我們可以呈現 SceneComponents 的概念,以及它們如何用來撰寫階層。 SceneComponents 是 SceneUnderstanding 中代表單一核心專案的最細微分解,例如網格或四邊形或周框方塊。 SceneComponents 是可以獨立更新且可由其他 SceneComponents 參考的專案,因此它們具有單一全域屬性,這是唯一識別碼,可允許這種類型的追蹤/參考機制。 識別碼用於場景階層的邏輯組合,以及物件持續性 (相對於另一個) 更新某個場景的動作。

如果您要將每個新計算的場景視為相異,而且只是列舉其中的所有資料,則識別碼對您而言大致上是透明的。 不過,如果您打算追蹤多個更新的元件,您將使用識別碼來編制索引並尋找 Scene 物件之間的 SceneComponents。

SceneObjects

SceneObject 是 SceneComponent,代表「事物」的實例,例如牆、樓層、上限等等...以其 Kind 屬性工作表示。 SceneObject 是幾何的,因此具有代表其在空間中位置的函式和屬性,不過它們不包含任何幾何或邏輯結構。 相反地,SceneObjects 會參考其他 SceneComponents,特別是 SceneQuads 和 SceneMeshes,以提供系統支援的不同標記法。 計算新的場景時,您的應用程式很可能會列舉場景的 SceneObjects 來處理它感興趣的專案。

SceneObjects 可以有下列任一項:

SceneObjectKind 描述
背景SceneObject 已知 不是 另一種可辨識的場景物件類型。 這個類別不應該與未知混淆,其中背景已知不是牆/樓層/上限等等...雖然未知尚未分類。
實體牆。 牆假設為不可移動的環境結構。
樓層樓層是一個可逐步執行的任何表面。 注意:階梯不是樓層。 另請注意,樓層會假設任何可逐步執行的表面,因此沒有明確假設單一樓層。 多層級結構、坡形等...應該全部分類為樓層。
Ceiling會議室的上表面。
平台您可以放置全像投影的大型平面表面。 這些通常會代表資料表、計數器和其他大型水準表面。
World與標籤無關的幾何資料保留標籤。 設定 EnableWorldMesh 更新旗標所產生的網格會分類為世界。
Unknown此場景物件尚未分類並指派種類。 這不應該與 Background 混淆,因為此物件可能是任何專案,因此系統尚未針對它提供足夠強大的分類。

SceneMesh

SceneMesh 是 SceneComponent,使用三角形清單近似任意幾何物件的幾何。 SceneMeshes 用於數個不同的內容中;它們可以代表 watertight 儲存格結構的元件或 WorldMesh,代表與場景相關聯的未系結空間對應網格。 每個網格所提供的索引和頂點資料,都會使用與所有新式轉譯 API 中用於轉譯三角形網格的 頂點和索引緩衝區 相同的熟悉配置。 在 Scene Understanding 中,網格會使用 32 位索引,而且可能需要針對特定轉譯引擎分割成區塊。

線圈順序和座標系統

Scene Understanding 所產生的所有網格,預期都會使用順時針繞線順序,傳回Right-Handed座標系統中的網格。

注意:在 .191105 之前的 OS 組建可能會有已知錯誤,其中「World」 網格會以Counter-Clockwise繞線順序傳回,之後已修正。

SceneQuad

SceneQuad 是 SceneComponent,代表佔用 3D 世界 2D 表面。 SceneQuads 類似于 ARKit ARPlaneAnchor 或 ARCore 平面,但它們提供更高層級的功能,因為平面應用程式或增強式 UX 要使用的 2d 畫布。2D 特定 API 是針對四邊形提供,讓放置和版面配置更容易使用,而且除了轉譯) 四邊形以外,開發 (應該更類似于使用 3d 網格的 2d 畫布。

SceneQuad 圖形

SceneQuads 會在 2d 中定義周框矩形表面。 不過,SceneQuads 代表具有任意且可能複雜圖形的介面 (例如環圈形表格。) 若要代表四邊形的複雜圖形,您可以使用 GetSurfaceMask API 將表面的形狀轉譯到您提供的影像緩衝區。 如果具有四邊形的 SceneObject 也有網格,則網格三角形應該相當於這個轉譯的影像,兩者都代表表面的實際幾何,不論是在 2d 或 3D 座標中。

場景瞭解 SDK 詳細資料和參考

注意

使用 MRTK 時,請注意,您將與 MRTK 的 ['WindowsSceneUnderstandingObserver'] (xref:Microsoft.MixedReality.Toolkit.WindowsSceneUnderstanding.Experimental.WindowsScene 互動UnderstandingObserver?view=mixed-reality-toolkit-unity-2020-dotnet-2.8.0&preserve-view=true) ,因此在大部分情況下可能會略過本節。 如需詳細資訊,請參閱 [MRTK Scene Understanding 檔] (/windows/mixed-reality/mrtk-unity/features/spatial-awareness/scene-understanding) 。

下一節將協助您熟悉 SceneUnderstanding 的基本概念。 本節應該提供基本概念,此時您應該有足夠的內容可流覽範例應用程式,以查看 SceneUnderunder 如何全面使用。

初始化

使用 SceneUnderstanding 的第一個步驟是讓應用程式取得 Scene 物件的參考。 這可以透過兩種方式之一來完成,場景可由驅動程式計算,或過去計算的現有場景可以取消序列化。 後者適用于在開發期間使用 SceneUnderunderunder,其中應用程式和體驗可以在沒有混合實境裝置的情況下快速建立原型。

場景是使用 SceneObserver 來計算。 建立場景之前,您的應用程式應該先查詢您的裝置,以確保它支援 SceneUnderunder,以及要求使用者存取 SceneUnderstanding 所需的資訊。

if (!SceneObserver.IsSupported())
{
    // Handle the error
}

// This call should grant the access we need.
await SceneObserver.RequestAccessAsync();

如果未呼叫 RequestAccessAsync () ,計算新的場景將會失敗。 接下來,我們將計算以Mixed Reality頭戴式裝置為根目錄的新場景,且半徑為 10 公尺。

// Create Query settings for the scene update
SceneQuerySettings querySettings;

querySettings.EnableSceneObjectQuads = true;                                       // Requests that the scene updates quads.
querySettings.EnableSceneObjectMeshes = true;                                      // Requests that the scene updates watertight mesh data.
querySettings.EnableOnlyObservedSceneObjects = false;                              // Do not explicitly turn off quad inference.
querySettings.EnableWorldMesh = true;                                              // Requests a static version of the spatial mapping mesh.
querySettings.RequestedMeshLevelOfDetail = SceneMeshLevelOfDetail.Fine;            // Requests the finest LOD of the static spatial mapping mesh.

// Initialize a new Scene
Scene myScene = SceneObserver.ComputeAsync(querySettings, 10.0f).GetAwaiter().GetResult();

從 Data (也稱為初始化。電腦路徑)

雖然可以計算場景以進行直接耗用量,但也可以以序列化形式計算,以供稍後使用。 這已證明適用于開發,因為它可讓開發人員在和測試 Scene Understanding 中工作,而不需要裝置。 序列化場景的動作幾乎與計算相同,資料會傳回至您的應用程式,而不是由 SDK 在本機還原序列化。 然後,您可以自行還原序列化,或儲存以供日後使用。

// Create Query settings for the scene update
SceneQuerySettings querySettings;

// Compute a scene but serialized as a byte array
SceneBuffer newSceneBuffer = SceneObserver.ComputeSerializedAsync(querySettings, 10.0f).GetAwaiter().GetResult();

// If we want to use it immediately we can de-serialize the scene ourselves
byte[] newSceneData = new byte[newSceneBuffer.Size];
newSceneBuffer.GetData(newSceneData);
Scene mySceneDeSerialized = Scene.Deserialize(newSceneData);

// Save newSceneData for later

SceneObject 列舉

您的應用程式現在已有場景,您的應用程式將會查看與 SceneObjects 互動。 這是藉由存取 SceneObjects 屬性來完成:

SceneObject firstFloor = null;

// Find the first floor object
foreach (var sceneObject in myScene.SceneObjects)
{
    if (sceneObject.Kind == SceneObjectKind.Floor)
    {
        firstFloor = sceneObject;
        break;
    }
}

元件更新和精簡元件

有另一個函式會擷取場景中稱為 FindComponent的元件。 更新追蹤物件並在稍後的場景中尋找它們時,此函式很有用。 下列程式碼會計算相對於先前場景的新場景,然後在新場景中尋找樓層。

// Compute a new scene, and tell the system that we want to compute relative to the previous scene
Scene myNextScene = SceneObserver.ComputeAsync(querySettings, 10.0f, myScene).GetAwaiter().GetResult();

// Use the Id for the floor we found last time, and find it again
firstFloor = (SceneObject)myNextScene.FindComponent(firstFloor.Id);

if (firstFloor != null)
{
    // We found it again, we can now update the transforms of all objects we attached to this floor transform
}

從場景物件存取網格和四邊形

一旦找到 SceneObjects,您的應用程式很可能想要存取它所組成四邊形/網格中包含的資料。 此資料是使用 四邊 形和 網格 屬性來存取。 下列程式碼會列舉我們 floor 物件的所有四邊形和網格。


// Get the transform for the SceneObject
System.Numerics.Matrix4x4 objectToSceneOrigin = firstFloor.GetLocationAsMatrix();

// Enumerate quads
foreach (var quad in firstFloor.Quads)
{
    // Process quads
}

// Enumerate meshes
foreach (var mesh in firstFloor.Meshes)
{
    // Process meshes
}

請注意,它是具有相對於場景來源之轉換的 SceneObject。 這是因為 SceneObject 代表「thing」 的實例,而且在空間中是 locatable 的,四邊形和網格代表相對於其父代轉換的幾何。 個別的 SceneObjects 可以參考相同的 SceneMesh/SceneQuad SceneComponents,而且 SceneObject 也有一個以上的 SceneMesh/SceneQuad。

處理轉換

Scene Understanding 已刻意嘗試在處理轉換時與傳統的 3D 場景標記法保持一致。 因此,每個場景都僅限於單一座標系統,就像最常見的 3D 環境標記法一樣。 SceneObjects 分別提供其相對於該座標系統的位置。 如果您的應用程式處理了延展單一原點所提供的場景限制,它可以將 SceneObjects 錨定到 SpatialAnchors,或產生數個場景並將它們合併在一起,但為了簡單起見,我們假設水力場景存在於由 Scene.OriginSpatialGraphNodeId 所定義的一個 NodeId 當地語系化的原始來源中。

例如,下列 Unity 程式碼示範如何使用 Windows Perception 和 Unity API,將座標系統對齊在一起。 如需 Windows Perception API 的詳細資料,請參閱SpatialCoordinateSystemSpatialGraphInteropPreview,如需取得對應至 Unity 世界原始來源 SpatialCoordinateSystem 的詳細資訊,請參閱Unity 中的 Mixed Reality原生物件

private System.Numerics.Matrix4x4? GetSceneToUnityTransformAsMatrix4x4(SceneUnderstanding.Scene scene)
{
    System.Numerics.Matrix4x4? sceneToUnityTransform = System.Numerics.Matrix4x4.Identity;

    
    Windows.Perception.Spatial.SpatialCoordinateSystem sceneCoordinateSystem = Microsoft.Windows.Perception.Spatial.Preview.SpatialGraphInteropPreview.CreateCoordinateSystemForNode(scene.OriginSpatialGraphNodeId);
    Windows.Perception.Spatial.SpatialCoordinateSystem unityCoordinateSystem = Microsoft.Windows.Perception.Spatial.SpatialCoordinateSystem.FromNativePtr(UnityEngine.XR.WindowsMR.WindowsMREnvironment.OriginSpatialCoordinateSystem);

    sceneToUnityTransform = sceneCoordinateSystem.TryGetTransformTo(unityCoordinateSystem);

    if (sceneToUnityTransform != null)
    {
        sceneToUnityTransform = ConvertRightHandedMatrix4x4ToLeftHanded(sceneToUnityTransform.Value);
    }
    else
    {
        return null;
    }
            
    return sceneToUnityTransform;
}

每個 都有 SceneObject 一個轉換,然後套用至該物件。 在 Unity 中,我們會轉換成右手座標,並指派本機轉換,如下所示:

private System.Numerics.Matrix4x4 ConvertRightHandedMatrix4x4ToLeftHanded(System.Numerics.Matrix4x4 matrix)
{
    matrix.M13 = -matrix.M13;
    matrix.M23 = -matrix.M23;
    matrix.M43 = -matrix.M43;

    matrix.M31 = -matrix.M31;
    matrix.M32 = -matrix.M32;
    matrix.M34 = -matrix.M34;

    return matrix;
}

 private void SetUnityTransformFromMatrix4x4(Transform targetTransform, System.Numerics.Matrix4x4 matrix, bool updateLocalTransformOnly = false)
 {
    if(targetTransform == null)
    {
        return;
    }

    Vector3 unityTranslation;
    Quaternion unityQuat;
    Vector3 unityScale;

    System.Numerics.Vector3 vector3;
    System.Numerics.Quaternion quaternion;
    System.Numerics.Vector3 scale;

    System.Numerics.Matrix4x4.Decompose(matrix, out scale, out quaternion, out vector3);

    unityTranslation = new Vector3(vector3.X, vector3.Y, vector3.Z);
    unityQuat        = new Quaternion(quaternion.X, quaternion.Y, quaternion.Z, quaternion.W);
    unityScale       = new Vector3(scale.X, scale.Y, scale.Z);

    if(updateLocalTransformOnly)
    {
        targetTransform.localPosition = unityTranslation;
        targetTransform.localRotation = unityQuat;
    }
    else
    {
        targetTransform.SetPositionAndRotation(unityTranslation, unityQuat);
    }
}

// Assume we have an SU object called suObject and a unity equivalent unityObject

System.Numerics.Matrix4x4 converted4x4LocationMatrix = ConvertRightHandedMatrix4x4ToLeftHanded(suObject.GetLocationAsMatrix());
SetUnityTransformFromMatrix4x4(unityObject.transform, converted4x4LocationMatrix, true);
        

四邊形的設計目的是協助 2D 放置案例,而且應該視為 2D 畫布 UX 元素的延伸模組。 雖然四邊形是 SceneObjects 的元件,而且可以在 3D 中轉譯,但四邊形 API 本身會假設四邊形是 2D 結構。 它們提供範圍、形狀和提供放置 API 等資訊。

四邊形具有矩形範圍,但它們代表任意形狀的 2D 表面。 若要在這些與 3D 環境四邊形互動的 2D 表面上啟用放置,可提供公用程式,讓此互動能夠進行。 目前 Scene Understanding 提供兩個這類函式 :FindCentermostPlacementGetSurfaceMask。 FindCentermostPlacement 是一個高階 API,可在四邊形中找出可放置物件的位置,並嘗試尋找物件的最佳位置,以確保您提供的周框方塊會留在基礎介面上。

注意

輸出的座標相對於「四邊形空間」中的四邊形,左上角 (x = 0,y = 0) ,就像其他視窗 Rect 類型一樣。 使用您自己的物件來源時,請務必將此納入考慮。

下列範例示範如何尋找最中央的位置,並將全像投影錨定到四邊形。

// This code assumes you already have a "Root" object that attaches the Scene's Origin.

// Find the first quad
foreach (var sceneObject in myScene.SceneObjects)
{
    // Find a wall
    if (sceneObject.Kind == SceneObjectKind.Wall)
    {
        // Get the quad
        var quads = sceneObject.Quads;
        if (quads.Count > 0)
        {
            // Find a good location for a 1mx1m object  
            System.Numerics.Vector2 location;
            if (quads[0].FindCentermostPlacement(new System.Numerics.Vector2(1.0f, 1.0f), out location))
            {
                // We found one, anchor something to the transform
                // Step 1: Create a new game object for the quad itself as a child of the scene root
                // Step 2: Set the local transform from quads[0].Position and quads[0].Orientation
                // Step 3: Create your hologram and set it as a child of the quad's game object
                // Step 4: Set the hologram's local transform to a translation (location.x, location.y, 0)
            }
        }
    }
}

步驟 1-4 高度相依于您的特定架構/實作,但主題應該類似。 請務必注意,四邊形只代表在空間中當地語系化的限定 2D 平面。 藉由讓引擎/架構知道四邊形的所在位置,並將物件相對於四邊形進行根目錄,您的全像投影會正確放置於真實世界。

網狀

網格代表物件或環境的幾何表示。 與每個空間表面網格所提供的 空間對應、網格索引和頂點資料都使用相同的熟悉配置,與所有新式轉譯 API 中用於轉譯三角形網格的頂點和索引緩衝區相同。 頂點位置是在 的 Scene 座標系統中提供。 用來參考此資料的特定 API 如下所示:

void GetTriangleIndices(int[] indices);
void GetVertices(System.Numerics.Vector3[] vertices);

下列程式碼提供從網格結構產生三角形清單的範例:

uint[] indices = new uint[mesh.TriangleIndexCount];
System.Numerics.Vector3[] positions = new System.Numerics.Vector3[mesh.VertexCount];

mesh.GetTriangleIndices(indices);
mesh.GetVertexPositions(positions);

索引/頂點緩衝區必須是 > = 索引/頂點計數,但也可以任意調整大小,以允許有效率的記憶體重複使用。

ColliderMesh

場景物件可透過 Meshes 和 ColliderMeshes 屬性存取網格和碰撞器網格資料。 這些網格一律相符,這表示 Meshes 屬性的第 i 個索引代表與 ColliderMeshes 屬性的第 i 個索引相同的幾何。 如果執行時間/物件支援碰撞器網格,您保證會取得最低的多邊形、最高順序近似值,而且最好在應用程式使用碰撞器的位置使用 ColliderMeshes。 如果系統不支援 ColliderMeshes 中傳回的 Mesh 物件與網格降低記憶體條件約束的物件相同。

使用場景瞭解進行開發

此時,您應該瞭解場景瞭解執行時間和 SDK 的核心建置組塊。 大量的電源和複雜度在於存取模式、與 3D 架構的互動,以及可在這些 API 上撰寫的工具,以執行更進階的工作,例如空間規劃、會議室分析、流覽、物理等。 我們希望在範例中擷取這些範例,希望引導您正確方向,讓您的案例更亮起。 如果有未處理的範例或案例,請讓我們知道,我們會嘗試記錄/建立您需要的原型。

我可以在哪裡取得範例程式碼?

您可以在 Unity 範例頁面找到 Unity 的 Scene Understanding 範例程式碼。 此應用程式可讓您與裝置通訊並轉譯各種場景物件,或者,它可讓您在電腦上載入序列化的場景,並讓您體驗沒有裝置的 Scene Understanding。

我可以在哪裡取得範例場景?

如果您有 HoloLens2,您可以將 ComputeSerializedAsync 的輸出儲存至檔案並自行還原序列化,以儲存您擷取的任何場景。

如果您沒有 HoloLens2 裝置,但想要使用 Scene Understanding 播放,則必須下載預先擷取的場景。 Scene Understanding 範例目前隨附序列化場景,可自行下載及使用。 您可以在這裡找到它們:

場景瞭解範例場景

另請參閱