当前位置:   article > 正文

【保姆进阶级】Three.js做一个酷炫的城市展示可视化大屏_vue threejs 大屏

vue threejs 大屏

hi,大家好,我是ethan。

想记录博客很久了,一直懒得开个头,以前写过全栈、java、写过python、写过前端,写过安全、写过互联网,但是我还是更喜欢前端可视化,平时也喜欢研究一下可视化的技术,也是从d3、gis、threejs、echarts、hicharts、cesium一步步淌过来的,可视化方向的路还有很长,我觉得一些shader实在是好难....

web3.0盛行,元宇宙也是跟前端密切相关的,也想学习一下unity、three.ar.js之类的,有想法的小伙伴可以一起沟通一下~

言归正传,最近呢在做一个可视化大屏,当然要炫,毕竟领导喜欢,废话不多说,先上预览:

bb185a2e-b902-48eb-91a6-5ea79eaf53c9

分解代码前,我们先介绍一些这里面有几个技术点:


1、d3.js通过投影把地图数据的json映射到3维空间中,城市地图的json下载我就不多讲了,网上有很多教程,换成自己所需的城市就行;

2、地图上展示的数据展示的label,一开始用的sprite小精灵模型做的,但是会失真不清楚,后来换成了CSS2DRenderer这种方式,就相当于把html渲染到3维空间里,屡试不爽;

3、为了达到“酷炫智能”效果,在一加载和点击区县的时候,做了camera的动画(镜头移动、拉近),在这里就要在vue中引入tween.js了,tween做补间动画,还是很好用的;

4、地图边缘做了个流光效果,这个有很多厉害的博主介绍过,我是稍作了下修改;

5、每切换一个tab,隐藏/显示相应模型,所以把一组模型放到一组group里;

接下来我们可以带着上面几个点,看代码~!

项目使用vue的框架,我们先来看看项目目录、依赖都有哪些,其中引入elementUI就是为了用用里面的按钮,不用自己写了:

 (Menu.vue是测试了一个3D的菜单,跟此项目没有关联,可以先不用理会)

  1. {
  2. "name": "default",
  3. "version": "0.1.0",
  4. "private": true,
  5. "scripts": {
  6. "serve": "vue-cli-service serve",
  7. "build": "vue-cli-service build"
  8. },
  9. "dependencies": {
  10. "@tweenjs/tween.js": "^18.6.4",
  11. "core-js": "^2.6.5",
  12. "element-ui": "^2.15.8",
  13. "three": "^0.140.2",
  14. "vue": "^2.6.10"
  15. },
  16. "devDependencies": {
  17. "@vue/cli-plugin-babel": "^3.8.0",
  18. "@vue/cli-service": "^3.8.0",
  19. "d3": "^7.4.4"
  20. }
  21. }

tween这个包不好在vue里面直接用,所以提前去下载好,然后还要在main.js里面做声明

  1. import Vue from 'vue'
  2. import App from './App.vue'
  3. import ElementUI from 'element-ui';
  4. import 'element-ui/lib/theme-chalk/index.css';
  5. // 补间动画
  6. import tween from "./utils/tween";
  7. Vue.use(ElementUI);
  8. Vue.use(tween);
  9. Vue.config.productionTip = false
  10. new Vue({
  11. render: h => h(App),
  12. }).$mount('#app')

接下来,我们看一下主要的代码Main.vue

  1. <template>
  2. <div>
  3. <div id="container"></div>
  4. <div id="tooltip"></div>
  5. <el-button-group class="button-group">
  6. <el-button type="" icon="" @click="groupOneChange">首页总览</el-button>
  7. <el-button type="" icon="" @click="groupTwoChange">应急管理</el-button>
  8. <el-button type="" icon="" @click="groupThreeChange">能源管理</el-button>
  9. <el-button type="" icon="" @click="groupFourChange">环境监测</el-button>
  10. <!-- <el-button type="" icon="">综合能源监控中心</el-button> -->
  11. </el-button-group>
  12. </div>
  13. </template>

其中:

container块是主要渲染3d画布的div;

tooltip是鼠标悬浮到区县时显示区县名称div;

button-group是左上部分做tab切换的按钮组(全篇引入了elementUI就在这用到了...)

 这是需要的组件,提前引入

  1. import * as THREE from "three";
  2. import * as d3 from 'd3';
  3. import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls.js';
  4. import { CSS2DRenderer, CSS2DObject } from 'three/examples/jsm/renderers/CSS2DRenderer.js';

