Three.js是一个轻量级的跨浏览器JavaScript库,用于在浏览器中创建和显示动画3D计算机图形。Cesium是一个3D库,旨在创建数字地球,其渲染与真实地球非常精确。
cesium的基本渲染原理与Three.js没有太大区别。通过在两个场景中复制cesium的球面坐标系和匹配的数字地球,很容易将两个单独的渲染引擎层整合到一个主场景中。
参考依据是威尔逊等将Three.js与Cesium集成的文章,链接地址:https://www.cesium.com/blog/2017/10/23/integrating-cesium-with-threejs/
使用vue-cli创建项目,这里使用的是vue-cli4。
1.安装cesium依赖
npm install cesium
2.配置vue.config.js
const CopyWebpackPlugin = require('copy-webpack-plugin')
const webpack = require('webpack')
const path = require('path')
let cesiumSource = './node_modules/cesium/Source'
let cesiumWorkers = '../Build/Cesium/Workers'
module.exports = {
publicPath: "./",
outputDir: "dist",
lintOnSave: false,
devServer: {
open: process.platform === "darwin",
host: "0.0.0.0",
port: 5000,
https: false,
hotOnly: false
},
configureWebpack: {
output: {
sourcePrefix: ' '
},
amd: {
toUrlUndefined: true
},
resolve: {
alias: {
'vue$': 'vue/dist/vue.esm.js',
'@': path.resolve('src'),
'cesium': path.resolve(__dirname, cesiumSource)
}
},
plugins: [
new CopyWebpackPlugin([{ from: path.join(cesiumSource, cesiumWorkers), to: 'Workers' }]),
new CopyWebpackPlugin([{ from: path.join(cesiumSource, 'Assets'), to: 'Assets' }]),
new CopyWebpackPlugin([{ from: path.join(cesiumSource, 'Widgets'), to: 'Widgets' }]),
new CopyWebpackPlugin([{ from: path.join(cesiumSource, 'ThirdParty/Workers'), to: 'ThirdParty/Workers' }]),
new webpack.DefinePlugin({
CESIUM_BASE_URL: JSON.stringify('./')
})
],
module: {
unknownContextCritical: /^.\/.*$/,
unknownContextCritical: false
}
}
};
3.配置main.js全局引入cesium相关文件
import Vue from 'vue'
import App from './App.vue'
Vue.config.productionTip = false
//引入cesium相关文件
var Cesium = require('cesium/Cesium');
var widgets = require('cesium/Widgets/widgets.css');
Vue.prototype.Cesium = Cesium
Vue.prototype.widgets = widgets
new Vue({
render: h => h(App)
}).$mount('#app')
4.测试cesium
<template>
<div id="container" class="box">
<div id="cesiumContainer"></div>
</div>
</template>
<script>
export default {
name: 'Home',
mounted(){
this.init()
},
methods: {
init() {
let Cesium = this.cesium
//这里的token使用自己注册的
Cesium.Ion.defaultAccessToken = 'xxxxx'
let viewer = new Cesium.Viewer('cesiumContainer');
}
}
};
</script>
<style lang='scss' scoped>
html,
body,
#cesiumContainer {
width: 100%;
height: 100%;
margin: 0;
padding: 0;
overflow: hidden;
}
.box {
height: 100%;
}
</style>
安装threejs
npm install threejs
注意,threejs和ceiusm结合会有版本问题,具体可以参考https://blog.csdn.net/u011540323/article/details/103522075
vue+cesium+threejs
<template>
<div>
<div id="cesiumContainer"></div>
<div id="ThreeContainer"></div>
</div>
</template>
<script>
import * as THREE from 'three'
export default {
data () {
return {
minWGS84: [115.23, 39.55],
maxWGS84: [116.23, 41.55],
objects3D: [],
three: {
renderer: null,
camera: null,
scene: null
},
cesium: {
viewer: null
},
ce: null,
}
},
methods: {
initCesium() {
let Cesium = this.Cesium;
// let cesiumContainer = document.getElementById("cesiumContainer");
Cesium.Ion.defaultAccessToken = 'xxxxxxx'; //注:这里需使用自己的token
this.cesium.viewer = new Cesium.Viewer("cesiumContainer", {
useDefaultRenderLoop: false, //关闭自动渲染循环,这意味着需要手动调用render()方法来渲染场景
selectionIndicator: false, //关闭选中指示器
homeButton: false, //关闭回到初始位置按钮
sceneModePicker: false, //关闭场景模式选择器
navigationHelpButton: false, //表示关闭导航帮助按钮
animate: false, //关闭动画
timeline: false, //关闭时间线
fullscreenButton: false, //关闭全屏按钮
navigationInstructionsInitiallyVisible: false,
allowTextureFilterAnisotropic: false,
contextOptions: { //配置WebGL上下文选项
webgl: {
alpha: false,
antialias: true,
preserveDrawingBuffer: true,
failIfMajorPerformanceCaveat: false,
depth: true,
stencil: false,
anialias: false
}
},
targetFrameRate: 60, //目标帧率
resolutionScale: 0.1, //场景分辨率的缩放比例
orderIndependentTranslucency: true,
baseLayerPicker: true, //启用底图选择器
geocoder: false, //关闭地名查找功能
automaticallyTrackDataSourceClocks: false, //关闭自动跟踪数据源时钟
dataSources: null,
clock: null,
terrainShadows: Cesium.ShadowMode.DISABLED, //关闭地形阴影
infoBox: false //关闭信息框
});
//设置场景中心点的经纬度和高度,将相机飞到该点
let center = Cesium.Cartesian3.fromDegrees(
(this.minWGS84[0] + this.maxWGS84[0]) / 2,
((this.minWGS84[1] + this.maxWGS84[1]) / 2) - 1,
200000
);
this.ce = center;
//将相机飞到指定位置
this.cesium.viewer.camera.flyTo({
destination: center, //相机位置
orientation: { //相机朝向
heading: Cesium.Math.toRadians(0),
pitch: Cesium.Math.toRadians(-60),
roll: Cesium.Math.toRadians(0)
},
duration: 3 //飞行动画的时长
});
},
initThree() {
let ThreeContainer = document.getElementById("ThreeContainer");
let fov = 45;
let width = window.innerWidth;
let height = window.innerHeight;
let aspect = width / height;
let near = 1;
let far = 10 * 1000 * 1000; // needs to be far to support Cesium's world-scale rendering
this.three.scene = new THREE.Scene(); //场景
this.three.camera = new THREE.PerspectiveCamera(fov, aspect, near, far); //透视相机
this.three.renderer = new THREE.WebGLRenderer({ //渲染器
alpha: true //true,代表透明度为0,完全透明(渲染器设置背景为透明,达成叠加效果)
});
ThreeContainer.appendChild(this.three.renderer.domElement);
},
Object3D(mesh, minWGS84, maxWGS84) {
this.threeMesh = mesh; //three网格对象
this.minWGS84 = minWGS84; //位置边界框最小坐标
this.maxWGS84 = maxWGS84; //位置边界框最大坐标
},
init3DObject() {
//创建three球体
let geometry1 = new THREE.SphereGeometry(1, 32, 32);
let sphere = new THREE.Mesh(geometry1, new THREE.MeshPhongMaterial({
color: 0xffffff,
side: THREE.DoubleSide
}));
sphere.scale.set(5000, 5000, 5000); //网格模型xyz方向都缩放5000倍
sphere.uuid = "sphere";
//创建组对象group
var group = new THREE.Group();
group.add(sphere); //把球体添加到组
this.three.scene.add(group); //把组添加到场景中
group.position.set(this.ce.x, this.ce.y, this.ce.z); //设置组的位置
let ob3D = new this.Object3D(group, this.minWGS84, this.maxWGS84);
this.objects3D.push(ob3D);
//创建three十二面体
let geometry2 = new THREE.DodecahedronGeometry();
let dodecahedronMesh = new THREE.Mesh(geometry2, new THREE.MeshNormalMaterial());
dodecahedronMesh.scale.set(5000, 5000, 5000);
dodecahedronMesh.position.z += 15000;
dodecahedronMesh.rotation.x = Math.PI / 2; // Three.js 渲染 z-up 而 Cesium 渲染 y-up,使three也变成y朝上
dodecahedronMesh.uuid = "12面体";
var group2 = new THREE.Group();
group2.add(dodecahedronMesh)
this.three.scene.add(group2);
group2.position.set(this.ce.x, this.ce.y, this.ce.z);
//添加到对象数组
let ob3D2 = new this.Object3D(group2, this.minWGS84, this.maxWGS84);
this.objects3D.push(ob3D2);
console.log(this.objects3D);
/*******************************************添加灯光**********************************/
//添加点光源
var spotLight = new THREE.SpotLight(0xffffff);
spotLight.position.set(0, 0, 50000);
spotLight.castShadow = true; //设置光源投射阴影
spotLight.intensity = 1;
group.add(spotLight)
//添加环境光
var hemiLight = new THREE.HemisphereLight(0xff0000, 0xff0000, 1);
group.add(hemiLight);
},
loop() {
requestAnimationFrame(this.loop);
this.renderCesium(); //渲染cesium
this.renderThreeObj(); //渲染three
},
renderCesium() {
this.cesium.viewer.render();
},
renderThreeObj() {
let Cesium = this.Cesium;
let ThreeContainer = document.getElementById("ThreeContainer");
// 设置相机跟cesium保持一致,使用的cesium的相机为主相机,使three的相机与cesium保持一致即可
this.three.camera.fov = Cesium.Math.toDegrees(this.cesium.viewer.camera.frustum.fovy)
this.three.camera.updateProjectionMatrix();
// 笛卡尔坐标转换为三维向量
var cartToVec = function(cart) {
return new THREE.Vector3(cart.x, cart.y, cart.z);
};
// 配置three对象的位置,进行坐标变换才能使对象在地球上正确显示
for (let id in this.objects3D) {
let minWGS84 = this.objects3D[id].minWGS84;
let maxWGS84 = this.objects3D[id].maxWGS84;
// 物体中心位置计算为对象的最小和最大WGS84纬度和经度值的平均值,并且把经纬度坐标(WGS84)转笛卡尔坐标
var center = Cesium.Cartesian3.fromDegrees((minWGS84[0] + maxWGS84[0]) / 2, (minWGS84[1] + maxWGS84[1]) / 2);
// 向前方向计算为高度为1的Cartesian3位置,以便对象指向远离地球中心的方向
var centerHigh = Cesium.Cartesian3.fromDegrees((minWGS84[0] + maxWGS84[0]) / 2, (minWGS84[1] + maxWGS84[1]) / 2, 1);
// 使用 WGS84 区域从左下角到左上角的方向作为向上矢量
var bottomLeft = cartToVec(Cesium.Cartesian3.fromDegrees(minWGS84[0], minWGS84[1]));
var topLeft = cartToVec(Cesium.Cartesian3.fromDegrees(minWGS84[0], maxWGS84[1]));
var latDir = new THREE.Vector3().subVectors(bottomLeft, topLeft).normalize();
// 物体位置调整
this.objects3D[id].threeMesh.position.copy(center); //位置设置为中心位置
//_3Dobjects[id].threeMesh.lookAt(centerHigh); // threejs-r87版本
//threejs-r87以上版本,需改写成如下
this.objects3D[id].threeMesh.lookAt(centerHigh.x, centerHigh.y, centerHigh.z);
this.objects3D[id].threeMesh.up.copy(latDir); //网格的向上矢量设置为计算出的向上矢量方向
}
//关闭相机自动更新
this.three.camera.matrixAutoUpdate = false;
// cesium相机位置
var cvm = this.cesium.viewer.camera.viewMatrix;
var civm = this.cesium.viewer.camera.inverseViewMatrix;
//NOTE:r87后版本,threejs源码中相机的lookat重新调整了矩阵,需要将原来放在相机设置参数前的这行代码上移到此处
three.camera.lookAt(new THREE.Vector3(0, 0, 0));
// 同步Three相机位置设置
this.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]
);
this.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]
);
// three.camera.lookAt(new THREE.Vector3(0, 0, 0));// threejs-r87版本 ,r87以后版本需要上移
// 相机设置参数
var width = ThreeContainer.clientWidth;
var height = ThreeContainer.clientHeight;
var aspect = width / height;
this.three.camera.aspect = aspect;
this.three.camera.updateProjectionMatrix(); // 相机参数更新
this.three.renderer.setSize(width, height);
this.three.renderer.render(this.three.scene, this.three.camera);
}
},
mounted () {
this.initCesium();
this.initThree();
this.init3DObject();
this.loop();
}
}
</script>
<style scoped>
#cesiumContainer {
position: absolute;
top: 0;
left: 0;
height: 100%;
width: 100%;
margin: 0;
overflow: hidden;
padding: 0;
font-family: sans-serif;
}
#ThreeContainer {
position: absolute;
top: 0;
left: 0;
height: 100%;
width: 100%;
margin: 0;
overflow: hidden;
padding: 0;
font-family: sans-serif;
/* 关闭three鼠标控制器 */
pointer-events: none;
}
</style>