一、引言
在当今数字化时代,三维可视化技术正以前所未有的速度发展,为我们带来了更加直观、生动的视觉体验。在众多的三维可视化库中,Three.js 凭借其简洁易用的 API 和强大的功能,成为了开发者们的首选。本文将基于一个使用 Three.js 实现的 3D 点云地球可视化项目,从技术原理、实现方法、应用场景等多个方面进行深入剖析。
二、项目概述
本项目旨在创建一个逼真的 3D 点云地球模型,展示地球的表面特征和星空背景。用户可以通过鼠标交互来旋转和缩放地球,观察不同角度的地球景象。项目主要包含以下几个核心部分:
- 地球模型的构建:使用 Three.js 的几何体和材质创建地球的基本形状。
- 点云效果的实现:通过自定义着色器和纹理映射,为地球表面添加点云效果,模拟地形起伏和颜色变化。
- 星空背景的添加:生成随机分布的星星,营造出宇宙星空的氛围。
- 交互功能的实现:使用 OrbitControls 实现鼠标交互,让用户可以自由控制地球的旋转和缩放。
三、技术原理
3.1 Three.js 基础
Three.js 是一个基于 WebGL 的 JavaScript 3D 库,它提供了丰富的 API,让开发者可以轻松地在网页上创建和渲染 3D 场景。Three.js 的核心概念包括场景(Scene)、相机(Camera)和渲染器(Renderer)。
- 场景(Scene):是所有 3D 对象的容器,类似于舞台,所有的物体都放置在这个舞台上。
- 相机(Camera):定义了观察者的视角,决定了我们从哪个角度观察场景。
- 渲染器(Renderer):负责将场景和相机的信息渲染成 2D 图像,显示在网页上。
3.2 几何体和材质
在 Three.js 中,几何体(Geometry)定义了物体的形状,而材质(Material)定义了物体的外观。本项目中使用了以下几种几何体和材质:
- IcosahedronGeometry:正二十面体几何体,用于创建地球的基本形状。通过细分参数可以控制几何体的精细程度。
- MeshBasicMaterial:基础网格材质,用于创建地球的线框模型,方便观察地球的基本形状。
- ShaderMaterial:自定义着色器材质,用于实现地球表面的点云效果。通过自定义顶点着色器和片元着色器,可以实现复杂的渲染效果。
3.3 纹理映射
纹理映射是将 2D 图像(纹理)应用到 3D 物体表面的过程。本项目中使用了多种纹理来增强地球的真实感:
- 颜色纹理(Color Map):用于定义地球表面的颜色分布。
- 高程纹理(Elevation Map):用于模拟地球表面的地形起伏。
- 透明度纹理(Alpha Map):用于控制地球表面某些区域的透明度,实现大气层等效果。
3.4 着色器编程
着色器是运行在 GPU 上的小程序,用于控制物体的渲染过程。本项目中使用了自定义的顶点着色器和片元着色器:
- 顶点着色器(Vertex Shader):负责处理每个顶点的位置和属性,例如根据高程纹理调整顶点的位置,实现地形起伏效果。
- 片元着色器(Fragment Shader):负责处理每个像素的颜色和透明度,例如根据颜色纹理和透明度纹理计算像素的最终颜色。
3.5 随机点生成算法
在生成星空背景时,需要随机生成大量的星星。本项目使用了一种随机点生成算法,将星星均匀地分布在一个球面上。具体步骤如下:
- 随机生成一个半径在一定范围内的球体。
- 在球面上随机选择一个点,通过极坐标转换计算该点在笛卡尔坐标系中的位置。
- 为每个星星设置颜色和亮度。
四、实现方法
4.1 项目结构
项目的文件结构如下:
.gitignore
index.html
index.js
src/
getStarfield.js
circle.png
04_rainbow1k.jpg
01_earthbump1k.jpg
02_earthspec1k.jpg
.gitignore:用于忽略不需要提交到版本控制系统的文件和文件夹。index.html:项目的入口文件,包含 HTML 结构和引入 JavaScript 文件的代码。index.js:项目的主逻辑文件,负责创建场景、相机、渲染器等,并初始化地球模型和星空背景。src/:源代码文件夹,包含自定义的 JavaScript 文件和纹理图像。getStarfield.js:用于生成星空背景的函数。circle.png:星星的纹理图像。04_rainbow1k.jpg:地球表面的颜色纹理。01_earthbump1k.jpg:地球表面的高程纹理。02_earthspec1k.jpg:地球表面的透明度纹理。
4.2 初始化场景、相机和渲染器
在 index.js 文件中,首先需要初始化场景、相机和渲染器:
import * as THREE from "three";
import { OrbitControls } from "three/examples/jsm/controls/OrbitControls.js";
import getStarfield from "./src/getStarfield.js";
import starSpriteImage from './src/circle.png';
import colorMapImage from './src/04_rainbow1k.jpg';
import elevMapImage from './src/01_earthbump1k.jpg';
import alphaMapImage from './src/02_earthspec1k.jpg';
// 创建场景
const scene = new THREE.Scene();
// 创建相机
const camera = new THREE.PerspectiveCamera(45, innerWidth / innerHeight, 0.1, 1000);
camera.position.set(0, 0, 3.5);
// 创建渲染器
const renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setSize(innerWidth, innerHeight);
renderer.setPixelRatio(window.devicePixelRatio);
document.body.appendChild(renderer.domElement);
// 创建轨道控制器
const orbitCtrl = new OrbitControls(camera, renderer.domElement);
orbitCtrl.enableDamping = true;
4.3 加载纹理
使用 THREE.TextureLoader 加载纹理图像:
const textureLoader = new THREE.TextureLoader();
const starSprite = textureLoader.load(starSpriteImage);
const colorMap = textureLoader.load(colorMapImage);
const elevMap = textureLoader.load(elevMapImage);
const alphaMap = textureLoader.load(alphaMapImage);
4.4 创建地球模型
创建地球的线框模型和点云模型:
const globeGroup = new THREE.Group();
scene.add(globeGroup);
// 创建线框模型
const geo = new THREE.IcosahedronGeometry(1, 10);
const mat = new THREE.MeshBasicMaterial({
color: 0x202020,
wireframe: true,
});
const cube = new THREE.Mesh(geo, mat);
globeGroup.add(cube);
// 创建点云模型
const detail = 120;
const pointsGeo = new THREE.IcosahedronGeometry(1, detail);
const vertexShader = `
uniform float size;
uniform sampler2D elevTexture;
varying vec2 vUv;
varying float vVisible;
void main() {
vUv = uv;
vec4 mvPosition = modelViewMatrix * vec4( position, 1.0 );
float elv = texture2D(elevTexture, vUv).r;
vec3 vNormal = normalMatrix * normal;
vVisible = step(0.0, dot( -normalize(mvPosition.xyz), normalize(vNormal)));
mvPosition.z += 0.35 * elv;
gl_PointSize = size;
gl_Position = projectionMatrix * mvPosition;
}
`;
const fragmentShader = `
uniform sampler2D colorTexture;
uniform sampler2D alphaTexture;
varying vec2 vUv;
varying float vVisible;
void main() {
if (floor(vVisible + 0.1) == 0.0) discard;
float alpha = 1.0 - texture2D(alphaTexture, vUv).r;
vec3 color = texture2D(colorTexture, vUv).rgb;
gl_FragColor = vec4(color, alpha);
}
`;
const uniforms = {
size: { type: "f", value: 4.0 },
colorTexture: { type: "t", value: colorMap },
elevTexture: { type: "t", value: elevMap },
alphaTexture: { type: "t", value: alphaMap }
};
const pointsMat = new THREE.ShaderMaterial({
uniforms: uniforms,
vertexShader,
fragmentShader,
transparent: true
});
const points = new THREE.Points(pointsGeo, pointsMat);
globeGroup.add(points);
4.5 添加光照
为场景添加半球光,增强场景的真实感:
const hemiLight = new THREE.HemisphereLight(0xffffff, 0x080820, 3);
scene.add(hemiLight);
4.6 生成星空背景
调用 getStarfield 函数生成星空背景:
const stars = getStarfield({ numStars: 4500, sprite: starSprite });
scene.add(stars);
4.7 动画循环
使用 requestAnimationFrame 实现动画循环,更新场景的渲染:
function animate() {
renderer.render(scene, camera);
globeGroup.rotation.y += 0.002;
requestAnimationFrame(animate);
orbitCtrl.update();
}
animate();
4.8 处理窗口大小变化
监听窗口大小变化事件,更新相机和渲染器的参数:
window.addEventListener('resize', function () {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(window.innerWidth, window.innerHeight);
}, false);
4.9 生成星空背景的具体实现
在 getStarfield.js 文件中,实现了生成星空背景的函数:
import * as THREE from "three";
export default function getStarfield({ numStars = 500, sprite } = {}) {
function randomSpherePoint() {
const radius = Math.random() * 25 + 25;
const u = Math.random();
const v = Math.random();
const theta = 2 * Math.PI * u;
const phi = Math.acos(2 * v - 1);
let x = radius * Math.sin(phi) * Math.cos(theta);
let y = radius * Math.sin(phi) * Math.sin(theta);
let z = radius * Math.cos(phi);
return {
pos: new THREE.Vector3(x, y, z),
hue: 0.6, // radius * 0.02 + 0.5
minDist: radius,
};
}
const verts = [];
const colors = [];
const positions = [];
let col;
for (let i = 0; i < numStars; i += 1) {
let p = randomSpherePoint();
const { pos, hue } = p;
positions.push(p);
col = new THREE.Color().setHSL(hue, 0.2, Math.random());
verts.push(pos.x, pos.y, pos.z);
colors.push(col.r, col.g, col.b);
}
const geo = new THREE.BufferGeometry();
geo.setAttribute("position", new THREE.Float32BufferAttribute(verts, 3));
geo.setAttribute("color", new THREE.Float32BufferAttribute(colors, 3));
const mat = new THREE.PointsMaterial({
size: 0.2,
vertexColors: true,
map: sprite,
});
const points = new THREE.Points(geo, mat);
return points;
}
五、应用场景
5.1 地理信息系统(GIS)
在地理信息系统中,3D 点云地球可以用于展示地球的地形、地貌、气象数据等信息。用户可以通过交互操作,查看不同地区的详细信息,进行地理分析和决策。
5.2 教育领域
在教育领域,3D 点云地球可以作为教学工具,帮助学生更好地理解地球的地理知识。例如,展示地球的自转、公转、气候分布等现象,提高学生的学习兴趣和效果。
5.3 虚拟旅游
在虚拟旅游领域,3D 点云地球可以用于创建逼真的虚拟旅游场景,让用户身临其境地感受不同地区的风景和文化。用户可以通过交互操作,自由探索虚拟世界,体验不同的旅游乐趣。
5.4 科学研究
在科学研究领域,3D 点云地球可以用于模拟地球的自然现象和环境变化,帮助科学家更好地理解地球的演化过程和生态系统。例如,模拟气候变化、地震活动、海洋流动等现象,为科学研究提供支持。
六、总结
通过对这个基于 Three.js 的 3D 点云地球可视化项目的剖析,我们深入了解了 Three.js 的基本原理和使用方法,以及如何利用 Three.js 创建复杂的 3D 场景。希望本文能够对读者在使用 Three.js 进行 3D 可视化开发时有所帮助。
源码下载请关注公众号: sky的数孪技术,后台回复【点云地球】即可获取网盘下载链接。