Cesium開發(fā)基礎(chǔ)篇 | 063D Tiles介紹及加載

上一節(jié)我們介紹了glTF的主要數(shù)據(jù)結(jié)構(gòu)以及Cesium是如何對其進行加載的,這一節(jié)我們來介紹一下glTF的升級版3D Tiles ,也是目前 Cesium 在加載海量三維模型數(shù)據(jù)方面必須采用的一種數(shù)據(jù)格式。

3D Tiles介紹

3D Tiles 是在glTF的基礎(chǔ)上,加入了分層LOD的概念(可以把3D Tiles簡單地理解為帶有 LOD 的 glTF ),專門為流式傳輸和渲染海量 3D 地理空間數(shù)據(jù)而設(shè)計的,例如傾斜攝影、3D 建筑、BIM/CAD、實例化要素集和點云。它定義了一種數(shù)據(jù)分層結(jié)構(gòu)和一組切片格式,用于渲染數(shù)據(jù)內(nèi)容。3D Tiles 沒有為數(shù)據(jù)的可視化定義明確的規(guī)則,客戶可以按照自己合適的方式來可視化 3D 空間數(shù)據(jù)。同時,3D Tiles 也是 OGC 標(biāo)準(zhǔn)規(guī)范成員之一,可用于在臺式機、Web端和移動應(yīng)用程序中實現(xiàn)與海量異構(gòu)3D地理空間數(shù)據(jù)的共享、可視化、融合以及交互功能。下圖的動畫則是加入了LOD的效果:


帶有 LOD 的3D Tiles

在 3D Tiles 中,一個瓦片集(Tileset)是由一組瓦片(Tile)按照空間數(shù)據(jù)結(jié)構(gòu)(樹狀結(jié)構(gòu))組織而成的,它至少包含一個用于描述瓦片集的 JSON 文件(包含瓦片集的元數(shù)據(jù)和瓦片對象),其中每一個瓦片對象可以引用下面的其中一種格式,用于渲染瓦片內(nèi)容:


瓦片的內(nèi)容(瓦片格式的一個單獨實例)是一個二進制blob,具有特定于格式的組件,包括要素表(Feature Table)和批處理表(Batch Table)。瓦片內(nèi)容參考多種要素集特征,例如表示建筑物或樹木的 3D 模型或點云中的點。每個要素的位置和外觀屬性都存儲在瓦片要素表中,其他應(yīng)用于特定程序的屬性則存儲在批處理表中??蛻舳丝蛇x擇在運行時選擇要素,并檢索其屬性以進行可視化或分析。

上面表格中的b3dm 和 i3dm 格式是基于 glTF(一種專為高效傳輸 3D 內(nèi)容而設(shè)計的開放性規(guī)范)構(gòu)建的,它們的瓦片內(nèi)容在二進制體中嵌入了 glTF 資源,包含模型的幾何和紋理信息,而 pnts 格式卻沒有嵌入 glTF 資源。

瓦片中的樹狀組織結(jié)合了層次細節(jié)模型(Hierarchical Level of Detail,簡稱HLOD)的概念,以便最佳地渲染空間數(shù)據(jù)。在樹狀結(jié)構(gòu)中,每個瓦片都有一個邊界范圍框?qū)傩裕撨吔绶秶蛟诳臻g中能夠完全包圍該瓦片和孩子節(jié)點的數(shù)據(jù)。下圖為一種 3D Tiles 邊界范圍框所形成的層次體系示例:


tree.png

瓦片集可以使用類似于 2D 空間的柵格和矢量瓦片方案(例如Web地圖切片服務(wù) WMTS 或 XYZ 方案),其在若干細節(jié)級別(或縮放級別)處提供預(yù)定義的瓦片。但是,由于瓦片集的內(nèi)容通常是不一致的,或者可能很難僅在二維上組織,因此樹可以是具有空間一致性的任何空間數(shù)據(jù)結(jié)構(gòu),包括k-d樹,四叉樹,八叉樹和網(wǎng)格。

3D Tiles 的樣式是可選的,可以將其應(yīng)用于 Tileset 。樣式是由可計算的表達式所定義,用于修改每個要素的顯示方式。

獲取更多關(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。下面主要簡單介紹一下最核心的兩個概念:Tiles、Tileset。

首先,我從一個簡單的3D Tiles數(shù)據(jù)示例說起。下面代碼為一個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)容,就是一個Tile,即一個瓦片。

Tiles—瓦片

瓦片包含用于確定是否渲染瓦片的元數(shù)據(jù)、對渲染內(nèi)容的引用以及任何子瓦片的數(shù)組。切片實際上也是一個JSON對象,它由以下屬性組成。如下所示:


tile.png

1)boundingVolumes(邊界范圍框)
定義了瓦片的最小邊界范圍,用于確定在運行時渲染哪個瓦片,有region、box、sphere三種形式。

2)geometricError(幾何誤差)
是一個非負數(shù),以米為單位定義了不同瓦片層級的幾何誤差,通過幾何誤差來計算以像素為單位的屏幕誤差(SSE),從而確定不同縮放級別下應(yīng)該調(diào)用哪個層級的瓦片。簡單來說,Tile的幾何誤差是用來確定瓦片切換層級的,即控制LOD的。

3)refine(細化方式)
確定瓦片從低級別(LOD)切換為高級別(LOD)的呈現(xiàn)過程,簡單來說就是瓦片是如何切換的,其中包括替換(REPLACE)和添加(ADD)兩種方式。替換就是直接把父級的瓦片替換掉,添加則是在父級瓦片的基礎(chǔ)增加細節(jié)部分。如下圖所示,說明了具體的切換方式:

REPLACE 方式

ADD 方式

理論上來說,ADD方式是一種非常好的方式,是一種增量的LOD策略,能夠減少數(shù)據(jù)的傳輸。這里強調(diào)一下,refine屬性在根節(jié)點的Tile中是必須定義的,子節(jié)點中是可選的。如果子節(jié)點沒有定義,則繼承父節(jié)點的該屬性。

4)content(內(nèi)容)
content屬性指定了瓦片實際渲染的內(nèi)容。content.uri屬性可以是一個指定二進制塊(b3dm、i3dm、pnts、cmpt)的位置,也可以是指向另一個外部的tileset.json。
content.boundingVolume屬性定義了類似 Tile屬性boundingVolume的邊界范圍框,但是content.boundingVolume是一個緊密貼合的邊界范圍框,僅包含切片的內(nèi)容。該屬性可以用來做視錐體裁剪,只渲染視圖范圍內(nèi)的內(nèi)容,如果該屬性沒定義,系統(tǒng)也會自動計算。下圖是關(guān)于Tile.boundingVolumes和content.boundingVolumes 的比較,紅色是Tile的boundingVolumes,包圍了Tileset的整個區(qū)域;藍色是content的boundingVolumes,僅包圍切片中的渲染模型。

5)children(孩子節(jié)點)
這個很容易理解,因為3D Tiles是分級別的,所以每個Tile還會有子Tile、子子Tile、子子子Tile ......,分的越多,層級劃分的越精細,和下面講到的Tileset瓦片集root.children是同一個概念。

6)viewerRequestVolume(可選,觀察者請求體)
定義了一個邊界范圍,使用與boundingVolumes相同的模式,只有當(dāng)觀察者處于其定義的范圍內(nèi)時,Tile才顯示,從而精細控制了個別瓦片的顯示與否。如下圖所示,只有相機拉近到某一個距離時,才顯示屋內(nèi)的球。

7)transform(可選,位置變換矩陣)
定義了一個4x4的變換矩陣 ,通過此屬性,Tile的坐標(biāo)就可以是自己的局部坐標(biāo)系內(nèi)的坐標(biāo),最后通過自己的transform矩陣變換到父節(jié)點的坐標(biāo)系中。它會對Tile的content、boudingVolume、viewerRequestVolume進行轉(zhuǎn)換。詳情可查看3D Tiles的規(guī)范文檔。

Tileset—瓦片集

通常,一個3D Tiles 數(shù)據(jù)會使用一個主 tileset JSON 文件作為定義 tileset 的入口點,一般是以 tileset.json 文件命名(當(dāng)然該文件名稱可以修改)。從上面示例代碼可以看出,tileset JSON 有四個頂級屬性:asset、properties、geometricError、root。

1)asset
asset包含整個tileSet的元數(shù)據(jù)對象。asset.Version屬性,用于定義3D Tiles版本,該版本指定tileset的JSON模式和基本的tileset格式。tileVersion屬性可選,用于定義特定的應(yīng)用程序的tileset。

2)properties
properties是一個對象,包含tileset中每個feature屬性的對象。上面的例子是一個建筑物的3DTiles,因此每個瓦片都含有三維建筑物模型,每個三維建筑物模型都有高度屬性,所以上面的例子中就定義了Height屬性。屬性中每個對象的名稱與每個要素屬性的名稱相對應(yīng)(如例子中的Height對應(yīng)高度),并且包含該屬性的最大值和最小值,這些值用于創(chuàng)建樣式的顏色漸變非常有用。

3)geometricError
geometricError是一個非負數(shù),是通過這個幾何誤差的值來計算屏幕誤差,確定Tileset是否渲染。如果在渲染的過程中,當(dāng)前屏幕誤差大于這里定義的屏幕誤差,這個Tileset就不渲染。即根據(jù)屏幕誤差來控制Tileset中的root是否渲染。

4)root
root 是一個 JSON 對象,定義了最根級的 Tile ,它存儲的是真正的Tile 。也就是說,root 的數(shù)據(jù)組織方式與 Tile 的數(shù)據(jù)組織方式是一樣的。

需要注意的是,root.geometricError 與 tileset 的頂級 geometricError 不同,tileSet的geometricError是根據(jù)屏幕誤差來控制tileSet中的root是否渲染,而root(tile)中的geometricError則是用來控制tile中的children是否渲染。

root.children 是一個定義子 Tile 的對象數(shù)組,每個Tile還會有其children,這樣就形成了一種遞歸定義的樹狀結(jié)構(gòu)。每個子 Tile 的內(nèi)容完全由其父 Tile 的boundingVolume 包圍,并且通常是其 geometricError 小于其父 Tile 的 geometricError,因為越接近葉子節(jié)點,模型越精細,與原模型的幾何誤差就越小。對于葉子節(jié)點的 Tile ,其數(shù)組的長度為零,或者是未定義 children 。

當(dāng)然,為了創(chuàng)建樹狀結(jié)構(gòu),tile 的 content.uri 也可以指向外部的 tileset(另一個 tileset 的 JSON 文件)。這樣做的一個好處就是,不同的tileset可以分開存儲,例如我國的每個城市可單獨存儲成一個tileset,然后再定義一個包含所有 tileset 的全局 tileset。


tilesets.png

Cesium加載3D Tiles

Cesium雖然也支持兩種方式(Entity和Primitive)加載3D Tiles數(shù)據(jù),但因為多數(shù)情況下3D Tiles數(shù)據(jù)都是成片區(qū)的數(shù)據(jù),數(shù)據(jù)量比較大,所以為了保證性能,建議使用Primitive方式。

1)Cesium中3D Tiles相關(guān)類
我們在Cesium API幫助文檔中搜索3dtile關(guān)鍵詞,搜出如下結(jié)果:

我把幾個非常重要的也非常常用的類用紅框標(biāo)了出來,便于大家記憶。

  • Cesium3Dtileset:用于流式傳輸大量的異構(gòu)3D地理空間數(shù)據(jù)集;
  • Cesium3DTileStyle:瓦片集樣式;
  • Cesium3DTile:數(shù)據(jù)集中的一個瓦片;
  • Cesium3DTileContent:瓦片內(nèi)容;
  • Cesium3DTileFeature:瓦片集要素,用于訪問Tile中批量表中的屬性數(shù)據(jù),可通過scene.pick方法來獲取一個 BATCH,即三維要素。Cesium3DTileFeature.getPropertyNames() 方法獲取批量表中所有屬性名,Cesium3DTileFeature.getProperty(string Name) 來獲取對應(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, //最大加載瓦片個數(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);
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容