You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在基于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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.26 05:08:11