废话不多说,老规矩先让干货。
3D 地球成果展示
github仓库地址:/RainManGO/3d-earth
npm:/package/3d-earth
支持vue/react/html 嵌入简单。
是不是有点干,咽不下去了。
前言
头一阵子B站,抖音都被 陶大宇大哥的倒转地球刷屏了,终于热度下去了,不用倒转头七了。
真的和地球扛上了,公司的大屏项目需要科技感的地球、飞线图。
为什么不用echarts
公司数据分类项目和大屏项目使用echart 比较多,对echart使用不能说是手到擒来,也是比较熟练地。
个人比较倾向于它,最重要的配置型,找到个案例复制粘贴完事。
于是和设计商量下做了一版,最终还是被客户否决了。
原因如下:
加载慢不漂亮
饿,echarts 灵活度没有那么高,只能想别的办法了,最后定位ThreeJs。ThreeJs需要一定的计算机视图知识,从来没有学过,必定是场恶战。
实现过程
做完了,回头看来,一些东西看似简单,还是需要细细品味分解。把总体过程分解一一说来。因为涉及代码过多,不全部贴出来说明,具体查看github 仓库源码。直说结构过程。
目标设计样子:
实现步骤分解:
ThreeJS环境初始化星空背景添加带纹理的地球世界地图轮廓边界绘制地球光晕添加地球云层城市位置标注和涟漪效果添加飞线B样条地球自转和镜头缩放动画
初始化
初始化列表:
webgl渲染器(WebGLRenderer)和css2d 渲染器(CSS2DRenderer)透视投影相机 (PerspectiveCamera)场景(scene)轨道控制器(OrbitControls)多种灯光
渲染器初始化
这个库和外界需要一个接口,可以通过id选择器拿到dom节点,从而获取到宽高。轨道控制器需要一个2D 渲染器所以一起初始化。
this.renderer = initRender(this.width, this.height);this.renderer2d = initRender2D(this.width, this.height);
WebGLRenderer初始化
export const initRender = (width:number,height:number)=>{let renderer = new WebGLRenderer({antialias: true,alpha: true});renderer.shadowMap.enabled = false;renderer.shadowMap.type = PCFShadowMap;renderer.setSize(width,height)return renderer}
CSS2DRenderer初始化
export const initRender2D = (width:number,height:number)=>{const renderer2d = new CSS2DRenderer();renderer2d.setSize(width,height)renderer2d.domElement.style.position = "absolute";renderer2d.domElement.style.top = "0px";renderer2d.domElement.tabIndex = 0;renderer2d.domElement.className = "coreInnerRenderer2d";return renderer2d}
相机和场景初始化没有什么特别的就不多说了,见代码。
轨道控制器初始化
注意点是用的2d 渲染器
const orbitControl = new OrbitControls(this.camera,this.renderer2d.domElement);orbitControl.minZoom = controlConfig.minZoom;orbitControl.maxZoom = controlConfig.maxZoom;orbitControl.minPolarAngle = controlConfig.minPolarAngle;orbitControl.maxPolarAngle = controlConfig.maxPolarAngle;orbitControl.update();
灯光添加
灯光有多种,主要是:
平行光点光半球光
地球的贴图是这种发光材质,需要光照来打效果。
export const initLight = (scene:Scene)=>{/*** 光源设置*/// 平行光var directionalLight = new DirectionalLight(0x80b5ff, 1);directionalLight.position.set(-250, 250, 100);scene.add(directionalLight);// 点光var pointLight = new PointLight(0x80d4ff, 1);pointLight.position.set(-250, 250, 100);scene.add(pointLight);// 半球光var hemisphereLight = new HemisphereLight(0xffffff, 0x3d6399, 1);hemisphereLight.position.set(-250, 250, 100);scene.add(hemisphereLight);//环境光var ambient = new AmbientLight(0x002bff, 0.8);scene.add(ambient);}
星空背景
星空背景主要是点光源,主要思路是随机位置和颜色大小等
效果如下:
代码:
export const starBackground = () => {const positions = [];const colors = [];const geometry = new BufferGeometry();for (var i = 0; i < 10000; i++) {var vertex = new Vector3();vertex.x = Math.random() * 2 - 1;vertex.y = Math.random() * 2 - 1;vertex.z = Math.random() * 2 - 1;positions.push(vertex.x, vertex.y, vertex.z);var color = new Color();color.setHSL(Math.random() * 0.2 + 0.5, 0.55, Math.random() * 0.25 + 0.55);colors.push(color.r, color.g, color.b);}geometry.setAttribute("position", new Float32BufferAttribute(positions, 3));geometry.setAttribute("color", new Float32BufferAttribute(colors, 3));var textureLoader = new TextureLoader();var texture = textureLoader.load(starPng); //加载纹理贴图var starsMaterial = new PointsMaterial({map: texture,size: 1,transparent: true,opacity: 1,vertexColors: true, //true:且该几何体的colors属性有值,则该粒子会舍弃第一个属性--color,而应用该几何体的colors属性的颜色blending: AdditiveBlending,sizeAttenuation: true,});let stars = new Points(geometry, starsMaterial);stars.scale.set(300, 300, 300);return stars};
添加3d地球对象
因为地球元素比较多,且需要转动,所以是一个3dobject对象,添加多个mesh。
var object3D = new Object3D();let earthMesh = createEarthImageMesh(earthRadius);
创建一个地球:
export const createEarthImageMesh = (radius: number) => {// TextureLoader创建一个纹理加载器对象,可以加载图片作为纹理贴图var textureLoader = new TextureLoader();//加载纹理贴图var texture = textureLoader.load(earthTexture);//创建一个球体几何对象var geometry = new SphereGeometry(radius, 96, 96);//材质对象Material// MeshLambertMaterial MeshBasicMaterialvar material = new MeshLambertMaterial({map: texture, //设置地球0颜色贴图map});var mesh = new Mesh(geometry, material); //网格模型对象Meshreturn mesh;};
现在是这个样子了,光秃秃的缺少了点什么:
世界地图轮廓边界绘制
刚才光秃秃的地球,需要加上点轮廓。
threejs 通过 LineLoop 和世界点数据,可以绘制多边形。利用这个原理绘制国家边界。
LineLoop和Line功能一样,区别在于首尾顶点相连,轮廓闭合,但是绘制条数太多会用性能问题,LineSegments 是一条线绘制,提高性能,需要复制顶点。
把计算好的数据,放到data里。
然后使用LineSegments绘出来,代码如下:
/** @Author: ZY* @Date: -12-31 15:47:59* @LastEditors: ZY* @LastEditTime: -01-05 10:15:57* @FilePath: /3d-earth/lib/src/earth/countryPolygon.ts* @Description: 世界轮廓*/import {BufferGeometry,BufferAttribute,LineBasicMaterial,LineSegments} from "three";//引入国家边界数据import pointArr from "../data/world";import {countryLineColor } from "../config/index";// R:球面半径function countryLine(R:number) {var geometry = new BufferGeometry(); //创建一个Buffer类型几何体对象//类型数组创建顶点数据var vertices = new Float32Array(pointArr);// 创建属性缓冲区对象var attribute = new BufferAttribute(vertices, 3); //3个为一组,表示一个顶点的xyz坐标// 设置几何体attributes属性的位置属性geometry.attributes.position = attribute;// 线条渲染几何体顶点数据var material = new LineBasicMaterial({color: countryLineColor, //线条颜色}); //材质对象var line = new LineSegments(geometry, material); //间隔绘制直线line.scale.set(R, R, R); //lineData.js对应球面半径是1,需要缩放R倍return line;}export {countryLine };
地球光晕
地球光晕其实是一个精灵贴图,这里放了两层,下面放一张看下。
大气层光晕代码:
/** @Author: ZY* @Date: -12-31 16:40:30* @LastEditors: ZY* @LastEditTime: -01-05 14:53:18* @FilePath: /3d-earth/lib/src/earth/glow.ts* @Description: 大气层光环效果*/import {TextureLoader, SpriteMaterial,Sprite} from "three";export const earthGlow = (radius:number,img:any,scale:number) =>{// TextureLoader创建一个纹理加载器对象,可以加载图片作为纹理贴图var textureLoader = new TextureLoader();var texture = textureLoader.load(img); //加载纹理贴图// 创建精灵材质对象SpriteMaterialvar spriteMaterial = new SpriteMaterial({map: texture, //设置精灵纹理贴图transparent: true, //开启透明// opacity: 0.5,//可以通过透明度整体调节光圈});// 创建表示地球光圈的精灵模型var sprite = new Sprite(spriteMaterial);sprite.scale.set(radius * scale, radius * scale, 1); //适当缩放精灵return sprite};
添加地球云层
云层效果不是一个精灵,它是相当于在地球上又套了一个圆球,半径比地球大一点。
云层图:
添加之后的效果:
还有飞线、动画和涟漪效果本篇内容过长,下篇奉上。
【星宇大前端】公众号,专注于技术,爱好造轮子,绝对干货,需要喝着茶看,不然咽不下去。🤣