如何在基于npm的Cesium地图项目中添加Three.js 3D模型?
在Cesium BIM场景中添加Three.js模型的实现方案
问题背景
我通过npm使用Cesium,现有一段加载3D Tiles BIM模型的代码(对应官方示例逻辑),想在此基础上添加Three.js 3D模型,求具体实现方案。
现有Cesium代码
const viewer = new Cesium.Viewer("cesiumContainer", { terrainProvider: Cesium.createWorldTerrain(), animation: false, timeline: false, }); const scene = viewer.scene; viewer.cesiumWidget.creditContainer.parentNode?.removeChild( viewer.cesiumWidget.creditContainer ); viewer.clock.currentTime = Cesium.JulianDate.fromIso8601( "2022-08-01T00:00:00Z" ); const tileset = scene.primitives.add( new Cesium.Cesium3DTileset({ url: Cesium.IonResource.fromAssetId(1240402), }) ); tileset.readyPromise .then(function (tileset: any) { viewer.zoomTo( tileset, new Cesium.HeadingPitchRange( 0.5, -0.4, tileset.boundingSphere.radius * 4.0 ) ); }) .catch(function (error: any) { console.log(error); }); tileset.colorBlendMode = Cesium.Cesium3DTileColorBlendMode.REPLACE; let selectedFeature: any; let picking = false; function setElementColor(element: any, color: any) { const featuresToColor = element[element]; const length = featuresToColor.length; for (let i = 0; i < length; ++i) { const feature = featuresToColor[i]; feature.color = Cesium.Color.clone(color, feature.color); } } function selectFeature(feature: any) { const element = feature.getProperty("element"); setElementColor(element, Cesium.Color.YELLOW); selectedFeature = feature; } function unselectFeature(feature: any) { if (!Cesium.defined(feature)) { return; } const element = feature.getProperty("element"); setElementColor(element, Cesium.Color.WHITE); if (feature === selectedFeature) { selectedFeature = undefined; } } const handler = new Cesium.ScreenSpaceEventHandler(scene.canvas); handler.setInputAction(function (movement: any) { if (!picking) { return; } const feature = scene.pick(movement.endPosition); unselectFeature(selectedFeature); if (feature instanceof Cesium.Cesium3DTileFeature) { selectFeature(feature); } }, Cesium.ScreenSpaceEventType.MOUSE_MOVE);
实现方案
1. 安装依赖
先确保安装Three.js及类型定义(TypeScript项目):
npm install three @types/three
2. 核心实现逻辑
通过相机同步+渲染合成的方式整合Three.js模型:在Cesium的postRender事件中同步相机参数,将Three.js渲染结果叠加到Cesium场景中,同时处理坐标转换将Three.js模型定位到Cesium场景的指定位置。
3. 完整整合代码
以下是整合后的完整代码,包含Three.js模型加载、相机同步、坐标转换:
import * as Cesium from "cesium"; import * as THREE from "three"; import { GLTFLoader } from "three/examples/jsm/loaders/GLTFLoader"; // 初始化Cesium(原有代码) const viewer = new Cesium.Viewer("cesiumContainer", { terrainProvider: Cesium.createWorldTerrain(), animation: false, timeline: false, }); const scene = viewer.scene; viewer.cesiumWidget.creditContainer.parentNode?.removeChild( viewer.cesiumWidget.creditContainer ); viewer.clock.currentTime = Cesium.JulianDate.fromIso8601( "2022-08-01T00:00:00Z" ); const tileset = scene.primitives.add( new Cesium.Cesium3DTileset({ url: Cesium.IonResource.fromAssetId(1240402), }) ); tileset.readyPromise .then(function (tileset: any) { viewer.zoomTo( tileset, new Cesium.HeadingPitchRange( 0.5, -0.4, tileset.boundingSphere.radius * 4.0 ) ); // BIM模型加载完成后初始化Three.js模型 initThreeJsModel(tileset.boundingSphere.center); }) .catch(function (error: any) { console.log(error); }); tileset.colorBlendMode = Cesium.Cesium3DTileColorBlendMode.REPLACE; // 原有交互逻辑保持不变 let selectedFeature: any; let picking = false; function setElementColor(element: any, color: any) { const featuresToColor = element[element]; const length = featuresToColor.length; for (let i = 0; i < length; ++i) { const feature = featuresToColor[i]; feature.color = Cesium.Color.clone(color, feature.color); } } function selectFeature(feature: any) { const element = feature.getProperty("element"); setElementColor(element, Cesium.Color.YELLOW); selectedFeature = feature; } function unselectFeature(feature: any) { if (!Cesium.defined(feature)) { return; } const element = feature.getProperty("element"); setElementColor(element, Cesium.Color.WHITE); if (feature === selectedFeature) { selectedFeature = undefined; } } const handler = new Cesium.ScreenSpaceEventHandler(scene.canvas); handler.setInputAction(function (movement: any) { if (!picking) { return; } const feature = scene.pick(movement.endPosition); unselectFeature(selectedFeature); if (feature instanceof Cesium.Cesium3DTileFeature) { selectFeature(feature); } }, Cesium.ScreenSpaceEventType.MOUSE_MOVE); // ---------------- Three.js 整合部分 ---------------- let threeScene: THREE.Scene; let threeCamera: THREE.PerspectiveCamera; let threeRenderer: THREE.WebGLRenderer; let threeModel: THREE.Object3D | null = null; function initThreeJsModel(cesiumPosition: Cesium.Cartesian3) { // 1. 初始化Three.js场景、相机、渲染器 threeScene = new THREE.Scene(); threeCamera = new THREE.PerspectiveCamera(); threeRenderer = new THREE.WebGLRenderer({ alpha: true, // 开启透明,避免遮挡Cesium场景 preserveDrawingBuffer: true }); threeRenderer.setSize(scene.canvas.width, scene.canvas.height); threeRenderer.setPixelRatio(window.devicePixelRatio); // 2. 添加光源(根据模型需求调整) const ambientLight = new THREE.AmbientLight(0xffffff, 0.5); threeScene.add(ambientLight); const directionalLight = new THREE.DirectionalLight(0xffffff, 0.8); directionalLight.position.set(5, 10, 7.5); threeScene.add(directionalLight); // 3. 加载Three.js模型(替换为你的模型路径) const loader = new GLTFLoader(); loader.load( "/path/to/your/model.glb", (gltf) => { threeModel = gltf.scene; // 将模型定位到Cesium场景的指定位置 const cartographic = Cesium.Cartographic.fromCartesian(cesiumPosition); const modelPosition = Cesium.Cartesian3.fromRadians( cartographic.longitude, cartographic.latitude, cartographic.height + 5 // 模型高度偏移,避免埋在地形里 ); // 转换Cesium坐标到Three.js坐标系 const threePosition = new THREE.Vector3( modelPosition.x, modelPosition.y, modelPosition.z ); threePosition.y *= -1; // 适配Cesium与Three.js的Y轴方向差异 threeModel.position.copy(threePosition); // 调整模型缩放(根据实际模型大小) threeModel.scale.set(1, 1, 1); threeScene.add(threeModel); }, (progress) => console.log(`加载进度: ${(progress.loaded / progress.total) * 100}%`), (error) => console.error("模型加载失败:", error) ); // 4. 同步Cesium与Three.js相机 scene.postRender.addEventListener(() => { if (!threeCamera || !threeRenderer) return; // 获取Cesium相机矩阵并转换为Three.js格式 const cesiumCamera = scene.camera; const modelViewMatrix = cesiumCamera.viewMatrix; const projectionMatrix = cesiumCamera.projectionMatrix; const threeModelViewMatrix = new THREE.Matrix4(); const threeProjectionMatrix = new THREE.Matrix4(); // Cesium矩阵是列主序,Three.js是行主序,需要转置 threeModelViewMatrix.fromArray(modelViewMatrix).transpose(); threeProjectionMatrix.fromArray(projectionMatrix).transpose(); // 更新Three.js相机参数 threeCamera.matrixWorldInverse = threeModelViewMatrix; threeCamera.projectionMatrix = threeProjectionMatrix; threeCamera.matrixWorld.getInverse(threeModelViewMatrix); // 渲染Three.js场景并绘制到Cesium画布 threeRenderer.render(threeScene, threeCamera); const cesiumContext = scene.context; cesiumContext.drawTexture( threeRenderer.domElement, 0, 0, scene.canvas.width, scene.canvas.height ); }); // 5. 处理窗口大小变化 window.addEventListener("resize", () => { if (!threeRenderer) return; threeRenderer.setSize(scene.canvas.width, scene.canvas.height); }); }
关键注意事项
- 坐标转换:Cesium的
Cartesian3是X东、Y北、Z上,Three.js默认是X右、Y上、Z前,需根据模型实际情况调整位置和旋转。 - 渲染层级:通过
postRender事件渲染Three.js模型,确保叠加在Cesium场景之上;若需要Three.js模型被Cesium遮挡,需调整渲染顺序。 - 性能优化:复杂模型建议关闭Three.js阴影、开启LOD等优化,避免影响Cesium帧率。
内容的提问来源于stack exchange,提问作者Beginner
相关产品推荐
相关产品推荐

