使用three-to-cesium.js轻松融合Three和Cesium场景

前言

在WEB三维开发过程中,将Three.js与Cesium结合的需求,相信不少小伙伴都曾遇到过。Cesium官网上提供了一个示例 ,但是这个解决方案集成起来还是比较复杂繁琐 。我抽空开发了three-to-cesium插件,仅需几行代码,无需关心矩阵转换相关问题,只需要像在Threejs中一样,通过调用add方法就能轻松地将物体加载到Cesium地球上的预期位置。如果有帮助到你,期待一个star,如果你发现bug,或有更好的提议请在issues提出。

下面通过三个示例快速掌握three-to-cesium的使用方法。

在线演示🐹🐸

安装插件并引入

three-to-cesium支持UMD或模块化方式引入,本文将使用UMD引入方式演示。

1.两种安装方式:

2.创建html并引入ThreejsCesiumthree-to-cesium

html 复制代码
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
  <link rel="stylesheet" href="./lib/Cesium/Widgets/widgets.css">
  <style>
    html,
    body,
    #map {
      height: 100%;
      margin: 0;
      padding: 0;
    }
  </style>
</head>

<body>
  <div id="map"></div>
</body>
<script src="./lib/Cesium/Cesium.js"></script>
<script src="./lib/three/build/three.js"></script>
<script src="./lib/three-to-cesium.umd.cjs"></script>
<script>


</script>
</html>

示例1 添加一个立方体

在three中添加向scene中添加一个立方体,需要关注两个关键元素:一个是网格对象,另一个是位置。在three-to-cesium也一样,你仍然只需关注这两个要素,无需关心其他的转换操作。

js 复制代码
Cesium.Ion.defaultAccessToken = "Your token";
//创建cesiumViewer
let cesiumViewer = new Cesium.Viewer('map', {
  sceneModePicker: false,
});
//创建场景融合对象
let sceneIntegrator = ThreeToCesium(cesiumViewer);
let position = Cesium.Cartesian3.fromDegrees(108.95943284886424, 34.288286155753546, 5);
let mesh = new THREE.Mesh(
  new THREE.BoxGeometry(10, 10, 10),
  new THREE.MeshNormalMaterial()
);
//将Threejs的网格对象添加到地球指定位置
sceneIntegrator.add(mesh,position)
//在CesiumViewer帧渲染事件中通过调用update方法来同步Cesium和Three的相机并渲染Threejs场景
cesiumViewer.scene.postRender.addEventListener(() => {
  sceneIntegrator.update();
});

仅仅几行代码,就像在单纯使用Threejs一样,轻松的加载了几何体到正确的位置:

示例2 添加折线

通过示例1 可以看到通过ThreeToCesium().add()方法将几何体加载到地球上时,它接收了两个参数:一个是Mesh对象,另一个是Cesium里的Cartesian3坐标。

试想如果我们需要往地球上加载折线多边形 等对象,此时不能简单的通过一个Cartesian3 坐标来描述它的位置,而是需要先计算几何体中心点的世界坐标,再将以几何体中的位置信息全部转为以中心点为原点的局部坐标,才能使用ThreeToCesium().add()方法加载到地球,还好上面的所有转换操作插件内部已提供:

js 复制代码
Cesium.Ion.defaultAccessToken = "Your token";
let cesiumViewer = new Cesium.Viewer('map', {
    sceneModePicker: false,
});
//定义折现拐点坐标并进行局部坐标转换
let localPositions = ThreeToCesium.localizePositions([
  Cesium.Cartesian3.fromDegrees(108.95993690885348, 34.28688948263291, 0),
  Cesium.Cartesian3.fromDegrees(108.95836123161854, 34.28461622000204, 0),
  Cesium.Cartesian3.fromDegrees(108.96052860333033, 34.28463093923793, 0),
  Cesium.Cartesian3.fromDegrees(108.95894270765785, 34.286895032131916, 0),
]);
let sceneIntegrator = ThreeToCesium(cesiumViewer);
//将转换好的局部坐标localPositions.positions传给geometry
let geometry = new THREE.BufferGeometry().setFromPoints(localPositions.positions);
let material = new THREE.LineBasicMaterial({color: 0xff0000});
let line = new THREE.Line(geometry, material);
//像示例1一样,不过这里的参数2使用几何体中心点坐标,直接从localPositions.centerInWorld获取
sceneIntegrator.add(line, localPositions.centerInWorld);
cesiumViewer.scene.postRender.addEventListener(() => {
  sceneIntegrator.update();
});

运行效果:

示例3 同时加载多个几何体

一个完整的Threejs场景可能包含多个对象,而且他们之间可能是局部关系,如果像前两个示例一样通过一个个的传入世界坐标去加载会繁琐些,此时可以借助THREE.Group轻松实现。

