zoukankan      html  css  js  c++  java
  • 3dTiles 数据规范详解[5] 扩展

    目录:https://www.cnblogs.com/onsummer/p/12799366.html

    1 可扩展的格式

    继承自 glTF 的可扩展性,3dTiles 在定义上也留下了可扩展的余地。包括但不局限于:优化几何数据的存储,扩展属性数据等。

    2 官方当前的两种扩展

    • 层级属性
    • 点云的 draco 压缩

    下面,将简单介绍这两个扩展。

    3 以 “b3dm 类型的瓦片属性信息” 引入

    b3dm 瓦片的属性信息写在批次表(batchtable) 中。b3dm 中每个独立的模型,叫做 batch,(等价于要素表中的要素)这个概念引申自图形编程,意思是“一次性向图形处理器(GPU)发送的数据”,即批次。一个 b3dm 瓦片有多少个 batch(有多少个要素),是由要素表的 JSON 表头中的 BATCH_LENGTH 属性记录的。

    而批次表(batchtable)的每个属性数据长度,都与这个 BATCH_LENGTH 相等。

    以上是 03 篇与 04 篇的回顾。

    批次表记录属性数据是有缺陷的。

    • 第一,对字符串、布尔值等非数字型数据的支持较差,只能记录在批次表JSON头,二进制体无法记录非数字型数据;
    • 第二,也就是此扩展重点解决的问题,当 batch 之间存在逻辑分层、从属关系时,如何记录它们的层级属性数据的问题

    3.1 区分每一个顶点是谁

    此小节需要对 glTF 格式规范比较熟悉。知道“顶点属性”的概念,知道 WebGL 的帧缓存技术。

    b3dm 瓦片内置的 glTF 模型中,每个 primitive 的 attribute,也即顶点属性中会加上一个新的属性,与 POSITIONUV0 等并列,叫做 _BATCHID

    这样,通过 _BATCHID,使用 WebGL 中的帧缓存技术,在 FBO 上绘制 _BATCHID 的颜色附件,即可完成快速查询。

    要素表通过 BATCH_ID 访问 批次表里的属性数据,几何数据(glTF 中的 vertex)通过 _BATCHID 绑定要素。

    image

    3.2 不同模型要素有不同的属性怎么办

    假设有这么一块空间范围,归属在 0.b3dm 瓦片内,瓦片的 glTF 模型拥有两个 BATCH,即两个要素,为了方便观察,不妨具象化:

    • 空间范围 = 一个停车场

    • BATCH1 = 充电桩

    • BATCH2 = 电动汽车

    如下图所示:

    image

    现在,我用一个简单的 JSON 来描述这两个要素的属性数据:

    {
      "Charger": {
        "Price": 0.5,
        "DeviceId": "abcdefg123"
      },
      "Car": {
        "Brand": "Tesla",
        "Owner": "Jacky"
      }
    }
    

    这样的数据不符合原生批次表的存储逻辑,即每个 batch 的属性名称应完全一致。

    显然,充电桩的 Price(就是单价)、DeviceId 和车子的 Brand(品牌)、Owner 并不是一样的。

    如果用这个扩展来表示,在批次表的 JSON 中将会是:

    {
      "extensions": {
        "3DTILES_batch_table_hierarchy": {
          // ...
        }
      }
    }
    

    映入眼帘的是 extensions,它是一个 JSON,下面有一个 3DTILES_batch_table_hierarchy 的属性,其值也是一个 JSON:

    {
    	"classes": [
        { /* ... */ },
        { /* ... */ }
      ],
      "instancesLength": 2,
      "classIds": [0, 1]
    }
    

    其中,classes 是描述每个分类的数组,这里有充电桩类、电动汽车类,详细展开电动汽车类:

    [
      { /* 电动汽车类,略 */ },
      {
        "name": "Car",
        "length": 1,
        "instances": {
          "Brand": ["Tesla"],
          "Owner": ["Jacky"]
        }
      }
    ]
    

    每个 class 就记录了该类别下,所有模型要素的属性值(此处是 Brand 和 Owner),以及有多少个模型要素(length 值,此处是 length = 1 辆车)。

    扩展:如果这个 b3dm 又多增加了一个电动汽车,那么这个 JSON 就应该变成下面的样子了

    {
      "name": "Car",
      "length": 2, // <- 变成 2
      "instances": {
        "Brand": ["Tesla", "Benz"], // <- 加一个值
        "Owner": ["Jacky", "Granger"] // <- 加一个值
      }
    }
    

    图示:

    image

    3.2.1 属性:3DTILES_batch_table_hierarchy.classes

    classes 代表此 b3dm 内有多少个模型种类,这里有充电桩、汽车两类。

    3.2.2 属性:3DTILES_batch_table_hierarchy.instancesLength

    instancesLength 代表所有模型种类的数量和,这里每个种类都只有 1 个 batch(要素),加起来就是 2

    instancesLength 和 b3dm 中要素表的 BATCH_LENGTH 并不是相等的。

    当且仅当模型之间不构成逻辑层级时,这两个数字才相等。显然,此例中的 “充电桩”和“电动汽车”不构成逻辑分层、从属关系。

    有关这一条,在 3.3 小节中的层级关系会详细展开。

    3.2.3 属性:3DTILES_batch_table_hierarchy.classIds

    classIds 是一个 classId 数组,每个数组元素代表每个 batch 的 分类 id,若两个 batch 是 classes 数组中的某个 class,那么它俩的 classId 是一样的。

    这个数组去重后的 id 数量,就等于 classes 数组的长度。

    例如,classIds: [0,0,0, 1,1],有 0、1 两个 classId,那么 classes 数组的长度就应该是 2.

    3.3 虚要素:由多个实际的要素构成的属性

    现在,换一个场景,假设有一块空间,上面有墙模型要素、窗模型要素、门模型要素、屋顶模型、楼板模型要素共 5 类,每个分类有 1、2、1、1、1 个模型要素,即

    • 1个墙模型要素
    • 2个窗模型要素
    • 1个门模型要素
    • 1个屋顶模型要素
    • 1个楼板模型要素

    通过 3.2,很快得到扩展 JSON:

    {
      "classes": [ /* 5个分类对象 */ 
        { "name": "Wall", /* length 和 intances 属性值略 */ },
        { "name": "Window", /* length 和 intances 属性值略 */ },
        { "name": "Door", /* length 和 intances 属性值略 */ },
        { "name": "Roof", /* length 和 intances 属性值略 */ },
        { "name": "Floor", /* length 和 intances 属性值略 */ },
      ],
      "instancesLength": 6,
      "classIds": [0,1,1,2,3,4]
    }
    

    显然,这 6 个模型要素可以构成一个屋子,此时,这 6 个模型要素并无逻辑信息写在 JSON 中。

    那么,现在可以新增一个 class

    {
      "classes": [
        /* 同上,省略 5 个分类对象 */
        {
          "name": "House",
          "length": 1,
          "instances": {
            "HouseArea": [48.94]
          }
        }
      ],
      "instancesLength": 7, // <- 注意,变成 7 了
      "classIds": [0,1,1,2,3,4, 5] // <- 注意,多了个 Id
    }
    

    这个新增的 House class,它在 glTF 中并没有对应的一个图形数据,但是它确确实实就是存在的,由上面 6 个模型要素构成,且有它自己的属性:HouseArea,房屋面积,其值是 48.94 平方米。

    同时,因虚构出来一个模型要素,instancesLength 不得不加一个,且 classIds 也加了一个。

    由此,不妨修改一下 instancesLength 的定义:classes 中各个 class 的 length 之和。

    提问,此时要素表的 BATCH_LENGTH 与 instancesLength 一样吗?

    表示从属关系:属性 3DTILES_batch_table_hierarchy.parentIds

    为了表示 House 类与其他 5 类的关系,新增一个属性与 classesinstancesLengthclassIds 并列:

    {
      /* 3DTILES_batch_table_hierarchy 三个属性 classes、instancesLength、classIds,略前两个 */
      "classIds": [0,1,1,2,3,4, 5],
      "parentIds": [6,6,6,6,6,6, 6]
    }
    

    parentId 是什么呢?

    重复一下 3.3 的假设,一共 6 个实体模型要素:1个墙模型要素、2个窗模型要素、1个门模型要素、1个屋顶模型要素、1个楼板模型要素

    那么,索引从 0 开始计算,第 2 个是窗模型要素,其 classIdclassIds[2] = 1,其 parentId = parentIds[2] = 6

    现在,得到它的 parentId 是 6,从 classes 中的 class 挨个往下找,终于在 House 这个 class 找到了第 6 个模型要素(因为 0~5 被前 5 个 class 包了)。

    结论

    parentId 是 classes 中记录的所有模型要素的 顺序序号,包括实体的模型要素,以及在本小节中提到的虚要素,即 House。

    读者应该注意到了,如果自身已经没有 parent 了,即它已经是这个 b3dm 中逻辑层级最高的要素模型了,它的 parentId 就是它在 classes 中的顺序号本身。

    优缺点

    优点:强大的可扩展性,理论上可以无限层级嵌套虚拟的要素属性,十分适合 BIM 数据的构造。

    缺点:不易读写,不适合 b3dm 的增减。难以修改。

    4 再看 “pnts 瓦片的几何压缩” 扩展

    和 glTF 的 顶点属性可以被 Google Draco 压缩工具压缩一样,点云瓦片也支持了此压缩工具,极大地降低了点云瓦片的体积。

    pnts 中 要素表 的数据压缩

    这个瓦片比起上面那个就简单多了,它位于 pnts 瓦片的 要素表JSON头中:

    {
      "POINTS_LENGTH": 20, // <- pnts 中有多少个点,这里有 20 个点
      /* 其他 pnts 要素表属性,略 */
      "extensions": {
        "3DTILES_draco_point_compression": {
          "properties": {
            "POSITION": 0,
            "RGB": 1,
            "BATCH_ID": 2
          },
          "byteOffset": 0,
          "byteLength": 100
        }
      }
    }
    

    它指示了 pnts 瓦片的 POSITIONRGBBATCH_ID 三个数据位于要素表二进制块中,从第 0 个字节开始计,长度为 100 个字节。读取出来,把这 100 个字节二进制数据交给 Draco 解码器,就能解码出来这 20 个点的对应数据。

    目前,这个扩展功能仅支持压缩 pnts 瓦片要素表中的 "POSITION""RGBA""RGB""NORMAL""BATCH_ID" 数据。

    被压缩的数据,例如这里的 POSITIONRGBBATCH_ID,它们的 byteLength 值一律为 0(原本是指的要素表二进制数据块的字节起始偏移量)。

    pnts 中 批次表 的属性信息压缩

    Draco 压缩工具能压缩的数据类型是数字。所以,批次表中的数据,也可以被压缩。

    假设,某 pnts 瓦片的批次表记录了 IntensityClassification 两个点云的属性信息,它的批次表 JSON 如下所示:

    {
      "Intensity": {
        "byteOffset": 0,
        "type": "SCALAR",
        "componentType": "UNSIGNED_BYTE"
      },
      "Classification": {
        "byteOffset": 0,
        "type": "SCALAR",
        "componentType": "UNSIGNED_BYTE"
      }
    }
    

    显然,两个属性信息都是标量,数字类型均为无符号的字节。那么,使用了 Draco 压缩之后,批次表的 extensions 应写为:

    {
      "Intensity": { /* 略,见上 */ },
      "Classification": { /* 略,见上 */ },
      "extensions": {
        "3DTILES_draco_point_compression": {
          "properties": {
            "Intensity": 3,
            "Classification": 4
          }
        }
      }
    }
    

    注意

    pnts 批次表的 3DTILES_draco_point_compression 扩展只需要 properties 属性即可,不需要 byteLength 和 byteOffset。

    究其原因,Cesium 团队是将批次表二进制数据一并压缩进了要素表二进制块内,而且会把所有被压缩的属性,不管是 要素表,还是批次表,的 byteOffset 均归零。

    回顾 pnts 瓦片的规范,若 pnts 瓦片内的点要进行 batch 分类,那么其分类信息在要素表中就记录得够详细了,全局的 BATCH_LENGTH、逐点的 BATCH_ID 足够将未压缩的批次表属性信息访问出来。

    5 即将到来的大变动

    • 隐式瓦片
    • glTF 瓦片
    • 元数据
    • ...

    精力有限,以后有可能的话专门出一个专题讲解更新中的扩展项。

    6 再谈 extensions 和 extras

    某个 extensions 用到的具体数据,如果不方便写在 extensions 的 JSON 中,可以挂在 extras 中。

  • 相关阅读:
    数组及其方法
    Web Worker
    nodejs输入输出
    head标签中的meta
    对象副本的拷贝
    bower指南(一)
    gulp指南(一)
    云服务器搭建
    http协议简单介绍(转)
    使用traits
  • 原文地址:https://www.cnblogs.com/onsummer/p/14886996.html
Copyright © 2011-2022 走看看