前言
在WEB三维开发过程中,将Three.js与Cesium结合的需求,相信不少小伙伴都曾遇到过。Cesium官网上提供了一个示例 ,但是这个解决方案集成起来还是比较复杂繁琐 。我抽空开发了three-to-cesium插件,仅需几行代码,无需关心矩阵转换相关问题,只需要像在Threejs
中一样,通过调用add
方法就能轻松地将物体加载到Cesium地球上的预期位置。如果有帮助到你,期待一个star,如果你发现bug,或有更好的提议请在issues提出。
下面通过三个示例快速掌握three-to-cesium的使用方法。
在线演示🐹🐸
安装插件并引入
three-to-cesium支持UMD或模块化方式引入,本文将使用UMD引入方式演示。
1.两种安装方式:
- npm install three-to-cesium
- 下载github仓库
2.创建html并引入Threejs 、Cesium 、three-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😊😊😊。