空间映射提供了HoloLens周围环境中真实世界表面的详细表示,允许开发人员创建令人信服的混合现实体验。通过将真实世界与虚拟世界合并,应用可以使全息图看起来是真实的。通过提供熟悉的现实世界行为和交互,应用程序也可以更自然地与用户期望一致。

Unity内置了对空间映射功能的支持,通过以下两种方式提供给开发者:
1. HoloToolkit项目中你可以找到空间映射组件,这可以让你便捷快速地开始使用空间映射特性。
2. Unity还提供更多底层的空间映射API,以便开发者能够完全控制空间映射特性,满足定制复杂的应用需求

本文主要介绍HoloToolKit提供的空间映射(SpatialMapping)组件,利用此组件可以快速的应用中集成使用HoloLens的空间映射特性。

空间映射(SpatialMapping)主要有下面的使用用途:

  • Occlusion
  • Visualization
  • Placement
  • Physics
  • Navigation

设置 SpatialMapping 功能开启

为了使应用能够使用空间映射数据,SpatialPerception能力必须被启用。

使用以下步骤启用此能力:
1. 在Unity编辑器中,进入Player Settings选项(Edit > Project Settings > Player)
2. 点击Window Store选项卡
3. 展开Publish Settings选项,并在Capabilities列表勾选SpatialPerception选项

use the API 使用底层API步骤


SurfaceObserver是主要使用到的API对象,下面是应用使用空间映射特性推荐的大致流程:

  1. 设定SurfaceObserver对象
    要为每一个需要空间映射数据的空间区域在应用中声明并初始化一个SurfaceObserver对象:
SurfaceObserver surfaceObserver;
void Start () { surfaceObserver = new SurfaceObserver();
}
  1. 通过调用SetVolumeAsSphere、SetVolumeAsAxisAlignedBox、 SetVolumeAsOrientedBox、 或 SetVolumeAsFrustum方法可以为每个SurfaceObserver对象指定它们需要获取数据的空间范围。以后你还可以通过再次调用它们来重新设定检测的空间范围。
void Start () { ... surfaceObserver.SetVolumeAsAxisAlignedBox(Vector3.zero, new Vector3(3, 3, 3));
}
  1. 处理OnDataReady事件
    OnDataReady事件方法会接收到一个SurfaceData对象,它包含了WorldAnchor、MeshFilter和MeshCollider对象数据,表示了当前关联的空间表面最新状态。通过访问Mesh Filter对象的Mesh数据可以进行性能分析或者处理网格。使用最新的Mesh数据来渲染空间表面并将它用于物理碰撞或者射线击中对象。确认SurfaceData内容不为空很重要。

  2. 处理空间表面变化,即处理OnSurfaceChanged事件

    关于空间表面变化,有几个典型情形需要处理。Added状态和Updated状态可以使用相同的代码处理,Removed状态则使用另一种代码来处理。

    • 在Added和Updated情形下,我们从字典中添加或者获取代码当前网格的对象,使用必要的组件来创建一个SurfaceData结构体,然后调用RequestMeshDataAsync方法在场景中使用网格数据和位置来填充对象。
    • 在Removed情形下,我们从字典中移除当前网格代表的对象并销毁它。
