i3dm,即 Instanced 3D Model
,實例三維模型的意思。
諸如樹木、路燈、路邊的垃圾桶、長椅等具有明顯 重復 特征的數據。這類數據用得較少(笑,現在都喜歡搞BIM、傾斜攝影、精模、白模等)
我的git地址:github.com/onsummer
轉載請規范化轉載。出處:@秋意正寒 https://www.cnblogs.com/onsummer/p/13252897.html
目錄:https://www.cnblogs.com/onsummer/p/12799366.html
瓦片文件二進制布局(文件結構)
與 b3dm 一致,文件頭多了個屬性。
1. 文件頭:32byte
i3dm的文件頭有8個屬性,前7個與b3dm是一樣的。
屬性的官方名稱 | 字節長 | 類型 | 含義 |
---|---|---|---|
magic |
4 | string(或char[4]) | 該瓦片文件的類型,在i3dm中是 "i3dm" |
version |
4 | uint32 | 該瓦片的版本,目前限定是 1. |
byteLength |
4 | uint32 | 該瓦片文件的文件大小,單位:byte |
featureTableJSONByteLength |
4 | uint32 | 要素表的JSON文本(二進制形式)長度 |
featureTableBinaryByteLength |
4 | uint32 | 要素表的二進制數據長度 |
batchTableJSONByteLength |
4 | uint32 | 批量表的JSON文本(二進制形式)長度 |
batchTableBinaryByteLength |
4 | uint32 | 批量表的二進制數據長度 |
gltfFormat |
4 | uint32 | gltf在i3dm瓦片中存在的形式 |
其中,前7個和b3dm意義一樣,不做解釋。
第8個,gltfFormat
只有兩個值:0和1.
0,則位於 i3dm 瓦片文件最后的 gltf 內容是一個 uri,指向gltf的數據內容(可能是Base64 DataURL,也可能是其他地方的地址,筆者沒見過...)
1,則位於 i3dm 瓦片文件最后的 gltf 內容是 二進制的 glb,大多數情況見的是這個。
默認情況,gltf 是 y 軸朝上,3dTiles 是z軸朝上,需要坐標轉換。
2. 要素表
在上篇,有介紹到要素表存在 全局屬性 和 要素屬性。在 i3dm 中,這對概念就能得到很好的解釋。
① 要素表的全局屬性
屬性名 | 數據類型 | 描述 | 是否必須 |
---|---|---|---|
INSTANCES_LENGTH | uint32 | instance的個數 | 是 |
RTC_CENTER | float32[3] | 如果坐標是相對坐標,那么相對中心由此屬性給出 | 否 |
QUANTIZED_VOLUME_OFFSET | float32[3] | 量化空間范圍體的偏移量 | 否,與要素屬性中的POSITION_QUANTIZED 共存亡 |
QUANTIZED_VOLUME_SCALE | float32[3] | 量化空間范圍體的縮放比例 | 否,與要素屬性中的POSITION_QUANTIZED 共存亡 |
EAST_NORTH_UP | boolean | 如果這個屬性值是true,而且每個實例的方向沒有定義,那么每個實例將默認指向WGS84橢球的正東、正北方向。 | 否 |
第一第二個能與 b3dm 中的 BATCH_LENGTH
和 RTC_CENTER
類比來理解,就不解釋了。
最后一個屬性指示當前 i3dm 瓦片的坐標軸朝向。
下列要着重介紹這個所謂的 QUANTIZED_VOLUME
,即 量化空間范圍體。
量化空間范圍體
這個詞“量化空間范圍體”是我自己意譯的。
每個瓦片,都有它自己的空間范圍,為了節約數據占用,可以使用相對坐標來記錄每個 instance 的位置,也即記錄全局屬性中的 RTC_CENTER
屬性。
但是,即便用了相對坐標,instance 的坐標值仍然是 FLOAT
類型,占 4字節。
假設,存在一個矩形空間,它的左下角點的坐標是 (x, y, z)
,將矩形空間按 \(2^{16}\) 等分其 x、y、z 三個方向,定義矩形空間的三條邊長對應瓦片本身的坐標空間的縮放比例為 (ScaleX, ScaleY, ScaleZ)
,如下圖所示:
這樣,被細分出來的每一個 “小矩形”,都有它自己在這個矩形空間的量化坐標,因為 x、y、z 三個方向被分割成了 \(2^{16}\) 塊,我們可以使用 uint16
類型的數值來記錄坐標,這樣每個數字只占了 16bit,也即 2byte,比 FLOAT
的4byte 小了一倍,對於頂點數據的壓縮十分具有價值。
那么,如何將 (16464, 2172, 63312)
這個量化的坐標映射回瓦片原本的坐標呢?參考公式:
\(\vec{Position} = Scale * \displaystyle\frac{\vec{PositionQuantized}}{65535} + \vec{Offset}\)
即量化坐標 PositionQuantized
各個坐標分量乘上縮放因子( Scale / 65535
),然后加偏移坐標即可。
三個方向的縮放因子 QUANTIZED_VOLUME_SCALE:float[3]
和 偏移量 QUANTIZED_VOLUME_OFFSET:float[3]
作為全局屬性寫在要素表JSON中。
如果這兩個全局屬性未定義,則 逐要素屬性中的 POSITION_QUANTIZED
這個量化坐標也不會存在,即使用原有的 float 類型坐標記法。
需要注意的是,量化坐標和普通坐標只能二選一,如果都不存在,那么這個 i3dm 瓦片就不會被渲染。
看到這,是否能理解“要素表的全局屬性是對於整個瓦片文件而言”這句話了呢?
② 要素表的(逐)要素屬性
屬性名稱 | 數據類型 | 描述 | 是否必須 |
---|---|---|---|
POSITION | float32[3] | 模型實例的坐標 | 是,與POSITION_QUANTIZED二選一 |
POSITION_QUANTIZED | uint16[3] | 量化空間范圍體內的模型實例坐標 | 是,與POSITION二選一 |
NORMAL_UP | float32[3] | 模型上方向向量 | 否,與NORMAL_RIGHT共存亡 |
NORMAL_RIGHT | float32[3] | 模型右方向向量,必須與up 向量正交 |
否,與NORMAL_UP共存亡 |
NORMAL_UP_OCT32P | uint16[2] | 模型上方向向量,32位精度八進制編碼 | 否,與NORMAL_RIGHT_OCT32P共存亡 |
NORMAL_RIGHT_OCT32P | uint16[2] | 模型右方向向量,必須與up 向量正交,32位精度8進制編碼 |
否,與NORMAL_UP_OCT32P共存亡 |
SCALE | float32 | 該 instance 對於 gltf 的縮放比例 | 否 |
SCALE_NON_UNIFORM | float32[3] | 該 instance 在三個方向上的縮放比例 | 否 |
BATCH_ID | uin8/uint16(默認)/uint32 | 用於在批量表里檢索數據用的batchId | 否 |
當 i3dm 瓦片中逐個 instance 的POSITION
被定義時,量化坐標 POSITION_QUANTIZED
就不應存在,反之亦然。
接下來四個方向向量屬性(法線)NORMAL_UP
、NORMAL_RIGHT
和 NORMAL_UP_OCT32P
、NORMAL_RIGHT_OCT32P
也是一對反依賴的逐要素屬性。
SCALE
屬性定義了當前要素(instance或實例)對使用的 gltf 模型的縮放比例。
SCALE_NON_UNIFORM
屬性與 SCALE
屬性差不多,只不過是在三個方向上分別不同的縮放比例。
BATCH_ID
,是當前要素(instance或實例)的 id 號,將 要素 與 批量表中的屬性 二者聯系起來。
個人覺得,應該叫
INSTANCE_ID
更合適一些?
默認方向
如果不給定要素屬性中與方向有關的向量時,每個實例的朝向有一個默認值:在WGS84橢球上,上方向指向正北,右方向指向正東。
③ 要素表的JSON
上述所有屬性全部會記錄在要素表的 JSON 中,對於 全局屬性,其值記錄在 JSON 中,對於其要素屬性,因為要素(即instance)很多的時候寫在JSON中體積會變大,所以使用 JSON引用要素表二進制數據體 的形式。
下列是一個要素表的JSON:
{
INSTANCES_LENGTH : 4, // 有4個實例
POSITION : {
byteOffset : 0 // POSITION的值從ftBinary的第0字節起開始計算
}
}
讀者不妨回顧上一篇,b3dm的要素表JSON,並未出現有對要素表體引用的屬性,在這里出現了:POSITION
,它從要素表體的第 0 個字節開始記錄數據。
而 POSITION
這個逐要素(實例、instance)屬性的定義,早已在上文提及,即三個 FLOAT
類型數字為一組,一共 INSTANCES_LENGTH
組的數據,記錄在要素表體。這是 instance 坐標數據,寫在 JSON 中雖然沒問題,但是會造成空間浪費,以二進制形式記錄會比較划算。
④ 要素表體
要素表JSON中引用的二進制數據均順次記錄在此。
3. 批量表
批量表與 b3dm 的一致,均為 JSON 記錄屬性元數據,批量表體記錄屬性具體數據。此處不再舉例。
4. 要素舉例說明
此部分參考官方文檔。
① 僅有 POSITION
的 i3dm 瓦片
var featureTableJSON = {
INSTANCES_LENGTH : 4, // 有4個實例
POSITION : {
byteOffset : 0 // POSITION的值從ftBinary的第0字節起開始計算
}
};
var featureTableBinary = new Buffer(new Float32Array([
0.0, 0.0, 0.0,
1.0, 0.0, 0.0,
0.0, 0.0, 1.0,
1.0, 0.0, 1.0
]).buffer);
使用 JavaScript 語言記錄了 要素表JSON,以及要素表二進制數據(以ES6 TypedArray 形式)。
② 使用量化位置與八進制方向向量
var featureTableJSON = {
INSTANCES_LENGTH : 4, // 有4個實例
QUANTIZED_VOLUME_OFFSET : [-250.0, 0.0, -250.0],
QUANTIZED_VOLUME_SCALE : [500.0, 0.0, 500.0],
POSITION_QUANTIZED : {
byteOffset : 0
},
NORMAL_UP_OCT32P : {
byteOffset : 24
},
NORMAL_RIGHT_OCT32P : {
byteOffset : 40
}
};
var positionQuantizedBinary = new Buffer(new Uint16Array([
0, 0, 0,
65535, 0, 0,
0, 0, 65535,
65535, 0, 65535
]).buffer);
var normalUpOct32PBinary = new Buffer(new Uint16Array([
32768, 65535,
32768, 65535,
32768, 65535,
32768, 65535
]).buffer);
var normalRightOct32PBinary = new Buffer(new Uint16Array([
65535, 32768,
65535, 32768,
65535, 32768,
65535, 32768
]).buffer);
var featureTableBinary = Buffer.concat([positionQuantizedBinary, normalUpOct32PBinary, normalRightOct32PBinary]);
規定了全局屬性 QUANTIZED_VOLUME_OFFSET
和 QUANTIZED_VOLUME_SCALE
,規定了量化坐標 POSITION_QUANTIZED
、八進制上方向和右方向向量NORMAL_UP_OCT32P
、NORMAL_RIGHT_OCT32P
在要素表體中的起始偏移值。
於是,使用三個 TypedArray
構造的 Buffer
對象,再拼接在一起,即要素表體 featureTableBinary
。
5. 字節對齊與編碼端序
與b3dm里寫的一致,可以回看:https://www.cnblogs.com/onsummer/p/13252896.html
6. 擴展(extensions)和額外信息(extras)
同樣,這部分內容與b3dm篇章內介紹的一致,會在后續文章內介紹。