惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

IT之家
IT之家
Recent Announcements
Recent Announcements
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
The GitHub Blog
The GitHub Blog
MyScale Blog
MyScale Blog
爱范儿
爱范儿
GbyAI
GbyAI
Cyber Security Advisories - MS-ISAC
Cyber Security Advisories - MS-ISAC
美团技术团队
Y
Y Combinator Blog
博客园 - 叶小钗
Apple Machine Learning Research
Apple Machine Learning Research
Martin Fowler
Martin Fowler
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
罗磊的独立博客
M
MIT News - Artificial intelligence
博客园 - Franky
V
Visual Studio Blog
I
InfoQ
V
V2EX
Hugging Face - Blog
Hugging Face - Blog
腾讯CDC
博客园 - 司徒正美
L
LangChain Blog

博客园 - charlee44

《GIS基础原理与技术实践》配套教学课件 3D Gaussian Splatting可视化渲染原理详解 Unity项目适配华为鸿蒙系统的原生库加载问题排查与解决 Unity在安卓端如何调试输出信息 代码之外:一个技术人的职场困境与自我和解 记一次GIS专业职称水平能力测试考试 《GIS基础原理与技术实践》配套案例(Python版) 最小二乘问题详解:目录 最小二乘问题详解22:抗差估计与增量式SFM的工程稳健实现 最小二乘问题详解21:稀疏GCP约束下的自由网平差与弱约束融合 最小二乘问题详解20:无先验约束下的增量式SFM自由网平差 最小二乘问题详解19:带先验约束的增量式SFM优化与实现 最小二乘问题详解18:增量式SFM核心流程实现 最小二乘问题详解17:SFM仿真数据生成 最小二乘问题详解16:束平差工程实践总结 最小二乘问题详解15:束平差原理与基础实现 最小二乘问题详解14:鲁棒估计与5点算法求解本质矩阵 最小二乘问题详解13:对极几何中本质矩阵求解 最小二乘问题详解12:三角化中的非线性优化 最小二乘问题详解11:基于李代数的PnP优化 最小二乘问题详解10:PnP问题求解 《实时渲染》第3章-图形处理单元-3.7几何着色器 《实时渲染》第3章-图形处理单元-3.6曲面细分阶段 《实时渲染》第3章-图形处理单元-3.5顶点着色器 《实时渲染》第3章-图形处理单元-3.4可编程着色和及其API的演变 为什么现代 C++ 库都用 PIMPL?一场关于封装、依赖与安全的演进 《实时渲染》第3章-图形处理单元-3.2GPU管线概览 我的GIS实践与思考:新书《GIS基础原理与技术实践》分享 从零实现一个生产级 RAG 语义搜索系统:C++ + ONNX + FAISS 实战
三维模型瓦片服务三剑客:3D Tiles、I3S与S3M全解析
charlee44 · 2026-02-04 · via 博客园 - charlee44

本文节选自新书《GIS基础原理与技术实践》第8章。当 GIS 迈入三维时代,如何高效发布与可视化海量三维模型成为关键挑战。目前,Cesium 的 3D Tiles、Esri 的 I3S 和 超图的 S3M 已成为三大主流三维瓦片标准。本文将带你深入其核心机制——从瓦片树、包围体、几何误差,到 b3dm/i3dm/pnts 格式细节,再到要素化与声明式样式,全面解析这“三维瓦片三剑客”的异同与适用场景。

GIS基础原理与技术实践

8.8 三维模型数据服务

与矢量切片服务和地形切片服务一样,三维模型数据服务也多数是以静态资源的形式进行发布的,毕竟他们还没形成比较标准的规范,不用提供额外的空间操作,只需要保证能获取资源进行可视化就可以了。因此,三维模型数据服务大多直接使用三维模型瓦片数据格式发布的静态资源即可。

8.8.1 三维模型瓦片数据格式