System.Collections.Generic.Dictionary<SurfaceId, GameObject> spatialMeshObjects = new System.Collections.Generic.Dictionary<SurfaceId, GameObject>();private void OnSurfaceChanged(SurfaceId surfaceId, SurfaceChange changeType, Bounds bounds, System.DateTime updateTime){switch (changeType){case SurfaceChange.Added:case SurfaceChange.Updated:if (!spatialMeshObjects.ContainsKey(surfaceId)){spatialMeshObjects[surfaceId] = new GameObject("spatial-mapping-" + surfaceId);spatialMeshObjects[surfaceId].transform.parent = this.transform;spatialMeshObjects[surfaceId].AddComponent<MeshRenderer>();}GameObject target = spatialMeshObjects[surfaceId];SurfaceData sd = new SurfaceData(//系统返回的surface id,//当前对象的MeshFilter组件target.GetComponent<MeshFilter>() ?? target.AddComponent<MeshFilter>(),//用于在空间中定位对象的空间锚target.GetComponent<WorldAnchor>() ?? target.AddComponent<WorldAnchor>(),//当前网格对象的MeshCollider组件target.GetComponent<MeshCollider>() ?? target.AddComponent<MeshCollider>(),//每立方米网格三角形的数量1000,//bakeMeshes -如果是true,MeshCollider会被数据填充,反之MeshCollider为空true);SurfaceObserver.RequestMeshAsync(sd, OnDataReady);break;case SurfaceChange.Removed:var obj = spatialMeshObjects[surfaceId];spatialMeshObjects.Remove(surfaceId);if (obj != null){GameObject.Destroy(obj);}break;default:break;}}

SpatialMapping Components 空间映射组件


下面通过一个例子来说明空间映射的使用

1.在HoloToolkit->SpatialMapping->Prefabs 中找到并添加SpatialMapping Prefabs

在SpatialMapping中能够看到已经存在了三个脚本组件:
- SpatialMappingObserver.cs
- SpatialMappingManager.cs
- ObjectSurfaceObserver.cs

SpatialMappingObserver.cs 主要是用于定期进行对周围环境进行扫描并更新Surface数据,具体可参考代码:

// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License. See LICENSE in the project root for license information.using System.Collections.Generic;
using UnityEngine;
using UnityEngine.VR.WSA;namespace HoloToolkit.Unity
{/// <summary>/// Spatial Mapping Observer states./// </summary>public enum ObserverStates{/// <summary>/// The SurfaceObserver is currently running./// </summary>Running = 0,/// <summary>/// The SurfaceObserver is currently idle./// </summary>Stopped = 1}/// <summary>/// The SpatialMappingObserver class encapsulates the SurfaceObserver into an easy to use/// object that handles managing the observed surfaces and the rendering of surface geometry./// </summary>public class SpatialMappingObserver : SpatialMappingSource{[Tooltip("The number of triangles to calculate per cubic meter.")]//每立方米的三角形网格的数量public float TrianglesPerCubicMeter = 500f;[Tooltip("The extents of the observation volume.")]//设置检测的空间体积范围public Vector3 Extents = Vector3.one * 10.0f;[Tooltip("How long to wait (in sec) between Spatial Mapping updates.")]//空间映射更新的等待时间public float TimeBetweenUpdates = 3.5f;[Tooltip("Recalculates normals whenever a mesh is updated.")]public bool RecalculateNormals = false;/// <summary>/// Our Surface Observer object for generating/updating Spatial Mapping data./// </summary>/// SurfaceObserver对象用于生成或更新空间映射数据private SurfaceObserver observer;/// <summary>/// A dictionary of surfaces that our Surface Observer knows about./// Key: surface id/// Value: GameObject containing a Mesh, a MeshRenderer and a Material/// </summary>private Dictionary<int, GameObject> surfaces = new Dictionary<int, GameObject>();/// <summary>/// A queue of SurfaceData objects. SurfaceData objects are sent to the/// SurfaceObserver to generate meshes of the environment./// </summary>private Queue<SurfaceData> surfaceWorkQueue = new Queue<SurfaceData>();/// <summary>/// To prevent too many meshes from being generated at the same time, we will/// only request one mesh to be created at a time.  This variable will track/// if a mesh creation request is in flight./// 防止不同surface的网格同时生成,置标记位,只允许一次性生成一个surface的网格。/// </summary>private bool surfaceWorkOutstanding = false;/// <summary>/// Used to track when the Observer was last updated./// 用于跟踪观察者的上次更新时间/// </summary>private float updateTime;/// <summary>/// Indicates the current state of the Surface Observer./// </summary>public ObserverStates ObserverState { get; private set; }private void Awake(){//为每一个需要空间映射数据的空间区域在应用中初始化一个SurfaceObserver对象observer = new SurfaceObserver();ObserverState = ObserverStates.Stopped;}/// <summary>/// Called when the GaemObject is initialized./// </summary>private void Start(){//通过调用SetVolumeAsSphere、SetVolumeAsAxisAlignedBox、  //SetVolumeAsOrientedBox、 或 SetVolumeAsFrustum方法可以为每个SurfaceObserver对象  //指定它们需要获取数据的空间范围。可再次调用来重新设定检测的空间范围。observer.SetVolumeAsAxisAlignedBox(Vector3.zero, Extents);}/// <summary>/// Called once per frame./// </summary>private void Update(){//只有在SurfaceObserver处于运行状态时进行处理if (ObserverState == ObserverStates.Running){// If we don't have mesh creation in flight, but we could schedule mesh creation, do so.if (surfaceWorkOutstanding == false && surfaceWorkQueue.Count > 0){// Pop the SurfaceData off the queue.  A more sophisticated algorithm could prioritize// the queue based on distance to the user or some other metric.//将SurfaceData从队列中取出SurfaceData surfaceData = surfaceWorkQueue.Dequeue();// If RequestMeshAsync succeeds, then we have successfully scheduled mesh creation.//当RequestMeshAsync调用成功,则就成功调度了网格创建,置surfaceWorkOutstanding为true//等待回调函数SurfaceObserver_OnDataReady处理完成,再将surfaceWorkOutstanding置为falsesurfaceWorkOutstanding = observer.RequestMeshAsync(surfaceData, SurfaceObserver_OnDataReady);}// If we don't have any other work to do, and enough time has passed since the previous// update request, request updates for the spatial mapping data.//每隔一段时间进行刷新,查看是否发生变化else if (surfaceWorkOutstanding == false && (Time.time - updateTime) >= TimeBetweenUpdates){observer.Update(SurfaceObserver_OnSurfaceChanged);updateTime = Time.time;}}}/// <summary>/// Starts the Surface Observer./// </summary>public void StartObserving(){if (ObserverState != ObserverStates.Running){Debug.Log("Starting the observer.");ObserverState = ObserverStates.Running;// We want the first update immediately.updateTime = 0;}}/// <summary>/// Stops the Surface Observer./// </summary>/// <remarks>Sets the Surface Observer state to ObserverStates.Stopped.</remarks>public void StopObserving(){if (ObserverState == ObserverStates.Running){Debug.Log("Stopping the observer.");ObserverState = ObserverStates.Stopped;}}/// <summary>/// Handles the SurfaceObserver's OnDataReady event./// </summary>/// <param name="cookedData">Struct containing output data.</param>/// <param name="outputWritten">Set to true if output has been written.</param>/// <param name="elapsedCookTimeSeconds">Seconds between mesh cook request and propagation of this event.</param>private void SurfaceObserver_OnDataReady(SurfaceData cookedData, bool outputWritten, float elapsedCookTimeSeconds){GameObject surface;if (surfaces.TryGetValue(cookedData.id.handle, out surface)){// Set the draw material for the renderer.//设置材质MeshRenderer renderer = surface.GetComponent<MeshRenderer>();renderer.sharedMaterial = SpatialMappingManager.Instance.SurfaceMaterial;renderer.enabled = SpatialMappingManager.Instance.DrawVisualMeshes;if (RecalculateNormals){MeshFilter filter = surface.GetComponent<MeshFilter>();if (filter != null && filter.sharedMesh != null){filter.sharedMesh.RecalculateNormals();}}if (SpatialMappingManager.Instance.CastShadows == false){renderer.shadowCastingMode = UnityEngine.Rendering.ShadowCastingMode.Off;}}surfaceWorkOutstanding = false;}/// <summary>/// Handles the SurfaceObserver's OnSurfaceChanged event.///处理空间表面变化事件/// </summary>/// <param name="id">The identifier assigned to the surface which has changed.</param>/// <param name="changeType">The type of change that occurred on the surface.</param>/// <param name="bounds">The bounds of the surface.</param>/// <param name="updateTime">The date and time at which the change occurred.</param>private void SurfaceObserver_OnSurfaceChanged(SurfaceId id, SurfaceChange changeType, Bounds bounds, System.DateTime updateTime){// Verify that the client of the Surface Observer is expecting updates.if (ObserverState != ObserverStates.Running){return;}GameObject surface;//关于空间表面变化,有几个典型情形需要处理。Added状态和Updated状态可以使用相同的代码处理,Removed状态则使用另一种代码来处理。switch (changeType){// Adding and updating are nearly identical.  The only difference is if a new GameObject to contain// the surface needs to be created.//在Added和Updated情形下,我们从字典中添加或者获取代码当前网格的对象,使用必要  //的组件来创建一个SurfaceData结构体,然后调用RequestMeshDataAsync方法在场景中  //使用网格数据和位置来填充对象。case SurfaceChange.Added:case SurfaceChange.Updated:// Check to see if the surface is known to the observer.if (!surfaces.TryGetValue(id.handle, out surface)){// If we are adding a new surface, construct a GameObject// to represent its state and attach some Mesh-related// components to it.surface = AddSurfaceObject(null, string.Format("Surface-{0}", id.handle), transform);surface.AddComponent<WorldAnchor>();// Add the surface to our dictionary of known surfaces so// we can interact with it later.surfaces.Add(id.handle, surface);}// Add the request to create the mesh for this surface to our work queue.//将surface添加到队列中,等待处理  QueueSurfaceDataRequest(id, surface);break;//在Removed情形下,我们从字典中移除当前网格代表的对象并销毁它。 case SurfaceChange.Removed:// Always process surface removal events.if (surfaces.TryGetValue(id.handle, out surface)){RemoveSurfaceObject(surface);surfaces.Remove(id.handle);}break;}}/// <summary>/// Calls GetMeshAsync to update the SurfaceData and re-activate the surface object when ready./// </summary>/// <param name="id">Identifier of the SurfaceData object to update.</param>/// <param name="surface">The SurfaceData object to update.</param>private void QueueSurfaceDataRequest(SurfaceId id, GameObject surface){SurfaceData surfaceData = new SurfaceData(id,surface.GetComponent<MeshFilter>(),         //当前对象的MeshFilter组件 surface.GetComponent<WorldAnchor>(),        //用于在空间中定位对象的空间锚 surface.GetComponent<MeshCollider>(),       //当前网格对象的MeshCollider组件TrianglesPerCubicMeter,                     //每立方米网格三角形的数量  true);surfaceWorkQueue.Enqueue(surfaceData);}/// <summary>/// Called when the GameObject is unloaded./// </summary>private void OnDestroy(){// Stop the observer.StopObserving();observer.Dispose();observer = null;// Clear our surface mesh collection.surfaces.Clear();}}
}

SpatialMappingManager.cs 主要是对Surface的材质,是否显示网格等一些参数进行配置获取管理:

// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License. See LICENSE in the project root for license information.using System.Collections.Generic;
using UnityEngine;namespace HoloToolkit.Unity
{/// <summary>/// The SpatialMappingManager class allows applications to use a SurfaceObserver or a stored /// Spatial Mapping mesh (loaded from a file)./// When an application loads a mesh file, the SurfaceObserver is stopped./// Calling StartObserver() clears the stored mesh and enables real-time SpatialMapping updates./// </summary>[RequireComponent(typeof(SpatialMappingObserver))]public partial class SpatialMappingManager : Singleton<SpatialMappingManager>{[Tooltip("The physics layer for spatial mapping objects to be set to.")]public int PhysicsLayer = 31;[Tooltip("The material to use for rendering spatial mapping data.")]public Material surfaceMaterial;[Tooltip("Determines if spatial mapping data will be rendered.")]public bool drawVisualMeshes = false;[Tooltip("Determines if spatial mapping data will cast shadows.")]public bool castShadows = false;/// <summary>/// Used for gathering real-time Spatial Mapping data on the HoloLens./// </summary>private SpatialMappingObserver surfaceObserver;/// <summary>/// Used for loading spatial mapping data from a room model./// </summary>private ObjectSurfaceObserver objectSurfaceObserver;/// <summary>/// Time when StartObserver() was called./// </summary>[HideInInspector]public float StartTime { get; private set; }/// <summary>/// The current source of spatial mapping data./// </summary>public SpatialMappingSource Source { get; private set; }// Called when the GameObject is first created.private void Awake(){surfaceObserver = gameObject.GetComponent<SpatialMappingObserver>();Source = surfaceObserver;}// Use for initialization.private void Start(){#if !UNITY_EDITORStartObserver();
#endif#if UNITY_EDITORobjectSurfaceObserver = GetComponent<ObjectSurfaceObserver>();if (objectSurfaceObserver != null){// In the Unity editor, try loading saved meshes from a model.objectSurfaceObserver.Load(objectSurfaceObserver.roomModel);if (objectSurfaceObserver.GetMeshFilters().Count > 0){SetSpatialMappingSource(objectSurfaceObserver);}}
#endif}/// <summary>/// Returns the layer as a bit mask./// </summary>public int LayerMask{get { return (1 << PhysicsLayer); }}/// <summary>/// The material to use when rendering surfaces./// </summary>public Material SurfaceMaterial{get{return surfaceMaterial;}set{if (value != surfaceMaterial){surfaceMaterial = value;SetSurfaceMaterial(surfaceMaterial);}}}/// <summary>/// Specifies whether or not the SpatialMapping meshes are to be rendered./// </summary>public bool DrawVisualMeshes{get{return drawVisualMeshes;}set{if (value != drawVisualMeshes){drawVisualMeshes = value;UpdateRendering(drawVisualMeshes);}}}/// <summary>/// Specifies whether or not the SpatialMapping meshes can cast shadows./// </summary>public bool CastShadows{get{return castShadows;}set{if (value != castShadows){castShadows = value;SetShadowCasting(castShadows);}}}/// <summary>/// Sets the source of surface information./// </summary>/// <param name="mappingSource">The source to switch to. Null means return to the live stream if possible.</param>public void SetSpatialMappingSource(SpatialMappingSource mappingSource){UpdateRendering(false);if (mappingSource == null){Source = surfaceObserver;}else{Source = mappingSource;}UpdateRendering(DrawVisualMeshes);}/// <summary>/// Sets the material used by all Spatial Mapping meshes./// </summary>/// <param name="surfaceMaterial">New material to apply.</param>public void SetSurfaceMaterial(Material surfaceMaterial){SurfaceMaterial = surfaceMaterial;if (DrawVisualMeshes){foreach (Renderer renderer in Source.GetMeshRenderers()){if (renderer != null){renderer.sharedMaterial = surfaceMaterial;}}}}/// <summary>/// Checks to see if the SurfaceObserver is currently running./// </summary>/// <returns>True, if the observer state is running.</returns>public bool IsObserverRunning(){return surfaceObserver.ObserverState == ObserverStates.Running;}/// <summary>/// Instructs the SurfaceObserver to start updating the SpatialMapping mesh./// </summary>public void StartObserver(){if (!IsObserverRunning()){surfaceObserver.StartObserving();StartTime = Time.time;}}/// <summary>/// Instructs the SurfacesurfaceObserver to stop updating the SpatialMapping mesh./// </summary>public void StopObserver(){if (IsObserverRunning()){surfaceObserver.StopObserving();}}/// <summary>/// Gets all meshes that are associated with the SpatialMapping mesh./// </summary>/// <returns>/// Collection of Mesh objects representing the SpatialMapping mesh./// </returns>public List<Mesh> GetMeshes(){List<Mesh> meshes = new List<Mesh>();List<MeshFilter> meshFilters = GetMeshFilters();// Get all valid mesh filters for observed surfaces.foreach (MeshFilter filter in meshFilters){// GetMeshFilters ensures that both filter and filter.sharedMesh are not null.meshes.Add(filter.sharedMesh);}return meshes;}/// <summary>/// Gets all Mesh Filter objects associated with the Spatial Mapping mesh./// </summary>/// <returns>Collection of Mesh Filter objects.</returns>public List<MeshFilter> GetMeshFilters(){return Source.GetMeshFilters();}/// <summary>/// Sets the Cast Shadows property for each Spatial Mapping mesh renderer./// </summary>private void SetShadowCasting(bool castShadows){CastShadows = castShadows;foreach (Renderer renderer in Source.GetMeshRenderers()){if (renderer != null){if (castShadows){renderer.shadowCastingMode = UnityEngine.Rendering.ShadowCastingMode.On;}else{renderer.shadowCastingMode = UnityEngine.Rendering.ShadowCastingMode.Off;}}}}/// <summary>/// Updates the rendering state on the currently enabled surfaces./// </summary>/// <param name="Enable">True, if meshes should be rendered.</param>private void UpdateRendering(bool Enable){List<MeshRenderer> renderers = Source.GetMeshRenderers();for (int index = 0; index < renderers.Count; index++){if (renderers[index] != null){renderers[index].enabled = Enable;if (Enable){renderers[index].sharedMaterial = SurfaceMaterial;}}}}}
}

ObjectSurfaceObserver.cs主要用于当处于Unity编辑环境下时,加载房间模型数据,来进行测试:

  1. 使用device Protal可以在浏览器查看所扫描的房间。点击update可以在上面视窗中显示查看,点击save进行将你所在的房间模型进行保存:

  2. 将保存的房间模型直接加载进unity项目Assets文件目录下,再将其拖拽到ObjectSurfaceObserver.cs脚本组件的Room Model中即可在项目文件中保留房间模型。当在unity环境中运行调试时,可加载此房间模型数据进行测试。

ObjectSurfaceObserver.cs详细代码如下:

// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License. See LICENSE in the project root for license information.  using UnityEngine;  namespace HoloToolkit.Unity
{  public class ObjectSurfaceObserver : SpatialMappingSource  {  [Tooltip("The room model to use when loading meshes in Unity.")]  public GameObject roomModel;  // Use this for initialization.  private void Start()  {
#if UNITY_EDITOR  // When in the Unity editor, try loading saved meshes from a model.  Load(roomModel);  if (GetMeshFilters().Count > 0)  {  SpatialMappingManager.Instance.SetSpatialMappingSource(this);  }
#endif  }  /// <summary>  /// Loads the SpatialMapping mesh from the specified room object.  /// </summary>  /// <param name="roomModel">The room model to load meshes from.</param>  public void Load(GameObject roomModel)  {  if (roomModel == null)  {  Debug.Log("No room model specified.");  return;  }  GameObject roomObject = GameObject.Instantiate(roomModel);  Cleanup();  try  {  MeshFilter[] roomFilters = roomObject.GetComponentsInChildren<MeshFilter>();  foreach (MeshFilter filter in roomFilters)  {  GameObject surface = AddSurfaceObject(filter.sharedMesh, "roomMesh-" + surfaceObjects.Count, transform);  Renderer renderer = surface.GetComponent<MeshRenderer>();  if (SpatialMappingManager.Instance.DrawVisualMeshes == false)  {  renderer.enabled = false;  }  if (SpatialMappingManager.Instance.CastShadows == false)  {  renderer.shadowCastingMode = UnityEngine.Rendering.ShadowCastingMode.Off;  }  // Reset the surface mesh collider to fit the updated mesh.   // Unity tribal knowledge indicates that to change the mesh assigned to a  // mesh collider, the mesh must first be set to null.  Presumably there  // is a side effect in the setter when setting the shared mesh to null.  MeshCollider collider = surface.GetComponent<MeshCollider>();  collider.sharedMesh = null;  collider.sharedMesh = surface.GetComponent<MeshFilter>().sharedMesh;  }  }  catch  {  Debug.Log("Failed to load object " + roomModel.name);  }  finally  {  if (roomModel != null && roomObject != null)  {  GameObject.DestroyImmediate(roomObject);  }  }  }  }
}  

2. 新增一个Cube Prefab,并且添加脚本组件CubeScript.cs

using UnityEngine;
using System.Collections;  public class CubeScript : MonoBehaviour {  // Use this for initialization  void Start () {  }  // Update is called once per frame  //刚启动应用时,空间映射还没准备好,将创建的Cube释放掉  void Update () {  if (transform.position.y < -3)  {  Destroy(gameObject);  }  }
} 

3.在MainCamera上新增脚本组件CubeCreator.cs,用于每隔一秒钟产生一个Cube,用于测试

using UnityEngine;
using System.Collections;  public class CubeCreator : MonoBehaviour {  public GameObject cubePrefab;  // Use this for initialization  void Start () {  StartCoroutine(CreateCube());  }  // Update is called once per frame  void Update () {  }  private IEnumerator CreateCube()  {  while (true)  {  float r = 1.5f;  var theta = transform.rotation.eulerAngles.y * Mathf.Deg2Rad;  var x = r * Mathf.Sin(theta);  var z = r * Mathf.Cos(theta);  Instantiate(cubePrefab,  new Vector3(x, 1, z),  Quaternion.Euler(0, transform.rotation.eulerAngles.y, z));  yield return new WaitForSeconds(1);  }  }
}  

4. 在SpatialMapping上添加DrawMeshChanger.cs脚本组件,用于改变Surface的材质,一个是带网格的,一个是没有网格线的

using UnityEngine;
using System.Collections;
using HoloToolkit.Unity;
using UnityEngine.VR.WSA.Input;
using System;  public class DrawMeshChanger : MonoBehaviour {  GestureRecognizer recognizer;  public bool isWireframe = true;  public Material Wireframe;  public Material Occlusion;  // Use this for initialization  void Start () {  recognizer = new GestureRecognizer();  recognizer.TappedEvent += Recognizer_TappedEvent;  recognizer.StartCapturingGestures();  }  private void Recognizer_TappedEvent(InteractionSourceKind source, int tapCount, Ray headRay)  {  SpatialMappingManager.Instance.SetSurfaceMaterial(isWireframe ? Occlusion : Wireframe);  isWireframe = !isWireframe;  }  // Update is called once per frame  void Update () {  }
}  

5. 运行测试

能够看到在现实世界的物体表面附着一层网格线,落下的Cube可以在桌子表面,或者落到了地面上。当在环境中进行点击,可以进行切换Surface的材质。当点击后可以看到网格消失了。
(MD编辑模式下不能插入视频,移步视频链接:https://v.qq.com/x/page/e0350sn17nw.html)

HoloLens开发手记 - 空间映射(SpatialMapping)相关推荐

  1. Hololens入门之空间映射

    Hololens入门之空间映射 本文主要讲述使用HoloToolkit项目中提供的空间映射组件,便捷快速的开始使用空间映射特性,本文示例在 Hololens入门之凝视射线 的基础上进行修改. 空间映射 ...

  2. HoloLens开发手记 - HoloLens shell概述 HoloLens shell overview

    使用HoloLens时,shell是由你周围的世界和来自系统的全息图像构成.我们将这种空间成为混合世界(mixed world). shell包含了一个可以让你将全息图像和应用放置在世界中的开始菜单( ...

  3. HoloLens开发手记-硬件细节 Hardware Detail

    微软HoloLens是世界第一款完全无线缆的全息计算机.通过在新方式上赋予用户的全息体验,HoloLens重新定义了个人计算(Personal Computing).为了将3D全息图形固定到你周围的真 ...

  4. HoloLens开发手记-全息Hologram

    全息 Hologram HoloLens使我们可以通过周边世界的光线和声音来创建全息场景和物体,使得它们像真实物体那样.全息场景能够响应你的凝视.手势和语音指令,同时还会和你周边世界的表面交互.借助全 ...

  5. 基于Hololens开发---本地化空间锚点

    基于Hololens开发-本地化空间锚点 本地化空间锚基于Hololens的空间映射,本项目本章内容主要是对Hololens端的离线瞄点进行保存,当再次启用项目时将数据进行读取重置当前位置.具体过程见 ...

  6. HoloLens开发手记-配置开发环境 Install the tools

    随着Build 2016开发者大会的结束,HoloLens开发包也正式开放下载.Hololens没有独立的SDK,开发特性被集成到最新的Visual Studio Update 2中.如果你没有Hol ...

  7. HoloLens开发手记 - Unity development overview 使用Unity开发概述

    Unity Technical Preview for HoloLens最新发行版为:Beta 24,发布于 09/07/2016 开始使用Unity开发HoloLens应用之前,确保你已经安装好了必 ...

  8. HoloLens开发手记 - 使用HoloLens模拟器 Using HoloLens emulator

    首先下载HoloLens模拟器 HoloLens模拟器运行在没有真机的情况下在你的PC上测试应用,属于HoloLens开发工具系列.模拟器使用了Hyper-V虚拟机.通常通过传感器获取的人体和环境输入 ...

  9. HoloLens开发手记 - Unity之Spatial Sounds 空间声音

    本文主要讲述如何在项目中使用空间声音特性.我们主要讲述必须的插件组件和Unity声音组件和属性的设置来确保空间声音的实现. Enabling Spatial Sound in Unity 在Unity ...

最新文章

  1. 软件测试面试题-如何测试复制粘贴功能
  2. HDLBits 系列(38)值得一看的状态机设计题目
  3. 调查问卷_员工满意度调查问卷
  4. python程序员在公司都是做什么的-程序员是做什么的?工资待遇怎么样?
  5. 智慧医疗机器人的普及还需很长一段路要走!
  6. 工作两年多的一个菜鸟感想
  7. Spring mvc 中文乱码
  8. 第9章 互相作用的圆球 (《Python趣味创意编程》教学视频)
  9. Spring MVC开发步骤以及执行流程
  10. 电驴让分享继续 服务器不稳定,为什么越来越多的人不再使用eD2k了?回顾电驴的兴与衰...
  11. multisim安装完成后显示安装程序损坏的免费解决方案
  12. 【NVIDIA】Win10 + CUDA10 + cuDNN 安装教程
  13. 分布式集群中如何保证线程安全?
  14. 2021计算机应用基础形考任务作业一答案,国开网计算机应用基础(本)形考作业三答案...
  15. 09-01 面向对象编程
  16. Android手机app的adb命令测试电量
  17. 免费图片转pdf的方法?学会图片转pdf很重要
  18. java9.0.4配置_Tomcat 9.0 安装配置
  19. 使用ffmpeg进行音频采样率转换
  20. C++ map和unordered_map详解

热门文章

  1. **生信自学记录1——获取Fastq格式的反向互补序列**
  2. 使用 satis 结合 gitlab 搭建 composer 私有包仓库
  3. 跨境电商亚马逊卖家入驻亚马逊选择哪个站点开始做呢
  4. 华为手机续航能力差?或许不是电池寿命问题,而是这4个偷电功能
  5. GitHub的私有(Private)项目添加合作者(collaborator)
  6. 告别“丝袜哥”,推荐这几个在线文档生成神器
  7. 安装websockets和pymysql
  8. 淘宝网站开发秘史:买来的系统 花名文化由来
  9. 结合二叉树和Graham扫描技术的高效Delaunay三角网构建算法
  10. Android下最好用的开源HTTP 服务器