外观
3DTiles三维模型
约 4731 字大约 16 分钟
2023-09-08
上一节我们介绍了glTF的主要数据结构以及Mars3D是如何对其进行加载的,这一节我们来介绍一下glTF的升级版3D Tiles ,也是目前 Mars3D 在加载海量三维模型数据方面必须采用的一种数据格式。
1. 3DTiles介绍
2016年,Cesium 团队借鉴传统2DGIS的地图规范:WMTS,借鉴图形学中的层次细节模型,打造出大规模的三维数据标准:3d-Tiles,中文译名:三维瓦片。
它在模型上利用了 gltf 渲染快的特点,对大规模的三维数据进行组织,包括层次细节模型、模型的属性数据、模型的层级数据等。
3D Tiles 是在glTF的基础上,加入了分层LOD的概念(可以把3D Tiles简单地理解为带有 LOD 的 glTF ),专门为流式传输和渲染海量 3D 地理空间数据而设计的,例如倾斜摄影、3D 建筑、BIM/CAD、实例化要素集和点云。它定义了一种数据分层结构和一组切片格式,用于渲染数据内容。3D Tiles 没有为数据的可视化定义明确的规则,客户可以按照自己合适的方式来可视化 3D 空间数据。同时,3D Tiles 也是 OGC 标准规范成员之一,可用于在台式机、Web端和移动应用程序中实现与海量异构3D地理空间数据的共享、可视化、融合以及交互功能。
2. 3dTiles数据示例及其加载
数据处理相关的,可以参看:各类三维模型转为3DTiles格式
2.1 3dTiles数据示例
首先,我从一个简单的3D Tiles数据示例说起,请看下面目录: 
上图是一份 3dTiles数据集在文件夹内的样子,层层打开可得以下特点:
- 入口文件是
tileset.json - 各级瓦片用文件夹来组织(类似套娃),目录中有零散的
*.json文件 - 叶子节点有
*.b3dm、*.i3dm等格式
对这些数据的结构和详细介绍,我们下一节再详细解说,第一步,我们先学会怎么加载使用这个模型。
2.2 在平台中加载模型
平台内加载3dtiles是使用 TilesetLayer类。
3dTiles至少有一个 tileset.json 文件,作为整个数据集的入口,在代码层面,我们拿到这个主瓦片集JSON 文件(tileset.json)的url地址即可使用加载三维模型了。 如: http://data.mars3d.cn/3dtiles/qx-dyt/tileset.json
当我们拿到这个模型服务地址后:
- (1)浏览器输入模型url地址验证下是否可以正常访问。
- (2)打开模型参数调试编辑页面,在这个页面调试的模型URL输入框内输入模型url地址,并单击加载模型按钮。

(3)可以勾选深度检测来方便测试和调试模型高度

(4)如果是人工模型,可以勾选“鼠标拖拽编辑”来手动拖拽模型的位置与地图上匹配。
(5)在该页面调整好所有参数后,单击“保存参数”按钮,保存的参数json,会自动下载一个json文件。