一般情况下,三维模型的数据量比单纯的栅格数据或者矢量数据大得多,因此也需要进行类似于切片的处理,将三维模型轻量化。其具体的原理也不复杂,使用的就是在第7.4节中我们介绍的分页LOD技术,通过分层和分块,将三维模型划分成不同精细度、不同范围的瓦片,根据三维场景的需要,使渲染端动态调度出适配场景精细度的三维模型瓦片。

第7.4节中我们是通过倾斜摄影模型介绍的具有分页LOD技术的OSGB格式数据,但推而广之,其实第7.5节中介绍的所有类型的三维模型数据都可以使用OSGB格式来进行表达。不过,OSGB格式数据是一个适合桌面端的数据格式,并没有针对Web端环境进行优化和适配。目前,经常用作三维模型数据服务的三维模型数据是Cesium的3D Tiles格式,ArcGIS的I3S格式以及国内超图软件的S3M格式。其中,3D Tiles和I3S已经是国际OGC标准,而S3M则是CAGIS(中国地理信息产业协会)空间三维模型数据格式标准。

根据3D Tiles官方文档(https://github.com/CesiumGS/3d-tiles) 提供的定义,3D Tiles是专为流式传输和渲染大量3D地理空间内容而设计的三维模型数据格式,例如倾斜摄影测量数据、3D建筑数据、BIM/CAD、实例化要素和点云数据等。与OSGB使用的分页LOD技术类似,3D Tiles使用分层细节级别 (HLOD,Hierarchical Level of Detail)的空间数据结构,保证只有可见的瓦片才会被流式传输和渲染,从而提高三维模型数据整体性能。

3D Tiles有1.0和1.1两个版本,但是目前3D Tiles 1.0是使用最广泛的三维模型瓦片数据格式,以下我们会以3D Tiles 1.0为例,具体介绍一下三维模型瓦片数据格式的内容。

8.8.2 瓦片集和瓦片(Tilesets and Tiles)

3D Tiles合适文件通常是一个散列的包含文件和文件夹的数据集,数据集的入口通常是一个名为tileset的JSON文件。如文件名表达的含义一样,这个JSON文件就是3D Tiles的根数据集(Tilesets),一个典型的例子如下例8.6所示:

例8.6 3D Tiles的根数据集

{
    "asset": {},
    "properties": {},
    "geometricError": 100,
    "root": {
        "geometricError": 20,
        "boundingVolume": {
            "region": []
        },
        "refine": "ADD",
        "children": [
            {
                "geometricError": 10,
                "boundingVolume": {},
                "content": {
                    "uri": "house.b3dm"
                },
                "children": [
                    {
                        "geometricError": 5,
                        "boundingVolume": {},
                        "content": {
                            "uri": "detailsA.b3dm"
                        }
                    },
                    {
                        "geometricError": 5,
                        "boundingVolume": {},
                        "content": {
                            "uri": "detailsB.b3dm"
                        }
                    }
                ]
            },
            {
                "geometricError": 10,
                "boundingVolume": {},
                "content": {
                    "uri": "tree.pnts"
                }
            },
            {
                "geometricError": 10,
                "boundingVolume": {},
                "content": {
                    "uri": "fence.i3dm"
                }
            },
            {
                "geometricError": 10,
                "boundingVolume": {},
                "content": {
                    "uri": "external.json"
                }
            }
        ]
    }
}

在这个JSON文件中,最主要的部分就是名为root的元素,以及children数组中的元素。其实两者的属性是相同的,都应该包含content、children、boundingVolume、geometricError以及refine键值对,只不过有的键值对被省略掉了。具体来说,3D Tiles中的瓦片(Tiles),指的就是这个元素。

从例8.1可以看出,root元素包含了一个children数组元素,children数组中的一个元素又可以包含一个children数组元素...如此可以进行多层嵌套,就组成了一个瓦片树。我们可以回忆一下第7.4.3节的内容,这与OSGB格式的节点树非常相似。在这个瓦片树中,越往上,模型精细度越低,但是分块越少;越往下,模型精细度越高,但是分块越多。父亲节点与所有的子节点表达的数据内容是一样的,只是精细度有差别。

3D Tiles瓦片和瓦片集的示意图如下图8.53所示,一个JSON瓦片集能包含多个瓦片,瓦片的content就是具体的模型实体。不过正如例8.6所展示的那样,瓦片的content也可以指向另一个JSON瓦片集,像这样重复嵌套,我们可以组成一个非常复杂的表达三维场景的瓦片树。

图8.53 3D Tiles瓦片和瓦片集的示意图

8.8.3 包围体(Bounding Volumes)

在例8.6中我们就看到的boundingVolume元素就是包围体。可能这里说包围盒这个概念更容易让人理解一点,但是3D Tiles中有三种不同的表达切片范围的体要素,所以将其称为包围体更好一点。这三种包围体分别是包围盒(Bounding Box),包围球(Bounding sqhere)和包围区域(Bounding region),如下图8.54所示:

图8.54 3D Tiles瓦片不同包围体类型

包围盒是我们最熟悉的,但是这里反而最不好理解,一个包围盒参数的例子如下所示:

"boundingVolume": {
    "box": [
        0, 0, 10,
        20, 0, 0,
        0, 30, 0,
        0, 0, 10
    ]
}

可以看到这里一共12个参数,前3个参数表示中心点的位置坐标,接下来的三个元素定义x轴方向和半长,再接下来的三个元素定义y轴方向和半长,最后三个元素定义z轴方向和半长。这个例子的包围盒的描述就是,中心点坐标为(0,0,10),X方向长度为40,Y方向长度为60,Z方向为20。这种包围盒在三维中称为AABB(Axis-Aligned Bounding Box,轴对称包围盒)包围盒,一般情况下这么用就可以了。

但是如果深入了解一下,就会发现12个参数中有很多值是0,这些0值其实是用来表达旋转的,或者说方向的。AABB包围盒其实对三维物体对象的贴合不够紧密,如果调整一下包围盒的方向,就有可能让包围盒的范围进一步缩小(想象一下从西北到东南的长条状物体的包围盒)。这种包围盒就被称为OBB包围盒(Oriented Bounding Box,有向包围盒)。复习前面第3.7.1节的知识就会明白,后面9个参数实质是定义了旋转变换+缩放变换的几何变换矩阵。因为OBB包围盒的方式复杂一些,所以这种表达形式使用的比较少。

而包围球就最简单了,由中心点坐标和半径定义四个参数定义,如下所示:

"boundingVolume": {
    "sphere": [
        10, 5, 15,
        140.0
    ]
}

最后的包围区域则是三维图形中没有的概念,实际上这个区域其实指的是地理区域,由6个参数定义,分别是WGS84坐标系中西至经度,南至纬度,东至经度,北至纬度,最小椭球高,最大椭球高,经纬度使用弧度为单位,高度以米为单位。如下所示:

"boundingVolume": {
    "region": [
        -1.319700,
        0.698858,
        -1.319659,
        0.698889,
        0.0,
        20.0
    ]
}

包围体是三维图形中就非常重要的参数,可用于优化渲染和高效空间查询,例如在Ceisum中,就通过使用包围体实现可见性查询和视锥体剔除,显著提升了渲染性能。

8.8.4 空间数据结构

我们在前面论述过,3D Tiles中的瓦片集以树形数据结构进行组织。但是,这种树形数据结构不是任意组织的,而是具有空间一致性:父瓦片的包围体始终包含其所有子瓦片的内容。这对于可见性测试和相交性测试特别重要,当在三维场景中我们看不到某个瓦片的时候,那么必然看不到它的所有子瓦片。通过这种方式,我们可以筛选需要的瓦片进行展示,这对性能的提升非常有帮助。

另外,与基于二维的地图切片不同,3D Tiles的瓦片数据结构通常是基于三维的,因此要更加复杂,例如KD树或者八叉树,且每个瓦片可能并不均匀。这样可能就会造成一个现象,就是父瓦片的包围盒可能并不能完全包含子瓦片的包围盒。当然,父瓦片的包围体包含其子瓦片的内容的特性还是存在的。具体的空间结构示意图如下图8.55所示:

图8.55 3D Tiles空间数据结构示意图

8.8.5 几何误差(Geometric Error)

几何误差(Geometric Error)就是例8.6中的geometricError元素。复习一下我们在第7.4.3节中介绍的知识,OpenSceneGraph和OSGB格式使用瓦片包围球映射到屏幕端直径来决定渲染的精细度层级;而几何误差的作用也非常类似,决定了3D Tiles在渲染客户端(如Cesium)以何种细节级别进行渲染,从而在性能和渲染质量之间提供最佳权衡。

虽然都是控制LOD级别的因子,3D Tiles格式的几何误差表达的含义则与OSGB格式使用的参数完全不同,几何误差表达的含义是简化的几何体与真实的几何体之间的误差,以米为单位。在可视化端实现的时候,会将这个参数转换成屏幕空间误差(screen-space error,SSE),单位为像素。当SSE超过某个阈值(CesiumJS中会设定一个最大屏幕空间误差值)的时候,运行的时候将会渲染更高级别的细节。具体示意图如下图8.56所示:

图8.56 3D Tiles中的几何误差和屏幕空间误差

那么,几何误差是如何转换成屏幕空间误差呢?Cesium官方给出了一个公式,对于透视投影,他们的转换公式如下式(8-3):

\[sse = \frac{geometricError ⋅ screenHeight}{tileDistance ⋅ 2 ⋅ tan(fovy / 2)} \tag{8-3} \]

其中,screenHeight是渲染屏幕的高度(以像素为单位),tileDistance是瓦片到视点的距离,fovy是视锥体的y方向的张角。

8.8.6 细化策略(Refinement Strategies)

细化策略(Refinement Strategies)就是例8.6中的refine参数。这个参数决定了以何种方式在高细节层级瓦片中增加细节。通常的方式是替换(REPLACE),意思是子瓦片节点会替换其父瓦片,这也是OSGB格式采取的策略;Cesium中还额外支持新增(ADD),意思是子瓦片在父瓦片的基础上,增加新的内容。具体示意图如下图8.57所示:

图8.57 3D Tiles中的细化策略

每个瓦片都可以设置细化策略参数,如果未指定,说明该瓦片的细化策略继承自父瓦片。

8.8.7 渲染优化算法

假设已经存在一个3D Tiles瓦片集和相机视锥体如下图8.58所示。3D Tiles瓦片集我们比较好理解,关键元素我们已经在前面几小节中介绍过了。相机视锥体是三维图形中经常要用到的一个概念,好比真实世界中,我们需要拍摄到一个物体,必须让相机调整到合适的位置(Position),调整好合适的角度(Orientation)以及调整合适的焦距(Field-of-view angle,视场角)。

图8.58 3D Tiles瓦片集和相机视锥体

接下来,我们可以模拟出在可视化客户端渲染实现中,3D Tiles格式是如何平衡任何比例的渲染性能和视觉质量了。虽然我们在前面中已经将这个思想(分页LOD机制/HLOD)论述了很多次了,但这里我们可以对照下图8.59所示进行进一步理解:

  1. 最开始加载的是JSON格式的瓦片集文件,并测试视锥体与根瓦片边界体积是否相交。在这里,视锥体与根瓦片的包围体相交,这意味着该瓦片可能需要被加载进行渲染。
  2. 由于根瓦片是没有内容的,那么就测试子瓦片的包围体与视锥体的相交。在这里,三个子瓦片中的两个的包围体确实与视锥体相交,这意味着这些子瓦片的内容会被考虑进行渲染;而剩下的一个瓦片就被直接剔除不用渲染。
  3. 此时检查瓦片的几何误差,根据式(8-3)计算此时的屏幕空间误差。此时由于没有超过阈值18.0,说明内容呈现的精细度正好合适。
  4. 然后,当用户进行交互,例如放大某个建筑物时,根据式(8-3)可知瓦片的屏幕空间误差会增大而超过阈值,有可能需要进行下一层级的渲染。并且新的视锥体可能会剔除更多的瓦片不用渲染,只有一小部分瓦片集可见。
  5. 根据所选的细化策略加载和渲染具有较高细节级别的内容。由于较高细节级别瓦片的几何误差较小,导致屏幕空间误差低于阈值,此时可以呈现更高精细度的视觉质量。

图8.59 3D Tiles中的细化策略

8.8.8 瓦片内容数据

3D Tiles瓦片内容数据通常以URI的形式引用外部文件,如例8.6中的house.b3dm、detailsA.b3dm和detailsB.b3dm。因为这些文件是3D Tiles瓦片的主体,所以很多情况下为了方便使用就将其当成瓦片本身。3D Tiles瓦片的格式可以有以下四种表现形式:

  1. Batched 3D Model(b3dm):批处理三维模型,最常规的三维模型。
  2. Instanced 3D Model(i3dm):实例化三维模型,相同三维模型的多个实例。
  3. Point Clouds(pnts):点云,大量点组成的数据。
  4. Composite Tiles(cmpt):以上三种的复合数据。

3D Tiles瓦片其实就是一种普通的三维模型数据,我们可以按照第7章三维模型介绍的内容来理解它。不过3D Tiles瓦片与普通三维模型最大的不同就在于它是按照GIS矢量要素特性来进行设计的,具体来说,就是3D Tiles瓦片中除了三维模型之外,还有要素表(Feature Table)和批处理表(Batch Table)来作为属性数据。另一方面,三维模型自身也被逻辑上拆分成多个要素模型,通过ID与属性表相关联。实际上,正如第7.5.2节中所述,这种设计实现了三维模型的单体化,在业务应用中有很大的实用意义。

1. 批处理三维模型(Batched 3D Models)

批处理三维模型(Batched 3D Models,b3dm)是3D Tiles常用的瓦片数据格式,因为其本质上就是最常规的三维模型数据。具体有多常规呢,b3dm内部直接嵌入了一个我们在第7.2节中介绍的glTF三维模型文件,具体数据布局如下图8.60所示。根据其数据布局,我们可以作一个大概的说明:

  • magic是魔法值的意思,其实就是文件标识符,具体就是“b3dm”四个字符。
  • version和byteLength分别代表版本和整个b3dm文件的字节长度。
  • featureTableJSONByteLength、featureTableBinaryByteLength、batchTableJSONByteLength和batchTableBinaryByteLength的大小分别描述了要素表JSON部分的字节长度、要素表二进制部分的字节长度、批处理表JSON部分的字节长度、批处理表二进制部分的字节长度。
  • 文件主体包含三个部分,分别是要素表(这是必须的),批处理表(可选的)以及内嵌的glTF三维模型文件。

图8.60 3D Tiles的b3dm格式瓦片数据布局

b3dm的文件数据组织我们已经初步了解,那么是如何将三维模型其拆分成多个要素模型呢?方法很简单,是通过扩展了一个名为batchId的顶点属性来实现的。对于不同的要素模型,我们分别赋予其不同的batchId值,这样在将三维模型渲染成二维画面的时候,通过二维画面像素关联的batchId值,我们就区分哪些画面像素是属于哪个要素的。如下图8.61所示:

图8.61 b3dm中不同的要素模型存储的不同的batchId值

现在已经有了batchId值了,那么我们就需要将其关联到要素表和批处理表。对于b3dm瓦片格式来说,图8.61对应的要素表的JSON部分通常为:

{
    "BATCH_LENGTH": 2
}

BATCH_LENGTH是要素表的必须属性,表示要素的个数为2。b3dm通常不使用要素表的二进制部分,而将要素模型的属性数据放入到批处理表中。例如,图8.61对应的批处理表的JSON部分通常为:

{
    "height": [
        16.2
        23.0,        
    ],
    "address": [
        "234 Second Street",
        "123 Main Street"
    ]
}

这里表达了批处理表中高度字段属性和地址字段属性,每个字段属性值都是一个数组元素,而batchId就是这个数组元素的索引。很显然,这正是batchId关联属性表的关键:第1个模型要素的高度是16.2,地址是234 Second Street;第2个模型要素的高度是23.0,地址是123 Main Street。

一般情况下,只使用批处理表的JSON部分就可以表达要素模型的属性表了。批处理表的二进制部分则是用来配合JSON部分来表达特定数据类型的属性,例如当JSON部分为如下所示时:

{
    "location": {
        "byteOffset": 0,
        "componentType": "FLOAT",
        "type": "VEC2"
    },
    "id": {
        "byteOffset": 32,
        "componentType": "INT",
        "type": "SCALAR"
    }
}

那么location和id属性字段值就会在二进制部分中进行查找,byteOffset表示起始位置字节偏移,type表示数据类型,componentType则表示数据分量类型。其实这三个参数与glTF中的顶点属性数据的表达非常像,type和componentType值的要求也与glTF中值的要求一致,复习以下第7.2节中glTF的介绍就会非常容易理解。

话说回来,我们说b3dm是参照矢量要素的设计思路实现的,是从GIS的角度进行出发论述。其实从“批处理”这个命名来说,设计者更多的是从图形渲染的角度出发来进行设计的。在图形渲染行业中,术语“批处理”是指多个模型的几何数据进行合并,组合成单个的缓冲区进入GPU显存中进行渲染,这样可以减少复制操作带来的损耗,最小化渲染绘制调用次数,从而提高渲染性能。不得不说,b3dm的设计确实很精妙,很多学问到了最深处往往都是相通的。

2. 实例化三维模型(Instanced 3D Models)

有了b3dm作为基础,实例化三维模型(Instanced 3D Model,i3dm)就比较容易理解了。不过,我们首先需要知道为什么这种瓦片格式叫做实例化三维模型。其实“实例化”这个术语是图形渲染中的一种技术,通过实例化技术可以一次性渲染大量相同的模型,只不过这些模型有一些特定的变化。例如我们渲染大量的树木,我们可以使用同一个树木模型,然后让每个树木模型的位置、旋转和缩放不同,就可以得到一大片形态各异的树林。实例化的优点就在于,既然创建一个树木对象进行渲染是很耗费性能的,那么就将这个树木对象改变一下位置、朝向以及大小进行复制粘贴,这样就可以很轻易绘制出包含大量三维模型数据的场景,并且能保证性能。

实例化技术具有非常多的应用场景,因为很多现实中的物体是有规范和标准的,比如城市中的部件,BIM中的基础设施,工业设计中的零件等,它们往往都有非常相似的外观,使用实例化技术可以有非常好的效果。这也是为什么3D Tiles将实例化三维模型作为一种瓦片数据格式。

从前面的介绍不难理解,i3dm相比较普通三维模型数据,最大的区别在于多了表达变化的实例化参数(比如前面提到的位置、旋转和缩放)。i3dm实例化参数信息是放置在要素表中的,因此,i3dm瓦片数据布局与b3dm瓦片数据布局基本一致,如下图8.62所示:

图8.62 3D Tiles的i3dm格式瓦片数据布局

除了多了一个表达gltf是外部还是内嵌的参数gltfFormat,i3dm与b3dm最大的不同就在于要素表和批处理表。要素表中需要存放实例化参数,例如一个要素表的JSON部分如下所示:

{
    "INSTANCES_LENGTH": 3,
    "POSITION": {
        "byteOffset": 0
    },
    "NORMAL_UP": {
        "byteOffset": 36
    },
    "NORMAL_RIGHT": {
        "byteOffset": 72
    },
    "SCALE": {
        "byteOffset": 108
    }
}

INSTANCES_LENGTH是必须的参数,表示实例化个数。POSITION、NORMAL_UP、NORMAL_RIGHT和SCALE是预先定义好的语义,分别表示位置、旋转的上方向、旋转的右方向以及缩放,它们分别用3个float型、3个float型、3个float型以及1个float型来表示,配合起始位置字节偏移byteOffset,我们可以很容易找出存储在要素表二进制部分的实例化参数,如下图8.63所示:

图8.63 i3dm中的实例化参数

另外,i3dm也是遵循要素化的设计思路的,不过与b3dm不同,i3dm是以单个的实例化对象为单个要素,并且关联属性。在要素表中,可以在JSON部分增加一个名为BATCH_ID的语义,在二进制部分存储不同实例化对象的batchId值。而批处理表中则像b3dm一样进行存储其他属性数据,这样就实现了单个的实例化模型与属性信息的关联。

3. 点云(Point Clouds)

相比较b3dm和i3dm,点云(Point Clouds,pnts)形式的瓦片数据格式就更加简单了,甚至不用内嵌glTF。点云pnts的数据布局如下图8.64所示:

图8.64 3D Tiles的pnts格式瓦片数据布局

点云除了记录点的位置属性之外,还可能有法向量、颜色等属性,这些属性数据都是记录在要素表中的。如下所示是一个pnts要素表的JSON部分:

{
    "POINTS_LENGTH": "219",
    "POSITION": {
        "byteOffset": 0
    },
    "NORMAL": {
        "byteOffset": 2628
    },
    "RGB": {
        "byteOffset": 5256
    }
}

类似i3dm的要素表,这里的POINTS_LENGTH表示点的个数,而POSITION、NORMAL和RGB这些属性名称也是预定义的语义类型,配合起始位置字节偏移量byteOffset可以找到点属性具体的属性值,具体示意图如下图8.65所示:

图8.65 pnts将点云属性存储在要素表中

pnts也是遵循要素化的设计思路,从要素表来看,似乎点云中一个点就是一个要素,但这样理解并不准确。pnts需要表达的是一个要素模型,例如一个点云瓦片表示的是一个房屋,那么房屋内部中的门、窗或者屋顶才是我们想要知道的要素模型。要实现这样的要素识别非常简单,还是使用如同b3dm或i3dm相同的办法,在要素表中增加一个名为BATCH_ID的字段,记录每个点云的batchId值,如下图8.66所示:

图8.66 pnts通过Batch ID区分不同的点云要素

剩下的就还是如同b3dm一样,在批处理表中存储其他属性数据,实现多个点组成的要素模型与属性信息相关联。

4. 复合瓦片(Composite Tiles)

复合瓦片(Composite Tiles,cmpt)是以上介绍的瓦片格式的复合数据格式。举例来说,一组建筑物可以存储在b3dm中,一组树木可以存储在i3dm中,如果这些元素出现在同一地理位置时,就可以将其组合成cmpt,实现单个的请求获取该地理位置所有的可渲染内容,如下图 8.67所示。这样的设计可以减少访问的请求个数,改善瓦片数据加载时的视觉效果。

图 8.67 3D Tiles的cmpt格式瓦片实现示意图

cmpt的数据组织非常灵活,可以包含b3dm、i3dm和pnts中的任意种类任意个数的瓦片数据,甚至可以包含另一个cmpt瓦片数据。但它的数据布局就简单了,如下图8.68所示。文件头通过tilesLength标识包含的子瓦片的个数,文件主体则是具体的子瓦片数据内容。

图8.68 3D Tiles的pnts格式瓦片数据布局

8.8.9 声明式样式(Declarative Styling)

既然3D Tiles的瓦片数据格式是按照要素特性来进行设计的,那么免不了要面对的就是模型要素符号化的问题。3D Tiles使用声明性样式在运行时修改功能的外观,所谓声明性样式,具体来说就是包含一组表达式的JSON。这种样式JSON规定了一些变量,表达式以及条件,可以看作是一种简单的样式语言。例如我们让一组表达建筑的3D Tiles根据其高度呈现不同的颜色,可以使用如下样式JSON:

{
    "color": {
        "conditions": [
            ["${height} >= 300", "rgba(45, 0, 75, 0.5)"],
            ["${height} >= 200", "rgb(102, 71, 151)"],
            ["${height} >= 100", "rgb(170, 162, 204)"],
            ["${height} >= 50", "rgb(224, 226, 238)"],
            ["${height} >= 25", "rgb(252, 230, 200)"],
            ["${height} >= 10", "rgb(248, 176, 87)"],
            ["${height} >= 5", "rgb(198, 106, 11)"],
            ["true", "rgb(127, 59, 8)"]
        ]
    }
}

其中,color是要素模型的颜色值属性,决定要素模型渲染的颜色。height则表示3D Tiles瓦片中批处理表种的height字段,根据这个字段值的不同,给模型要素赋予不同的颜色。在CesiumJS中实现效果如下图8.69所示:

图8.69 3D Tiles的声明式样式的效果图

虽然很多写实的三维模型可能用不到这个功能,但是这个设计实现在业务系统中很有用处,也很容易扩展,可以帮助我们实现更酷炫更有价值的可视化效果,值得我们进一步研究。

8.8.10 其他

从以上对3D Tiles格式的介绍可以感受到,3D Tiles确实是设计的非常完善的三维模型瓦片数据格式,也因此得到了最为广泛的使用。除此之外,另一个OGC标准——ArcGIS设计的I3S(Indexed 3D Scene Layers)三维模型瓦片数据格式也很优秀,与3D Tiles相比,它的一些特点给笔者留下了比较深刻的印象,主要是:

  • 3D Tiles是离散文件集形式的静态资源,I3S则可以打包成.slpk这种zip格式的单文件,也支持使用RESTful接口访问。
  • 3D Tiles空间坐标参考默认是WGS84椭球的地心地固坐标系,少部分参数使用WGS84地理坐标系;而I3S则专业很多,支持目前绝大多数地理空间坐标参考。
  • 不知道是否是处于兼容性的考虑,I3S设计的参数非常多,但可视化的时候很多参数都没有用上(这也是ArcGIS的一贯特色);3D Tiles这方面则简练很多,只提供了最简单的参数要求,其余的需求通过扩展来实现。
  • I3S在设计中实现了几何数据、属性数据、纹理材质的解耦,这意味着这些资源可以共享,在一些渲染实现中可以通过这种机制来提升性能。
  • I3S确定LOD层级的算法与3D Tiles不同,而跟OSGB比较类似,通过计算包围球投影到屏幕空间的像素大小来确定。

而I3S其余的设计实现,基于与3D Tiles大同小异,笔者这里就不多作介绍了。值得一提的是,I3S虽然没有提供具体的代码实现,但是其官方在线文档 https://github.com/Esri/i3s-spec/ 中提供了一个可用于浏览I3S数据的在线浏览器,以及各个版本的I3S数据下载,这对于我们的研究学习很有帮助。

最后,国内还有一种使用的比较多的三维模型瓦片数据格式:主要由超图软件开发设计的S3M(spatial 3D model)格式。尽管S3M是中国地理信息产业协会的空间三维模型数据格式标准,但这个格式笔者接触的不多,毕竟愿意使用S3M格式的数据,多少有点敏感性,是不太容易获取进行研究的。

不过,笔者还是查阅了一下S3M官方在线文档(https://github.com/SuperMap/s3m-spec),对比3D Tiles和I3S最有诚意的一点是除了提供与其他三维瓦片数据的转换工具,还提供了读写S3M瓦片数据的JavaScript和C++代码实现,并且一直在更新。不过,缺点就是文档不够完善,至少笔者也没有看到S3M1.0、S3M2.0和S3M3.0不同版本之间的演进。而仅存的一版S3M标准文档的内容,相对于3D Tiles文档中完善的技术指导和参数说明也失之简陋。重于实现而轻于文档,这一点也只能说是国内开源工作的通病了。

本文节选自作者新书《GIS基础原理与技术实践》第8章。书中系统讲解 GIS 核心理论与多语言实战,适合开发者与高校师生。

📚 配套资源开源GitHub | GitCode
🛒 支持正版京东当当