SceneObject_3
-
SceneMesh_3
此图突出显示了场景的物理布局和逻辑布局之间的差别。 在左侧,我们看到应用程序在枚举场景时所看到的数据的层次结构布局。 在右侧,我们看到场景由 12 个不同的组件(如有必要,可单独访问)组成。 处理新场景时,我们希望应用程序以逻辑方式遍历此层次结构,但是,在跟踪各场景更新时,某些应用程序可能只希望定位两个场景之间共享的特定组件。
API 概述
以下部分简要概述了场景理解中的构造。 本部分将帮助你了解场景的表示形式,以及各种组件的作用/用途。 下一部分将提供本概述中提到的具体代码示例和其他详细信息。
下面介绍的所有类型都驻留在
Microsoft.MixedReality.SceneUnderstanding
命名空间中。
SceneComponent
了解场景的逻辑布局后,我们可以介绍 SceneComponent 的概念,以及如何使用它们来构成层次结构。 SceneComponent 是 SceneUnderunder 中最精细的分解部分,表示单个核心体,例如网格、四边形或边界框。 SceneComponent 可独立更新,并且可由其他 SceneComponent 引用,因此它们具有单个全局属性和唯一 ID,支持针对此类型的跟踪/引用机制。 ID 用于场景层次结构以及对象持久性(某个场景相对于其他场景的更新行为)的逻辑构成。
如果将每个新计算的场景视为不同场景,并且只是枚举其中的所有数据,那么你基本可以忽略 ID。 但是,如果计划跟踪组件的多个更新,就需要使用这些 ID 在场景对象中索引和查找 SceneComponent。
SceneObject
SceneObject 是一个 SceneComponent,它表示一个“物体”(例如墙壁、地面、天花板等)的实例,由其 Kind 属性来表示。 SceneObject 是几何形,因此具有函数和属性来表示它们在空间中的位置,但是它们不包含任何几何结构或逻辑结构。 相反,SceneObject 引用其他 SceneComponent,特别是 SceneQuad 和 SceneMesh,这些 SceneComponent 提供系统支持的各种表示形式。 计算新场景时,应用程序很可能枚举场景的 SceneObject 来处理其感兴趣的内容。
SceneObject 可以具有以下任一项:
SceneObjectKind 说明
背景此 SceneObject 已知不是其他可识别的场景对象类型之一。
此类不应与“未知”混淆,其中“背景”已知不是墙壁/地面/天花板等,而“未知”则表示尚未分类。
Wall物理墙壁。 假定墙壁是不可移动的环境结构。
Floor地面是可在其上行走的任何图面。 注意:楼梯不是地面。 另请注意,地面假定任何可行走的图面,因此没有明确假设单一地面。 多层结构、坡道等应全部归为地面类别。
Ceiling房间的上表面。
平台可以放置全息影像的较大平面。 它们往往表示桌子、台面和其他较大水平面。
World与标记无关的几何数据的保留标签。 通过设置 EnableWorldMesh 更新标志而生成的网格将归为世界类别。
Unknown此场景对象尚未分类和分配类型。 它不应与“背景”混淆,因为此对象可以是任何物体,只是系统尚未针对它提出足够有力的分类。
SceneMesh
SceneMesh 是一种 SceneComponent,它使用三角形列表近似表示任意几何对象的几何形状。 SceneMeshe 可用于多个不同的上下文;它们可以表示水密单元结构的组件,或表示为 WorldMesh,即与场景关联的无限空间映射网格。 每个网格提供的索引和顶点数据,使用与所有现代呈现 API 中用于呈现三角形网格的
顶点和索引缓冲区
相同且熟悉的布局。 在场景理解中,网格使用 32 位索引,可能需要针对某些呈现引擎分解为区块。
顶点连接顺序和坐标系
场景理解生成的所有网格都应按顺时针顶点连接顺序在右手坐标系中返回网格。
注意:.191105 版本之前的 OS 内部版本可能存在一个已知 bug,即“世界”网格按逆时针顶点连接顺序返回,该 bug 随后已修复。
SceneQuad
SceneQuad 是一个 SceneComponent,它表示占用 3D 世界的 2D 图面。 SceneQuad 的使用类似于 ARKit ARPlaneAnchor 或 ARCore Planes,但前者提供了更高级的功能,即供平面应用或增强 UX 使用的 2D 画布。它为四边形提供特定于 2D 的 API,便于轻松放置和布局,并且在使用四边形进行开发(除呈现以外)时,应该感觉更像是在使用 2D 画布而非 3D 网格。
SceneQuad 形状
SceneQuad 定义有边界的 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 场景理解文档] (/windows/mixed-reality/mrtk-unity/features/spatial-awareness/scene-understanding)。
以下部分将帮助你熟悉 SceneUnderstanding 的基础知识。 本部分提供的基础知识应使你获得足够的背景知识来浏览示例应用程序,以了解如何全面使用 SceneUnderstanding。
使用 SceneUnderunder 的第一步是让应用程序获取对场景对象的引用。 可通过两种方式之一完成此操作:一种是由驱动程序来计算场景,另一种是反序列化过去计算的现有场景。 后者对于在开发过程中使用 SceneUnderstanding 非常有用,即无需使用混合现实设备即可快速构建应用程序和体验的原型。
使用 SceneObserver 计算场景。 在创建场景之前,应用程序应查询设备以确保其支持 SceneUnderstanding,并请求用户访问 SceneUnderstanding 所需信息的权限。
if (!SceneObserver.IsSupported())
// Handle the error
// This call should grant the access we need.
await SceneObserver.RequestAccessAsync();
如果未调用 RequestAccessAsync(),则计算新场景将失败。 接下来,我们将计算以混合现实头戴显示设备为中心、半径为 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();
从数据初始化(也称为电脑路径)
虽然可以计算场景以供直接使用,但也可以采用序列化形式计算这些场景,以供之后使用。 对于开发而言,这一点经证实非常有用,因为它使开发人员无需设备即可使用和测试场景理解。 场景序列化行为与计算行为几乎相同,数据将返回到应用程序,而不是由 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 枚举
应用程序具有场景后,它将查看 SceneObject 并与之交互。 这是通过访问 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
从场景对象访问网格和四边形
找到 SceneObject 后,应用程序很可能希望访问构成对象的四边形/网格中包含的数据。 使用四边形和网格属性访问此数据。 以下代码将枚举地面对象的所有四边形和网格。
// 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 表示“物体”的实例,它在空间中是可定位的,而四边形和网格则表示相对于其父级转换的几何图形。 单独的 SceneObject 可以引用相同的 SceneMesh/SceneQuad SceneComponent,并且 SceneObject 可以具有多个 SceneMesh/SceneQuad。
在处理转换时,场景理解有意尝试与传统的 3D 场景表示形式保持一致。 因此,每个场景会限制在单个坐标系中,就像大多数常见的 3D 环境表示形式一样。 SceneObject 各自提供它们相对于该坐标系的位置。 如果应用程序处理的场景超出了单个原点所提供的范围,应用程序可以将 SceneObject 定位在 SpatialAnchor 上,或生成多个场景并将其合并,但为了简单起见,我们假设水密场景存在于它们自己的原点中,该原点由 Scene.OriginSpatialGraphNodeId 定义的一个 NodeId 来定位。
例如,以下 Unity 代码将演示如何使用 Windows Perception 和 Unity API 将坐标系彼此对齐。 有关 Windows Perception API 的详细信息,请参阅 SpatialCoordinateSystem 和 SpatialGraphInteropPreview,有关如何获取与 Unity 世界原点对应的 SpatialCoordinateSystem 的详细信息,请参阅 Unity 中的混合现实原生对象。
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);
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;
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 元素的扩展。 虽然四边形是 SceneObject 的组成部分,并且可以在 3D 中呈现,但四边形 API 本身假定四边形是 2D 结构。 它们提供范围、形状等信息,并提供 API 以用于放置。
四边形提供矩形范围,但它们表示任意形状的 2D 图面。 为在这些与 3D 环境交互的 2D 图面上实现放置,四边形提供了实用程序,来实现这种交互。 目前,场景理解提供两个此类函数:FindCentermostPlacement 和 GetSurfaceMask。 FindCentermostPlacement 是一个高级别 API,它定位四边形上可以放置对象的位置,并尝试查找对象的最佳位置,确保提供的边界框始终在基础图面之上。
输出的坐标是相对于“四边形空间”中的四边形而言的,其左上角坐标为(x = 0,y = 0),就像其他窗口矩形类型一样。 在对自己的对象使用原点时,请务必考虑这一点。
以下示例演示如何查找最中央的可放置位置,以及如何将全息影像定位到四边形。
// 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 属性的索引表示与 ColliderMeshes 属性的索引相同的几何图形。 如果运行时/对象支持碰撞体网格,则可以确保获得最少的多边形和最多的近似图形,如果应用程序要使用碰撞体,使用 ColliderMesh 是不错的做法。 如果系统不支持碰撞体,则 ColliderMesh 返回的 Mesh 对象将与网格对象相同,以减少内存约束。
使用场景理解进行开发
现在你应已了解场景理解运行时和 SDK 的核心构建基块。 其功能复杂性主要在于访问模式、与 3D 框架的交互,以及可基于这些 API 编写以便执行更高级任务(例如空间规划、房间分析、导航、物理任务等)的工具。 我们希望通过示例描述这些内容,并希望这些示例能够正确引导你成功完成方案。 如果有未涉及的示例或方案,请告知我们,我们将尝试记录/原型化所需的内容。
在哪里可以获取示例代码?
可以在 Unity 示例页找到 Unity 的场景理解示例代码。 通过此应用程序,你可以与设备通信并呈现各种场景对象,或者,可以在电脑上加载序列化场景,并在没有设备的情况下体验场景理解。
在哪里可以获取示例场景?
如果拥有 HoloLens2,可以通过将 ComputeSerializedAsync 的输出保存到文件并自行将其反序列化,来保存捕获的任何场景。
如果没有 HoloLens2 设备,但想要使用场景理解,则需要下载预先捕获的场景。 场景理解示例目前附带序列化场景,你可以自行下载并使用这些场景。 可在以下位置找到它们:
场景理解示例场景
Unity 示例