(6) 如果模型是初始化就加载的,可以打开项目的
config.json文件,拷贝刚下载的json到config.json文件的layers参数中即可。
{
"scene": {},
"terrain": {},
"basemaps":[],// 已忽略其他参数
"layers":[
{
"name": "模型名称",
"type": "tileset",
"url": "http://data.mars3d.cn/3dtiles/qx-dyt/tileset.json",
"maximumScreenSpaceError": 1,
"position": {"alt": 452.9 },
"center": { "lat": 34.216894, "lng": 108.959834, "alt": 591, "heading": 4,"pitch": -37 },
"show": true
}
]
}- (7)如果代码中直接使用TilesetLayer类构造三维模型,可以将json中的参数拷贝到类参数中。
const tiles3dLayer = new mars3d.layer.TilesetLayer({
url: 'http://data.mars3d.cn/3dtiles/qx-simiao/tileset.json',
maximumScreenSpaceError: 16,
maxMemory: 1024, // 最大缓存内存大小(MB)
maximumCacheOverflowBytes: 2147483648,
position: { alt: 452.9 },
center: { lat: 34.216894, lng: 108.959834, alt: 591, heading: 4,pitch: -37 },
flyTo: true,
})
map.addLayer(tiles3dLayer)运行效果
新窗口查看
- (8) 最后再说几个影响加载效率的关键参数:
- maximumScreenSpaceError
控制加载的精度,这个参数默认是16,数值加大,能让最终成像变模糊。
- maximumMemoryUsage 【cesium 1.107+弃用】
这个参数默认是512,也即是当几何体和纹理资源大于512MB的时候,Cesium就会淘汰掉当前帧中没有visited的所有块,这个值其实很小,也是cesium为了避免资源占用过高的一个保障,不过上述我们也估算过最差情况下,没有做纹理crn压缩的情况下,这个值很容易被超过,导致很多人误以为cesium的淘汰没有效果。这个值如果设置的过小,导致cesium几乎每帧都在尝试淘汰数据,增加了遍历的时间,也同时增加了崩溃的风险。这个值如果设置的过大,cesium的淘汰机制失效,那么容易导致显存超过显卡内存,也会导致崩溃。这个值应该处于最差视角下资源占用 和 显存最大量之间。建议显存大小的50%左右,内存分配变小有利于倾斜摄影数据回收,提升性能体验。
- cacheBytes、 maximumCacheOverflowBytes
这2个参数替代了maximumMemoryUsage,用于控制显存资源消耗。
- skipLevelOfDetail
这个参数默认值是 true,是Cesium在1.5x 引入的一个优化参数,这个参数在金字塔数据加载中,可以跳过一些级别,这样整体的效率会高一些,数据占用也会小一些。 但是带来的异常是:
- 加载过程中闪烁,看起来像是透过去了,数据载入完成后正常。
- 有些异常的面片,这个还是因为两级LOD之间数据差异较大,导致的。 当这个参数设置false,两级之间的变化更平滑,不会跳跃穿透,但是清晰的数据需要更长,而且还有个致命问题,一旦某一个tile数据无法请求到或者失败,导致一直不清晰。 所以我们建议:对于网络条件好,并且数据总量较小的情况下,可以设置false,提升数据显示质量。
- preferLeaves
这个参数默认是false,同等条件下,叶子节点会优先加载。但是Cesium的tile加载优先级有很多考虑条件,这个只是其中之一,如果skipLevelOfDetail=false,这个参数几乎无意义。所以要配合skipLevelOfDetail=true来使用,此时设置preferLeaves=true。这样我们就能最快的看见符合当前视觉精度的块,对于提升大数据以及网络环境不好的前提下有一点点改善意义。
3. 3D Tiles 数据结构
3dTiles 是一种规范(要区分规范和实现的概念),在规范的指导下,各种资源文件可以是独立存在于硬盘中的目录、文件,也可以以二进制形式写入数据库中。
3dTiles还有一个特点:那就是不记录模型数据(指三维模型的顶点、贴图材质、法线、颜色等信息),只记录各级“Tile”的逻辑关系(指LOD是如何组织的),以及“Tile”自己的属性信息(如房子的名称、建设年限、面积等)。

3.1 3D Tiles支持的模型
- 城市建筑白膜:在拥有如shp、kml等格式的建筑物二维面边界坐标数据,和高度或楼层数属性信息,再通过工具转换为三维立体的白膜建筑物3DTiles模型。
- 倾斜摄影:一般是无人机拍摄,拍摄的数据通过建模工具ContextCapture Cente等建模软件可以直接导出3DTiles格式。也可以通过osgb通用格式转3DTiles格式后在平台中使用。
- 点云数据:一般是激光扫描后生产的数据,有las、pts、ply等格式。
- 人工建模:数据来源于3dmax、Maya等建模软件建模,建好的三维模型导出为dae和obj数据后,再转换为3DTiles数据格式。
- BIM模型:数据来源于专业的BIM软件,常见的有rvt和dgn格式。
提示
这些数据的原始格式都需要转换为3dtiles格式后,才能在平台加载使用。
3.2 LOD树结构
通过上面介绍,3dTiles数据的入口文件是一个名叫tileset.json的文件,在这个json文件中root属性下通过children属性来关联子级节点*.json ,这样一层层关联到叶子节点(如*.b3dm等格式)。

