赞
踩
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的菜单,跟此项目没有关联,可以先不用理会)
- {
- "name": "default",
- "version": "0.1.0",
- "private": true,
- "scripts": {
- "serve": "vue-cli-service serve",
- "build": "vue-cli-service build"
- },
- "dependencies": {
- "@tweenjs/tween.js": "^18.6.4",
- "core-js": "^2.6.5",
- "element-ui": "^2.15.8",
- "three": "^0.140.2",
- "vue": "^2.6.10"
- },
- "devDependencies": {
- "@vue/cli-plugin-babel": "^3.8.0",
- "@vue/cli-service": "^3.8.0",
- "d3": "^7.4.4"
- }
- }
tween这个包不好在vue里面直接用,所以提前去下载好,然后还要在main.js里面做声明
- import Vue from 'vue'
- import App from './App.vue'
- import ElementUI from 'element-ui';
- import 'element-ui/lib/theme-chalk/index.css';
- // 补间动画
- import tween from "./utils/tween";
-
- Vue.use(ElementUI);
- Vue.use(tween);
-
- Vue.config.productionTip = false
-
- new Vue({
- render: h => h(App),
- }).$mount('#app')
接下来,我们看一下主要的代码Main.vue
- <template>
- <div>
- <div id="container"></div>
- <div id="tooltip"></div>
-
- <el-button-group class="button-group">
- <el-button type="" icon="" @click="groupOneChange">首页总览</el-button>
- <el-button type="" icon="" @click="groupTwoChange">应急管理</el-button>
- <el-button type="" icon="" @click="groupThreeChange">能源管理</el-button>
- <el-button type="" icon="" @click="groupFourChange">环境监测</el-button>
- <!-- <el-button type="" icon="">综合能源监控中心</el-button> -->
-
- </el-button-group>
- </div>
- </template>
其中:
container块是主要渲染3d画布的div;
tooltip是鼠标悬浮到区县时显示区县名称div;
button-group是左上部分做tab切换的按钮组(全篇引入了elementUI就在这用到了...)
这是需要的组件,提前引入
- import * as THREE from "three";
- import * as d3 from 'd3';
- import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls.js';
- import { CSS2DRenderer, CSS2DObject } from 'three/examples/jsm/renderers/CSS2DRenderer.js';
下面是放在data里的属性,把摄像机、场景、控制器、城市上的数据、城市上的模型,都放在这先声明一下,因为牵扯到很多模型、摄像机、动画的逻辑变化,所以放到这就相当于全局变量,后续用的话都很方便。
- data() {
- return {
- camera: null,
- scene: null,
- renderer: null,
- labelRenderer: null,
- container: null,
- // mesh: null,
- controller: null,
- map: null,
- raycaster: null,
- mouse: null,
- tooltip: null,
- lastPick: null,
- mapEdgeLightObj: {
- mapEdgePoints: [],
- lightOpacityGeometry: null, // 单独把geometry提出来,动画用
-
- // 边缘流光参数
- lightSpeed: 3,
- lightCurrentPos: 0,
- lightOpacitys: null,
- },
-
- // 每个屏幕模型一组
- groupOne: new THREE.Group(),
- groupTwo: new THREE.Group(),
- groupThree: new THREE.Group(),
- groupFour: new THREE.Group(),
-
-
- // groupOne 统计信息
- cityWaveMeshArr: [],
- cityCylinderMeshArr: [],
- cityMarkerMeshArr: [],
- cityNumMeshArr: [],
-
- // groupTwo 告警信息
- alarmWaveMeshArr: [],
- alarmCylinderMeshArr: [],
- alarmNameMeshArr: [],
-
- // groupThree 能源
- energyWaveMeshArr: [],
- energyCylinderMeshArr: [],
- energyNameMeshArr: [],
-
- // groupFour 环境
- monitorWaveMeshArr: [],
- monitorIconMeshArr: [],
- monitorNameMeshArr: [],
-
- // 城市信息
- mapConfig: {
- deep: 0.2,
- },
- // 摄像机移动位置,初始:0, -5, 1
- cameraPosArr: [
- // {x: 0.0, y: -0.3, z: 1},
- // {x: 5.0, y: 5.0, z: 2},
- // {x: 3.0, y: 3.0, z: 2},
- // {x: 0, y: 5.0, z: 2},
- // {x: -2.0, y: 3.0, z: 1},
- {x: 0, y: -3.0, z: 3.8},
- ],
-
- // 数据 - 区县总数量
- dataTotal: [xxxxxx],
- dataAlarm: [xxxxxx],
- dataEnergy: [xxxxxx],
- dataMonitor: [xxxxxx],
- };
- },
mounted函数不多说了,初始化什么的都放在这
- mounted() {
- this.init();
- this.animate();
- window.addEventListener('resize', this.onWindowSize)
- },
着重看一下methods里面的方法,首先是把three的几大基本元素初始化了
- //初始化
- init() {
- this.container = document.getElementById("container");
- this.setScene();
- this.setCamera();
- this.setRenderer(); // 创建渲染器对象
- this.setController(); // 创建控件对象
- this.addHelper();
- this.loadMapData();
- this.setEarth();
- this.setRaycaster();
- this.setLight();
- },
-
- setScene() {
- // 创建场景对象Scene
- this.scene = new THREE.Scene();
- },
-
- setCamera() {
- // 第二参数就是 长度和宽度比 默认采用浏览器 返回以像素为单位的窗口的内部宽度和高度
- this.camera = new THREE.PerspectiveCamera(
- 75,
- window.innerWidth / window.innerHeight,
- 0.1,
- 500
- );
-
- this.camera.position.set(0, -5, 1); // 0, -5, 1
- this.camera.lookAt(new THREE.Vector3(0, 0, 0)); // 0, 0, 0 this.scene.position
- },
-
- setRenderer() {
- this.renderer = new THREE.WebGLRenderer({
- antialias: true,
- // logarithmicDepthBuffer: true, // 是否使用对数深度缓存
- });
- this.renderer.setSize(this.container.clientWidth, this.container.clientHeight);
- this.renderer.setPixelRatio(window.devicePixelRatio);
- // this.renderer.sortObjects = false; // 是否需要对对象排序
- this.container.appendChild(this.renderer.domElement);
-
-
- this.labelRenderer = new CSS2DRenderer();
- this.labelRenderer.setSize(this.container.clientWidth, this.container.clientHeight);
- this.labelRenderer.domElement.style.position = 'absolute';
- this.labelRenderer.domElement.style.top = 0;
- this.container.appendChild(this.labelRenderer.domElement);
- },
-
- setController() {
- this.controller = new OrbitControls(this.camera, this.labelRenderer.domElement);
- this.controller.minDistance = 2;
- this.controller.maxDistance = 5.5 // 5.5
-
- // 阻尼(惯性)
- // this.controller.enableDamping = true;
- // this.controller.dampingFactor = 0.04;
-
- this.controller.minAzimuthAngle = -Math.PI / 4;
- this.controller.maxAzimuthAngle = Math.PI / 4;
-
- this.controller.minPolarAngle = 1;
- this.controller.maxPolarAngle = Math.PI - 0.1;
-
- // 修改相机的lookAt是不会影响THREE.OrbitControls的target的
- // this.controller.target = new THREE.Vector3(0, -5, 2);
-
- },
-
- // 辅助线
- addHelper() {
- // let helper =
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。