超实用!数字孪生 Cesium 园区 3D 模型加载,一次学会的保姆级教程

接前文# 拿来就用!Vue3+Cesium 飞入效果封装,3D大屏多场景直接复用,目前已经实现了Cesium的飞入效果,虽然暂时没有腾出手解决离线地图的问题,但已经可以简单看看效果了。

需求

最终要实现的终极目标其实是针对园区级别的数字孪生大屏系统

其实数字孪生这个概念相对比较宽泛,更精确地说其实是针对目前园区内部的各类设备进行大屏展示。

比如厂区各地方的摄像头厂区内部设备状态人员在岗状态等等

其实更进一步升级可以做到自动化 的一些联动操作。

比如当出现突发的人员闯入,大屏系统自动给出预警,实时追踪异常人员的位置,同步给安保人员。

或者是厂区内某设备异常,自动联动到相应的人员/部门,达到快速处置的目的。

现在咱们先简单的进行第一步,园区的搭建。

解决方案

因为是针对园区的数字孪生大屏系统,所以园区内部的各个模型是一定要分开的。

也就是说各个设备的模型是一个个单独的文件。

考虑到设备可能比较多,所以建议大家在搭建此类园区的时候,优先按 功能区域/独立交互单元 拆分模型,而非单一整体或完全零散的建筑。

建议将园区地形/地面导出为一个模型,这部分包括园区的地面、道路、绿化、围墙等非交互性基础场景。

这样做的好处是保证作为整个园区数字孪生场景的基底 ,一次性加载,保证园区整体视觉完整性,不至于初始化时候看起来非常简陋。

然后将每栋独立建筑(如 1号厂房、2号办公楼、中控楼、配电柜等等)各导出为单独模型

需要注意,每个建筑模型的原点要统一对齐到园区地理坐标系(经纬度),确保加载后位置精准。

最后将园区内需要单独交互的设备(如门禁、充电桩、摄像头、传感器),单独导出为小型模型

实际代码

因为之前的文章内已经对Cesium进行了组件化的简单封装,所以这里不再赘述。详细参考此篇文章:# 拿来就用!Vue3+Cesium 飞入效果封装,3D大屏多场景直接复用

模型加载作为后期比较核心的方法,这里进行简单的抽离和封装。

js 复制代码
/**
 * 加载 glTF/glb 建筑模型到 Cesium 地图
 * @param {Object} viewer Cesium Viewer 实例
 * @param {Object} options 模型配置
 * @param {string} options.id 模型唯一ID(如 'building-01',用于交互识别)
 * @param {string} options.modelUrl glTF/glb 模型路径(如 '/models/building-01.gltf')
 * @param {number} options.lon 经度
 * @param {number} options.lat 纬度
 * @param {number} [options.height=0] 高度(米,默认0,贴地面)
 * @param {number} [options.scale=1.0] 缩放比例
 * @param {number} [options.heading=0] 航向角(度,0=正北,调整建筑朝向)
 * @param {Object} [options.properties={}] 自定义属性(如状态接口、建筑名称,用于交互)
 * @returns {Cesium.Entity} 加载后的模型实体(便于后续管理)
 */
export function loadBuildingModel(viewer, options) {
	if (!viewer || !options.id || !options.modelUrl || !options.lon || !options.lat) {
		console.error('模型加载参数缺失!');
		return null;
	}

	// 经纬度转 Cesium 笛卡尔坐标
	const position = Cesium.Cartesian3.fromDegrees(
		options.lon,
		options.lat,
		options.height || 0
	);

	const heading = Cesium.Math.toRadians(options.heading || 0);
	const orientation = Cesium.Transforms.headingPitchRollQuaternion(
		position,
		new Cesium.HeadingPitchRoll(heading, 0, 0)
	);

	// 加载模型
	const buildingEntity = viewer.entities.add({
		id: options.id,
		name: options.properties.name || options.id,
		position: position,
		orientation: orientation,
		model: {
			uri: options.modelUrl,
			scale: options.scale || 1.0,
			minimumPixelSize: 0,
			maximumScale: 20000,
			runAnimations: false,
			clampToGround: true,
		},
		properties: options.properties || {},
	});
	return buildingEntity;
}

