HoloLens開發(fā)手記 - 空間映射(SpatialMapping)

空間映射提供了HoloLens周圍環(huán)境中真實(shí)世界表面的詳細(xì)表示分井,允許開發(fā)人員創(chuàng)建令人信服的混合現(xiàn)實(shí)體驗(yàn)吻贿。通過將真實(shí)世界與虛擬世界合并撤奸,應(yīng)用可以使全息圖看起來是真實(shí)的啸驯。通過提供熟悉的現(xiàn)實(shí)世界行為和交互,應(yīng)用程序也可以更自然地與用戶期望一致英上。

Unity內(nèi)置了對空間映射功能的支持炭序,通過以下兩種方式提供給開發(fā)者:

  1. HoloToolkit項(xiàng)目中你可以找到空間映射組件,這可以讓你便捷快速地開始使用空間映射特性苍日。
  2. Unity還提供更多底層的空間映射API惭聂,以便開發(fā)者能夠完全控制空間映射特性,滿足定制復(fù)雜的應(yīng)用需求

本文主要介紹HoloToolKit提供的空間映射(SpatialMapping)組件相恃,利用此組件可以快速的應(yīng)用中集成使用HoloLens的空間映射特性辜纲。

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

  • Occlusion
  • Visualization
  • Placement
  • Physics
  • Navigation

設(shè)置 SpatialMapping 功能開啟

為了使應(yīng)用能夠使用空間映射數(shù)據(jù),SpatialPerception能力必須被啟用。

使用以下步驟啟用此能力:

  1. 在Unity編輯器中耕腾,進(jìn)入Player Settings選項(xiàng)(Edit > Project Settings > Player)
  2. 點(diǎn)擊Window Store選項(xiàng)卡
  3. 展開Publish Settings選項(xiàng)见剩,并在Capabilities列表勾選SpatialPerception選項(xiàng)

use the API 使用底層API步驟


SurfaceObserver是主要使用到的API對象,下面是應(yīng)用使用空間映射特性推薦的大致流程:

  1. 設(shè)定SurfaceObserver對象
    要為每一個(gè)需要空間映射數(shù)據(jù)的空間區(qū)域在應(yīng)用中聲明并初始化一個(gè)SurfaceObserver對象:
SurfaceObserver surfaceObserver; 
void Start () { 
      surfaceObserver = new SurfaceObserver(); 
}
  1. 通過調(diào)用SetVolumeAsSphere扫俺、SetVolumeAsAxisAlignedBox苍苞、 SetVolumeAsOrientedBox、 或 SetVolumeAsFrustum方法可以為每個(gè)SurfaceObserver對象指定它們需要獲取數(shù)據(jù)的空間范圍狼纬。以后你還可以通過再次調(diào)用它們來重新設(shè)定檢測的空間范圍羹呵。
