Threejs implements a collection of cool 3D earth technology points
Preview online: joy1412.cn/online/show…
preface
Here’s how to create a cool 3D Earth effect with Threejs, using the following skill points:
- Starry dynamic background
- The earth model
- Atmospheric aperture
- Satellite orbit special effects
- Latitude and longitude coordinates are converted to 3D spatial coordinates
- Label and label diffused aperture
- Light effects
- Fly line effects
- Geojson data to generate Chinese stroke and dynamic streamer effects
The body of the
The techniques used are described here one by one. The first step is to set up the initialization interface and set up the renderer, camera and basic lighting.
If you don’t understand, refer to the template on this page.
<! DOCTYPEhtml>
<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, the 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 Initialize render scene */
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 Initialize the camera */
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 Initialize the scene */
function initScene() {
scene = new THREE.Scene();
scene.background = new THREE.Color( 0x020924 );
scene.fog = new THREE.Fog( 0x020924.200.1000 );
window.scene = scene;
}
/** * Initializes user interaction **/
function initControls() {
controls = new OrbitControls( camera, renderer.domElement );
controls.enableDamping = true;
controls.enableZoom = true;
controls.autoRotate = false;
controls.autoRotateSpeed = 2;
controls.enablePan = true;
}
/ * * *@description Initialize light */
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);
}
/** * window changes **/
function onWindowResize() {
camera.aspect = innerWidth / innerHeight;
camera.updateProjectionMatrix();
renderer.setSize( innerWidth, innerHeight );
renders();
}
/ * * *@description Render * /
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>
Copy the code
Introduction to dynamic starry sky background
As the background of the earth, the dynamic starry sky is more cool. The prototype map is used to simulate the sphere of the original square points, and the dynamic color and rotation offset are used to better simulate the starry sky effect.
- Randomly generate 10000 coordinate points, set different colors
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));Copy the code
Add the generated Vector3 and Color into the array, respectively, and add geometry to the geometry to create the star geometry. In Color, setHSL can set the Color and saturation. In this case, random is used to random the Color.
- Generate the material using ParticleBasicMaterial
ParticleBasicMaterial is used to match the sample system. Here we can set the particle size, texture, transparency and other Settings as follows:
var starsMaterial = new THREE.ParticleBasicMaterial( {
map: texture,
size: 1.transparent: true.opacity: 1.vertexColors: true.//true: If the geometry's colors attribute has a value, the particle will discard the first attribute, color, and apply the color of the geometry's colors attribute
blending: THREE.AdditiveBlending,
sizeAttenuation: true});Copy the code
- Generate the model using ParticleSystem
The Particle system used here is to provide performance. If Particle is used to dynamically and randomly generate 10000 particles, the frame rate will definitely be affected. The Particle system used here is equal to only one Mesh, which greatly improves performance.
Add the geometry and ParticleBasicMaterial generated above to generate a ParticleSystem as follows:
let stars = new THREE.ParticleSystem( geometry, starsMaterial );
stars.scale.set( 300.300.300 );
scene.add( stars );
Copy the code
The earth model
The earth model is relatively simple, with a direct map and a ball
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 ); }); }Copy the code
Atmospheric aperture
The atmospheric aperture is also mapped here, as shown below.
The following code
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 );
Copy the code
Satellite orbit special effects
Here, a Mesh and a Poinst are combined to realize the outer ring and two small satellites respectively
The aura can be mapped using the PlaneGeometry rectangular plane
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 = newTHREE.Mesh( geometry, material ); groupHalo.add( mesh ); });Copy the code
The two orbiting satellites can be directly used as Points to set up two coordinates to display the two small satellites
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 = newTHREE.Points( geometry, material ); groupHalo.add( earthPoints ); }); groupHalo.rotation.set(1.9.0.5.1 );
Copy the code
Latitude and longitude are transformed into 3D spatial coordinates
The basic effects have been achieved above, now we need to add some special effects on the earth, such as annotations, light columns, apertures, etc.
But there is a problem, to define a point on the earth, using latitude and longitude is a common way, but latitude and longitude is not suitable for Threejs, so here we need to do a step of conversion, convert latitude and longitude coordinates into XYZ space coordinates.
Two conversion methods are directly provided here:
- Method 1: JS method conversion
/** * LNG: longitude * Lat: dimension *radius: earth 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),
}
}
Copy the code
-
Method 2: Threejs comes with it
/** * LNG: longitude * Lat: dimension *radius: earth 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)) } Copy the code
Label and label diffused aperture
To achieve the annotation function is very simple, directly a plane to paste the map
The only thing to notice here is that the object on the surface of the sphere needs to be set at a good Angle, otherwise you will find that the effect is not what you expect
Refer to the following method for details,
function createPointMesh( pos, texture ) {
var material = new THREE.MeshBasicMaterial( {
map: texture,
transparent: true.// Use a PNG map with a transparent background
// Side: THREE.DoubleSide, // Both sides visible
depthWrite: false.// Forbid writing depth buffer data});var mesh = new THREE.Mesh( planGeometry, material );
var size = radius * 0.04;// The size of the rectangular plane Mesh
mesh.scale.set( size, size, size );// Set mesh size
// Set the mesh position
mesh.position.set( pos.x, pos.y, pos.z );
// The normal direction of the mesh on the sphere (direction vector composed of spherical center and spherical coordinates)
var coordVec3 = new THREE.Vector3( pos.x, pos.y, pos.z ).normalize();
Vector3(0, 0, 1) // The mesh defaults to the XOY plane, and the normal direction is along the z-axis.
var meshNormal = new THREE.Vector3( 0.0.1 );
// The quaternion attribute represents the Angle state of the mesh
//.setFromUnitVectors(); Compute the quaternion value formed between two vectors
mesh.quaternion.setFromUnitVectors( meshNormal, coordVec3 );
return mesh;
}
Copy the code
For aperture, use a PNG image with gradient effect
The geometry is attached to PlaneBufferGeometry, as indicated above, and the dimensions and transparency are changed dynamically in animate.
The WaveMeshArr is an array of all aperture mesh arrays.
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, transparency =0 mesh._s=1.5, transparency =1
mesh.material.opacity = ( mesh._s - 1 ) * 2;
} else if (mesh._s > 1.5 && mesh._s <= 2) {
//mesh._s=1.5, transparency =1 mesh._s=2, transparency =0
mesh.material.opacity = 1 - ( mesh._s - 1.5 ) * 2;
} else {
mesh._s = 1.0; }}); }Copy the code
Light effects
If you want to create a light column effect in three. js, create a mesh model using the rectangular PlaneGeometry of three. js and use a transparent. PNG image as a texture map for the rectangular mesh model.
var plane = new THREE.PlaneGeometry(50.200)
var material = new THREE.MeshPhongMaterial({
// Set the texture map of the rectangular mesh model (light column effect)
map: textureLoader.load('light. PNG'),
// Double-sided display
side: THREE.DoubleSide,
// Enable the transparency effect, otherwise the color map's transparency does not work
transparent: true});var mesh = new THREE.Mesh(plane, material);
Copy the code
To enhance the stereo effect, create two rectangular mesh models and cross them at 90 degrees
// Rectangle grid 1
var mesh1 = new THREE.Mesh(plane, material);
// Clone the mesh model mesH1 and rotate it 90 degrees
var mesh2 = mesh1.clone().rotateY(Math.PI/2)
var groupMesh= new THREE.Group()
groupMesh.add(mesh1,mesh2);
Copy the code
The final result is as follows
Fly line effects
The flying line is divided into two parts. One is to draw three-dimensional cubic Bezier curve, and the other is to simulate object movement on the flying line.
-
Fly line
When I introduced the annotation above, I already knew the coordinates of the different positions. Here I encapsulated a method that passed in two coordinates to generate a Bezier curve.
Now take the first coordinate point as the starting point of the fly line. For example, if you choose Beijing as the original point, the fly line effect will fly from Beijing to all places.
For lines, I use Line2 because this supports setting the width of the line.
The core code is as follows:
function addLines( v0, v3 ) { / / the Angle 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 ); // Normal vector var rayLine = new THREE.Ray( p0, getVCenter( v0.clone(), v3.clone() ) ); // Vertex coordinates var vtop = rayLine.at( hLen / rayLine.at( 1 ).distanceTo( p0 ) ); // Control point coordinates var v1 = getLenVcetor( v0.clone(), vtop, aLen ); var v2 = getLenVcetor( v3.clone(), vtop, aLen ); // Draw three-dimensional cubic Bezier curves 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(); /** * HUE value between 0.0 and 1.0 * s - Saturation between 0.0 and 1.0 * L - Brightness between 0.0 and 1.0 */ for (var j = 0; j < points.length; j ++) { / / color. SetHSL (. 31666 + j * 0.005, 0.7, 0.7); / / green color.setHSL( 81666.+j,0.88.0.715+j*0.0025); / / pink 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 ) }; } Copy the code
-
Object movement effect
Object movement is to fly from the beginning of the fly line to the end of the object.
Add the curve generated by the above flying line, curve, to the array.
Then loop through the array, generating a geometric sphere for each array, which is used as a moving carrier, and place the sphere into an array.
The key is to loop through the array of spheres. Divide the top curve segment by 100, and then set the coordinates of the spheres to those of the top curve segment by 100. Then you can see that the spheres begin to circulate.
The core code is as follows:
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();
Copy the code
Geojson data to generate Chinese stroke and dynamic streamer effects
In fact, this section can be introduced separately, which is used to generate maps based on geoJSON data, but the complex one is used for pulling up and generating geometry models. This side is just a simple line for the ground drawing.
Another is the shader effect used for a line streamer, which is introduced separately here.
Geojson can go to this website to download data: datav.aliyun.com/tools/atlas…
-
Draw lines based on the GeoJSON data
First, load and read the GEOJSON data. After the loop, convert the latitude and longitude into space XYZ coordinates
2: Based on these coordinates, use Line to generate lines.
Core code:
function initMap( chinaJson ) { // Build the model through the provinces chinaJson.features.forEach( elem= > { // Create a new province container to store the model and contour of the province 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 = newTHREE.Line( linGeometry, lineMaterial ); province.add( line ); }); }); map.add( province ); }); group.add( map ); }Copy the code
-
Set the streamer effect according to geoJSON
After drawing the stroke of China, we can also make the outline of the outer border of China have a dynamic streamer effect. Here, we still need to download the GeoJSON data of the outer border of China, just use the address above, and remove the check box of the option containing the sub-region.
This is different from the above method, where lines are generated and Points are generated, and the running streamer effect is then generated by shader.
The core shader is written as follows
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 } }; Copy the code
Vertex shaders and slice shaders:
<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> Copy the code
The effect is as follows:
conclusion
The above is the technical disassembly of 3D Earth. After mastering each technical point, we can develop more special effects in the later stage.
There is a need for source code of children’s shoes, you can search a treasure threejs 3D Earth
The project is based on the latest version of the official R129 development, no additional packaging, small white can understand ha.
I am dudu MD, the former Java development, now Threejs development ~ interested can pay attention to the public number, regularly share dry goods