深度解析Holograms 230: Spatial mapping 空間映射

Hololens官方給出的Tutorial不算多,其中內容最豐富的要數Holograms 230: Spatial mapping 空間映射這一篇了,主要講的是空間的概念與操作,封裝在HoloToolkit裏面,這裏逐步解析一下。

首先是Scanning掃描,這個跟前面Hologram101差不多就是打開SpatialMapping,讓Hololens掃描空間,不過這裏提供了一種途徑讓你把掃描的結果存下來,並且可以在Unity打開,這個還是挺棒的,比如自己的房間除了自己在3ds max建模,還可以用這種掃描的方式獲得3d模型,當然這個的精度一般。因爲這個模型是用三角形爲單位來描述的,描述曲面就沒什麼精度可言了,其次就是可調節每立方米的三角形數量來提高精度,不過這個會影響性能。這個性能是由Hololen的處理器即並不快的atom來決定的,而不是你強大的pc的處理器。保存下來的SRMesh.obj可以在unity打開,可以在unity模擬hololens現場獲得房間的結果。聯想起當初玩Kinect的時候也可以3D掃描,當然精度也是不咋地。

後面開始就一環扣一環了。先說一下總的步驟,先是掃描獲得Mesh數據,然後在Mesh數據裏面提取平面信息,分析平面信息用SurfaceNormal來判斷平面類型是地面、天花、桌子,還是牆。然後用以cube來表示的定製化平面分別創建這些面,然後在面上面放置物體。如果還沒暈,那我們繼續。

第一步,在PlaySpaceManager裏面,我們可以看到一下參數,分別講解一下。

limitScanningByTime:布爾類型標識是否限定時間。

scanTime:如果限定那是多少秒。

defaultMaterial:掃描的時候用什麼來mesh空間。

secondaryMaterial:掃描完了換種顯示方式來說明掃描結束了。

minimumFloors:最少的地面數。

minimumWalls:最少的牆面數。

初始化一下,用默認材料顯示空間mesh,添加掃描完成事件。然後每1幀都檢查是否限定了時間和掃描是否結束,如果掃描時間還沒到什麼都不做,繼續掃描就是了,否則停止掃描,調用CreatePlanes()生成平面並標記結束。生成平面就要用到第二步的東西了,一會再詳細解釋。

剛說了有個掃描完成事件,那麼在生成平面並標記結束後就會觸發這個SurfaceMeshesToPlanes_MakePlanesComplete。這一步調用第二的GetActivePlanes()函數來生成horizontal和vertical兩個list來分別存放水平和垂直的面(生成的不是現實的)。如果面的數量不足,也就是說1面牆和1個地面都沒有,那就一切重新開始。如果面的數量足夠,那麼先刪掉一些定點和三角形,提高性能嘛。並且替換mesh材料來指示完成。然後就開始在生成的平面(注:不是現實的牆或地板,而是我們用cube來表示的牆或地板的平面)的牆上上掛相框,地上放投影機,這個第3步會解釋一下。


第二步,在SurfaceMeshesToPlanes裏面,先看參數

ActivePlanes:存儲生成的平面,不是現實的牆或地面。

SurfacePlanePrefab:是我們用來表示面的物體,這裏沒有用plane而是用的cube,原因應該是cube的單位是1米可以用來匹配真實環境。這個cube設置厚度爲1cm也就是像個面而不是一個方塊。有個參數是UpNormalThreshold,用來表示可接受法線的臨界值,從註釋看越接近1越精確到標準,這裏設置的是0.9。這是個神奇的參數,可以用來判斷面是水平的還是垂直的,垂直的必定是牆,水平的還可以判斷是天花還是地面還是桌子(深入研究表明,如果是牆,數值應該是0,如果是地面就是朝上的,數值應該是1,如果是天花朝下的,數值應該是-1,誤差在0.1範圍內可接受)。要做到這點還要設置天花buffer和地面buffer,也就是地面以上多高以上纔算桌子。最後還可以爲每一種類型的面設置材料。實際操作的時候可以根據Mesh的平面信息賦值給這個面,來初始化位置大小旋轉等。具體參考SurfacePlane.cs文件,不太難就不解釋了。

MinArea:最小掃描面積。

drawPlanesMask:將要畫出來的平面的集合,不在裏面的不畫。

destroyPlanesMask:不畫的平面集合,需要銷燬來降低內存使用量。

