基于Three.js的3D点云地球可视化项目剖析(附源码下载)

424 阅读7分钟

ezgif-1c593736c7623a.gif

一、引言

在当今数字化时代,三维可视化技术正以前所未有的速度发展,为我们带来了更加直观、生动的视觉体验。在众多的三维可视化库中,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 随机点生成算法

在生成星空背景时,需要随机生成大量的星星。本项目使用了一种随机点生成算法,将星星均匀地分布在一个球面上。具体步骤如下:

  1. 随机生成一个半径在一定范围内的球体。
  2. 在球面上随机选择一个点,通过极坐标转换计算该点在笛卡尔坐标系中的位置。
  3. 为每个星星设置颜色和亮度。

四、实现方法

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的数孪技术,后台回复【点云地球】即可获取网盘下载链接。