下面是放在data里的属性,把摄像机、场景、控制器、城市上的数据、城市上的模型,都放在这先声明一下,因为牵扯到很多模型、摄像机、动画的逻辑变化,所以放到这就相当于全局变量,后续用的话都很方便。

  1. data() {
  2. return {
  3. camera: null,
  4. scene: null,
  5. renderer: null,
  6. labelRenderer: null,
  7. container: null,
  8. // mesh: null,
  9. controller: null,
  10. map: null,
  11. raycaster: null,
  12. mouse: null,
  13. tooltip: null,
  14. lastPick: null,
  15. mapEdgeLightObj: {
  16. mapEdgePoints: [],
  17. lightOpacityGeometry: null, // 单独把geometry提出来,动画用
  18. // 边缘流光参数
  19. lightSpeed: 3,
  20. lightCurrentPos: 0,
  21. lightOpacitys: null,
  22. },
  23. // 每个屏幕模型一组
  24. groupOne: new THREE.Group(),
  25. groupTwo: new THREE.Group(),
  26. groupThree: new THREE.Group(),
  27. groupFour: new THREE.Group(),
  28. // groupOne 统计信息
  29. cityWaveMeshArr: [],
  30. cityCylinderMeshArr: [],
  31. cityMarkerMeshArr: [],
  32. cityNumMeshArr: [],
  33. // groupTwo 告警信息
  34. alarmWaveMeshArr: [],
  35. alarmCylinderMeshArr: [],
  36. alarmNameMeshArr: [],
  37. // groupThree 能源
  38. energyWaveMeshArr: [],
  39. energyCylinderMeshArr: [],
  40. energyNameMeshArr: [],
  41. // groupFour 环境
  42. monitorWaveMeshArr: [],
  43. monitorIconMeshArr: [],
  44. monitorNameMeshArr: [],
  45. // 城市信息
  46. mapConfig: {
  47. deep: 0.2,
  48. },
  49. // 摄像机移动位置,初始:0, -5, 1
  50. cameraPosArr: [
  51. // {x: 0.0, y: -0.3, z: 1},
  52. // {x: 5.0, y: 5.0, z: 2},
  53. // {x: 3.0, y: 3.0, z: 2},
  54. // {x: 0, y: 5.0, z: 2},
  55. // {x: -2.0, y: 3.0, z: 1},
  56. {x: 0, y: -3.0, z: 3.8},
  57. ],
  58. // 数据 - 区县总数量
  59. dataTotal: [xxxxxx],
  60. dataAlarm: [xxxxxx],
  61. dataEnergy: [xxxxxx],
  62. dataMonitor: [xxxxxx],
  63. };
  64. },

mounted函数不多说了,初始化什么的都放在这

  1. mounted() {
  2. this.init();
  3. this.animate();
  4. window.addEventListener('resize', this.onWindowSize)
  5. },

着重看一下methods里面的方法,首先是把three的几大基本元素初始化了

  1. //初始化
  2. init() {
  3. this.container = document.getElementById("container");
  4. this.setScene();
  5. this.setCamera();
  6. this.setRenderer(); // 创建渲染器对象
  7. this.setController(); // 创建控件对象
  8. this.addHelper();
  9. this.loadMapData();
  10. this.setEarth();
  11. this.setRaycaster();
  12. this.setLight();
  13. },
  14. setScene() {
  15. // 创建场景对象Scene
  16. this.scene = new THREE.Scene();
  17. },
  18. setCamera() {
  19. // 第二参数就是 长度和宽度比 默认采用浏览器 返回以像素为单位的窗口的内部宽度和高度
  20. this.camera = new THREE.PerspectiveCamera(
  21. 75,
  22. window.innerWidth / window.innerHeight,
  23. 0.1,
  24. 500
  25. );
  26. this.camera.position.set(0, -5, 1); // 0, -5, 1
  27. this.camera.lookAt(new THREE.Vector3(0, 0, 0)); // 0, 0, 0 this.scene.position
  28. },
  29. setRenderer() {
  30. this.renderer = new THREE.WebGLRenderer({
  31. antialias: true,
  32. // logarithmicDepthBuffer: true, // 是否使用对数深度缓存
  33. });
  34. this.renderer.setSize(this.container.clientWidth, this.container.clientHeight);
  35. this.renderer.setPixelRatio(window.devicePixelRatio);
  36. // this.renderer.sortObjects = false; // 是否需要对对象排序
  37. this.container.appendChild(this.renderer.domElement);
  38. this.labelRenderer = new CSS2DRenderer();
  39. this.labelRenderer.setSize(this.container.clientWidth, this.container.clientHeight);
  40. this.labelRenderer.domElement.style.position = 'absolute';
  41. this.labelRenderer.domElement.style.top = 0;
  42. this.container.appendChild(this.labelRenderer.domElement);
  43. },
  44. setController() {
  45. this.controller = new OrbitControls(this.camera, this.labelRenderer.domElement);
  46. this.controller.minDistance = 2;
  47. this.controller.maxDistance = 5.5 // 5.5
  48. // 阻尼(惯性)
  49. // this.controller.enableDamping = true;
  50. // this.controller.dampingFactor = 0.04;
  51. this.controller.minAzimuthAngle = -Math.PI / 4;
  52. this.controller.maxAzimuthAngle = Math.PI / 4;
  53. this.controller.minPolarAngle = 1;
  54. this.controller.maxPolarAngle = Math.PI - 0.1;
  55. // 修改相机的lookAt是不会影响THREE.OrbitControls的target的
  56. // this.controller.target = new THREE.Vector3(0, -5, 2);
  57. },
  58. // 辅助线
  59. addHelper() {
  60. // let helper =
声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/我家小花儿/article/detail/665453
推荐阅读