Threejs реализует крутую сводку по технологии 3D Earth

внешний интерфейс three.js
Threejs реализует крутую сводку по технологии 3D Earth

Оригинальный адрес:Threejs реализует крутую сводку по технологии 3D Earth

онлайн просмотр:Joy1412. Талант/онлайн/шоу…

предисловие

ezgif.com-gif-maker

В этой статье рассказывается, как использовать 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>

Внедрение динамического звездного фона

image-20210621164543735

В качестве фона земли более круто использовать динамическое звездное небо.Используя карту-прототип, чтобы исходная квадратная точка имитировала сферу, а также динамически устанавливая цвет и задавая смещение вращения, можно лучше имитировать звездное небо. эффект.

  • Произвольно сгенерируйте 10 000 точек координат и установите разные цвета
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 в массив соответственно, а затем добавьте их в геометрию, чтобы была доступна геометрия звездного неба. В Color setHSL может установить цвет и насыщенность, здесь используются случайные цвета.

  • Создание материалов с помощью 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

Эта система частиц используется здесь, она заключается в том, чтобы обеспечить производительность. Если вы используете частицу мастера для генерации 10 000 слов, частота кадров определенно поражает. Здесь частицея слов, это эквивалентно только одной сетке, что может значительно повысить производительность.

Сгенерированная выше геометрическая геометрия и материал, создающие ParticleBasicMaterial ParticleSystem, как показано ниже:

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

Модель Земли

Модель земли относительно проста, прямая карта + шар, чтобы получить ее.

image-20210621164709105

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

Атмосферная апертура

Атмосферная апертура также реализована с помощью текстур, таких как приведенная ниже.

image-20210621164959169

image-20210621164924403

код показывает, как показано ниже

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

Эффект спутникового объемного звучания

image-20210621165555877

Здесь используется комбинация Mesh и Pointst для реализации внешнего кольца и двух малых спутников соответственно.

Гало может использовать прямоугольную плоскость 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 );
		} );

Два окружающих спутника могут напрямую использовать точки, установите две координаты для отображения этих двух маленьких спутников.

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

Преобразование широты и долготы в трехмерные пространственные координаты

Самые основные эффекты были достигнуты выше, теперь нам нужно добавить специальные эффекты на земле, такие как метки, световые лучи, отверстия и т. д.

Но будет проблема.Определение точки на земле с использованием широты и долготы является распространенным методом, но широта и долгота не подходят для использования на Threejs, поэтому здесь нам нужно сделать шаг работы по преобразованию, преобразовать координаты широты и долготы в пространственные координаты xyz.

Здесь непосредственно представлены два метода преобразования:

  • Способ 1: преобразование метода 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),
  }
}
  • Способ 2: поставляется с 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 с эффектом градиента для текстуры.

image-20210621203748125

Затем он также вставляется в PlaneBufferGeometry, что аналогично эффекту, отмеченному выше.Наконец, размер и прозрачность должны динамически изменяться в функции рендеринга animate.

Эффект анимации относится к следующему коду: WaveMeshArr — это коллекция массивов всех апертурных сеток.

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

Окончательный эффект выглядит следующим образом

image-20210621222637050

Эффекты летящей линии

Линия полета состоит из двух частей: одна предназначена для рисования трехмерной кубической кривой Безье, а другая — для имитации движения объектов по линии полета.

  • линия полета

    Когда аннотация была введена выше, координаты разных положений уже были известны.Здесь метод инкапсулирован, и кривая Безье может быть сгенерирована путем передачи 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 )
    		};
    }
    
  • Эффекты движения объекта

Движение объекта происходит от начальной точки линии полета до конечной точки объекта.

Добавьте кривую кривой, созданную летящей линией выше, в массив.

Затем зациклите этот массив, каждый массив сопоставляется для создания геометрической сферы, которая используется в качестве движущейся несущей, а затем помещает эту сферу в массив.

Ключ здесь, зациклите массив сфер, разделите сегмент кривой выше на 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, но комплекс используется для создания вытягивания генерируют используемую геометрию, используемую, здесь просто для рисования линий.

Другой — это шейдерный эффект используемого линейного стримера, который будет представлен здесь отдельно.

Данные Geojson можно загрузить с этого веб-сайта:DAT av.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, а затем обрабатывается шейдером для создания эффекта бегущего стримера.

    Основной шейдер написан следующим образом

    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>
    

    Эффект следующий:

ezgif.com-gif-maker (1)

Суммировать

Вышеизложенное является техническим разбором этой 3D-Земли.После освоения каждого технического пункта на более позднем этапе можно разработать дополнительные спецэффекты.

Есть детская обувь, для которой нужен исходный код, вы можете поискать на сокровищетрехмерный глобус

Проект разработан на основе последней официальной версии r129, без дополнительной упаковки, в ней разберется даже Xiaobai.

Я Dudu MD. Раньше я разрабатывал на Java, а теперь разрабатываю Threejs~ Если вам интересно, вы можете обратить внимание на публичный аккаунт и регулярно делиться галантерейными товарами.