使用上述方法的时候需要注意,buildingEntitymodel.minimumPixelSize 属性我这里设置了 0,表示取消最小像素限制。

目的是为了在缩放地图的时候让模型与地图一起进行缩放,让地图与模型按真实比例进行缩放看起来比较合理。

如果你想要保证缩放时模型不消失,可以设置为 128

需要加载的模型建议存放在 public/models 文件夹下,避免出现打包丢失模型的问题。另外如果你使用的是 GLTF 模型文件,要记得把 .bin 文件也放进来。

将模型文件整理好以后,创建一个数组,包含所有文件的详细信息,以 for 循环的方式调用 loadBuildingModel 方法,实现模型加载。

js 复制代码
const loadParkBuildingModels = () => {
  if (!isCesiumReady.value || !cesiumViewer.value) return;

  // 园区建筑数组
  const parkBuildings = [
    {
      id: 'building-01',
      modelUrl: '/models/building-01.gltf', // glTF 模型路径
      lon: 116.404567, // 经度
      lat: 39.915234, // 纬度
      height: 0,
      scale: 1.0,
      heading: 15, // 建筑朝向
      properties: {
        name: '1号生产厂房'
      }
    },
    {
      id: 'building-02',
      modelUrl: '/models/building-02.gltf',
      lon: 116.405123,
      lat: 39.914987,
      height: 0,
      scale: 1.0,
      heading: 0,
      properties: {
        name: '2号办公楼'
      }
    }
  ];

  // 循环加载每个建筑模型
  parkBuildings.forEach(building => {
    const entity = loadBuildingModel(cesiumViewer.value, building);
    if (entity) {
        // TODO 这里还可以添加loading
      buildingEntities.value.set(building.id, entity);
    }
  });
};

总结

这样基本实现了建筑模型加载在地图上,如果你的模型贴图足够真实,在设置好缩放倍率以后能够非常完美的融合在地图上。

这里我的模型因为做的比较粗糙,效果看起来有些格格不入。

如果有两个园区实现带跨度的位置调整,在加载模型的时候还可以增加距离显隐的方法。

通过对相机到模型距离的计算,控制模型的显隐,这样能够极大的降低同时加载多个模型带来的压力。

js 复制代码
const cameraPosition = viewer.camera.position;
const modelPosition = entity.position.getValue(Cesium.JulianDate.now());
const distance = Cesium.Cartesian3.distance(cameraPosition, modelPosition);

如果单纯一个园区,设备不多的话影响不大,但如果园区较大,隐藏部分模型带来的性能提升还是显而易见的。

相关推荐
天启HTTP5 分钟前
开启全局代理后网络变慢,问题出在哪
开发语言·前端·网络·tcp/ip·php
卡布鲁11 分钟前
Webpack 核心原理与自定义 Loader/Plugin 实战
前端·javascript
智码看视界17 分钟前
Web Storage 的无障碍实践与工程化应用
前端·javascript·web
孟陬19 分钟前
国外技术周刊 #140:在 Jeff Bezos 的私密 Campfire 峰会上,我学到了关于亿万富翁的事
前端·后端
槑有老呆21 分钟前
Bun:一个让 Node 开发者原地起飞的 JS/TS 运行时
前端
小小小小宇22 分钟前
AI Agent 核心流程与底层逻辑
前端
wuhen_n24 分钟前
RAG 实战:语义检索 + 大模型生成精准问答
前端·langchain·ai编程
卤蛋fg628 分钟前
vxe-table 列拖拽排序与行拖拽排序:让表格布局任意排序
vue.js
沉尘58829 分钟前
ACE-GCM加解密微信小程序
前端