github仓库地址:https://github.com/RainManGO/3d-earth
npm:https://www.npmjs.com/package/3d-earth
支持vue/react/html 嵌入简单。
是不是有点干,咽不下去了。
头一阵子B站,抖音都被 陶大宇大哥的倒转地球刷屏了,终于热度下去了,不用倒转头七了。
真的和地球扛上了,公司的大屏项目需要科技感的地球、飞线图。
公司数据分类项目和大屏项目使用echart 比较多,对echart使用不能说是手到擒来,也是比较熟练地。
个人比较倾向于它,最重要的配置型,找到个案例复制粘贴完事。
于是和设计商量下做了一版,最终还是被客户否决了。
原因如下:
饿,echarts 灵活度没有那么高,只能想别的办法了,最后定位ThreeJs。ThreeJs需要一定的计算机视图知识,从来没有学过,必定是场恶战。
做完了,回头看来,一些东西看似简单,还是需要细细品味分解。把总体过程分解一一说来。因为涉及代码过多,不全部贴出来说明,具体查看github 仓库源码。直说结构过程。
目标设计样子:
实现步骤分解:
初始化列表:
这个库和外界需要一个接口,可以通过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
};
因为地球元素比较多,且需要转动,所以是一个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 MeshBasicMaterial
var material = new MeshLambertMaterial({
map: texture, //设置地球0颜色贴图map
});
var mesh = new Mesh(geometry, material); //网格模型对象Mesh
return mesh;
};
现在是这个样子了,光秃秃的缺少了点什么:
刚才光秃秃的地球,需要加上点轮廓。
threejs 通过 LineLoop 和世界点数据,可以绘制多边形。利用这个原理绘制国家边界。
LineLoop和Line功能一样,区别在于首尾顶点相连,轮廓闭合,但是绘制条数太多会用性能问题,LineSegments 是一条线绘制,提高性能,需要复制顶点。
把计算好的数据,放到data里。
然后使用LineSegments绘出来,代码如下:
/*
* @Author: ZY
* @Date: 2021-12-31 15:47:59
* @LastEditors: ZY
* @LastEditTime: 2022-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: 2021-12-31 16:40:30
* @LastEditors: ZY
* @LastEditTime: 2022-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); //加载纹理贴图
// 创建精灵材质对象SpriteMaterial
var 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
};
云层效果不是一个精灵,它是相当于在地球上又套了一个圆球,半径比地球大一点。
云层图:
添加之后的效果:
还有飞线、动画和涟漪效果本篇内容过长,下篇奉上。