CesiumJS是一个开源、免费的三维地图开发框架,Three.js是一个也开源、免费的三维渲染框架,两者都是基于WebGL技术、使用JavaScript开发的Web前端三维可视化框架,目前在国内外的三维Web开发中使用极为广泛。但Three.js是一个轻量级的跨浏览器JavaScript库,用于在浏览器中创建和显示动画3D计算机图形;而Cesium的可视化内容以地理空间数据为主,如卫星影像、地形、城市级三维模型等,数据量和空间范围都非常大,对数据精度要求更高,并且还具有一系列GIS相关的空间分析功能,但在模型的渲染及可视化方面略逊于Three。
因此,将Cesium的地理数据渲染和GIS功能与Three.js广泛而易用的通用3D API相结合,不仅可以优化模型的渲染精度,也可以加入许多地理信息相关的应用,为新的WebGL体验开启了许多可能性。
目录
主要原理:两个框架分成不同的视图层,参考 HTML Canvas 元素,并将它们的控制器组合在同一个坐标系中。
可参考如下官方案例:
实现步骤:
1)在HTML中设置两个容器分别容纳Cesium和Three(或初始化Cesium容器后将Three容器追加到Cesium后)。
2)使Three产生的场景覆盖Cesium之上( Three.js DOM 元素位于 Cesium 之上),并禁用Three容器的鼠标事件,通过Cesium同步控制Three。
3)并分别对Cesium渲染器和Three渲染器进行初始化,通过禁用Cesium的默认渲染循环,我们可以将其动画帧与Three.js同步。并设置Three场景的渲染器背景为透明,以达成叠加效果。
4)初始化物体并分别加入至各自的场景之中(注意 Three.js 渲染 z-up 而 Cesium 渲染 y-up。)。
5)进行坐标转换,使对象在地球上正确显示。这包括将大地纬度/经度位置转换为笛卡儿XYZ,并使用WGS84区域从左下角到左上角的方向作为向上矢量,使物体指向地球中心。这也可以通过使用本地笛卡尔东北向或东北向下来计算。
6)请求关键帧并循环渲染器。
项目创建可参考往期内容:
安装Three相关依赖:
- npm install three --save
- or
- yarn add three
Three初始化可参考往期内容:
- <div id="cesiumContainer" ref="cesiumContainer">div>
-
- <script setup>
- import { onMounted } from "vue";
- import * as Cesium from "cesium";
- import "./Widgets/widgets.css";
- import * as THREE from "three";
- // console.log(flightData);
-
- // 设置cesium的token
- Cesium.Ion.defaultAccessToken = "yourToken";
- // cesium默认资源路径
- window.CESIUM_BASE_URL = "/";
- // 设置默认的视角为中国
- Cesium.Camera.DEFAULT_VIEW_RECTANGLE = Cesium.Rectangle.fromDegrees(
- // 西边经度
- 89.5,
- // 南边维度
- 20.4,
- // 东边经度
- 110.4,
- // 北边维度
- 61.2
- );
-
- onMounted(() => {
- main();
- });
-
- // 初始化cesium渲染器
- // 初始化three渲染器
- // 初始化2个库的3D物体
- // 循环渲染
-
- // three全局对象
- let three = {
- renderer: null,
- camera: null,
- scene: null,
- };
-
- // 设置全局cesium对象
- let cesium = {
- viewer: null,
- };
-
- function main() {
- // 设置北京显示模型的渲染范围
- var minWGS84 = [115.39, 38.9];
- var maxWGS84 = [117.39, 40.9];
-
- // 设置cesium容器
- var cesiumContainer = document.getElementById("cesiumContainer");
-
- // three.js物体
- let objects3D = [];
- //封装three物体(使three物体具有经纬度)
- function Object3D(mesh, minWGS84, maxWGS84) {
- this.threeMesh = mesh;//物体
- this.minWGS84 = minWGS84;//范围
- this.maxWGS84 = maxWGS84;//范围
- }
-
- // 初始化cesium渲染器
- function initCesium() {
- cesium.viewer = new Cesium.Viewer(cesiumContainer, {
- useDefaultRenderLoop: false,
- selectionIndicator: false,
- homeButton: false,
- infoBox: false,
- sceneModePicker: false,
- navigationHelpButton: false,
- animation: false,
- timeline: false,
- fullscreenButton: false,
- baseLayerPicker: false,
- clock: false,
- geocoder: false,
- // 天地图矢量路径图
- imageryProvider: new Cesium.WebMapTileServiceImageryProvider({
- url: "http://t0.tianditu.com/vec_w/wmts?service=wmts&request=GetTile&version=1.0.0&LAYER=vec&tileMatrixSet=w&TileMatrix={TileMatrix}&TileRow={TileRow}&TileCol={TileCol}&style=default&format=tiles&tk=30d07720fa76f07732d83c748bb84211",
- layer: "tdtBasicLayer",
- style: "default",
- format: "image/jpeg",
- tileMatrixSetID: "GoogleMapsCompatible",
- }),
- //cesium中webgl选项
- contextOptions: {
- webgl: {
- //透明度
- alpha: false,
- // 抗锯齿
- antialias: true,
- //深度检测
- depth: true,
- },
- },
- });
- // 地图叠加
- var imageryLayers = cesium.viewer.imageryLayers;
- console.log(imageryLayers);
- var layer = imageryLayers.addImageryProvider(
- new Cesium.WebMapTileServiceImageryProvider({
- url: "http://t0.tianditu.com/img_w/wmts?service=wmts&request=GetTile&version=1.0.0&LAYER=img&tileMatrixSet=w&TileMatrix={TileMatrix}&TileRow={TileRow}&TileCol={TileCol}&style=default&format=tiles&tk=30d07720fa76f07732d83c748bb84211",
- layer: "tdtBasicLayer",
- style: "default",
- format: "image/jpeg",
- tileMatrixSetID: "GoogleMapsCompatible",
- })
- );
- layer.alpha = 0.5;
- // 设置前往地点
- let center = Cesium.Cartesian3.fromDegrees(
- (minWGS84[0] + maxWGS84[0]) / 2,
- (minWGS84[1] + maxWGS84[1]) / 2,
- 20000
- );
-
- // 设置相机飞往该区域
- cesium.viewer.camera.flyTo({
- destination: center,
- duration: 2,
- orientation: {
- heading: Cesium.Math.toRadians(0),
- pitch: Cesium.Math.toRadians(-90),
- roll: 0,
- },
- });
- }
- //初始化Three
- function initThree() {
- // 设置相机配置
- let fov = 45;//视角
- let aspect = window.innerWidth / window.innerHeight;//宽高比例
- let near = 0.1;
- let far = 10 * 1000 * 1000;//视域范围
-
- // 初始化场景
- three.scene = new THREE.Scene();
- three.camera = new THREE.PerspectiveCamera(fov, aspect, near, far);
- three.renderer = new THREE.WebGLRenderer({
- antialias: true,//抗锯齿
- alpha: true,
- });
- // 设置渲染器大小
- three.renderer.setSize(window.innerWidth, window.innerHeight);
-
- // 添加环境光
- let ambientLight = new THREE.AmbientLight(0xffffff, 1);
- three.scene.add(ambientLight);
- // 添加three.jscanvas元素到cesium容器
- cesiumContainer.appendChild(three.renderer.domElement);
- }
-
- // 创建three.js物体
- function createMesh() {
- let geometry = new THREE.BoxBufferGeometry(1, 1, 1);
- let material = new THREE.MeshBasicMaterial({
- color: 0x00ff00,
- });
- let mesh = new THREE.Mesh(geometry, material);
-
- // 放大物体
- mesh.scale.set(100, 100, 100); // 放大
- mesh.position.set(0, 0, 50); // 平移
-
- let meshGroup = new THREE.Group();
- meshGroup.add(mesh);
- // 添加至场景
- three.scene.add(meshGroup);
- // 创建3d物体
- let OB3d = new Object3D(
- meshGroup,
- [minWGS84[0], minWGS84[1]],
- [maxWGS84[0], maxWGS84[1]]
- );
-
- // 添加到3d物体数组
- objects3D.push(OB3d);
- }
-
- function renderThree() {
- // 设置相机跟cesium保持一致
- three.camera.fov = Cesium.Math.toDegrees(cesium.viewer.camera.frustum.fovy);
- // 声明一个将cesium框架的cartesian3转换为three.js的vector3(笛卡尔坐标转换为三维向量)
- let cartToVec = function (cart) {
- return new THREE.Vector3(cart.x, cart.y, cart.z);
- };
- // 将3D的物体通过经纬度转换成对应的位置
- objects3D.forEach((item, index) => {
- // 通过经纬度获取中心点的位置
- let center = Cesium.Cartesian3.fromDegrees(
- (item.minWGS84[0] + item.maxWGS84[0]) / 2,
- (item.minWGS84[1] + item.maxWGS84[1]) / 2
- );
- item.threeMesh.position.copy(cartToVec(center));
-
- //计算朝向(切面方向-切线向量)
- //中心高度点
- let centerHeight = Cesium.Cartesian3.fromDegrees(
- (item.minWGS84[0] + item.maxWGS84[0]) / 2,
- (item.minWGS84[1] + item.maxWGS84[1]) / 2,
- 1
- );
- //左下
- let bottomLeft = cartToVec(
- Cesium.Cartesian3.fromDegrees(item.minWGS84[0], item.minWGS84[1])
- );
- //左上
- let topLeft = cartToVec(
- Cesium.Cartesian3.fromDegrees(item.minWGS84[0], item.maxWGS84[1])
- );
- //朝向()
- let latDir = new THREE.Vector3()
- .subVectors(bottomLeft, topLeft)
- .normalize();
-
- // console.log(item);
- //设置查看方向
- item.threeMesh.lookAt(centerHeight.x, centerHeight.y, centerHeight.z);
- //设置朝向
- item.threeMesh.up.copy(latDir);
- });
-
- //设置摄像机矩阵
- // 设置相机跟cesium保持一致
- three.camera.matrixAutoUpdate = false;//自动更新
- //复制cesium相机矩阵
- let cvm = cesium.viewer.camera.viewMatrix;
- let civm = cesium.viewer.camera.inverseViewMatrix;
- // three相机默认朝向0,0,0
- three.camera.lookAt(0, 0, 0);
-
- // 设置threejs相机矩阵
- three.camera.matrixWorld.set(
- civm[0],
- civm[4],
- civm[8],
- civm[12],
- civm[1],
- civm[5],
- civm[9],
- civm[13],
- civm[2],
- civm[6],
- civm[10],
- civm[14],
- civm[3],
- civm[7],
- civm[11],
- civm[15]
- );
-
- three.camera.matrixWorldInverse.set(
- cvm[0],
- cvm[4],
- cvm[8],
- cvm[12],
- cvm[1],
- cvm[5],
- cvm[9],
- cvm[13],
- cvm[2],
- cvm[6],
- cvm[10],
- cvm[14],
- cvm[3],
- cvm[7],
- cvm[11],
- cvm[15]
- );
- //设置宽高比例
- let width = cesiumContainer.clientWidth;
- let height = cesiumContainer.clientHeight;
- three.camera.aspect = width / height;
- //更新相机矩阵
- three.camera.updateProjectionMatrix();
- //设置尺寸大小
- three.renderer.setSize(width, height);
- three.renderer.clear();
- three.renderer.render(three.scene, three.camera);
- }
- function renderCesium() {
- cesium.viewer.render();
- }
- //循环函数,不断请求动画帧渲染
- function loop() {
- requestAnimationFrame(loop);
- // cesium渲染
- renderCesium();
- // three.js渲染
- renderThree();
- }
- //初始化调用
- initCesium();
- initThree();
- createMesh();
- loop();
- }
- script>
-
- <style>
- * {
- margin: 0;
- padding: 0;
- }
-
- #cesiumContainer {
- width: 100vw;
- height: 100vh;
- position: relative;
- }
-
- #cesiumContainer>canvas {
- position: absolute;
- top: 0;
- left: 0;
- /* 设置鼠标事件穿透 */
- pointer-events: none;
- }
- style>
实现效果:
