大家好,我是日拱一卒的攻城师不浪,专注可视化、数字孪生、前端、nodejs、AI学习、GIS等学习沉淀,这是2024年输出的第18/100篇文章;
前言
之前在参加城市应急
数字孪生项目开发过程中,遇到一个场景,就是模拟水淹分析
。
也就是说,甲方需要根据你这个平台,在下暴雨的时候,精准监测到城市具体方位的水淹情况,根据大数据推算
并精准预警,能够提前监测到风险,防患于未然。
当时的三维渲染引擎用的是Unreal
,今天,我将用cesium
实现这样的场景模拟,主要需要两大步:
- 加载3D模型,也就是建筑物;
- 渲染水面并根据时间推移模拟淹没动画;
加载3D瓦片
首先我们需要加载实体建筑物,这回我们用3D瓦片
去渲染模型。
什么是3D瓦片数据?
在Cesium中,3D瓦片数据(3D Tiles)是一种用于高效存储
和传输大规模3D场景数据
的格式。这种格式允许开发者将复杂的3D模型、建筑物、地形以及其他场景元素以瓦片的形式进行组织和渲染。
主要优点:
-
层次化结构 :3D瓦片数据以层次化的方式组织,类似于
地图瓦片
,这样可以在不同的缩放级别
上提供不同的细节级别,从而优化渲染性能
; -
异步加载 :3D瓦片数据支持异步加载,这意味着可以在用户浏览场景时
逐步加载所需
的数据,减少初始加载时间。 -
多级细节(LOD):每个瓦片可以包含多个细节级别,根据摄像机与瓦片的距离动态调整显示的细节,提高渲染效率。
-
多种数据格式支持 :3D瓦片可以包含多种类型的数据,如
点云数据
、多边形网格
、实例化模型
等。 -
优化的存储:3D瓦片数据格式针对网络传输进行了优化,能够高效地压缩和传输数据。
-
交互性 :用户可以与3D瓦片数据进行交互,例如
点击
、选择
和查询
场景中的元素。 -
扩展性 :3D瓦片数据支持扩展,允许开发者添加
自定义属性
和功能。
瓦片数据格式示例
json
{
"asset": {
"version": "1.0", // 3D Tiles 版本
"tilesetVersion": "1.0" // 瓦片集版本
},
"geometricError": 10, // 瓦片的几何误差,用于确定瓦片的渲染精度
"root": { // 根节点定义
"boundingVolume": { // 根节点的边界体积
"region": [ // 地区定义,包含经纬度和高度范围
-122.4, // 西经
37.7, // 北纬
-122.1, // 东经
37.8, // 南纬
0, // 最小高度
100 // 最大高度
]
},
"geometricError": 5, // 子瓦片的几何误差
"refine": "ADD", // 细化策略,"ADD"表示子瓦片是可选的
"children": [ // 子瓦片列表
{
"boundingVolume": {
"box": [ // 子瓦片的边界盒
[-122.4, 37.7, 0],
[-122.1, 37.8, 100]
]
},
"geometricError": 2, // 子瓦片的几何误差
"content": { // 瓦片内容定义
"url": "0/0/0.b3dm" // 子瓦片的URL,指向.b3dm文件
}
}
// 其他子瓦片...
]
}
}
在这个示例中,我们定义了一个3D Tiles瓦片集的根节点,包括:
- asset: 包含瓦片集的版本信息。
- geometricError: 瓦片的几何误差,用于确定瓦片的渲染精度。
- root: 定义了瓦片集的根节点,包括:
- boundingVolume: 根节点的边界体积,这里使用了一个地区定义。
- geometricError: 子瓦片的几何误差。
- refine: 细化策略,可以是"ADD"或"REPLACE"。
- children: 子瓦片列表,每个子瓦片都有自己的边界体积和内容定义。
每个子瓦片可以是一个更小的瓦片集,具有自己的边界体积
、几何误差
和内容定义。
content字段的url指向实际的3D内容文件,通常是.b3dm
格式,这是3D Tiles中用于存储3D模型数据的二进制文件格式。
好了,了解完瓦片是什么,接下来让我们用瓦片数据去绘制一个建筑物。
要用到Cesium.Cesium3DTileset
大类去加载瓦片数据
。
Cesium3DTileset
Cesium3DTileset :一个3D Tiles
瓦片集,用于流式传输海量异构3D地理空间数据集。
API一览直达:cesium.xin/cesium/cn/D...
js
const set3Dtitle3 = () => {
let translation = Cesium.Cartesian3.fromArray([0, 0, 0]);
let m = Cesium.Matrix4.fromTranslation(translation);
const url = "http://data.mars3d.cn/3dtiles/max-fsdzm/tileset.json";
let tilesetJson = {
url,
modelMatrix: m,
show: true, // 是否显示图块集(默认true)
skipLevelOfDetail: true, // --- 优化选项。确定是否应在遍历期间应用详细级别跳过(默认false)
baseScreenSpaceError: 1024, // --- When skipLevelOfDetailis true,在跳过详细级别之前必须达到的屏幕空间错误(默认1024)
maximumScreenSpaceError: 32, // 数值加大,能让最终成像变模糊---用于驱动细节细化级别的最大屏幕空间误差(默认16)原128
skipScreenSpaceErrorFactor: 16, // --- 何时skipLevelOfDetail是true,定义要跳过的最小屏幕空间错误的乘数。与 一起使用skipLevels来确定要加载哪些图块(默认16)
skipLevels: 1, // --- WhenskipLevelOfDetail是true一个常量,定义了加载图块时要跳过的最小级别数。为 0 时,不跳过任何级别。与 一起使用skipScreenSpaceErrorFactor来确定要加载哪些图块。(默认1)
immediatelyLoadDesiredLevelOfDetail: false, // --- 当skipLevelOfDetail是时true,只会下载满足最大屏幕空间错误的图块。忽略跳过因素,只加载所需的图块(默认false)
loadSiblings: false, // 如果为true则不会在已加载完概况房屋后,自动从中心开始超清化房屋 --- 何时确定在遍历期间skipLevelOfDetail是否true始终下载可见瓦片的兄弟姐妹(默认false)
cullWithChildrenBounds: false, // ---优化选项。是否使用子边界体积的并集来剔除瓦片(默认true)
cullRequestsWhileMoving: false, // ---优化选项。不要请求由于相机移动而在返回时可能未使用的图块。这种优化只适用于静止的瓦片集(默认true)
cullRequestsWhileMovingMultiplier: 10, // 值越小能够更快的剔除 ---优化选项。移动时用于剔除请求的乘数。较大的是更积极的剔除,较小的较不积极的剔除(默认60)原10
preloadWhenHidden: true, // ---tileset.show时 预加载瓷砖false。加载图块,就好像图块集可见但不渲染它们(默认false)
preloadFlightDestinations: true, // ---优化选项。在相机飞行时在相机的飞行目的地预加载图块(默认true)
preferLeaves: true, // ---优化选项。最好先装载叶子(默认false)
maximumMemoryUsage: 2048, // 内存分配变小有利于倾斜摄影数据回收,提升性能体验---瓦片集可以使用的最大内存量(以 MB 为单位)(默认512)原512 4096
progressiveResolutionHeightFraction: 0.5, // 数值偏于0能够让初始加载变得模糊 --- 这有助于在继续加载全分辨率图块的同时快速放下图块层(默认0.3)
dynamicScreenSpaceErrorDensity: 10, // 数值加大,能让周边加载变快 --- 用于调整动态屏幕空间误差的密度,类似于雾密度(默认0.00278)
dynamicScreenSpaceErrorFactor: 1, // 不知道起了什么作用没,反正放着吧先 --- 用于增加计算的动态屏幕空间误差的因素(默认4.0)
dynamicScreenSpaceErrorHeightFalloff: 0.25, // --- 密度开始下降的瓦片集高度的比率(默认0.25)
foveatedScreenSpaceError: true, // --- 优化选项。通过暂时提高屏幕边缘周围图块的屏幕空间错误,优先加载屏幕中心的图块。一旦Cesium3DTileset#foveatedConeSize加载确定的屏幕中心的所有图块,屏幕空间错误就会恢复正常。(默认true)
foveatedConeSize: 0.1, // --- 优化选项。当Cesium3DTileset#foveatedScreenSpaceError为 true 时使用来控制决定延迟哪些图块的锥体大小。立即加载此圆锥内的瓷砖。圆锥外的瓷砖可能会根据它们在圆锥外的距离及其屏幕空间误差而延迟。这是由Cesium3DTileset#foveatedInterpolationCallback和控制的Cesium3DTileset#foveatedMinimumScreenSpaceErrorRelaxation。将此设置为 0.0 意味着圆锥将是由相机位置及其视图方向形成的线。将此设置为 1.0 意味着锥体包含相机的整个视野,禁用效果(默认0.1)
foveatedMinimumScreenSpaceErrorRelaxation: 0.0, // --- 优化选项。当Cesium3DTileset#foveatedScreenSpaceError为 true 时使用以控制中央凹锥之外的图块的起始屏幕空间误差松弛。屏幕空间错误将从 tileset 值开始Cesium3DTileset#maximumScreenSpaceError根据提供的Cesium3DTileset#foveatedInterpolationCallback.(默认0.0)
// foveatedTimeDelay: 0.2, // ---优化选项。使用 whenCesium3DTileset#foveatedScreenSpaceError为 true 来控制在相机停止移动后延迟瓷砖开始加载之前等待的时间(以秒为单位)。此时间延迟可防止在相机移动时请求屏幕边缘周围的瓷砖。将此设置为 0.0 将立即请求任何给定视图中的所有图块。(默认0.2)
luminanceAtZenith: 0.2, // --- 用于此模型的程序环境贴图的天顶处的太阳亮度(以千坎德拉每平方米为单位)(默认0.2)
backFaceCulling: true, // --- 是否剔除背面几何体。当为 true 时,背面剔除由 glTF 材质的 doubleSided 属性确定;如果为 false,则禁用背面剔除(默认true)
debugFreezeFrame: false, // --- 仅用于调试。确定是否应仅使用最后一帧的图块进行渲染(默认false)
debugColorizeTiles: false, // --- 仅用于调试。如果为 true,则为每个图块分配随机颜色(默认false)
debugWireframe: false, // --- 仅用于调试。如果为 true,则将每个图块的内容渲染为线框(默认false)
debugShowBoundingVolume: false, // --- 仅用于调试。如果为 true,则为每个图块渲染边界体积(默认false)
debugShowContentBoundingVolume: false, // --- 仅用于调试。如果为 true,则为每个图块的内容渲染边界体积(默认false)
debugShowViewerRequestVolume: false, // --- 仅用于调试。如果为 true,则呈现每个图块的查看器请求量(默认false)
debugShowGeometricError: false, // --- 仅用于调试。如果为 true,则绘制标签以指示每个图块的几何误差(默认false)
debugShowRenderingStatistics: false, // --- 仅用于调试。如果为 true,则绘制标签以指示每个图块的命令、点、三角形和特征的数量(默认false)
debugShowMemoryUsage: false, // --- 仅用于调试。如果为 true,则绘制标签以指示每个图块使用的纹理和几何内存(以兆字节为单位)(默认false)
debugShowUrl: false, // --- 仅用于调试。如果为 true,则绘制标签以指示每个图块的 url(默认false)
dynamicScreenSpaceError: true, // 根据测试,有了这个后,会在真正的全屏加载完之后才清晰化房屋 --- 优化选项。减少距离相机较远的图块的屏幕空间错误(默认false)
};
const tileset = new Cesium.Cesium3DTileset(tilesetJson);
// 非异步加载
viewer.scene.primitives.add(tileset);
// 定位到瓦片渲染的模型位置
viewer.flyTo(tileset);
tileset.allTilesLoaded.addEventListener(function () {
console.log("模型已经全部加载完成");
});
};
set3Dtitle3();
瓦片数据中已经包含了位置信息,因此我们不用关心这个模型要放在哪里,只需要相机定位到模型即可。
模拟水面
这里我们直接使用entities
去构建一个几何多边形
,然后赋予水的颜色,或者说直接用动态水的材质
都可以(后者效果要逼真一些)。
这期我们先用第一种方法,画出水之后,我们要做水位不断上升的动画。
js
let waterEntity = null;
let waterTimer = null;
const onStart = () => {
// 几何体的顶点坐标
const waterCoord = [
121.48033090358801, 29.790483294870796, 0, 121.4778771950879,
29.79083578574342, 0, 121.47877939338282, 29.79193540741442, 0,
121.4804061804202, 29.791480141327728, 0,
];
let startHeight = 10; // 几何体初始高度
const targetHeight = 20; // 几何体最终的高度
waterEntity = viewer.entities.add({
polygon: {
hierarchy: Cesium.Cartesian3.fromDegreesArrayHeights(waterCoord),
material: Cesium.Color.fromBytes(64, 157, 253, 200),
perPositionHeight: true,
// 不断监听startHeight的值的变化而更新集合体的高度
extrudedHeight: new Cesium.CallbackProperty(() => {
return startHeight;
}, false),
},
});
// 模拟水位上升的动画,改变startHeight的值
waterTimer = setInterval(() => {
if (startHeight < targetHeight) {
startHeight += 0.1;
if (startHeight >= targetHeight) {
startHeight = targetHeight;
clearInterval(waterTimer);
}
// 使用该方式会闪烁,改用 Cesium.CallbackProperty 平滑
// this.waterEntity.polygon.extrudedHeight.setValue(startHeight)
}
}, 50);
};
OK,这时只要执行onStart方法,就可看到水位不断上升的效果了,并且水面会自动根据高程淹没不同高度的建筑物体。
总结
实际上,模拟水淹
或者洪水演进
,如果再逼真和有效一点的话,需要根据IOT
的数据回传加上算法推算
,例如三角顶点每个顶点的高程的动态变化,每个顶点的颜色变化,动态绘制三角面组成更真实的场景。
【开源地址】:github.com/tingyuxuan2...
有需要进技术产品开发交流群(可视化&GIS)可以加我:brown_7778,也欢迎
数字孪生可视化领域
的交流合作。
最后,如果觉得文章对你有帮助,也希望可以一键三连👏👏👏,给开源点点star
,你的鼓励是支持我持续开源和分享下去的动力~