HoloLens开发手记 - 空间映射(SpatialMapping)

空间映射提供了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置为false
                    surfaceWorkOutstanding = 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_EDITOR
            StartObserver();
#endif

#if UNITY_EDITOR
            objectSurfaceObserver = 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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,602评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,442评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,878评论 0 344
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,306评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,330评论 5 373
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,071评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,382评论 3 400
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,006评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,512评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,965评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,094评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,732评论 4 323
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,283评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,286评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,512评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,536评论 2 354
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,828评论 2 345

推荐阅读更多精彩内容