void Start () { 
      ... 
      surfaceObserver.SetVolumeAsAxisAlignedBox(Vector3.zero, new Vector3(3, 3, 3));
}
  1. 處理OnDataReady事件
    OnDataReady事件方法會(huì)接收到一個(gè)SurfaceData對象,它包含了WorldAnchor疗琉、MeshFilter和MeshCollider對象數(shù)據(jù)担巩,表示了當(dāng)前關(guān)聯(lián)的空間表面最新狀態(tài)。通過訪問Mesh Filter對象的Mesh數(shù)據(jù)可以進(jìn)行性能分析或者處理網(wǎng)格没炒。使用最新的Mesh數(shù)據(jù)來渲染空間表面并將它用于物理碰撞或者射線擊中對象。確認(rèn)SurfaceData內(nèi)容不為空很重要犯戏。

  2. 處理空間表面變化送火,即處理OnSurfaceChanged事件

    關(guān)于空間表面變化,有幾個(gè)典型情形需要處理先匪。Added狀態(tài)和Updated狀態(tài)可以使用相同的代碼處理种吸,Removed狀態(tài)則使用另一種代碼來處理。

  • 在Added和Updated情形下呀非,我們從字典中添加或者獲取代碼當(dāng)前網(wǎng)格的對象坚俗,使用必要的組件來創(chuàng)建一個(gè)SurfaceData結(jié)構(gòu)體,然后調(diào)用RequestMeshDataAsync方法在場景中使用網(wǎng)格數(shù)據(jù)和位置來填充對象岸裙。
  • 在Removed情形下猖败,我們從字典中移除當(dāng)前網(wǎng)格代表的對象并銷毀它。
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(
                   //系統(tǒng)返回的surface id,
                   //當(dāng)前對象的MeshFilter組件
                   target.GetComponent<MeshFilter>() ?? target.AddComponent<MeshFilter>(),
                   //用于在空間中定位對象的空間錨
                   target.GetComponent<WorldAnchor>() ?? target.AddComponent<WorldAnchor>(),
                   //當(dāng)前網(wǎng)格對象的MeshCollider組件
                   target.GetComponent<MeshCollider>() ?? target.AddComponent<MeshCollider>(),
                   //每立方米網(wǎng)格三角形的數(shù)量
                   1000,
                   //bakeMeshes -如果是true降允,MeshCollider會(huì)被數(shù)據(jù)填充恩闻,反之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 空間映射組件


下面通過一個(gè)例子來說明空間映射的使用

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

在SpatialMapping中能夠看到已經(jīng)存在了三個(gè)腳本組件:

  • SpatialMappingObserver.cs
  • SpatialMappingManager.cs
  • ObjectSurfaceObserver.cs

SpatialMappingObserver.cs 主要是用于定期進(jìn)行對周圍環(huán)境進(jìn)行掃描并更新Surface數(shù)據(jù),具體可參考代碼:

// 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.")]
        //每立方米的三角形網(wǎng)格的數(shù)量
        public float TrianglesPerCubicMeter = 500f;

        [Tooltip("The extents of the observation volume.")]
        //設(shè)置檢測的空間體積范圍
        public Vector3 Extents = Vector3.one * 10.0f;

        [Tooltip("How long to wait (in sec) between Spatial Mapping updates.")]
        //空間映射更新的等待時(shí)間
        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對象用于生成或更新空間映射數(shù)據(jù)
        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的網(wǎng)格同時(shí)生成剧董,置標(biāo)記位幢尚,只允許一次性生成一個(gè)surface的網(wǎng)格。
        /// </summary>
        private bool surfaceWorkOutstanding = false;

        /// <summary>
        /// Used to track when the Observer was last updated.
        /// 用于跟蹤觀察者的上次更新時(shí)間
        /// </summary>
        private float updateTime;

        /// <summary>
        /// Indicates the current state of the Surface Observer.
        /// </summary>
        public ObserverStates ObserverState { get; private set; }

        private void Awake()
        {
            //為每一個(gè)需要空間映射數(shù)據(jù)的空間區(qū)域在應(yīng)用中初始化一個(gè)SurfaceObserver對象
            observer = new SurfaceObserver();
            ObserverState = ObserverStates.Stopped;
        }

        /// <summary>
        /// Called when the GaemObject is initialized.
        /// </summary>
        private void Start()
        {
            //通過調(diào)用SetVolumeAsSphere翅楼、SetVolumeAsAxisAlignedBox尉剩、  
            //SetVolumeAsOrientedBox、 或 SetVolumeAsFrustum方法可以為每個(gè)SurfaceObserver對象  
            //指定它們需要獲取數(shù)據(jù)的空間范圍毅臊±砭ィ可再次調(diào)用來重新設(shè)定檢測的空間范圍。
            observer.SetVolumeAsAxisAlignedBox(Vector3.zero, Extents);
        }

        /// <summary>
        /// Called once per frame.
        /// </summary>
        private void Update()
        {
            //只有在SurfaceObserver處于運(yùn)行狀態(tài)時(shí)進(jìn)行處理
            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從隊(duì)列中取出
                    SurfaceData surfaceData = surfaceWorkQueue.Dequeue();

                    // If RequestMeshAsync succeeds, then we have successfully scheduled mesh creation.
                    //當(dāng)RequestMeshAsync調(diào)用成功,則就成功調(diào)度了網(wǎng)格創(chuàng)建,置surfaceWorkOutstanding為true
                    //等待回調(diào)函數(shù)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.
                //每隔一段時(shí)間進(jìn)行刷新园爷,查看是否發(fā)生變化
                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.
                //設(shè)置材質(zhì)
                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;

            //關(guān)于空間表面變化,有幾個(gè)典型情形需要處理式撼。Added狀態(tài)和Updated狀態(tài)可以使用相同的代碼處理童社,Removed狀態(tài)則使用另一種代碼來處理。
            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情形下著隆,我們從字典中添加或者獲取代碼當(dāng)前網(wǎng)格的對象扰楼,使用必要  
                //的組件來創(chuàng)建一個(gè)SurfaceData結(jié)構(gòu)體,然后調(diào)用RequestMeshDataAsync方法在場景中  
                //使用網(wǎng)格數(shù)據(jù)和位置來填充對象美浦。
                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添加到隊(duì)列中弦赖,等待處理  
                    QueueSurfaceDataRequest(id, surface);
                    break;

                //在Removed情形下,我們從字典中移除當(dāng)前網(wǎng)格代表的對象并銷毀它浦辨。 
                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>(),         //當(dāng)前對象的MeshFilter組件 
                                                        surface.GetComponent<WorldAnchor>(),        //用于在空間中定位對象的空間錨 
                                                        surface.GetComponent<MeshCollider>(),       //當(dāng)前網(wǎng)格對象的MeshCollider組件
                                                        TrianglesPerCubicMeter,                     //每立方米網(wǎng)格三角形的數(shù)量  
                                                        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的材質(zhì)蹬竖,是否顯示網(wǎng)格等一些參數(shù)進(jìn)行配置獲取管理:

// 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主要用于當(dāng)處于Unity編輯環(huán)境下時(shí),加載房間模型數(shù)據(jù)流酬,來進(jìn)行測試:

  1. 使用device Protal可以在瀏覽器查看所掃描的房間币厕。點(diǎn)擊update可以在上面視窗中顯示查看,點(diǎn)擊save進(jìn)行將你所在的房間模型進(jìn)行保存:


  2. 將保存的房間模型直接加載進(jìn)unity項(xiàng)目Assets文件目錄下芽腾,再將其拖拽到ObjectSurfaceObserver.cs腳本組件的Room Model中即可在項(xiàng)目文件中保留房間模型旦装。當(dāng)在unity環(huán)境中運(yùn)行調(diào)試時(shí),可加載此房間模型數(shù)據(jù)進(jìn)行測試摊滔。


ObjectSurfaceObserver.cs詳細(xì)代碼如下:

// 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. 新增一個(gè)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  
    //剛啟動(dòng)應(yīng)用時(shí),空間映射還沒準(zhǔn)備好艰躺,將創(chuàng)建的Cube釋放掉  
    void Update () {  
        if (transform.position.y < -3)  
        {  
            Destroy(gameObject);  
        }  
    }  
} 
3.在MainCamera上新增腳本組件CubeCreator.cs呻袭,用于每隔一秒鐘產(chǎn)生一個(gè)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的材質(zhì)描滔,一個(gè)是帶網(wǎng)格的棒妨,一個(gè)是沒有網(wǎng)格線的
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. 運(yùn)行測試

能夠看到在現(xiàn)實(shí)世界的物體表面附著一層網(wǎng)格線,落下的Cube可以在桌子表面含长,或者落到了地面上券腔。當(dāng)在環(huán)境中進(jìn)行點(diǎn)擊,可以進(jìn)行切換Surface的材質(zhì)拘泞。當(dāng)點(diǎn)擊后可以看到網(wǎng)格消失了纷纫。
(簡書MD編輯模式下不能插入視頻,移步視頻鏈接:https://v.qq.com/x/page/e0350sn17nw.html

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末陪腌,一起剝皮案震驚了整個(gè)濱河市辱魁,隨后出現(xiàn)的幾起案子烟瞧,更是在濱河造成了極大的恐慌,老刑警劉巖染簇,帶你破解...
    沈念sama閱讀 222,681評論 6 517
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件参滴,死亡現(xiàn)場離奇詭異,居然都是意外死亡锻弓,警方通過查閱死者的電腦和手機(jī)砾赔,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 95,205評論 3 399
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來青灼,“玉大人暴心,你說我怎么就攤上這事≡硬Γ” “怎么了专普?”我有些...
    開封第一講書人閱讀 169,421評論 0 362
  • 文/不壞的土叔 我叫張陵,是天一觀的道長弹沽。 經(jīng)常有香客問我檀夹,道長,這世上最難降的妖魔是什么策橘? 我笑而不...
    開封第一講書人閱讀 60,114評論 1 300
  • 正文 為了忘掉前任击胜,我火速辦了婚禮,結(jié)果婚禮上役纹,老公的妹妹穿的比我還像新娘。我一直安慰自己暇唾,他們只是感情好促脉,可當(dāng)我...
    茶點(diǎn)故事閱讀 69,116評論 6 398
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著策州,像睡著了一般瘸味。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上够挂,一...
    開封第一講書人閱讀 52,713評論 1 312
  • 那天旁仿,我揣著相機(jī)與錄音,去河邊找鬼孽糖。 笑死枯冈,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的办悟。 我是一名探鬼主播尘奏,決...
    沈念sama閱讀 41,170評論 3 422
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼病蛉!你這毒婦竟也來了炫加?” 一聲冷哼從身側(cè)響起瑰煎,我...
    開封第一講書人閱讀 40,116評論 0 277
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎俗孝,沒想到半個(gè)月后酒甸,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,651評論 1 320
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡赋铝,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,714評論 3 342
  • 正文 我和宋清朗相戀三年插勤,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片柬甥。...
    茶點(diǎn)故事閱讀 40,865評論 1 353
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡饮六,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出苛蒲,到底是詐尸還是另有隱情卤橄,我是刑警寧澤,帶...
    沈念sama閱讀 36,527評論 5 351
  • 正文 年R本政府宣布臂外,位于F島的核電站窟扑,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏漏健。R本人自食惡果不足惜嚎货,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 42,211評論 3 336
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望蔫浆。 院中可真熱鬧殖属,春花似錦、人聲如沸瓦盛。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,699評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽原环。三九已至挠唆,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間嘱吗,已是汗流浹背玄组。 一陣腳步聲響...
    開封第一講書人閱讀 33,814評論 1 274
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留谒麦,地道東北人俄讹。 一個(gè)月前我還...
    沈念sama閱讀 49,299評論 3 379
  • 正文 我出身青樓,卻偏偏與公主長得像绕德,于是被迫代替她去往敵國和親颅悉。 傳聞我的和親對象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,870評論 2 361

推薦閱讀更多精彩內(nèi)容