1. 程式人生 > >3dTiles 資料規範詳解[3] 內嵌在瓦片檔案中的兩大資料表

3dTiles 資料規範詳解[3] 內嵌在瓦片檔案中的兩大資料表

> 轉載請宣告出處:全網@秋意正寒 # 零、本篇前言 說實話,我很糾結是先介紹瓦片的二進位制資料檔案結構,還是先介紹這兩個重要的表。思前想後,我決定還是先介紹這兩個資料表。 因為這兩個表不先給讀者灌輸,那麼介紹到瓦片的二進位制資料檔案結構時,就滿嘴“晦澀難懂”啦。 ## 資料與模型 上文介紹到,瓦片的三維模型實際上是由gltf承擔起來的(作為glb格式嵌入到瓦片二進位制檔案中),那麼,除了模型資料,肯定模型自己本身也有屬性資料的。 就比如,門有長寬高、密度、生產日期等資訊,樓棟模型有建築面積、樓層數等資訊。 所以,“屬性資料” 和 “模型” 是如何產生聯絡的呢? ![](https://img2020.cnblogs.com/blog/1097074/202007/1097074-20200706030812366-821642538.png) 早在我的部落格《[聊聊GIS資料的四個分層與GIS服務](https://www.cnblogs.com/onsummer/p/12082568.html)》中有提及,只需把模型的幾何資料作為一個屬性,寫入屬性資料中,即把屬性資料和幾何資料並列,就可以了。 但是,在3dTiles中,模型資料是以glb的形式嵌入在瓦片檔案中的(點雲直接就寫xyz和顏色資訊了),模糊了二維中“要素”的概念,而且gltf規範看起來並沒有所謂的“要素”的概念,僅僅是對GPU友好的vertex、normal、texture等資訊。 **如何讓gltf模型的每一個模型,甚至每一個三角面,甚至每一個頂點打上“我屬於哪個模型”的印記呢?**我們本篇稍稍晚一些介紹。 再回憶一個重要的 3dTiles 理念: > 3dTiles 規範本身不包含模型資料的定義,它僅僅記錄模型變成瓦片後的空間組織關係、模型與其屬性資料之間的關係。 所以,3dTiles 規範在瓦片二進位制資料檔案中,使用了兩個重要的表來記錄這種 ”模型與屬性“ 的聯絡: - FeatureTable(要素表) - BatchTable(批量表) ## 瓦片二進位制資料檔案的大致位元組佈局結構 ![](https://img2020.cnblogs.com/blog/1097074/202007/1097074-20200706030856186-92035005.png) 上一篇簡單提過,瓦片引用的二進位制檔案有4種,即:b3dm、i3dm、pnts、cmpt。 除去cmpt這個複合型別不談,前三種的大致佈局見上圖。 > **每一種瓦片二進位制資料檔案都有一個記錄該瓦片的檔案頭資訊,檔案頭包括若干個因瓦片不同而不太一致的資料資訊,緊隨其後的是兩大資料表:FeatureTable(我翻譯其為:要素表)、BatchTable(我翻譯其為:批量表)。** 這兩個表既然是二進位制的資料,儘管它名字裡帶“表”,但是卻不是二維表格,它更多的是一些 **鍵值** 資訊。 關於不同瓦片二進位制檔案的這兩個表的結構,在後續博文會詳細介紹。 # 一、記錄渲染相關的資料:FeatureTable,要素表 在 b3dm 瓦片中,要素表記錄這個批量模型瓦片中模型的個數,這個模型單體在人類邏輯上不可再分。 (在房屋級別來看,房子並不是單體,構造它的門、門把、窗戶、屋頂、牆等才是模型單體;但是在模型殼子的普通表面建模資料中,房子就是一個簡單的模型) 要素表還可以記錄當前瓦片的中心座標,以便gltf使用相對座標,壓縮頂點座標數字的資料量。 官方給的定義是: > 要素表記錄的是與渲染有關的資料。 直球!聽不懂! 我來“翻譯”一下好了: > 要素表,記錄的是整個瓦片渲染相關的資料,而不是渲染所需的資料。 > > 渲染相關,即有多少個模型,座標是相對的話相對於哪個中心,如果是點雲的話顏色資訊是什麼以及座標如何等; > > 渲染所需,例如頂點資訊、法線貼圖材質資訊均有glb部分完成。 我們以pnts(點雲瓦片)舉例,它的要素表允許有兩大類資料(看不懂沒關係,之後的部落格還會繼續介紹四種瓦片檔案的結構): - *點屬性:記錄每個點雲點的資訊* | 屬性名 | 資料型別 | 描述 | 是否必須 | | ------------------ | ------------------------- | -------------------------------------- | ---------------------- | | POSITION | float32 * 3 | 直角座標的點 | 是,除非下面的屬性存在 | | POSITION_QUANTIZED | uint16 * 3 | 量化的直角座標點 | 是,除非上面的屬性存在 | | RGBA | uint8 * 4 | 四通道顏色 | | | RGB | uint8 * 3 | RGB顏色 | / | | RGB565 | uint16 | 有失真壓縮顏色,紅5綠6藍5,即65536種顏色 | / | | NORMAL | float32 *3 | 法線 | / | | NORMAL_OCT16P | uint8 * 2 | 點的法線,10進位制單位向量,有16bit精度 | / | | BATCH_ID | uint8/uint16(預設)/uint32 | 從BatchTable種檢索元資料的id | / | - *全域性屬性:記錄整個點雲瓦片的資訊* | 屬性名 | 資料型別 | 描述 | 是否必須 | | ----------------------- | ----------- | ------------------------------------------------------------ | -------------------------------- | | POINTS_LENGTH | uint32 | 瓦片中點的數量。所有的點屬性的長度必須與這個一樣。 | 是 | | RTC_CENTER | float32 * 3 | 如果所有點是相對於某個點定位的,那麼這個屬性就是這個相對的點的座標。 | / | | QUANTIZED_VOLUME_OFFSET | float32 * 3 | 量化偏移值(不知道是什麼) | 與下面的屬性必須同時存在 | | QUANTIZED_VOLUME_SCALE | float32 * 3 | 量化縮放比例(不知道是什麼) | 與上面的屬性必須同時存在 | | CONSTANT_RGBA | uint8 * 4 | 為所有點定義同一個顏色 | / | | BATCH_LENGTH | uint32 | BATCH_ID的個數 | 與點屬性中的BATCH_ID必須同時存在 | 簡略一瞥,可以看出點雲因為沒有使用gltf模型(也沒必要),把點雲要渲染到螢幕上所需的座標、法線、顏色等資訊寫在了要素表中。 如果還是不能理解何為“渲染相關”,那麼請閱讀後續四種瓦片檔案格式的詳細介紹,相信你會有所收穫——有可能是我表達比較菜。 要說明一個“業界黑話”: > 在一個瓦片中,一個三維**要素**(GIS中的通常叫法)= 一個**模型**(圖形學、工業建模叫法) = 一個**BATCH**(3dtiles叫法) 然後,我向讀者隆重介紹要素表的簡單結構,因為要素表、批量表都是以 **二進位制** 形式儲存,所以瞭解每一種瓦片的二進位制資料佈局十分重要。 ## 要素表的結構:JSON描述資訊+要素表資料體 要素表緊隨在若干個位元組的檔案頭後,它本身還可以再分為 `二進位制的JSON文字頭` + `二進位制的資料體`。 如下圖所示: ![](https://img2020.cnblogs.com/blog/1097074/202007/1097074-20200706030915964-1222127388.png) 有迫不及待的讀者希望更進一步瞭解要素表了,不要急,後續篇章一定展開,例如如何讀取要素表和其中的資料等。 接下來,是另一個數據表:批量表。 # 二、記錄屬性資料:BatchTable,批量表 如果把批量表刪除,那麼3dTiles資料還能正常渲染。 是的,批量表就是所謂的模型屬性表,批量表中每個屬性陣列的個數,就等於模型的個數,因為有多少個模型就對應多少個屬性嘛! (嘿嘿,其實也有例外的情況,我們到後續聊3dtiles資料規範的擴充套件能力時,再把這個坑填上,不然怎麼說3dtiles很靈 [keng] 活 [die] 呢) 批量表相對比較自由,只要能與模型對得上號,想寫啥就寫啥。 ## 批量表中的屬性資料 ↔ 模型的關聯 假定讀者在閱讀此 3dtiles 部落格之前,已經對 gltf 資料規範有一定的瞭解。 gltf 資料有三層邏輯:Node ← Mesh ← Primitive。 其中,Primitive 即 gltf 資料規範中最小的圖形單位,其頂點定義由其下的 `attributes` 物件下 `POSITION` 屬性來尋找訪問器(`Accessor`),從而獲取到資料。 ``` JSON { ... "meshes": [ { "primitives": [ { "attributes": { "POSITION": 0, "TEXTURE_0": 2 ... }, "indices": 1, "mode": 4, "material": 0 } ] } ], ... } ``` 獲取到 `POSITION` 、`indices` 對應的訪問器、快取檢視、快取檔案後,即可獲取 gltf 模型的所有頂點,即幾何模型,即三維要素的幾何資料。 現在問題來了,如何將這些頂點 “打” 上一個印記呢?就像檢疫的豬肉一樣,打個印記,說明豬是健康的。 Cesium團隊在設計 3dtiles 規範的時候充分利用了 gltf 規範的特點:開源。因此,每一個 `primitive` 被在其 `attributes` 中添加了額外的訪問器:`_BATCHID`。 ``` JSON "primitives": [ { "attributes": { "POSITION": 0, "_BATCHID": 3, "TEXTURE_0": 2 ... }, "indices": 1, "mode": 4, "material": 0 } ] ``` 它與 `POSITION` 等沒什麼兩樣,同樣會佔用一部分資料。聰明的讀者應該能想到了,如果每一個頂點都有一個所謂的 `_BATCHID` 對應,那麼我隨便給個點,我不就知道這個點的 `_BATCHID`,從而就知道這個點屬於哪一個 `BATCH` 了嗎? 翻翻前面的 “黑話”,GIS的讀者更容易關聯起來: 一個 `BATCH` (即三維要素)用自己的 `BATCHID` 與幾何資料一一對應,屬性資料也與這個 `BATCHID` 一一對應,由傳遞關係,那麼三維的幾何資料 (gltf) 也就能和 屬性資料 (批量表) 一一對應了。 > 不過遺憾的是,並不是所有的瓦片都有 gltf 模型,例如pnts瓦片。 > > 所以這個 幾何 與 屬性 兩大資料如何關聯,在之後的博文再具體問題具體分析。 *關於這個模型和屬性的關聯,在b3dm瓦片的博文中要重點介紹。* ## 批量表的結構:JSON描述資訊+批量表資料本體 與 要素表 很像,批量表也是由: `二進位制的JSON文字頭` + `二進位制的資料體` 構成的。如下圖所示: ![](https://img2020.cnblogs.com/blog/1097074/202007/1097074-20200706030933431-346302156.png) 關於兩個表的更深層次的資料內容,例如如何承載模型與模型之間的邏輯關係,如何記錄使用 Google 壓縮演算法的模型資料,那就是後續文章的內容了。 # 三、結語 本篇沒有特定的資料作為說明,寫得不太好,因為是第一次嘗試用自己的語言表達這兩大資料表,謀篇佈局能力不太行,還請讀者包涵。 這兩個資料表真正的內容,還要等到接下來的四篇對瓦片二進位制檔案的重點介紹中,才能細細講完,本篇僅僅作為接下來四篇部落格的 “總結”,也可以說是 “介紹”。 ## 附:CesiumJS API 如何查詢瓦片的批量表 我們通常在Cesium中使用 點選 事件,來獲取一個 `BATCH`,即三維要素。在Cesium API中,這個被叫做:Cesium3DTileFeature。 那麼,這個 Cesium3DTileFeature 就能訪問到它自己的批量表中的屬性資料: ``` JS const handler = new Cesium.ScreenSpaceEventHandler(viewer.canvas); handler.setInputAction(function(movement) { let feature = scene.pick(movement.endPosition); if (feature instanceof Cesium.Cesium3DTileFeature) { let propertyNames = feature.getPropertyNames(); let length = propertyNames.length; for (var i = 0; i < length; ++i) { let propertyName = propertyNames[i]; console.log(propertyName + ': ' + feature.getProperty(propertyName)); } } }, Cesium.ScreenSpaceEventType.LEFT_CLICK); ``` 用到了 `Cesium3DTileFeature.getPropertyNames()` 方法獲取批量表中所有屬性名,用了 `Cesium3DTileFeature.getProperty(string Name)` 來獲取對應屬性名的屬性值。更多 API 見官方