最新公告
  • 欢迎您光临起源地模板网,本站秉承服务宗旨 履行“站长”责任,销售只是起点 服务永无止境!立即加入钻石VIP
  • Threejs实现酷炫3D地球技术点汇总

    正文概述 掘金(嘟嘟MD)   2021-06-24   959

    前言

    Threejs实现酷炫3D地球技术点汇总

    本篇介绍一下如何用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>
    
    

    动态星空背景介绍

    Threejs实现酷炫3D地球技术点汇总

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

    • 随机生成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 );
    

    地球模型

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

    Threejs实现酷炫3D地球技术点汇总

    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 );
      } );
    }
    

    大气层光圈

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

    Threejs实现酷炫3D地球技术点汇总

    Threejs实现酷炫3D地球技术点汇总

    代码如下

    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 );
    

    卫星环绕特效

    Threejs实现酷炫3D地球技术点汇总

    这里用到一个 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图

    Threejs实现酷炫3D地球技术点汇总

    然后也是贴在 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,透明度=1
              mesh.material.opacity = ( mesh._s - 1 ) * 2;
            } else if (mesh._s > 1.5 && mesh._s <= 2) {
               //mesh._s=1.5,透明度=1 mesh._s=2,透明度=0
               mesh.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);
    

    最终实现效果如下

    Threejs实现酷炫3D地球技术点汇总

    飞线特效

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

    • 飞线

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

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

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

      核心代码如下:

      function addLines( v0, v3 ) {
      		// 夹角
      		var angle = ( v0.angleTo( v3 ) * 1.8 ) / Math.PI / 0.1; // 0 ~ Math.PI
      		var 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数据可以去这个网站下载:datav.aliyun.com/tools/atlas…

    • 根据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 } ); //0x3BFA9E
      					const 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_ES
          precision mediump float;
          #endif
          varying float opacity;
          uniform vec3 color;
          void main(){
              if(opacity <=0.2){
                  discard;
              }
              gl_FragColor = vec4(color,1.0);
          }
      </script>
      

      效果如下:

    Threejs实现酷炫3D地球技术点汇总

    总结

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

    有需要源码的童鞋,可以某宝搜一下 threejs 3d地球

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

    我是嘟嘟MD,前Java开发,现Threejs开发~ 有兴趣的可以关注一下公众号,定期分享干货 Threejs实现酷炫3D地球技术点汇总


    起源地下载网 » Threejs实现酷炫3D地球技术点汇总

    常见问题FAQ

    免费下载或者VIP会员专享资源能否直接商用?
    本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
    提示下载完但解压或打开不了?
    最常见的情况是下载不完整: 可对比下载完压缩包的与网盘上的容量,若小于网盘提示的容量则是这个原因。这是浏览器下载的bug,建议用百度网盘软件或迅雷下载。若排除这种情况,可在对应资源底部留言,或 联络我们.。
    找不到素材资源介绍文章里的示例图片?
    对于PPT,KEY,Mockups,APP,网页模版等类型的素材,文章内用于介绍的图片通常并不包含在对应可供下载素材包内。这些相关商业图片需另外购买,且本站不负责(也没有办法)找到出处。 同样地一些字体文件也是这种情况,但部分素材会在素材包内有一份字体下载链接清单。
    模板不会安装或需要功能定制以及二次开发?
    请QQ联系我们

    发表评论

    还没有评论,快来抢沙发吧!

    如需帝国cms功能定制以及二次开发请联系我们

    联系作者

    请选择支付方式

    ×
    迅虎支付宝
    迅虎微信
    支付宝当面付
    余额支付
    ×
    微信扫码支付 0 元