原文地址:Threejs实现酷炫3D地球技术点汇总

在线预览:https://joy1412.cn/online/

前言

本篇介绍一下如何用Threejs实现一个酷炫的3D地球特效,使用到的技能点如下:

  • 星空动态背景
  • 地球模型
  • 大气层光圈
  • 卫星环绕特效
  • 经纬度坐标转成3D空间坐标
  • 标注以及标注扩散光圈
  • 光柱特效
  • 飞线特效
  • geojson数据生成中国描边以及动态流光效果

正文

这里一个个介绍使用到的技术,首先先搭建初始化界面,把渲染器,相机以及基本的光照设置好。

不懂的可以参考这个页面的模板。

<!DOCTYPE html>
<html lang="en">
<head><title>three.js webgl - mirror</title><meta charset="utf-8"><meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0"><link type="text/css" rel="stylesheet" href="main.css"><style>html, body {height: 100%;width: 100%;}</style>
</head>
<body>
<div id="container" style="width:100%;height:100vh;position:relative; overflow: hidden;"></div>
</div>
<script type="module">import * as THREE from '../build/three.module.js';import { OrbitControls } from './jsm/controls/OrbitControls.js';let renderer, camera, scene, light, controls;const Dom = document.querySelector( '#container' );const width = Dom.clientWidth, height = Dom.clientHeight;/*** @description 初始化渲染场景*/function initRenderer() {renderer = new THREE.WebGLRenderer( { antialias: true, alpha: true } );renderer.setPixelRatio( window.devicePixelRatio );renderer.setSize( width, height );const containerDom = document.querySelector( '#container' );containerDom.appendChild( renderer.domElement );}/*** @description 初始化相机*/function initCamera() {camera = new THREE.PerspectiveCamera( 45, width / height, 1, 10000 );camera.position.set( 5, - 20, 200 );camera.lookAt( 0, 3, 0 );window.camera = camera;}/*** @description 初始化场景*/function initScene() {scene = new THREE.Scene();scene.background = new THREE.Color( 0x020924 );scene.fog = new THREE.Fog( 0x020924, 200, 1000 );window.scene = scene;}/*** 初始化用户交互**/function initControls() {controls = new OrbitControls( camera, renderer.domElement );controls.enableDamping = true;controls.enableZoom = true;controls.autoRotate = false;controls.autoRotateSpeed = 2;controls.enablePan = true;}/*** @description 初始化光*/function initLight() {const ambientLight = new THREE.AmbientLight( 0xcccccc, 1.1 );scene.add( ambientLight );var directionalLight = new THREE.DirectionalLight( 0xffffff, 0.2 );directionalLight.position.set( 1, 0.1, 0 ).normalize();var directionalLight2 = new THREE.DirectionalLight( 0xff2ffff, 0.2 );directionalLight2.position.set( 1, 0.1, 0.1 ).normalize();scene.add( directionalLight );scene.add( directionalLight2 );var hemiLight = new THREE.HemisphereLight( 0xffffff, 0x444444, 0.2 );hemiLight.position.set( 0, 1, 0 );scene.add( hemiLight );var directionalLight = new THREE.DirectionalLight( 0xffffff );directionalLight.position.set( 1, 500, - 20 );directionalLight.castShadow = true;directionalLight.shadow.camera.top = 18;directionalLight.shadow.camera.bottom = - 10;directionalLight.shadow.camera.left = - 52;directionalLight.shadow.camera.right = 12;scene.add(directionalLight);}/*** 窗口变动**/function onWindowResize() {camera.aspect = innerWidth / innerHeight;camera.updateProjectionMatrix();renderer.setSize( innerWidth, innerHeight );renders();}/*** @description 渲染*/function renders() {renderer.clear();renderer.render( scene, camera );}/*** 更新**/function animate() {window.requestAnimationFrame( () => {if (controls) controls.update();renders();animate();} );}window.onload = () => {initRenderer();initCamera();initScene();initLight();initControls();animate();window.addEventListener('resize', onWindowResize, false);};
</script>
</body>
</html>

动态星空背景介绍

作为地球的背景,用动态星空的方式显得更加酷炫,使用原型贴图让原本方形的点模拟球形,再加上动态设置颜色以及设置旋转偏移,更好的模拟星空效果。

  • 随机生成10000个坐标点,设置不同的颜色
const positions = [];
const colors = [];
const geometry = new THREE.BufferGeometry();
for (var i = 0; i < 10000; i ++) {var vertex = new THREE.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 THREE.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 THREE.Float32BufferAttribute( positions, 3 ) );
geometry.setAttribute( 'color', new THREE.Float32BufferAttribute( colors, 3 ) );

分别把生成的Vector3和Color放入数组,然后添加到geometry中,这样星空几何体有了。 Color里面setHSL可以设置颜色和饱和度,这里通过random来随机颜色。

  • 使用 ParticleBasicMaterial 生成材质

ParticleBasicMaterial 基础粒子材质用来搭配例子系统,这里我们可以设置粒子的大小,贴图,透明度等设置详细如下:

var starsMaterial = new THREE.ParticleBasicMaterial( {map: texture,size: 1,transparent: true,opacity: 1,vertexColors: true, //true:且该几何体的colors属性有值,则该粒子会舍弃第一个属性--color,而应用该几何体的colors属性的颜色blending: THREE.AdditiveBlending,sizeAttenuation: true
} );
  • 使用 ParticleSystem 生成模型

这里使用ParticleSystem这个粒子系统,是为了提供性能,如果用精灵Particle动态随机生成10000个的话,帧率肯定收到影响,这里ParticleSystem的话,等于只有一个Mesh,能大大提高性能。

把上面生成的几何体geometry 以及材质ParticleBasicMaterial来生成一个ParticleSystem,如下:

let stars = new THREE.ParticleSystem( geometry, starsMaterial );
stars.scale.set( 300, 300, 300 );
scene.add( stars );

地球模型

地球模型比较简单,直接贴图+一个球搞定

function initEarth() {globeTextureLoader.load( './imgs/diqiu2/earth2.jpg', function ( texture ) {var globeGgeometry = new THREE.SphereGeometry( radius, 100, 100 );var globeMaterial = new THREE.MeshStandardMaterial( { map: texture } );var globeMesh = new THREE.Mesh( globeGgeometry, globeMaterial );group.rotation.set( 0.5, 2.9, 0.1 );group.add( globeMesh );scene.add( group );} );
}

大气层光圈

大气层光圈这里也是用贴图实现,如下面这张。

代码如下

var texture = globeTextureLoader.load( './imgs/diqiu2/earth_aperture.png' );var spriteMaterial = new THREE.SpriteMaterial( {map: texture,transparent: true,opacity: 0.5,depthWrite: false} );var sprite = new THREE.Sprite( spriteMaterial );sprite.scale.set( radius * 3, radius * 3, 1 );group.add( sprite );

卫星环绕特效

这里用到一个 Mesh 和一个 Poinst 结合,分别用来实现外圈的环形和两个小卫星

光环用 PlaneGeometry 矩形平面即可,加上贴图

globeTextureLoader.load( './imgs/diqiu2/halo.png', function ( texture ) {var geometry = new THREE.PlaneGeometry( 14, 14 );var material = new THREE.MeshLambertMaterial( {map: texture, transparent: true,side: THREE.DoubleSide, depthWrite: false} );var mesh = new THREE.Mesh( geometry, material );groupHalo.add( mesh );} );

两个环绕的卫星直接使用Points即可,设置两个坐标,用来展示这2个小卫星

 globeTextureLoader.load( './imgs/diqiu2/smallEarth.png', function ( texture ) {var p1 = new THREE.Vector3( - 7, 0, 0 );var p2 = new THREE.Vector3( 7, 0, 0 );const points = [ p1,p2];const geometry = new THREE.BufferGeometry().setFromPoints( points );var material = new THREE.PointsMaterial( {map: texture,transparent: true,side: THREE.DoubleSide, size: 1, depthWrite: false} );var earthPoints = new THREE.Points( geometry, material );groupHalo.add( earthPoints );} );groupHalo.rotation.set( 1.9, 0.5, 1 );

经纬度转标转成3D空间坐标

上面已经实现了最基本的效果,接下来需要在地球上添加一下特效,比如标注、光柱、光圈等。

但是会存在一个问题,要定义地球上的一点,用经纬度是常用的办法,但是经纬度又不适合在Threejs上面使用,所以这里就需要先做一步转换工作,把经纬度坐标转换成xyz空间坐标。

这边直接提供两种转换方法:

  • 方法一:js方法转换
/**
*lng:经度
*lat:维度
*radius:地球半径
*/
function lglt2xyz(lng, lat, radius) {const phi = (180 + lng) * (Math.PI / 180)const theta = (90 - lat) * (Math.PI / 180)return {x: -radius * Math.sin(theta) * Math.cos(phi),y: radius * Math.cos(theta),z: radius * Math.sin(theta) * Math.sin(phi),}
}
  • 方法二:threejs自带

    /**
    *lng:经度
    *lat:维度
    *radius:地球半径
    */
    lglt2xyz(lng, lat, radius) {const theta = (90 + lng) * (Math.PI / 180)const phi = (90 - lat) * (Math.PI / 180)return (new THREE.Vector3()).setFromSpherical(new THREE.Spherical(radius, phi, theta))
    }
    

标注以及标注扩散光圈

要实现标注功能很简单,直接一个平面加贴图即可

这里唯一要注意的就是,再球面上的物体,需要设置好角度,不然你会发现效果会和你预想的不一样

具体参考下面方法,

 function createPointMesh( pos, texture ) {var material = new THREE.MeshBasicMaterial( {map: texture,transparent: true, //使用背景透明的png贴图,注意开启透明计算// side: THREE.DoubleSide, //双面可见depthWrite: false, //禁止写入深度缓冲区数据} );var mesh = new THREE.Mesh( planGeometry, material );var size = radius * 0.04;//矩形平面Mesh的尺寸mesh.scale.set( size, size, size );//设置mesh大小//设置mesh位置mesh.position.set( pos.x, pos.y, pos.z );// mesh在球面上的法线方向(球心和球面坐标构成的方向向量)var coordVec3 = new THREE.Vector3( pos.x, pos.y, pos.z ).normalize();// mesh默认在XOY平面上,法线方向沿着z轴new THREE.Vector3(0, 0, 1)var meshNormal = new THREE.Vector3( 0, 0, 1 );// 四元数属性.quaternion表示mesh的角度状态//.setFromUnitVectors();计算两个向量之间构成的四元数值mesh.quaternion.setFromUnitVectors( meshNormal, coordVec3 );return mesh;}

光圈的话,贴图用一张有渐变效果的png图

然后也是贴在 PlaneBufferGeometry 上,和上面标注的效果实现一样,最后要在渲染函数 animate 里面动态的修改尺寸和透明度即可。

动画效果参考如下代码,WaveMeshArr是所有光圈mesh的数组集合。

if (WaveMeshArr.length) {WaveMeshArr.forEach( function ( mesh ) {mesh._s += 0.007;mesh.scale.set( mesh.size * mesh._s, mesh.size * mesh._s, mesh.size * mesh._s );if (mesh._s <= 1.5) {//mesh._s=1,透明度=0 mesh._s=1.5,透明度=1mesh.material.opacity = ( mesh._s - 1 ) * 2;} else if (mesh._s > 1.5 && mesh._s <= 2) {//mesh._s=1.5,透明度=1 mesh._s=2,透明度=0mesh.material.opacity = 1 - ( mesh._s - 1.5 ) * 2;} else {mesh._s = 1.0;}} );}

光柱特效

你如果想在Three.js创建一个光柱效果,可以通过Three.js的矩形平面几何体PlaneGeometry创建一个网格模型,然后把一个背景透明的.png格式图片作为矩形网格模型的纹理贴图。

var plane = new THREE.PlaneGeometry(50,200)
var material = new THREE.MeshPhongMaterial({//设置矩形网格模型的纹理贴图(光柱特效)map: textureLoader.load('光柱.png'),// 双面显示side: THREE.DoubleSide,// 开启透明效果,否则颜色贴图map的透明不起作用transparent: true,
});
var mesh = new THREE.Mesh(plane, material);

为了增强立体效果,可以创建两个矩形网格模型然后90度交叉即可

// 矩形网格1
var mesh1 = new THREE.Mesh(plane, material);
// 克隆网格模型mesh1,并旋转90度
var mesh2 = mesh1.clone().rotateY(Math.PI/2)
var groupMesh= new THREE.Group()
groupMesh.add(mesh1,mesh2);

最终实现效果如下

飞线特效

飞线这里分两块介绍,一个是绘制三维三次贝赛尔曲线,另一个是飞线上模拟物体移动。

  • 飞线

    上面介绍标注的时候,就已经知道了不同位置的坐标了,这里封装了一个方法,传入2个坐标就可以生成一条贝赛尔曲线。

    目前取第一个坐标点作为飞线的起始点,比如你选择北京作为原始点,那飞线特效就是从北京飞往各个地方。

    线条的话,我这里使用Line2,因为这个支持设置线条的宽度。

    核心代码如下:

    function addLines( v0, v3 ) {// 夹角var angle = ( v0.angleTo( v3 ) * 1.8 ) / Math.PI / 0.1; // 0 ~ Math.PIvar aLen = angle * 0.4, hLen = angle * angle * 12;var p0 = new THREE.Vector3( 0, 0, 0 );// 法线向量var rayLine = new THREE.Ray( p0, getVCenter( v0.clone(), v3.clone() ) );// 顶点坐标var vtop = rayLine.at( hLen / rayLine.at( 1 ).distanceTo( p0 ) );// 控制点坐标var v1 = getLenVcetor( v0.clone(), vtop, aLen );var v2 = getLenVcetor( v3.clone(), vtop, aLen );// 绘制三维三次贝赛尔曲线var curve = new THREE.CubicBezierCurve3( v0, v1, v2, v3 );var geometry = new LineGeometry();var points = curve.getPoints( 50 );var positions = [];var colors = [];var color = new THREE.Color();/*** HSL中使用渐变* h — hue value between 0.0 and 1.0* s — 饱和度 between 0.0 and 1.0* l — 亮度 between 0.0 and 1.0*/for (var j = 0; j < points.length; j ++) {// color.setHSL( .31666+j*0.005,0.7, 0.7); //绿色color.setHSL( .81666+j,0.88, 0.715+j*0.0025); //粉色colors.push( color.r, color.g, color.b );positions.push( points[j].x, points[j].y, points[j].z );}geometry.setPositions( positions );geometry.setColors( colors );var matLine = new LineMaterial( {linewidth: 0.0006,vertexColors: true,dashed: false} );return {curve: curve,lineMesh: new Line2( geometry, matLine )};
    }
    
  • 物体移动特效

物体移动就是从飞线的起始点,飞到物体的终点。

把上面飞线生成的曲线curve,添加到数组里面。

然后循环这个数组,每个数组配套生成一个几何球体,用来当做移动的载体,再把这个球体放入一个数组。

关键来了,循环这个球体数组,把上面curve这条线段再等分100份,然后让球体坐标分别设置为上面等分100份的坐标即可,这样就可以看到球体开始循环移动了。

核心代码如下:

for (let i = 0; i < animateDots.length; i ++) {const aGeo = new THREE.SphereGeometry( 0.03, 0.03, 0.03 );const aMater = new THREE.MeshPhongMaterial( { color: '#F8D764' } );const aMesh = new THREE.Mesh( aGeo, aMater );aGroup.add( aMesh );}var vIndex = 0;function animateLine() {aGroup.children.forEach( ( elem, index ) => {const v = animateDots[index][vIndex];elem.position.set( v.x, v.y, v.z );});vIndex ++;if (vIndex > 100) {vIndex = 0;}setTimeout( animateLine, 20 );}group.add( aGroup );animateLine();

geojson数据生成中国描边以及动态流光效果

这块其实可以单独拿出来介绍,根据geojson数据生成地图用的,不过复杂的是用来做拉升,生成几何体模型用的,这边只是简单的给地图画线条。

另外就是用到的一个线条流光的shader特效,这里分开来介绍。

geojson数据可以去这个网站下载:https://datav.aliyun.com/tools/atlas/index.html

  • 根据geojson数据画线条

    上面已经有过划线的经验了,所以这块做起来也很方便,只要就是2个地方要注意
    1:加载读取geojson数据,循环后把经纬度转化成空间xyz坐标

    2:根据这些坐标,使用Line生成线条即可。

    核心代码:

    function initMap( chinaJson ) {// 遍历省份构建模型chinaJson.features.forEach( elem => {// 新建一个省份容器:用来存放省份对应的模型和轮廓线const province = new THREE.Object3D();const coordinates = elem.geometry.coordinates;coordinates.forEach( multiPolygon => {multiPolygon.forEach( polygon => {const lineMaterial = new THREE.LineBasicMaterial( { color: 0XF19553 } ); //0x3BFA9Econst positions = [];const linGeometry = new THREE.BufferGeometry();for (let i = 0; i < polygon.length; i ++) {var pos = lglt2xyz( polygon[i][0], polygon[i][1] );positions.push( pos.x, pos.y, pos.z );}linGeometry.setAttribute( 'position', new THREE.Float32BufferAttribute( positions, 3 ) );const line = new THREE.Line( linGeometry, lineMaterial );province.add( line );} );} );map.add( province );} );group.add( map );}
    
  • 根据geojson设置流光效果

    画完中国的描边后,我们还可以让中国的外边框轮廓有动态流光效果,这里还是需要下载中国外边框的geojson数据,用上面那个地址即可,把包含子区域的选项去掉打钩就行。

    这里和上面生成的方法不一样,上面最终生成的是Line,而这里最终生成的是Points,然后通过shader处理生成跑动的流光效果。

    核心shader写法如下

    uniforms:

    const singleUniforms = {u_time: uniforms2.u_time,number: { type: 'f', value: number },speed: { type: 'f', value: speed },length: { type: 'f', value: length },size: { type: 'f', value: size },color: { type: 'v3', value: color }
    };
    

    顶点着色器和片元着色器:

    <script id="vertexShader2" type="x-shader/x-vertex">varying vec2 vUv;attribute float percent;uniform float u_time;uniform float number;uniform float speed;uniform float length;varying float opacity;uniform float size;void main(){vUv = uv;vec4 mvPosition = modelViewMatrix * vec4( position, 1.0 );float l = clamp(1.0-length,0.0,1.0);gl_PointSize = clamp(fract(percent*number + l - u_time*number*speed)-l ,0.0,1.) * size * (1./length);opacity = gl_PointSize/size;gl_Position = projectionMatrix * mvPosition;}
    </script>
    <script id="fragmentShader2" type="x-shader/x-vertex">#ifdef GL_ESprecision mediump float;#endifvarying float opacity;uniform vec3 color;void main(){if(opacity <=0.2){discard;}gl_FragColor = vec4(color,opacity);}
    </script>
    

    效果如下:

总结

以上就是这篇3D地球的技术拆解,把每个技术点都掌握了,后期就能配合开发出更多的特效了。

上面已经把基本的功能代码都分享了,如果还是有童鞋需要完整源码,可以某宝搜一下 threejs 3d地球

项目基于官方最新r129版本开发,没有额外封装,小白都能看得懂哈。

我是嘟嘟MD,前Java开发,现Threejs开发~ 有兴趣的可以关注一下公众号,定期分享干货

Threejs实现酷炫3D地球技术点汇总相关推荐

  1. html5 canvas酷炫3D背景打开动画特效

    html5 canvas酷炫3D背景打开动画特效 点击跳转到演示地址 点击进入资源下载地址

  2. ztext - 简单几行代码创建酷炫 3D 特效文字的开源 JS 库

    把网页上的文字变成酷炫的 3D 风格,还能制作旋转动效,有了 ztext.js,只需要几行代码. ztext 能做什么 ztext.js 是一个能把常规的平面文字变成 3D 样式的前端开源代码库,让开 ...

  3. 用Three.js打造酷炫3D个人网站(含源码)

    引言 个人网站是程序员的第二张简历.如果你有酷炫的个人网页,面试官对你的好感度会蹭蹭蹭往上涨. 在疫情隔离期间,我用Three.js和Ammo.js制作了一个可交互的3D个人网页. 在线预览地址: w ...

  4. 【threejs】可视化大屏酷炫3D地图附源码

    目录 效果如下: 1. 前言: 2. 使用: 1.修改整体的背景图可以使用颜色或用贴图改材质​编辑 方法: 2. 取消地图上柱状图显示 3.更换地图.更换省份.市 4.修改相机的视角,页面展示的远近角 ...

  5. TensorSpace:超酷炫3D神经网络可视化框架

    TensorSpace - 一款 3D 模型可视化框架,支持多种模型,帮助你可视化层间输出,更直观地展示模型的输入输出,帮助理解模型结构和输出方法. 文末福利预警:人手必备论文阅读神器限时免费放送~? ...

  6. canvas实现2019最酷炫3D特效

    效果展示: 你可以玩一天 源码展示: <!DOCTYPE > <html> <head><title> canvas 实现3d特效 </title ...

  7. HTML+CSS+JS实现 ❤️酷炫3D瀑布流动画特效❤️

  8. HTML+CSS+JS实现 ❤️slicebox酷炫3d图片轮播切换❤️

  9. 纯CSS实现抖音3D酷炫旋转相册

    序言 最近在抖音上看到了一个酷炫3D旋转相册表白效果,博主我表示很是艳羡呐!而且博主也是做前端的,表示不能输给抖音上的小姐姐,于是我就自学了下CSS3的一些动画属性并实现了类似效果,现在分享给大家. ...

  10. 非常酷炫漂亮的3D立体照片展示墙 纯JAVASCRIPT显示

    今天主要给大家分享下一个3d显示照片墙的纯JavaScript效果demo,效果很酷炫.可以通过鼠标上下左右拉动,从而把照片墙进行360°展示. 鼠标停止后,在操作过程中,会发现,照片会以某角度轴,继 ...

最新文章

  1. 过滤所有用户的行车轨迹查找在某一区域内的用户
  2. xxl-job使用实例
  3. Python 面向对象1-面向对象介绍
  4. Chrome现在也能编辑pdf文件了!64位安卓版上线
  5. 认识flex中的sprite
  6. 追踪源码自定义负载均衡策略
  7. 手机技巧:微信这个“设置”建议关闭!否则不到半年就卡爆了
  8. MySQL 客户端命令
  9. 8.类定义、属性、初始化和析构
  10. 刘作虎亲曝一加7T外包装盒:里里外外重新设计
  11. 台湾域名总量一周统计:9月第一周新增59个
  12. 使用python进行re拆分网页内容
  13. 给浪费时间找种方法?
  14. 4.3.8 使用模板
  15. 【Proteus仿真】Arduino UNO步进电机驱动示例
  16. 理正深基坑弹性计算方法_理正深基坑整体计算与单元计算的区别
  17. 汇编语言--D/A转换实验
  18. 《算法笔记》10.5小节——图算法专题->最小生成树
  19. 【渝粤教育】电大中专常见病药物治疗 (3)作业 题库
  20. JavaScript怎么识别360浏览器?JS识别360急速模式方案,360流氓浏览器

热门文章

  1. 泛微oa流程表单之流程阻止提交
  2. java拯救公主_Java实现 计蒜客 拯救行动
  3. idea 报@Override is not allowed when implementing interface method的问题解决
  4. 网络广告中各种广告形式
  5. 新一批交通强国试点工作启动
  6. Excel·VBA单元格内容拆分
  7. 张冬:OpenPOWER CAPI为什么这么快?(二)
  8. GLM(General Language Model)代码分析
  9. centos各文件夹作用
  10. mysql7.5安装教程_腾讯云CentOS7.5安装Mysql