js 复制代码
Cesium.Ion.defaultAccessToken = "Your token";
let cesiumViewer = new Cesium.Viewer('map', {
    sceneModePicker: false,
});
let sceneIntegrator = ThreeToCesium(cesiumViewer);
let position = Cesium.Cartesian3.fromDegrees(108.95943284886424, 34.288286155753546, 0.1);
//先向地球的指定位置添加THREE.Group对象
let group = new THREE.Group();
sceneIntegrator.add(group, position);
//后续的所有对象便可使用局部坐标加人到THREE.Group中
let spotLight = new THREE.SpotLight(0xffffff, 7000);
spotLight.position.set(-10, 60, -10);
spotLight.shadow.mapSize = new THREE.Vector2(1024, 1024);
spotLight.shadow.camera.far = 130;
spotLight.shadow.camera.near = 40;
spotLight.shadow.mapSize = new THREE.Vector2(3000, 3000)
group.add(spotLight);

let ambientLight = new THREE.AmbientLight(0xffffff, 0.2);
group.add(ambientLight);

let planeGeometry = new THREE.PlaneGeometry(60, 30);
let planeMaterial = new THREE.MeshLambertMaterial({
    color: "#eee"
});
let plane = new THREE.Mesh(planeGeometry, planeMaterial);
plane.rotation.x = -0.5 * Math.PI;
plane.position.y = 0;
plane.position.z = 0;
group.add(plane);

let sphereGeometry = new THREE.SphereGeometry(5, 50, 50);
let sphereMaterial = new THREE.MeshLambertMaterial({
    color: "blue",
});
let sphere = new THREE.Mesh(sphereGeometry, sphereMaterial);
group.add(sphere);
//开启Three场景的投影
sceneIntegrator.threeRenderer.shadowMap.enabled = true;
spotLight.castShadow = true;
plane.receiveShadow = true;
sphere.castShadow = true;

let step = 0;
let clock = new THREE.Clock();
cesiumViewer.scene.postRender.addEventListener(() => {
    let delta = clock.getDelta();
    step += delta * 3;
    sphere.position.x = 10 * Math.cos(step);
    sphere.position.y = 5 + 10 * Math.abs(Math.sin(step));
    sceneIntegrator.update();
});

运行结果:

需要注意的一点

我们知道在Threejs中相机默认看向的方向是Z轴 ,但是通过该插件将Three场景集成到Cesium时,使用的是Cesium中的东(X轴 )北(Y轴 )上(Z轴 )坐标系,所以你需要注意的是:此时Y轴 指向正北方,Z轴指向正上方(或者说指向天空)。

例如有几何体:THREE.BoxGeometry(10, 100, 10),在纯Three场景中它会是"瘦高"型,如果是通过插件加载到地球上,因为坐标轴变换的原因,那它只能"矮胖"啦。请参照下图:

另外重要的一点 :如果你已经在纯Three环境中调试好了场景,在使用插件向Cesium集成时疲于一个个的变更几何体坐标轴,换一种思路,你也可以对整个场景施加绕X轴旋转90°,最终得到的结果都是一样的。

js 复制代码
THREE.Object3D.rotation.x = Math.PI / 2;

总结

通过上述三个示例,我们可以看出three-to-cesium插件极大地简化了Threejs与Cesium之间的集成过程。无论是简单的几何体还是复杂形状,该插件都提供了简便的方法来实现这些需求,而无需关注坐标转换、矩阵等细节。使得更多的开发者能够轻松地将两者集成。

此外,three-to-cesium不仅限于本文提到的功能,更多配置项、属性、方法等请查阅GitHub仓库

希望这个插件能为你的项目带来便捷和灵感。如果有帮助到你,期待一个star😊😊😊。

相关推荐
duansamve2 天前
Cesium中实现在地图上移动/旋转点、线、面
cesium
冥界摄政王3 天前
CesiumJS学习第四章 替换指定3D建筑模型
3d·vue·html·webgl·js·cesium
冥界摄政王5 天前
Cesium学习第二章 camera 相机
node.js·html·vue3·js·cesium
冥界摄政王6 天前
Cesium学习第一章 安装下载 基于vue3引入Cesium项目开发
vue·vue3·html5·webgl·cesium
你们瞎搞8 天前
Cesium加载20GB航测影像.tif
前端·cesium·gdal·地图切片
闲云一鹤9 天前
Cesium 使用 Turf 实现坐标点移动(偏移)
前端·gis·cesium
二狗哈9 天前
Cesium快速入门34:3dTile高级样式设置
前端·javascript·算法·3d·webgl·cesium·地图可视化
AlanHou9 天前
Three.js:Web 最重要的 3D 渲染引擎的技术综述
前端·webgl·three.js
二狗哈10 天前
Cesium快速入门33:tile3d设置样式
3d·状态模式·webgl·cesium·地图可视化
一颗烂土豆12 天前
🚴‍♂️ Vue3 + Three.js 实战:如何写一个“不晕车”的沉浸式骑行播放器 🎥
vue.js·游戏·three.js