HoloToolkit項目源碼剖析 - Spatial Mapping功能實現


就像我之前所描述的,HoloToolkit項目是微軟基於Unity內置的底層API封裝的一套工具集合,幫助我們快速使用Unity集成開發HoloLens應用。

本文主要通過源碼研究其中Spatial Mapping的實現,關於底層的API細節,請閱讀我前一篇文章:HoloLens開發手記 - Unity之Spatial mapping 空間映射

 

0x00 組件結構


 

Spatial Mapping目錄下有很多內容,其中Prefabs目錄里有我們可以直接使用的預置組件,本文關注的重點是Scripts目錄的腳本。組件目錄結構如下:

 

 

本文重點研究SpatialMappingObserver.cs和SpatialMappingSource.cs,這是當前組件的核心內容。

 

 

0x01 SpatialMappingObserver.cs


 

源碼地址:https://github.com/Microsoft/HoloToolkit-Unity/blob/master/Assets/HoloToolkit/SpatialMapping/Scripts/SpatialMappingObserver.cs

 我們先分析其屬性,如下:

 

        //每立方米網格三角形數量,控制mesh質量
        public float TrianglesPerCubicMeter = 500f;

       //當前Observer檢測的空間范圍
        public Vector3 Extents = Vector3.one * 10.0f;

        //刷新時間間隔
        public float TimeBetweenUpdates = 3.5f;

        //用於掃描空間平面的核心組件
        private SurfaceObserver observer;

        //存儲已構建的空間網格對象
        private Dictionary<int, GameObject> surfaces = new Dictionary<int, GameObject>();

        //SurfaceData隊列,用於生成空間mesh
        private Queue<SurfaceData> surfaceWorkQueue = new Queue<SurfaceData>();

        //為了避免同一時刻生成太多mesh,保證同一時刻只生成一個mesh,這個變量用於判斷當前時刻是否有mesh正在創建
        private bool surfaceWorkOutstanding = false;

        //用於追蹤Observer對象上次更新時間
        private float updateTime;

        //表示當前掃描狀態,Running和Stopped兩種狀態
        public ObserverStates ObserverState { get; private set; }

 

 再分析其程序邏輯及流程:

  • Awake()方法最先被執行,這里對SurfaceObserver對象進行了初始化。

 

 private void Awake()
        {
            observer = new SurfaceObserver();
            ObserverState = ObserverStates.Stopped;
        }

 

  • 接下來是Start()方法,里面設定了掃描的空間范圍。

 

private void Start()
        {
            observer.SetVolumeAsAxisAlignedBox(Vector3.zero, Extents);
        }

 

  • Update()方法中則會根據當前狀態來調用API請求空間表面信息或者生成mesh對象

 

private void Update()
        {
            
            if (ObserverState == ObserverStates.Running)
            {
                // 如果當前沒有再生成mesh,且SurfaceData中有需要生成mesh的對象
                if (surfaceWorkOutstanding == false && surfaceWorkQueue.Count > 0)
                {
                    
                    SurfaceData surfaceData = surfaceWorkQueue.Dequeue();

                    // 如果能成功請求到mesh對象,當前任務狀態變為生成mesh中
                    surfaceWorkOutstanding = observer.RequestMeshAsync(surfaceData, SurfaceObserver_OnDataReady);
                }
                //如果當前沒有任務運行且上次更新距現在大於時間間隔,則重新請求SurfaceData數據
                else if (surfaceWorkOutstanding == false && (Time.time - updateTime) >= TimeBetweenUpdates)
                {
                    observer.Update(SurfaceObserver_OnSurfaceChanged);
                    updateTime = Time.time;
                }
            }
        }

 

  • SurfaceObserver_OnDataReady()事件方法用於處理使用SurfaceData請求到的mesh對象信息,用於后續的使用,比如處理其材質效果等。

 

private void SurfaceObserver_OnDataReady(SurfaceData cookedData, bool outputWritten, float elapsedCookTimeSeconds)
        {
            GameObject surface;
            if (surfaces.TryGetValue(cookedData.id.handle, out surface))
            {
                // 設置 renderer組件的材質.
                MeshRenderer renderer = surface.GetComponent<MeshRenderer>();
                renderer.sharedMaterial = SpatialMappingManager.Instance.SurfaceMaterial;
//是否渲染mesh對象 renderer.enabled
= SpatialMappingManager.Instance.DrawVisualMeshes; if (SpatialMappingManager.Instance.CastShadows == false) { renderer.shadowCastingMode = UnityEngine.Rendering.ShadowCastingMode.Off; } } surfaceWorkOutstanding = false; }

 

  • SurfaceObserver_OnSurfaceChanged()事件方法用於處理SurfaceObserver獲取到的空間表面數據,用於后續的請求mesh對象操作。

 

  private void SurfaceObserver_OnSurfaceChanged(SurfaceId id, SurfaceChange changeType, Bounds bounds, System.DateTime updateTime)
        {
            // 判斷當前掃描狀態
            if (ObserverState != ObserverStates.Running)
            {
                return;
            }

            GameObject surface;

            switch (changeType)
            {
                
                case SurfaceChange.Added:
                case SurfaceChange.Updated:
                    // 檢測當前表面是否已被掃描過
                    if (!surfaces.TryGetValue(id.handle, out surface))
                    {
                        // 創建一個和當前表面關聯的mesh對象
                        surface = AddSurfaceObject(null, string.Format("Surface-{0}", id.handle), transform);

                        surface.AddComponent<WorldAnchor>();

                        // 將surface對象加入已知空間表面字典
                        surfaces.Add(id.handle, surface);
                    }

                    // 請求生成或更新對應的mesh對象
                    QueueSurfaceDataRequest(id, surface);
                    break;

                case SurfaceChange.Removed:
                    // 移除關聯的mesh對象
                    if (surfaces.TryGetValue(id.handle, out surface))
                    {
                        surfaces.Remove(id.handle);
                        Destroy(surface);
                    }
                    break;
            }
        }

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM