文章目录

  • 一、glTF
  • 二、3DTiles
  • 1.3dTiles的特点
  • 2.一个简单的3dTiles数据示例
  • 3.Tileset——(三维)瓦片数据集——.json
  • 顶级属性概览(必需):asset、root、geometricError
  • 其他属性root、children、refine、content、boundingVolume
  • children
  • refine 细化
  • bounding volumes 边界范围框
  • content
  • viewer request volume 视图请求范围框
  • Tile——构成3dtiles的成员:瓦片/切片——.json
  • tile content 切片内容
  • tile format 切片格式
  • tile info 切片信息
  • 坐标参考系 (CRS)
  • 4.数据与模型
  • tile瓦片二进制数据文件的大致字节布局结构(b3dm、i3dm、pnts)
  • FeatureTable,要素表 —— 记录渲染相关的数据
  • 要素表的结构
  • BatchTable,批次表 —— 记录属性数据
  • 批次表的结构
  • 扩展数据(extensions)与额外补充信息(extras)(后补)
  • 三、b3dm——tile二进制数据文件结构
  • Header和body
  • Feature Table
  • BatchTable
  • tiny_gltf(解析osgverse内容)——可读取b3dm(后补)
  • 四、pnt——tile二进制数据文件结构
  • Header和body
  • Feature Table
  • 例子
  • BatchTable
  • 五、坐标系
  • GIS——地理空间数据_坐标系统
  • 工具+详解+版权所属@四季留歌
    重要参考
    CesiumGS/3d-tiles

    一、glTF

    详见这里。
    KhronosGroup/glTF

    二、3DTiles

    1.3dTiles的特点

  • 三维模型使用了 glTF 规范,继承它的渲染高性能。
  • 3DTiles由tileset.json和tile组成,其中tile可以是.b3dm、.i3dm、.pnts、.vctr和.cmpt中的任一种格式文件。
  • 除了嵌入的 glTF,3dTiles 自己 只记录各级Tile的空间逻辑关系(如何构成整个3dtiles)和属性信息,以及模型与属性如何挂接在一起的信息,不记录模型数据。
  • 模型数据:三维模型的顶点、贴图材质、法线、颜色等信息。
    逻辑关系:各级Tile是如何在空间中保持连续的,LOD是如何组织的。

    2.一个简单的3dTiles数据示例

  • 入口文件是 tileset.json,描述了整个三维瓦片数据集,记录“逻辑信息”,还包括一些其他的元数据。
  • 各级瓦片用文件夹(目录)来组织,“属性信息”、“嵌入的gltf模型” 则位于各个二进制瓦片文件中,这些二进制文件则由 tileset.json 中的瓦片中的 uri 来引用。
  • 数据集的名称与所在文件夹的名称并无关系,数据集的名称写在入口文件中。
  • 瓦片只有两种情况:叶子瓦片(无子节点),非叶子瓦片。
  • 3.Tileset——(三维)瓦片数据集——.json

  • 通常,一个三维瓦片数据集(之后简称:一个3dtiles数据)的入口就是那个tileset.json。
  • 顶级属性概览(必需):asset、root、geometricError

    而通常来说,这个json必须存在以下几个顶级对象:

  • asset:有关整个tileset的元数据,应用于特定程序的数据有glTF版本号,生成工具等。
  • root:3D Tiles tileset中的根切片
  • geometricError:几何误差,表示简化后的切片/瓦片(tile)与原始几何图形的差异(米为单位)。
  • 这个数值的大小能控制 LOD 的显示隐藏,且这个数值父级瓦片一定比子级瓦片大。
  • tile的几何误差代表了该切片的选择指标,是一个非负值。
  • 根tile是源几何体的最简化版本,将具有最大的几何误差,然后每个连续级别的子级别将具有比其父级更低的几何误差,其中叶子具有接近0的几何误差
  • 较高的几何误差意味着切片重新定义为更加细化的切片。
  • Screen-Space Error(SSE),屏幕空间误差:如果渲染tile和其子节点没有内容,则会引入tile的源几何图形与简化后的图形之间的差异(以像素为单位)。
  • 其他属性root、children、refine、content、boundingVolume

    children

    定义子切片的对象数组。
    每个子切片内容都由其父切片的边界范围框完全包围,并且通常具有小于其父切片的几何误差
    对于叶子切片,此数组长度为零,并且可能未定义子切片。

    3dTiles在空间上允许数据集使用如下几种树结构:

  • 四叉树:适合高度上不太好切分的数据

  • 八叉树:追求极致的空间分割和分级(例如点云数据)

  • KD树

  • 格网结构

    例如上面的children下有17个子节点,每个节点对应一个uri,与下面中的17个文件一一对应:

  • refine 细化

    确定在选择较低分辨率的父平铺进行渲染时,该父平铺的渲染过程。
    允许的优化类型有:

  • 替换 (“REPLACE”),则子图块将代替父图块进行渲染,即不再呈现父图块。

  • 附加 (“ADD”),除了父平铺之外,还会渲染子平铺。

  • 省略时,继承父级切片的。

    bounding volumes 边界范围框

    每个子切片内容都由其父切片的边界范围框完全包围。
    包含切片或其内容的边界框,只需要一个box、region或sphere。

  • box —— number[12]


  • region —— number[6]
  • sphere —— number[4]

  • content

    有关切片内容的元数据和指向内容的链接

    viewer request volume 视图请求范围框


    Tile——构成3dtiles的成员:瓦片/切片——.json

  • tile是tileset的子集,所以一般也是.json后缀,里面包含可渲染内容的引用(即切片内容,如用uri)和元数据(如内容的边界范围框boundingVolume)。
  • tile content 切片内容
  • 通常,瓦片对象会引用一个二进制的瓦片数据文件(也有例外),是渲染所需的二进制信息:

  • tile format 切片格式


    有两大数据表:要素表/特征表Feature Table和批处理表Batch Table

    tile info 切片信息

    从瓦片内容可知,瓦片对象都有如下属性:

  • boundingVolume:空间范围框,允许有box、sphere、region三种范围框,但是只能定义一种
  • geometricError:几何误差
  • content:瓦片内容,uri属性引用二进制瓦片数据文件。
  • 瓦片还可以再引用 3dTiles 数据集,Tile不仅仅可以在其uri属性中引用 诸如 .b3dm、.i3dm、.pnts等二进制瓦片数据文件,还可以再引用一个 3dTiles!

  • 其他属性:viewerRequestVolume、transform
  • 没错,瓦片对象记录的就是瓦片的元数据,真正瓦片的本体数据在content所引用的二进制文件中。

    坐标参考系 (CRS)

    见下文。

    4.数据与模型

  • 3DTiles由tileset.json和tile组成,其中tile可以是.b3dm、.i3dm、.pnts、.vctr和.cmpt中的任一种格式文件。
  • 除了嵌入的 glTF,3dTiles 自己 只记录各级Tile的空间逻辑关系(如何构成整个3dtiles)和属性信息,以及模型与属性如何挂接在一起的信息不记录模型数据
  • 模型数据:三维模型的顶点、贴图材质、法线、颜色等信息,由gltf承担起来的(作为glb格式嵌入到瓦片二进制文件中)。
    逻辑关系:各级Tile是如何在空间中保持连续的,LOD是如何组织的。

    所以,“属性数据” 和 “模型” 是如何产生联系的呢?
    使用了两个重要的表来记录这种 “模型与属性” 的联系

  • FeatureTable(要素表)
  • BatchTable(批量表)
  • 要素表、批量表都是以 二进制 形式存储。

    tile瓦片二进制数据文件的大致字节布局结构(b3dm、i3dm、pnts)

    除去cmpt这个复合类型不谈,前三种的大致布局见下图:

    每一种瓦片二进制数据文件都有一个记录该瓦片的文件头信息,文件头包括若干个因瓦片不同而不太一致的数据信息。

  • 当fileHead含有要素表时,fileHead还将包含featureTableJSONByteLength和featureTableBinaryByteLength uint32,用于提取功能表的每个相应部分。
  • 当fileHead含有批次表时,fileHead还将包含batchTableJSONByteLength和batchTableBinaryByteLength uint32,用于提取功能表的每个相应部分。
  • FeatureTable,要素表 —— 记录渲染相关的数据

    CesiumGS_FeatureTable

  • 即描述了要素每个要素的位置和外观属性。
  • 例如b3dm,每一个模型都是一个要素;例如pnts,每个点都是一个要素。
  • 要素表的结构

  • 填充:
  • JSON头必须以包含的tile二进制文件中的8字节边界结束,必须使用后继空格字符0x20填充JSON头以满足字节对齐。
  • 二进制body必须以包含的tile二进制文件中的8字节边界开始与结束,必须使用任何值的附加字节填充二进制体,以满足此要求。
  • 二进制属性必须以字节偏移量开始,该字节偏移量是属性componentType的字节大小的倍数(参考glTF)。例如,FLOAT组件类型的属性每个元素有4个字节,因此开始位置的偏移量必须是4的倍数。为了满足此要求,必须在前面的二进制属性中填充其他任意值的字节。
  • JSON头:

  • 二进制体:
  • BatchTable,批次表 —— 记录属性数据

    CesiumGS_BatchTable

  • 如果把批次表删掉,那么 3DTiles 数据还能正常渲染。因为在3dtiles布局中:

  • 记录的是每个模型的属性数据,以及扩展数据(扩展数据以后再谈)。

  • 批次表就是所谓的模型属性表,批次表中每个属性数组的元素个数 == 模型的个数,即一个数组代表一种属性,每一种属性的元素和每一个模型一 一对应。

  • 其实也有例外的情况,有关 3DTiles 数据规范的扩展能力。

  • 要素表和批次表唯一的联系就是 BATCH_LENGTH(每一个batch为一个要素,即代表批次表中每种属性的元素个数),在 i3dm 中叫 INSTANCE_LENGTH,在 pnts 中叫 POINTS_LENGTH(每一个点为一个要素)。
  • 批次表的结构

  • 填充:和特征表一样
  • Batch Table 由两部分组成:JSON 标头和可选的 little endian 二进制正文。
  • JSON头:
  • JSON 描述属性,其值可以直接在 JSON 中定义为数组,也可以引用二进制正文中的部分。在二进制体中存储长数值数组效率更高。



  • 二进制体:
  • 扩展数据(extensions)与额外补充信息(extras)(后补)

    其实,无论是要素表,还是批量表,都允许在 JSON 中存在扩展数据,以扩充当前瓦片模型的功能,而并不是单一的一个一个模型顺次存储在瓦片文件、glb 中。

    三、b3dm——tile二进制数据文件结构

  • B3dm,Batched 3D Model,成批量的三维模型的意思,允许对异构3D模型(例如城市中的不同模型建筑物)批处理。
  • 倾斜摄影数据(例如osgb)、BIM数据(如rvt)、传统三维模型(如obj、dae、3dMax制作的模型等),均可创建此类瓦片。
  • 每个模型属性(例如ID)在运行时能够识别和更新各个模型。

  • Header和body




    Feature Table

    要素表,记录的是整个瓦片渲染相关的数据,而不是渲染所需的数据(glb中)。

  • 全局属性:
  • 属性名 属性数据类型 属性描述 是否必需
    BATCH_LENGTH uint32 当前瓦片文件内三维模型(BATCH、要素)的个数 yes
    RTC_CENTER float32[3] 如果模型的坐标是相对坐标,那么相对坐标的中心即此 no

    注意,如果glb模型并不需要属性数据,即要素表和批量表有可能是空表,那么 BATCH_LENGTH 的值应设为 0 .

  • 要素属性:
  • BatchTable

    {
      "height" : {
        "byteOffset" : 0,
        "componentType" : "FLOAT",//4
        "type" : "SCALAR"//1
      },
      "geographic" : {
        "byteOffset" : 40,
        "componentType" : "DOUBLE",
        "type" : "VEC3"
      },
    }
    

    从上表可以看出,height 属性跨越 0 ~ 39 字节,一共40个字节.
    通过 FeatureTableJSON 可以获取 BATCH_LENGTH 为10,那么就有10个模型,对应的,这 40 个字节就存储了10个 height 值。即FLOAT类型的数据字节长度为 4,刚好 4 byte × 10 = 40 byte,即 height 属性的全部数据的总长。

    不写二进制本体数据:

    {
        "height": [10.0, 20.0, 15.0, ...], // 太长了不写那么多,一共10个
        "geographic": [
            [113.42, 23.10, 102.1],
            [111.08, 22.98, 24.94],
        	// 太长不写
        ]
    }
    
  • 同样是一个数字,二进制的JSON文本大多数时候体积会比二进制数据大。
  • 对于属性的值类型是 JSON 中的 object、string、bool 类型,则必须存于 JSON 中,因为二进制体只能存 标量、234维向量四种类型的数字数据。
  • tiny_gltf(解析osgverse内容)——可读取b3dm(后补)

    需要了解gltf,参考这里:gltf
    源码
    使用方法参考

    四、pnt——tile二进制数据文件结构

    3d-tiles_PointCloud

    Header和body

  • pnts瓦片文件不内嵌 glTF 模型,故结构如下:

  • Feature Table

  • 全局属性:
  • 属性名 属性数据类型 属性描述 是否必需
    POINTS_LENGTH uint32 瓦片中点的数量。所有的点属性的长度必须与这个一样 yes
    QUANTIZED_VOLUME_OFFSET float32 * 3 量化偏移值 与QUANTIZED_VOLUME_SCALE属性必须同时存在或同时不存在
    QUANTIZED_VOLUME_SCALE float32 * 3 量化缩放比例 与QUANTIZED_VOLUME_OFFSET属性必须同时存在或同时不存在
    CONSTANT_RGBA uint8 * 4 为所有点定义同一个颜色,默认是灰色 no
    BATCH_LENGTH uint32 当前瓦片文件内三维模型(BATCH、要素)的个数 yes
    RTC_CENTER float32[3] 如果模型的坐标是相对坐标,那么相对坐标的中心即此 no

    BATCH_LENGTH 的意义是:点云瓦片中这么多点是可以划分的,每一类叫做 BATCH。
    例如对一栋建筑进行点云扫射,那么墙体的所有的点可以归属为 墙体BATCH,窗户的所有点可以归属为 窗户BATCH。
    BATCH_LENGTH 指示了当前pnts瓦片的点被划分成了多少个类别。

    1.pnts瓦片中颜色的优先级

    RGBA>RGB>RGB565>CONSTANT_RGBA。其中,CONSTANT_RGBA 是全局的,前三个是点要素属性里的。
    当然,还可以使用 3dTiles 的 style 来改变样式。

    2.QUANTIZED_VOLUME量化空间范围体

    通常点云数据只留其“形”,而具体每个点的坐标可以不那么精确。

    每个瓦片,都有它自己的空间范围,为了节约数据占用,可以使用相对坐标来记录每个 instance 的位置,也即记录全局属性中的 RTC_CENTER 属性。

    但是,即便用了相对坐标,instance 的坐标值仍然是 FLOAT 类型,占 4字节。

    例如,如何将 (16464, 2172, 63312) 这个量化的坐标映射回瓦片原本的坐标呢?参考公式:

    即量化坐标 PositionQuantized 各个坐标分量乘上缩放因子( Scale / 65535 ),然后加偏移坐标即可。

    三个方向的缩放因子 QUANTIZED_VOLUME_SCALE:float[3] 和 偏移量 QUANTIZED_VOLUME_OFFSET:float[3] 作为全局属性写在要素表JSON中。

    如果这两个全局属性未定义,则 逐要素属性中的 POSITION_QUANTIZED 这个量化坐标也不会存在,即使用原有的 float 类型坐标记法。

    需要注意的是,量化坐标和普通坐标只能二选一。

  • 要素属性/逐要素(每个点)属性:
  • 属性名 属性数据类型 属性描述 是否必需
    POSITION float32 * 3 直角坐标的点 yes ,除非POSITION_QUANTIZED属性存在
    POSITION_QUANTIZED uint16 * 3 量化空间范围体内的直角坐标点 是,除非POSITION属性存在;与QUANTIZED_VOLUME_SCALE和QUANTIZED_VOLUME_OFFSET必需同时存在。
    RGBA uint16 * 3 量化空间范围体内的直角坐标点 no
    RGB uint8 * 3 RGB颜色 no
    RGB565 uint16 有损压缩颜色,红5绿6蓝5,即65536种颜色 no
    NORMAL float32 *3 法线 no
    NORMAL_OCT16P uint8 * 2 点的法线,10进制单位向量,有16bit精度 no
    BATCH_ID uint8/uint16(默认)/uint32 从BatchTable种检索元数据的id 不必须,取决于全局属性BATCH_LENGTH

    例子

    //只有点坐标
    const featureTableJSON = {
      POINTS_LENGTH : 4, // 意味着有4个点
      POSITION : {
        byteOffset : 0 // 意味着从ftBinary的第0个byte开始读取
      }
    }
    
    const 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)
    
    
    //相对坐标与颜色信息
    const featureTableJSON = {
      POINTS_LENGTH : 4, // 意味着有4个点
      RTC_CENTER : [1215013.8, -4736316.7, 4081608.4], // 意味着相对于这个点
      POSITION : {
        byteOffset : 0 // 意味着从ftBinary的第0个byte开始读取
      },
      RGB : {
        byteOffset : 48 // 颜色值意味着从ftBinary的第48个byte读取,紧接在POSITION后
        //4字节*3分量*4个点 = 48字节
      }
    }
    
    // Nodejs Buffer
    const positionBinary = 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) // 一共12*4byte = 48byte
    
    const colorBinary = new Buffer(new Uint8Array([
      255, 0, 0,
      0, 255, 0,
      0, 0, 255,
      255, 255, 0,
    ]).buffer) // 一共12*1byte = 12byte
    
    // ftBinary一共48+12=60byte
    const featureTableBinary = Buffer.concat([positionBinary, colorBinary]) 
    
    // 量化坐标与八进制编码法向量
    const featureTableJSON = {
      POINTS_LENGTH : 4, // 意味着有4个点
      QUANTIZED_VOLUME_OFFSET : [-250.0, 0.0, -250.0], // 意味着偏移基坐标是 (-250, 0, -250)
      QUANTIZED_VOLUME_SCALE : [500.0, 0.0, 500.0], // 意味着x和z方向的缩放比是500
      POSITION_QUANTIZED : {//uint16 * 3
        byteOffset : 0 // 意味着量化坐标的数据存在ftBinary的第0个字节往后
      },
      NORMAL_OCT16P : {//uint8 * 2
        byteOffset : 24 // 意味着量化坐标顶点法线的数据存在ftBinary的第24个字节往后
      }
    }
    
    const positionQuantizedBinary = new Buffer(new Uint16Array([
      0, 0, 0,
      65535, 0, 0,
      0, 0, 65535,
      65535, 0, 65535
    ]).buffer) // 一共2byte*3分量*4个点=24byte
    
    const normalOct16PBinary = new Buffer(new Uint8Array([
      128, 255,
      128, 255,
      128, 255,
      128, 255
    ]).buffer) // 一共8*1=8byte,Uint8=8bit=1byte
    
    const featureTableBinary = Buffer.concat([positionQuantizedBinary, normalOct16PBinary])
    
    //点数据分类(BATCH)/ Batched points 批处理点
    const featureTableJSON = {
      POINTS_LENGTH : 4, // 意味着有4个点
      BATCH_LENGTH : 2, // 意味着4个点分成了2类(批、batch)
      POSITION : {
        byteOffset : 0 // 意味着POSITION将存储在ftBinary的第 0 byte之后
      },
      BATCH_ID : {
        byteOffset : 48, // 意味着BATCH_ID的值将从ftBinary的第 48 byte之后
        componentType : "UNSIGNED_BYTE" // 意味着BATCH_ID的值类型是无符号字节数
      }
    }
    
    const positionBinary = 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) // 4个点,一共12个值,每个值4byte(Float每个数字占4byte,即32bit),一共48byte
    
    const batchIdBinary = new Buffer(new Uint8Array([
      0,
      0,
      1,
      1
    ]).buffer) // 前2个点属于batchId = 0的batch。
    
    const featureTableBinary = Buffer.concat([positionBinary, batchIdBinary]); // 合并
    
    const batchTableJSON = {
      names : ['object1', 'object2']
    } // 批量表JSON记录了属性值,有两个,刚好对上 BATCH_LENGTH
    
    // 每个点都有属性
    const featureTableJSON = {
      POINTS_LENGTH : 4, // 意味着有4个点
      POSITION : {
        byteOffset : 0 // 意味着从ftBinary的第0byte开始
      }
    }
    
    const 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)
    
    const batchTableJSON = {
      names : ['point1', 'point2', 'point3', 'point4'] // 意味着这4个点都有names属性,其值写在这里
    }
    

    BatchTable

  • 批量表与b3dm的差不多,如果在 pnts 的要素表和批量表中存储了 BATCH_LENGTH 信息,那每个 BATCH 的属性就存于此。
  • 但是与 b3dm、i3dm 略有不同的是,如果要素表JSON中没有 BATCH_ID 的定义,但是批量表中却存在与点的数量 POINTS_LENGTH 一样长的属性数组,那么说明该点云瓦片的每一个点都有属性。
  • 参考b3dm的BatchTable。

    五、坐标系

    3D Tiles 本地坐标系使用右手 3 轴 (x, y, z) 笛卡尔坐标系;也就是说,x 和 y 的叉积得到 z。3D Tiles 将 z 轴定义为本地笛卡尔坐标系的 up(另请参阅坐标参考系统)。

  • 瓦片集的全局坐标系通常位于 WGS 84 以地球为中心、地球固定 (ECEF) 参考框架 (EPSG 4978) 中。
  • 3D Tiles默认使用的坐标系是地心地固坐标系(Earth-Centered, Earth-Fixed,简称ECEF),该坐标系的原点位于地球质心,主要轴如下:
    X轴指向通过赤道和格林威治本初子午线的平面。
    Y轴指向通过赤道并与X轴垂直的平面。
    Z轴指向北极。
    这种坐标系的优点是它与地球自转同步,适用于表示全球范围的三维地理数据。

  • 当使用区域边界框region时,使用地理坐标系(纬度,经度,高度)指定边界,常是EPSG 4979。
  • GIS——地理空间数据_坐标系统

  • 地理空间数据涉及空间要素的位置。我们可以使用地理坐标投影坐标系
    来确定地球表面空间要素的位置。
  • 地理坐标系统以经度和纬度表示,而投影坐标系以 x、y 坐标表示。

  • 许多投影坐标系统可在 GIS 中使用。例如,统一横轴墨卡托(UTM)格网
    系统
    ,将 84°N 与 80°S 之间的地球表面划分为 60 个地带。GIS 的基本原则是,表示不同
    地理空间数据的图层必须在空间上相互匹配,换言之,它们必须基于相同的坐标系统。
  • 这里是引用

  • 更多见另一篇文章:GIS坐标系统
  • 作者:dankokoko

    物联沃分享整理
    物联沃-IOTWORD物联网 » 3DTiles —— 三维瓦片

    发表回复