繼承自 glTF 的可擴展性,3dTiles 在定義上也留下了可擴展的餘地。包括但不侷限於:優化幾何數據的存儲,擴展屬性數據等。編程
下面,將簡單介紹這兩個擴展。json
b3dm 瓦片的屬性信息寫在批次表(batchtable) 中。b3dm 中每一個獨立的模型,叫作
batch
,(等價於要素表中的要素)這個概念引伸自圖形編程,意思是「一次性向圖形處理器(GPU)發送的數據」,即批次。一個 b3dm 瓦片有多少個batch
(有多少個要素),是由要素表的 JSON 表頭中的BATCH_LENGTH
屬性記錄的。數組而批次表(batchtable)的每一個屬性數據長度,都與這個
BATCH_LENGTH
相等。緩存
以上是 03 篇與 04 篇的回顧。工具
批次表記錄屬性數據是有缺陷的。優化
batch
之間存在邏輯分層、從屬關係時,如何記錄它們的層級屬性數據的問題此小節須要對 glTF 格式規範比較熟悉。知道「頂點屬性」的概念,知道 WebGL 的幀緩存技術。3d
b3dm 瓦片內置的 glTF 模型中,每一個 primitive 的 attribute,也即頂點屬性中會加上一個新的屬性,與 POSITION
、UV0
等並列,叫作 _BATCHID
。code
這樣,經過 _BATCHID
,使用 WebGL 中的幀緩存技術,在 FBO 上繪製 _BATCHID
的顏色附件,便可完成快速查詢。component
要素表經過 BATCH_ID 訪問 批次表裏的屬性數據,幾何數據(glTF 中的 vertex)經過 _BATCHID 綁定要素。
假設有這麼一塊空間範圍,歸屬在 0.b3dm
瓦片內,瓦片的 glTF 模型擁有兩個 BATCH
,即兩個要素,爲了方便觀察,不妨具象化:
空間範圍 = 一個停車場
BATCH1 = 充電樁
BATCH2 = 電動汽車
以下圖所示:
如今,我用一個簡單的 JSON 來描述這兩個要素的屬性數據:
{ "Charger": { "Price": 0.5, "DeviceId": "abcdefg123" }, "Car": { "Brand": "Tesla", "Owner": "Jacky" } }
這樣的數據不符合原生批次表的存儲邏輯,即每一個 batch
的屬性名稱應徹底一致。
顯然,充電樁的 Price(就是單價)、DeviceId 和車子的 Brand(品牌)、Owner 並非同樣的。
若是用這個擴展來表示,在批次表的 JSON 中將會是:
{ "extensions": { "3DTILES_batch_table_hierarchy": { // ... } } }
映入眼簾的是 extensions
,它是一個 JSON,下面有一個 3DTILES_batch_table_hierarchy
的屬性,其值也是一個 JSON:
{ "classes": [ { /* ... */ }, { /* ... */ } ], "instancesLength": 2, "classIds": [0, 1] }
其中,classes
是描述每一個分類的數組,這裏有充電樁類、電動汽車類,詳細展開電動汽車類:
[ { /* 電動汽車類,略 */ }, { "name": "Car", "length": 1, "instances": { "Brand": ["Tesla"], "Owner": ["Jacky"] } } ]
每一個 class
就記錄了該類別下,全部模型要素的屬性值(此處是 Brand 和 Owner),以及有多少個模型要素(length 值,此處是 length = 1 輛車)。
擴展:若是這個 b3dm 又多增長了一個電動汽車,那麼這個 JSON 就應該變成下面的樣子了
{ "name": "Car", "length": 2, // <- 變成 2 "instances": { "Brand": ["Tesla", "Benz"], // <- 加一個值 "Owner": ["Jacky", "Granger"] // <- 加一個值 } }
圖示:
classes
表明此 b3dm 內有多少個模型種類,這裏有充電樁、汽車兩類。
instancesLength
表明全部模型種類的數量和,這裏每一個種類都只有 1 個 batch(要素),加起來就是 2
instancesLength
和 b3dm 中要素表的 BATCH_LENGTH
並非相等的。
當且僅當模型之間不構成邏輯層級時,這兩個數字才相等。顯然,此例中的 「充電樁」和「電動汽車」不構成邏輯分層、從屬關係。
有關這一條,在 3.3 小節中的層級關係會詳細展開。
classIds
是一個 classId 數組,每一個數組元素表明每一個 batch 的 分類 id,若兩個 batch 是 classes
數組中的某個 class,那麼它倆的 classId 是同樣的。
這個數組去重後的 id 數量,就等於 classes
數組的長度。
例如,classIds: [0,0,0, 1,1]
,有 0、1 兩個 classId,那麼 classes
數組的長度就應該是 2.
如今,換一個場景,假設有一塊空間,上面有牆模型要素、窗模型要素、門模型要素、屋頂模型、樓板模型要素共 5 類,每一個分類有 一、二、一、一、1 個模型要素,即
經過 3.2,很快獲得擴展 JSON:
{ "classes": [ /* 5個分類對象 */ { "name": "Wall", /* length 和 intances 屬性值略 */ }, { "name": "Window", /* length 和 intances 屬性值略 */ }, { "name": "Door", /* length 和 intances 屬性值略 */ }, { "name": "Roof", /* length 和 intances 屬性值略 */ }, { "name": "Floor", /* length 和 intances 屬性值略 */ }, ], "instancesLength": 6, "classIds": [0,1,1,2,3,4] }
顯然,這 6 個模型要素能夠構成一個屋子,此時,這 6 個模型要素並沒有邏輯信息寫在 JSON 中。
那麼,如今能夠新增一個 class
:
{ "classes": [ /* 同上,省略 5 個分類對象 */ { "name": "House", "length": 1, "instances": { "HouseArea": [48.94] } } ], "instancesLength": 7, // <- 注意,變成 7 了 "classIds": [0,1,1,2,3,4, 5] // <- 注意,多了個 Id }
這個新增的 House
class,它在 glTF 中並無對應的一個圖形數據,可是它確確實實就是存在的,由上面 6 個模型要素構成,且有它本身的屬性:HouseArea,房屋面積,其值是 48.94 平方米。
同時,因虛構出來一個模型要素,instancesLength
不得不加一個,且 classIds
也加了一個。
由此,不妨修改一下 instancesLength
的定義:classes
中各個 class 的 length 之和。
提問,此時要素表的 BATCH_LENGTH 與 instancesLength 同樣嗎?
爲了表示 House 類與其餘 5 類的關係,新增一個屬性與 classes
、instancesLength
、classIds
並列:
{ /* 3DTILES_batch_table_hierarchy 三個屬性 classes、instancesLength、classIds,略前兩個 */ "classIds": [0,1,1,2,3,4, 5], "parentIds": [6,6,6,6,6,6, 6] }
parentId
是什麼呢?
重複一下 3.3 的假設,一共 6 個實體模型要素:1個牆模型要素、2個窗模型要素、1個門模型要素、1個屋頂模型要素、1個樓板模型要素
那麼,索引從 0 開始計算,第 2 個是窗模型要素,其 classId
是 classIds[2] = 1
,其 parentId = parentIds[2] = 6
。
如今,獲得它的 parentId
是 6,從 classes
中的 class 挨個往下找,終於在 House 這個 class 找到了第 6 個模型要素(由於 0~5 被前 5 個 class 包了)。
結論
parentId 是 classes 中記錄的全部模型要素的 順序序號,包括實體的模型要素,以及在本小節中提到的虛要素,即 House。
讀者應該注意到了,若是自身已經沒有 parent 了,即它已是這個 b3dm 中邏輯層級最高的要素模型了,它的 parentId 就是它在 classes 中的順序號自己。
優勢:強大的可擴展性,理論上能夠無限層級嵌套虛擬的要素屬性,十分適合 BIM 數據的構造。
缺點:不易讀寫,不適合 b3dm 的增減。難以修改。
和 glTF 的 頂點屬性能夠被 Google Draco 壓縮工具壓縮同樣,點雲瓦片也支持了此壓縮工具,極大地下降了點雲瓦片的體積。
這個瓦片比起上面那個就簡單多了,它位於 pnts 瓦片的 要素表JSON頭中:
{ "POINTS_LENGTH": 20, // <- pnts 中有多少個點,這裏有 20 個點 /* 其餘 pnts 要素表屬性,略 */ "extensions": { "3DTILES_draco_point_compression": { "properties": { "POSITION": 0, "RGB": 1, "BATCH_ID": 2 }, "byteOffset": 0, "byteLength": 100 } } }
它指示了 pnts 瓦片的 POSITION
、RGB
、BATCH_ID
三個數據位於要素表二進制塊中,從第 0 個字節開始計,長度爲 100 個字節。讀取出來,把這 100 個字節二進制數據交給 Draco 解碼器,就能解碼出來這 20 個點的對應數據。
目前,這個擴展功能僅支持壓縮 pnts 瓦片要素表中的 "POSITION"
,"RGBA"
,"RGB"
,"NORMAL"
和"BATCH_ID"
數據。
被壓縮的數據,例如這裏的 POSITION
、RGB
、BATCH_ID
,它們的 byteLength
值一概爲 0(本來是指的要素表二進制數據塊的字節起始偏移量)。
Draco 壓縮工具能壓縮的數據類型是數字。因此,批次表中的數據,也能夠被壓縮。
假設,某 pnts 瓦片的批次表記錄了 Intensity
、Classification
兩個點雲的屬性信息,它的批次表 JSON 以下所示:
{ "Intensity": { "byteOffset": 0, "type": "SCALAR", "componentType": "UNSIGNED_BYTE" }, "Classification": { "byteOffset": 0, "type": "SCALAR", "componentType": "UNSIGNED_BYTE" } }
顯然,兩個屬性信息都是標量,數字類型均爲無符號的字節。那麼,使用了 Draco 壓縮以後,批次表的 extensions
應寫爲:
{ "Intensity": { /* 略,見上 */ }, "Classification": { /* 略,見上 */ }, "extensions": { "3DTILES_draco_point_compression": { "properties": { "Intensity": 3, "Classification": 4 } } } }
pnts 批次表的 3DTILES_draco_point_compression 擴展只須要 properties
屬性便可,不須要 byteLength 和 byteOffset。
究其緣由,Cesium 團隊是將批次表二進制數據一併壓縮進了要素表二進制塊內,並且會把全部被壓縮的屬性,不論是 要素表,仍是批次表,的 byteOffset 均歸零。
回顧 pnts 瓦片的規範,若 pnts 瓦片內的點要進行 batch 分類,那麼其分類信息在要素表中就記錄得夠詳細了,全局的 BATCH_LENGTH
、逐點的 BATCH_ID
足夠將未壓縮的批次表屬性信息訪問出來。
精力有限,之後有可能的話專門出一個專題講解更新中的擴展項。
某個 extensions 用到的具體數據,若是不方便寫在 extensions 的 JSON 中,能夠掛在 extras 中。