赞
踩
大家好,我是南木元元,热衷分享有趣实用的文章。Three.js和Cesium.js是两个功能强大的JavaScript库,用于创建和渲染3D图形和场景。它们在某些方面有相似之处,但也有一些重要的区别。
两者的定位和应用领域不同,所以为了更好地发挥它们各自的优势,本文就来分享一下如何将three和cesium结合起来,并在vue中使用。
cesium与Three.js有一些共同点:
cesium的基本渲染原理与Three.js也没有太大的区别,通过在两个场景中复制cesium的球面坐标系和匹配的数字地球,很容易将两个单独的渲染引擎层整合到一个主场景中。主要过程如下:
这里主要参考了威尔逊等的将Three.js与Cesium集成的文章。
首先需要创建一个vue项目,本文使用的是vue-cli4,这部分网上有很多文章,这里不再详细展开。
npm install cesium
Cesium是一个庞大而复杂的库,除了 JavaScript 模块之外,它还包括静态资源,例如CSS、图像和 json 文件,还包括
Web Worker 文件,用于在单独的线程中执行密集计算,需要额外配置来确保Cesium的Assets、Widgets和 Web
Worker文件能正确提供和加载。在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 } } };
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')
<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结合会有版本问题,具体可以参考这篇文章。
如下图所示,在cesium上方创建了两个three的图形,即完成了两者的结合。
这里附上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>
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。