FloorYPosition:地面Y值,通過不斷比較低於用戶頭部的平面,找出面積最大的平面表示地面。

CeilingYPosition:同上,表示天花。

delegate EventHandler:生成平面完成的觸發事件。

MakePlanesComplete:同上,這是句柄

planesParent:相當於create empty,用來存放生成的所有面

snapToGravityThreshold:用來對齊

makingPlanes:布爾類型用來標識生成平面結束了沒有

FrameTime:每幀內允許代碼的處理時間。後面會看到不少yield return null;即暫停一下到下一幀繼續執行,否則持續佔用cpu的話下一幀到來就會很卡。如果按25幀每秒計算,1幀的時間有40毫秒。假設生成平面1共有1000毫秒的工作量,這裏設置每幀只工作8毫秒來處理,那麼就有時間處理別的工作。事實上永遠是多於8毫秒的時候才停止。


初始化,設置還沒開始生成平面,實例化ActivePlanes這個list,實例化planesParent並初始化。第一步裏面生成平面的函數CreatePlanes()調用了這一步的MakePlanes(),而MakePlanes使用Coroutine協同處理這個比較耗時間的生成過程,實際執行函數是MakePlanesRoutine()。

MakePlanesRoutine函數會先銷燬ActivePlanes裏面的所有平面並清空list,這是因爲在第一步如果面數不足要重新開始的話,本類裏面的start()不會重新執行,就要先清空一下。這個函數有多個yield return null;參考上面的FrameTime解釋就不多說了。首先創建meshData來存放mesh數據,並創建filters獲取mesh數據。對於每一個不爲空的filter,固定平面法線並添加進meshData裏面。然後使用PlaneFinding.FindPlanes()函數從meshData裏面找出所有大於最小面積的平面(此函數使用指針,不要深究了)。注意找出來的平面類型爲BoundedPlane,也就是這個平面還包含邊界即大小。後面有個獲取用戶設置的upNormalThreshold的一步。然後開始從平面裏面找出地面和天花,boundedPlane.Plane.normal.y >= upNormalThreshold對平面做出限制,比較boundedPlane.Bounds.Center.y < 0即低於人頭部位置,逐個比較所有平面留下面積最大的記錄下boundedPlane.Bounds.Center.y作爲FloorYPosition。天花也是同樣步驟。

到前面的步驟都是真實空間的東西,後面開始創建虛擬的面來取代真實的面(說取代也不合適但比附加好理解)。對於每一個平面,首先用我們預設的SurfacePlanePrefab初始化一個GameObject叫destPlane,並加進planesParent作爲子物體(設置transform就可以了)。然後surfacePlane.Plane = boundedPlane;這句把平面的信息賦值給這個destPlane的surfacePlane.Plane,這個Plane成員不在unity的inspector裏面設置。這個賦值會執行SurfacePlane.cs裏面的UpdateSurfacePlane()函數,包含設置位置大小旋轉,根據SurfaceNormal.y判斷平面類型屬於地面、天花、桌子、牆,還是未知。並根據每種類型賦值材料。然後設置哪些類型可見,也就是未知的不可見,還把未知的銷燬。可見的根據現實的層級設置層級,並且添加到ActivePlanes裏面。所有平面都執行過後,設置makingPlanes爲假也就是生成結束。


第三步,在SpaceCollectionManager,在生成的平面裏面放hologram,參數只有1個spaceObjectPrefabs在unity拖放要放置的物體list。

首先建horizontalObjects和verticalObjects兩個list用來存放要橫着放和豎着放的物體list。然後把spaceObjectPrefabs裏面的每個物體分配到前面2個list裏面,然後調用CreateSpaceObjects()創建物體。

在CreateSpaceObjects裏面,先創建UsedPlanes來存放那個平面已經放了東西,就不會再放了。然後有一段surfaces.Sort來把所有平面按距離人的遠近排序,拍好後按由近到遠放置物體。然後foreach遍歷每一個橫着放或豎着放的list裏面的物體,用FindNearestPlane()找到最近的合適平面。合適包括沒有使用過,平面面積比物體面積大,的最前面那個也就是最近的那個平面。沒找到的話就懸浮在用戶右前方。找到的話先設置平面用了,根據平面設置物體的位置和旋轉(這個可以學一下)。最後根據位置和旋轉把物體實例化一份加到SpaceCollection裏面。


後面還有設置物體可以移動放置到哪裏,和物體在牆裏被遮擋的時候透視顯示。


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章