树结构对于三维空间数据的组织有很大的优势。3dTiles在空间上允许数据集使用如下几种树结构:
- 四叉树:允许使用传统的均匀四叉树,也允许使用松散四叉树等变种(例如,允许子节点,即子瓦片允许存在空间范围重叠),常用于 建筑物(存在不可能严格切分的特点)。
- 八叉树: 四叉树对在高度上不太好切分的数据比较适合,而如果追求极致的空间分割和分级(例如点云数据),那么八叉树更合适。八叉树也允许使用各种变种。
- KD树:
- 格网结构:允许瓦片存在多个子瓦片,通常出现在倾斜摄影数据上,但是这会导致网络请求过多的问题。
八叉树 
4. 模型样式与显示控制
4.1 统一改色(样式表达式)
倾斜摄影/BIM 模型本身没有"颜色属性",但可以通过 style 里的 color.conditions 表达式按需改色,常用于"政务蓝底图上的统一色系建筑"或按属性分级渲染:
const tiles3dLayer = new mars3d.layer.TilesetLayer({
name: "合肥市区",
url: "https://data.mars3d.cn/3dtiles/jzw-hefei/tileset.json",
maximumScreenSpaceError: 1,
style: { // 3dTiles 样式
color: {
conditions: [
["true", "color('rgba(42, 160, 224, 1)')"] // 统一改成蓝色
]
}
},
marsJzwStyle: true, // 是否启用平台对建筑模型的默认样式(暗面/顶面处理)
highlight: { type: "click", color: "#FFFF00" }, // 单击高亮
popup: "all", // 显示全部属性
show: true
})
map.addLayer(tiles3dLayer)
conditions中还可以写"按属性判断"的表达式,例如按height属性分段配色,语法与 Cesium 3D Tiles Styling 一致。
4.2 交互拾取与弹窗
// 单击模型(或模型构件)并获取属性
tiles3dLayer.on(mars3d.EventType.click, (event) => {
console.log("单击了模型", event.graphic, event.graphic?.attr)
})
// 也可以通过地图事件拿到底层对象
map.on(mars3d.EventType.click, (event) => {
const feature = event.pickedObject
if (feature instanceof Cesium.Cesium3DTileFeature) {
const attr = mars3d.Util.get3DTileFeatureAttr(feature) // 平台的取属性方法
console.log("构件属性", attr)
}
})4.3 模型整体颜色校正
多份倾斜摄影数据拼在一起时常出现色调不一致,可以用 mars3d.thing.TilesetColorCorrection 统一调整:
const colorCorrection = new mars3d.thing.TilesetColorCorrection({
layer: tiles3dLayer, // 需要调整的模型图层
brightness: 1.2, // 亮度
contrast: 1.1, // 对比度
hue: 0.1, // 色相
saturation: 1.3 // 饱和度
})
map.addThing(colorCorrection)运行效果
新窗口查看
5. 模型的裁剪、压平、淹没与限高
模型的空间分析(剖切、裁剪、压平、淹没、限高、地震抖动等)都由 thing 对象提供,它们不影响模型数据本身,只影响渲染结果,因此可以随时开关:
| 功能 | 类名 | 典型用途 | 功能示例 |
|---|---|---|---|
| 平面剖切 | mars3d.thing.TilesetPlanClip | 剖开建筑看内部结构 | 查看 |
| 盒子裁剪 | mars3d.thing.TilesetBoxClip | 削掉模型的一角 | 查看 |
| 模型裁剪 | mars3d.thing.TilesetClip | 按多边形范围裁剪模型 | 查看 |
| 模型压平 | mars3d.thing.TilesetFlat | 场地平整、拆迁模拟 | 查看 |
| 模型淹没 | mars3d.thing.TilesetFlood | 内涝、水位上涨模拟 | 查看 |
| 限高分析 | mars3d.thing.LimitHeight | 机场净空、建筑限高 | 查看 |
| 地震模拟 | mars3d.thing.TilesetShake | 模型抖动模拟地震 | 查看 |
以"限高分析"为例(完整参数与更多分析代码见Thing管理及分析功能):
const limitHeight = new mars3d.thing.LimitHeight({
color: "rgba(255,0,0,0.5)",
height: 80, // 限高高度
bottomHeight: 32, // 模型地面的大地高
positions: [
[117.210446, 31.829032, 0],
[117.226334, 31.826662, 0],
[117.226694, 31.807882, 0],
[117.209776, 31.808359, 0]
]
})
map.addThing(limitHeight)运行效果
新窗口查看
6. 模型单体化
倾斜摄影/合并后的 3DTiles 是"一整块模型",无法直接按栋/按层/按户选中。单体化就是给它"套"上可交互的矢量面(分类面),实现单独高亮、查询属性、出图统计。
6.1 分类面单体化(最常用)
原理:用与模型完全贴合的矢量面,设置 classification: true(分类渲染),让面"切"进模型实现逐栋上色/高亮:
// 1) 加载三维模型
const tilesetLayer = new mars3d.layer.TilesetLayer({
type: "3dtiles",
name: "校园",
url: "https://data.mars3d.cn/3dtiles/qx-xuexiao/tileset.json",
maximumScreenSpaceError: 1
})
map.addLayer(tilesetLayer)
// 2) 加载同范围的分类面(单体化矢量面)
const geoJsonLayerDTH = new mars3d.layer.GeoJsonLayer({
name: "学校-单体化",
url: "https://data.mars3d.cn/file/geojson/dth-xuexiao-fd.json",
symbol: {
type: "polygonP",
styleOptions: {
color: "#00ffff", // 单体化默认显示样式
opacity: 0.2,
clampToGround: true,
classification: true, // [关键] 分类渲染,面会"切"进模型
// buffer: 1,
highlight: { // 鼠标移入或单击后的高亮样式
type: mars3d.EventType.click,
color: "#ffff00",
opacity: 0.6
},
label: { // 单体化面上可以加标注
text: "{name}",
height: 240, // 单体化面没有高度,标注需指定一个高度值
font_size: 30,
color: "#ffffff",
outline: true,
outlineColor: "#000000",
outlineWidth: 3,
scaleByDistance: true,
scaleByDistance_far: 1000,
scaleByDistance_farValue: 0.3,
scaleByDistance_near: 10,
scaleByDistance_nearValue: 1
}
}
},
popup: [ // 点击弹出的属性
{ field: "name", name: "学校场所" },
{ field: "sfkf", name: "是否开放" },
{ field: "remark", name: "备注信息" }
]
})
map.addLayer(geoJsonLayerDTH)提示:图层属性里的
classification与clampToGround不要同时为 false,且分类面需要与模型在同一位置、同一坐标系;如果模型做了高度偏移(position.alt),分类面也要相应处理。
可以通过遍历单体化面的方式统一样式(如"开/关各栋配色"):
geoJsonLayerDTH.eachGraphic((graphic) => {
graphic.setStyle({ opacity: 0.2 }) // 统一修改透明度等样式
})运行效果
新窗口查看
6.2 其它单体化形式
| 形式 | 说明 | 功能示例 |
|---|---|---|
| 建筑单体化 | 按栋单体化并支持交互编辑 | layer-tileset/monomer/building-edit |
| BIM 分层 | 按楼层单体化(楼层展开/收起) | layer-tileset/monomer/floor |
| 分户单体化 | 按户室单体化并查询户信息 | layer-tileset/monomer/household |
| 属性类型单体化 | 直接读取模型自带的分类属性(classificationType) | layer-tileset/monomer/tileset-classificationType |
| WFS 单体化 | 用 WFS 服务提供分类面 | layer-tileset/monomer/wfs |
单体化相关常见问题(分类面不贴合、点击无属性、性能等)见模型单体化问题。
7. 模型加载性能优化
7.1 关键参数速查
| 参数 | 默认值 | 作用 | 调整建议 |
|---|---|---|---|
maximumScreenSpaceError | 16 | 屏幕空间误差,决定加载精细程度 | 越小越清晰、越吃性能;1~4 适合重点区域,16 适合大范围浏览 |
maxMemory | 512 | 最大缓存内存(MB) | 配合显存设置,建议显存 50% 左右 |
cacheBytes、maximumCacheOverflowBytes | - | 新版替代 maximumMemoryUsage 的显存控制参数 | 按显卡显存与数据量权衡 |
skipLevelOfDetail | false | 跳过某些层级的细节,加速加载 | 大范围倾斜摄影建议 true |
preferLeaves | false | 优先加载叶子节点(最清晰瓦片) | 与 skipLevelOfDetail 配合使用 |
cullWithChildrenBounds | true | 用子节点包围盒做剔除,减少无用瓦片 | 出现"模型边缘闪烁/缺块"时可设为 false |
maximumLevel | - | 限制加载的最大层级 | 限制层级可以显著降低显存与请求量 |
// 一个"大范围浏览 + 局部精细"的常用组合
const tiles3dLayer = new mars3d.layer.TilesetLayer({
url: "https://data.mars3d.cn/3dtiles/qx-teh/tileset.json",
maximumScreenSpaceError: 16,
maxMemory: 2048,
cullWithChildrenBounds: false,
skipLevelOfDetail: true,
preferLeaves: true
})
map.addLayer(tiles3dLayer)7.2 工程层面的优化建议
- 按需加载:不要把整个城市、整个园区的模型一次性全开,可以按区域/楼层拆成多个 TilesetLayer,结合视野范围(
rectangle参数)只加载当前区域。 - 数据侧优化:倾斜摄影做纹理压缩(CRN/WebP)、合理设置 LOD 层级与切块大小,能成倍降低网络与显存压力。
- 模型拆分:小单体(设备、构件)用 glTF + 矢量对象加载,不要塞进 3DTiles。
- 合并实例:大量重复模型(树木、路灯、椅子)用 i3dm 实例化瓦片,比逐个 glTF 加载省得多。
- 及时释放:切换场景/页面时移除不再使用的模型图层(
map.removeLayer(layer, true))。 - 关注显存与内存:通过浏览器任务管理器/Chrome 性能面板观察显存占用,出现"白屏/崩溃"多半是缓存参数过大或数据量过大。
更多效率相关问题(WebGL 渲染错误、Vue 下卡顿、CPU 占用 100%、国产信创系统调优)见效率相关问题系列文档。
8. 模型常见问题
8.1 模型加载后不显示?
按顺序排查:URL 能否在浏览器直接打开(tileset.json 返回 JSON)、模型坐标系与偏移(chinaCRS)是否正确、模型是否被裁到视口外(可 flyTo: true)、高度是否埋在地下(position: { alt: ... })、是否设置了 show: false。完整排查清单见加载 3dtiles 不出现。
8.2 模型位置或高度不对?
position.alt是模型整体抬高/降低的关键参数,倾斜摄影数据通常需要根据实际地形微调;- 模型与底图整体偏移,通常是坐标系问题(见坐标系);
- 建议先在模型参数调试编辑页面用"鼠标拖拽编辑"调好,再"保存参数"写回代码:
const tiles3dLayer = new mars3d.layer.TilesetLayer({
url: "http://data.mars3d.cn/3dtiles/qx-dyt/tileset.json",
position: { alt: 452.9 },
center: { lat: 34.216894, lng: 108.959834, alt: 591, heading: 4, pitch: -37 },
flyTo: true
})8.3 模型很糊或者很卡?
- 很糊:
maximumScreenSpaceError调小(1~4),或检查数据本身是否只有低精度层级; - 很卡:见第 7 章,重点检查
maximumScreenSpaceError、缓存参数与同时加载的模型数量。
8.4 点击模型拿不到构件属性?
需要模型数据带 BatchTable 属性(不是所有倾斜摄影都有),并且图层开启了 popup(如 popup: "all");若模型本身无属性,则只能通过单体化分类面(第 6 章)来提供属性。
8.5 模型分析(裁剪/开挖)不生效?
模型分析类 thing 需要先 map.addThing(),并且部分功能需要区域坐标(positions)与模型在同一位置;模型尚未加载完成时执行分析也可能无效,建议监听图层 load 事件后再操作,详见模型分析问题。
