上一節(jié)我們介紹了glTF的主要數(shù)據(jù)結(jié)構(gòu)以及Cesium是如何對(duì)其進(jìn)行加載的陨亡,這一節(jié)我們來(lái)介紹一下glTF的升級(jí)版3D Tiles 曲伊,也是目前 Cesium 在加載海量三維模型數(shù)據(jù)方面必須采用的一種數(shù)據(jù)格式贪磺。
3D Tiles介紹
3D Tiles 是在glTF的基礎(chǔ)上坯苹,加入了分層LOD的概念(可以把3D Tiles簡(jiǎn)單地理解為帶有 LOD 的 glTF )参咙,專(zhuān)門(mén)為流式傳輸和渲染海量 3D 地理空間數(shù)據(jù)而設(shè)計(jì)的犀农,例如傾斜攝影惰赋、3D 建筑、BIM/CAD呵哨、實(shí)例化要素集和點(diǎn)云赁濒。它定義了一種數(shù)據(jù)分層結(jié)構(gòu)和一組切片格式,用于渲染數(shù)據(jù)內(nèi)容孟害。3D Tiles 沒(méi)有為數(shù)據(jù)的可視化定義明確的規(guī)則流部,客戶(hù)可以按照自己合適的方式來(lái)可視化 3D 空間數(shù)據(jù)。同時(shí)纹坐,3D Tiles 也是 OGC 標(biāo)準(zhǔn)規(guī)范成員之一枝冀,可用于在臺(tái)式機(jī)、Web端和移動(dòng)應(yīng)用程序中實(shí)現(xiàn)與海量異構(gòu)3D地理空間數(shù)據(jù)的共享耘子、可視化果漾、融合以及交互功能。下圖的動(dòng)畫(huà)則是加入了LOD的效果:
在 3D Tiles 中谷誓,一個(gè)瓦片集(Tileset)是由一組瓦片(Tile)按照空間數(shù)據(jù)結(jié)構(gòu)(樹(shù)狀結(jié)構(gòu))組織而成的绒障,它至少包含一個(gè)用于描述瓦片集的 JSON 文件(包含瓦片集的元數(shù)據(jù)和瓦片對(duì)象),其中每一個(gè)瓦片對(duì)象可以引用下面的其中一種格式捍歪,用于渲染瓦片內(nèi)容:
瓦片的內(nèi)容(瓦片格式的一個(gè)單獨(dú)實(shí)例)是一個(gè)二進(jìn)制blob户辱,具有特定于格式的組件鸵钝,包括要素表(Feature Table)和批處理表(Batch Table)。瓦片內(nèi)容參考多種要素集特征庐镐,例如表示建筑物或樹(shù)木的 3D 模型或點(diǎn)云中的點(diǎn)恩商。每個(gè)要素的位置和外觀(guān)屬性都存儲(chǔ)在瓦片要素表中,其他應(yīng)用于特定程序的屬性則存儲(chǔ)在批處理表中必逆〉】埃客戶(hù)端可選擇在運(yùn)行時(shí)選擇要素,并檢索其屬性以進(jìn)行可視化或分析名眉。
上面表格中的b3dm 和 i3dm 格式是基于 glTF(一種專(zhuān)為高效傳輸 3D 內(nèi)容而設(shè)計(jì)的開(kāi)放性規(guī)范)構(gòu)建的粟矿,它們的瓦片內(nèi)容在二進(jìn)制體中嵌入了 glTF 資源,包含模型的幾何和紋理信息损拢,而 pnts 格式卻沒(méi)有嵌入 glTF 資源陌粹。
瓦片中的樹(shù)狀組織結(jié)合了層次細(xì)節(jié)模型(Hierarchical Level of Detail,簡(jiǎn)稱(chēng)HLOD)的概念福压,以便最佳地渲染空間數(shù)據(jù)掏秩。在樹(shù)狀結(jié)構(gòu)中,每個(gè)瓦片都有一個(gè)邊界范圍框?qū)傩运砀啵撨吔绶秶蛟诳臻g中能夠完全包圍該瓦片和孩子節(jié)點(diǎn)的數(shù)據(jù)哗讥。下圖為一種 3D Tiles 邊界范圍框所形成的層次體系示例:
瓦片集可以使用類(lèi)似于 2D 空間的柵格和矢量瓦片方案(例如Web地圖切片服務(wù) WMTS 或 XYZ 方案)嚷那,其在若干細(xì)節(jié)級(jí)別(或縮放級(jí)別)處提供預(yù)定義的瓦片胞枕。但是,由于瓦片集的內(nèi)容通常是不一致的魏宽,或者可能很難僅在二維上組織腐泻,因此樹(shù)可以是具有空間一致性的任何空間數(shù)據(jù)結(jié)構(gòu),包括k-d樹(shù)队询,四叉樹(shù)派桩,八叉樹(shù)和網(wǎng)格。
3D Tiles 的樣式是可選的蚌斩,可以將其應(yīng)用于 Tileset 铆惑。樣式是由可計(jì)算的表達(dá)式所定義,用于修改每個(gè)要素的顯示方式送膳。
獲取更多關(guān)于3D Tiles 的信息可查其GitHub地址:https://github.com/CesiumGS/3d-tiles和 OGC 相關(guān)規(guī)范地址:http://docs.opengeospatial.org/cs/18-053r2/18-053r2.html员魏。下面主要簡(jiǎn)單介紹一下最核心的兩個(gè)概念:Tiles、Tileset叠聋。
首先撕阎,我從一個(gè)簡(jiǎn)單的3D Tiles數(shù)據(jù)示例說(shuō)起。下面代碼為一個(gè)3D Tiles的主瓦片集JSON 文件(tileset.json)的一部分碌补,也是調(diào)用3D Tiles數(shù)據(jù)的入口文件虏束。為了盡可能少占篇幅棉饶,children部分已省略,獲取完整tileset.json可查看該地址:https://github.com/CesiumGS/3d-tiles/blob/master/examples/tileset.json镇匀。
{
"asset" : {
"version": "1.0",
"tilesetVersion": "e575c6f1-a45b-420a-b172-6449fa6e0a59",
},
"properties": {
"Height": {
"minimum": 1,
"maximum": 241.6
}
},
"geometricError": 494.50961650991815,
"root": {
"boundingVolume": {
"region": [
-0.0005682966577418737,
0.8987233516605286,
0.00011646582098558159,
0.8990603398325034,
0,
241.6
]
},
"geometricError": 268.37878244706053,
"refine": "ADD",
"content": {
"uri": "0/0/0.b3dm",
"boundingVolume": {
"region": [
-0.0004001690908972599,
0.8988700116775743,
0.00010096729722787196,
0.8989625664878067,
0,
241.6
]
}
},
"children": [..]
}
}
上面代碼中 root 下面的內(nèi)容照藻,就是一個(gè)Tile,即一個(gè)瓦片坑律。
Tiles—瓦片
瓦片包含用于確定是否渲染瓦片的元數(shù)據(jù)岩梳、對(duì)渲染內(nèi)容的引用以及任何子瓦片的數(shù)組。切片實(shí)際上也是一個(gè)JSON對(duì)象晃择,它由以下屬性組成冀值。如下所示:
1)boundingVolumes(邊界范圍框)
定義了瓦片的最小邊界范圍,用于確定在運(yùn)行時(shí)渲染哪個(gè)瓦片宫屠,有region列疗、box、sphere三種形式浪蹂。
2)geometricError(幾何誤差)
是一個(gè)非負(fù)數(shù)抵栈,以米為單位定義了不同瓦片層級(jí)的幾何誤差,通過(guò)幾何誤差來(lái)計(jì)算以像素為單位的屏幕誤差(SSE)坤次,從而確定不同縮放級(jí)別下應(yīng)該調(diào)用哪個(gè)層級(jí)的瓦片古劲。簡(jiǎn)單來(lái)說(shuō),Tile的幾何誤差是用來(lái)確定瓦片切換層級(jí)的缰猴,即控制LOD的产艾。
3)refine(細(xì)化方式)
確定瓦片從低級(jí)別(LOD)切換為高級(jí)別(LOD)的呈現(xiàn)過(guò)程,簡(jiǎn)單來(lái)說(shuō)就是瓦片是如何切換的滑绒,其中包括替換(REPLACE)和添加(ADD)兩種方式闷堡。替換就是直接把父級(jí)的瓦片替換掉,添加則是在父級(jí)瓦片的基礎(chǔ)增加細(xì)節(jié)部分疑故。如下圖所示杠览,說(shuō)明了具體的切換方式:
理論上來(lái)說(shuō),ADD方式是一種非常好的方式纵势,是一種增量的LOD策略踱阿,能夠減少數(shù)據(jù)的傳輸。這里強(qiáng)調(diào)一下钦铁,refine屬性在根節(jié)點(diǎn)的Tile中是必須定義的软舌,子節(jié)點(diǎn)中是可選的。如果子節(jié)點(diǎn)沒(méi)有定義育瓜,則繼承父節(jié)點(diǎn)的該屬性葫隙。
4)content(內(nèi)容)
content屬性指定了瓦片實(shí)際渲染的內(nèi)容。content.uri屬性可以是一個(gè)指定二進(jìn)制塊(b3dm躏仇、i3dm恋脚、pnts腺办、cmpt)的位置,也可以是指向另一個(gè)外部的tileset.json糟描。
content.boundingVolume屬性定義了類(lèi)似 Tile屬性boundingVolume的邊界范圍框怀喉,但是content.boundingVolume是一個(gè)緊密貼合的邊界范圍框,僅包含切片的內(nèi)容船响。該屬性可以用來(lái)做視錐體裁剪躬拢,只渲染視圖范圍內(nèi)的內(nèi)容,如果該屬性沒(méi)定義见间,系統(tǒng)也會(huì)自動(dòng)計(jì)算聊闯。下圖是關(guān)于Tile.boundingVolumes和content.boundingVolumes 的比較,紅色是Tile的boundingVolumes米诉,包圍了Tileset的整個(gè)區(qū)域菱蔬;藍(lán)色是content的boundingVolumes,僅包圍切片中的渲染模型史侣。
5)children(孩子節(jié)點(diǎn))
這個(gè)很容易理解拴泌,因?yàn)?D Tiles是分級(jí)別的,所以每個(gè)Tile還會(huì)有子Tile惊橱、子子Tile蚪腐、子子子Tile ......,分的越多税朴,層級(jí)劃分的越精細(xì)回季,和下面講到的Tileset瓦片集root.children是同一個(gè)概念。
6)viewerRequestVolume(可選掉房,觀(guān)察者請(qǐng)求體)
定義了一個(gè)邊界范圍茧跋,使用與boundingVolumes相同的模式慰丛,只有當(dāng)觀(guān)察者處于其定義的范圍內(nèi)時(shí)卓囚,Tile才顯示,從而精細(xì)控制了個(gè)別瓦片的顯示與否诅病。如下圖所示哪亿,只有相機(jī)拉近到某一個(gè)距離時(shí),才顯示屋內(nèi)的球贤笆。
7)transform(可選蝇棉,位置變換矩陣)
定義了一個(gè)4x4的變換矩陣 ,通過(guò)此屬性芥永,Tile的坐標(biāo)就可以是自己的局部坐標(biāo)系內(nèi)的坐標(biāo)篡殷,最后通過(guò)自己的transform矩陣變換到父節(jié)點(diǎn)的坐標(biāo)系中。它會(huì)對(duì)Tile的content埋涧、boudingVolume板辽、viewerRequestVolume進(jìn)行轉(zhuǎn)換奇瘦。詳情可查看3D Tiles的規(guī)范文檔。
Tileset—瓦片集
通常劲弦,一個(gè)3D Tiles 數(shù)據(jù)會(huì)使用一個(gè)主 tileset JSON 文件作為定義 tileset 的入口點(diǎn)耳标,一般是以 tileset.json 文件命名(當(dāng)然該文件名稱(chēng)可以修改)。從上面示例代碼可以看出邑跪,tileset JSON 有四個(gè)頂級(jí)屬性:asset次坡、properties、geometricError画畅、root砸琅。
1)asset
asset包含整個(gè)tileSet的元數(shù)據(jù)對(duì)象。asset.Version屬性轴踱,用于定義3D Tiles版本明棍,該版本指定tileset的JSON模式和基本的tileset格式。tileVersion屬性可選寇僧,用于定義特定的應(yīng)用程序的tileset摊腋。
2)properties
properties是一個(gè)對(duì)象,包含tileset中每個(gè)feature屬性的對(duì)象嘁傀。上面的例子是一個(gè)建筑物的3DTiles兴蒸,因此每個(gè)瓦片都含有三維建筑物模型,每個(gè)三維建筑物模型都有高度屬性细办,所以上面的例子中就定義了Height屬性橙凳。屬性中每個(gè)對(duì)象的名稱(chēng)與每個(gè)要素屬性的名稱(chēng)相對(duì)應(yīng)(如例子中的Height對(duì)應(yīng)高度),并且包含該屬性的最大值和最小值笑撞,這些值用于創(chuàng)建樣式的顏色漸變非常有用岛啸。
3)geometricError
geometricError是一個(gè)非負(fù)數(shù),是通過(guò)這個(gè)幾何誤差的值來(lái)計(jì)算屏幕誤差茴肥,確定Tileset是否渲染坚踩。如果在渲染的過(guò)程中,當(dāng)前屏幕誤差大于這里定義的屏幕誤差瓤狐,這個(gè)Tileset就不渲染瞬铸。即根據(jù)屏幕誤差來(lái)控制Tileset中的root是否渲染。
4)root
root 是一個(gè) JSON 對(duì)象础锐,定義了最根級(jí)的 Tile 嗓节,它存儲(chǔ)的是真正的Tile 。也就是說(shuō)皆警,root 的數(shù)據(jù)組織方式與 Tile 的數(shù)據(jù)組織方式是一樣的拦宣。
需要注意的是,root.geometricError 與 tileset 的頂級(jí) geometricError 不同,tileSet的geometricError是根據(jù)屏幕誤差來(lái)控制tileSet中的root是否渲染鸵隧,而root(tile)中的geometricError則是用來(lái)控制tile中的children是否渲染桐愉。
root.children 是一個(gè)定義子 Tile 的對(duì)象數(shù)組,每個(gè)Tile還會(huì)有其children掰派,這樣就形成了一種遞歸定義的樹(shù)狀結(jié)構(gòu)从诲。每個(gè)子 Tile 的內(nèi)容完全由其父 Tile 的boundingVolume 包圍,并且通常是其 geometricError 小于其父 Tile 的 geometricError靡羡,因?yàn)樵浇咏~子節(jié)點(diǎn)系洛,模型越精細(xì),與原模型的幾何誤差就越小略步。對(duì)于葉子節(jié)點(diǎn)的 Tile 描扯,其數(shù)組的長(zhǎng)度為零,或者是未定義 children 趟薄。
當(dāng)然绽诚,為了創(chuàng)建樹(shù)狀結(jié)構(gòu),tile 的 content.uri 也可以指向外部的 tileset(另一個(gè) tileset 的 JSON 文件)杭煎。這樣做的一個(gè)好處就是恩够,不同的tileset可以分開(kāi)存儲(chǔ),例如我國(guó)的每個(gè)城市可單獨(dú)存儲(chǔ)成一個(gè)tileset羡铲,然后再定義一個(gè)包含所有 tileset 的全局 tileset蜂桶。
Cesium加載3D Tiles
Cesium雖然也支持兩種方式(Entity和Primitive)加載3D Tiles數(shù)據(jù),但因?yàn)槎鄶?shù)情況下3D Tiles數(shù)據(jù)都是成片區(qū)的數(shù)據(jù)也切,數(shù)據(jù)量比較大扑媚,所以為了保證性能,建議使用Primitive方式雷恃。
1)Cesium中3D Tiles相關(guān)類(lèi)
我們?cè)贑esium API幫助文檔中搜索3dtile關(guān)鍵詞疆股,搜出如下結(jié)果:
我把幾個(gè)非常重要的也非常常用的類(lèi)用紅框標(biāo)了出來(lái),便于大家記憶倒槐。
- Cesium3Dtileset:用于流式傳輸大量的異構(gòu)3D地理空間數(shù)據(jù)集旬痹;
- Cesium3DTileStyle:瓦片集樣式;
- Cesium3DTile:數(shù)據(jù)集中的一個(gè)瓦片;
- Cesium3DTileContent:瓦片內(nèi)容导犹;
- Cesium3DTileFeature:瓦片集要素唱凯,用于訪(fǎng)問(wèn)Tile中批量表中的屬性數(shù)據(jù)羡忘,可通過(guò)scene.pick方法來(lái)獲取一個(gè) BATCH谎痢,即三維要素。Cesium3DTileFeature.getPropertyNames() 方法獲取批量表中所有屬性名卷雕,Cesium3DTileFeature.getProperty(string Name) 來(lái)獲取對(duì)應(yīng)屬性名的屬性值节猿。
2)加載3D Tiles
var viewer = new Cesium.Viewer("cesiumContainer");
// 添加3D Tiles
var tileset = viewer.scene.primitives.add(
new Cesium.Cesium3DTileset({
url: "./data/Cesium3DTiles/Tilesets/Tileset/tileset.json",
// maximumScreenSpaceError: 2, //最大的屏幕空間誤差
// maximumNumberOfLoadedTiles: 1000, //最大加載瓦片個(gè)數(shù)
})
);
3)設(shè)置樣式
var properties = tileset.properties;
if (Cesium.defined(properties) && Cesium.defined(properties.Height)) {
tileset.style = new Cesium.Cesium3DTileStyle({
color: {
conditions: [
["${Height} >= 83", "color('purple', 0.5)"],
["${Height} >= 80", "color('red')"],
["${Height} >= 70", "color('orange')"],
["${Height} >= 12", "color('yellow')"],
["${Height} >= 7", "color('lime')"],
["${Height} >= 1", "color('cyan')"],
["true", "color('blue')"],
],
},
});
}
4)位置調(diào)整
var cartographic = Cesium.Cartographic.fromCartesian(
tileset.boundingSphere.center
);
var surface = Cesium.Cartesian3.fromRadians(
cartographic.longitude,
cartographic.latitude,
0.0
);
var offset = Cesium.Cartesian3.fromRadians(
cartographic.longitude,
cartographic.latitude,
height
);
var translation = Cesium.Cartesian3.subtract(
offset,
surface,
new Cesium.Cartesian3()
);
tileset.modelMatrix = Cesium.Matrix4.fromTranslation(translation);
})
.otherwise(function (error) {
console.log(error);
});
5)拾取要素
var handler = new Cesium.ScreenSpaceEventHandler(viewer.canvas);
handler.setInputAction(function (movement) {
var feature = viewer.scene.pick(movement.position);
if (Cesium.defined(feature) && feature instanceof Cesium.Cesium3DTileFeature) {
var propertyNames = feature.getPropertyNames();
var length = propertyNames.length;
for (var i = 0; i < length; ++i) {
var propertyName = propertyNames[i];
console.log(propertyName + ": " + feature.getProperty(propertyName));
}
}
}, Cesium.ScreenSpaceEventType.LEFT_CLICK);