Cesium在2016年3月份左右推出3D Tiles數據規范,在glTF基礎上提供了LOD能力,定位就是Web環境下海量三維模型數據。雖然目前3D Tiles還是Beta階段,有不少硬傷,但3D Tiles數據規范於2016年9月30日開始了OGC標准化進程,積極成分還是很大。
之前的glTF時分享了個人對二進制格式的一些想法和謹慎的態度。3D Tiles簡單說就是具備LOD能力的glTF。有了數據首先是提供API可以渲染,保證用起來,下一步就要了解該數據規范的具體特點,比如傾斜,矢量,點雲,OSM等支持情況,項目實施和風險評估等。最后,作為一個數據規范,從數據生產到深層次應用,需要時間沉淀出完善豐富的解決方案。
本文主要集中在渲染調度層面。看完本文可能會覺得思路很簡單。在實際應用中有很多細節,比如瀏覽時各種操作的差異,並發量,內存和顯存管理,異步傳輸和Workers線程等等各種調優。思路簡單,但要把這些小細節打磨好就不容易了。本文只講詩和遠方,鞋里的沙子自己來處理吧。
先看看如何加載3D Tiles數據,如上所示,Cesium提供了Cesium3DTileset類來管理,主要負責Tile的調度。在Cesium中,3DTiles就相當於一個Primitive的位置。
3D Tile表述
當我們創建一個Cesium3DTileset后,每一個Tile對應一個Cesium3DTile。如上根節點是root,content是根節點對應的文件名,這里是parent.b3dm,四個子節點,子節點對應的文件名分別為ll.b3dm……。
如上,在獲取JSON對象后,首先創建rootTile根節點,然后在while循環中,以廣度優先的方式遍歷這個樹,每個節點都有一個parentTile屬性綁定父節點(根節點除外),同時有一個children數組,保存該節點對應的所有子節點。
這樣,在初始化階段,Cesium3DTileset中就保存了該3DTiles樹上的所有節點及關聯,當然此時只是屬性信息,並沒有加載數據內容,所以內存上還是可以接受。這就相當於我們讀書,都會先看這本書的目錄,了解一個大概。但個人認為,第一,沒必要,實際上只需要找到根節點,下面按需順藤摸瓜就可以(JSON在搜索節點上很麻煩);第二,如果數據量很大的情況下,初次加載全部節點是一個性能瓶頸。這是3D Tiles目前設計上的不足。
Cesium3DTile中通過一個簡單的Cesium3DTileContentFactory工廠模式,目前主要提供四種類型。根據當前數據的具體類型(Type)來創建對應的內容(Content),本篇不涉及這塊。初始化結束后,和之前glTF或primitive一樣,基於狀態的驅動流程:
如上是調度管理的邏輯,四個函數的作用大概如下:
-
processTiles
-
處理Tile對應的DrawCommand狀態,判斷一些半透明等渲染順序
-
-
selectTiles
-
請求具體的b3dm數據,不同Type根據對應的類來完成數據的下載,根據LOD策略決定哪些Tile進入渲染隊列。
-
-
updateTiles
-
當前幀狀態下遍歷這棵樹,調用該Tile對應的Model::update,完成數據的解析最終構造出DrawCommand
-
-
unloadTiles
-
判斷當前Tiles數目是否超過上限,卸載多余的Tile
-
在selectTiles函數中,首先是下載Tile對應的數據內容(b3dm后綴),通過contentUnloaded標識來判斷,如果根節點的數據還沒有下載,則request,然后返回。
一個簡單的request,里面的信息量其實也不小,調用對應Cesium3DTile和Content對應的request來下載數據,這這里Cesium專門封裝了一個RequestScheduler類來管理並發,我們最后在介紹這個。這里注意,當該數據下載完成后,則添加到兩個隊列:processQueue和removeQueue。
在Key2中,就是一個LOD策略的實現,上圖給出了追加的邏輯注釋。Cesium目前支持兩種方式Add追加和Replace替換兩種方式。Add方式較為簡單,是Tiles求並的思路,而Replace是覆蓋的思路,較為復雜,因為要控制父子節點直接的可見不可見,從代碼來看,Cesium在這一塊處理的比較簡單,應該會出現閃爍的效果,不知道是否有人可以證實這個推測是否正確?
UpdateTiles看上去就比較簡單了,指定具體的Content..update,這個過程就是之前Pimitive和Model對應的update。
對於可以卸載的Tile,則滿足條件后調用unloadContent,但個人覺得這塊設計的還是有些疑慮。首先,設計的很不錯,從Content到BatchTable,到Model,以及Texture都提供了destory方法,但紋理還是應該提供紋理管理器的概念,解決重用紋理的釋放。
讓我學到的一點就是RequestScheduler類,大概思路是規定每次並發的最大請求數,每一幀收集下載請求但並不發送該請求,在下一幀對請求隊列排序(相機遠近),然后再發送。實現的很巧妙,方便管理。畢竟在海量數據下,有這樣一個Manager來控制是很有必要的。
問題比較大的地方是刪除上(因為沒有大數據測試,僅從代碼邏輯猜測)。第一,update和select兩個都是異步或者workers線程機制,在數據量比較大的情況下會有內存泄漏。第二,Replace隊列無腦刪除,並不是根據當前的范圍和LOD,這個在設計上是一個很大的缺陷,只考慮了可見不可見,但沒有優化刪除策略。
總體來說,作為一個開源項目,3D Tiles邁出了很堅實的一步,數據規范設計的很優雅,基於glTF也降低了學習成本。同時Cesium提供了渲染3D Tiles的接口,稍顯不足的就是還沒有成熟的,免費的數據生成工具,可以從osg轉為3d tiles,這是目前最大的瓶頸。簡單說,Cesium目前提供了基本技術和規范,但並沒有提供完整的解決方案,同時還缺少基於3D Tiles的豐富的擴展和應用。當然,我們不能對一個開源項目要求苛刻,而且我相信,也希望Cesium后續后慢慢完善,前面把路走踏實了,慢一點都可以接受,路遙知馬力,踏踏實實做事情方能立本,本立而道生,在這個物欲橫流的世界,這么簡單的道理,其實並不簡單。