使用three.js实现3D地球

效果示例

引入 Three.js

首先,在 工程中中引入 Three.js 库:

css 复制代码
npm install three
npm i --save-dev @types/three

实现步骤

  1. 初始化场景、相机与渲染器
    • 创建场景 new THREE.Scene()
    • 添加雾化效果 new THREE.FogExp2
    • 创建透视相机 new THREE.PerspectiveCamera
    • 渲染器 new THREE.WebGLRenderer
    • 添加相机控件 OrbitControls
ts 复制代码
  scene = new THREE.Scene()
  scene.fog = new THREE.FogExp2(0x000033, 0.1)
  camera = new THREE.PerspectiveCamera(
    65,
    container.value.clientWidth / container.value.clientHeight,
    0.1,
    1000
  )
  renderer = new THREE.WebGLRenderer({ 
    antialias: true,
    alpha: true
  })
  renderer.setSize(container.value.clientWidth, container.value.clientHeight)
  renderer.setPixelRatio(window.devicePixelRatio)
  container.value.appendChild(renderer.domElement)

  controls = new OrbitControls(camera, renderer.domElement)
  controls.enableDamping = true
  controls.dampingFactor = 0.05
  controls.minDistance = 1.8
  controls.maxDistance = 5
  1. 创建地球模型
    • 创建三维球体 new THREE.SphereGeometry
    • 加载贴图地球 new THREE.TextureLoader
    • 添加高光 new THREE.MeshPhongMaterial
    • 球体外围添加一层云层贴图
ts 复制代码
  const geometry = new THREE.SphereGeometry(1, 128, 128)
  const textureLoader = new THREE.TextureLoader()
  const earthTexture = textureLoader.load(earth_atmos)

  const material = new THREE.MeshPhongMaterial({
    map: earthTexture,
    specular: new THREE.Color(0xaaaaaa),
    shininess: 15,
    emissive: 0x000000, // 确保地球不自发光
    emissiveIntensity: 0
  })

  earthMesh = new THREE.Mesh(geometry, material)
  earthMesh.position.y = yOffest
  scene.add(earthMesh)

  // 添加云层
  const cloudsTexture = textureLoader.load(earth_clouds)
  const cloudsMaterial = new THREE.MeshPhongMaterial({
    map: cloudsTexture,
    transparent: true,
    opacity: 0.6,
    depthWrite: false
  })
  const cloudsMesh = new THREE.Mesh(
    new THREE.SphereGeometry(1.01, 128, 128),
    cloudsMaterial
  )
  cloudsMesh.position.y = yOffest
  scene.add(cloudsMesh)
  1. 实现飞线
    • 创建曲线路径 new THREE.QuadraticBezierCurve3
    • 创建曲线材质 new THREE.ShaderMaterial
    • 添加箭头 new THREE.ConeGeometry
    • 添加文字 new THREE.CanvasTexture
ts 复制代码
  const curve = new THREE.QuadraticBezierCurve3(
    startVec,
    new THREE.Vector3().addVectors(startVec, endVec).multiplyScalar(0.5).normalize().multiplyScalar(1.2),
    endVec
  )
const createFlyLineMaterial = (
  startColor: THREE.Color,
  endColor: THREE.Color,
  lineWidth: number
) => {
  return new THREE.ShaderMaterial({
    uniforms: {
      time: { value: 0 },
      startColor: { value: startColor },
      endColor: { value: endColor },
      opacity: { value: 0.8 },
      lineWidth: { value: lineWidth }
    },
    vertexShader: `
      attribute float progress;
      varying float vProgress;
      
      void main() {
        vProgress = progress;
        gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
      }
    `,
    fragmentShader: `
      uniform float time;
      uniform vec3 startColor;
      uniform vec3 endColor;
      uniform float opacity;
      uniform float lineWidth;
      varying float vProgress;
      
      void main() {
        vec3 color = mix(startColor, endColor, vProgress);
        float glowIntensity = lineWidth * 0.5;
        float glow = sin(5.0 * (vProgress - time * 0.3)) * 0.3 + 0.7;
        glow = pow(glow, 2.0) * glowIntensity;
        float alpha = mix(0.1, 1.0, vProgress) * opacity * glow;
        gl_FragColor = vec4(color * (1.0 + glow * 0.3), alpha);
      }
    `,
    transparent: true,
    depthTest: false,
    blending: THREE.AdditiveBlending,
    linewidth: lineWidth
  })
}
const createArrow = (color: THREE.Color) => {
  const arrowGeometry = new THREE.ConeGeometry(0.004, 0.02, 8)
  const arrowMaterial = new THREE.MeshBasicMaterial({ 
    color: color,
    transparent: true,
    opacity: 0.9
  })
  const arrow = new THREE.Mesh(arrowGeometry, arrowMaterial)
  arrow.rotation.x = Math.PI / 2
  arrow.position.set(0, -100, 0)
  
  return arrow
}
  const texture = new THREE.CanvasTexture(canvas)
  const spriteMaterial = new THREE.SpriteMaterial({ 
    map: texture,
    transparent: true
  })
  1. 动画优化
    • 添加云层自转 requestAnimationFrame
    • 添加箭头动画
ts 复制代码
  const animateEarth = () => {
    cloudsMesh.rotation.y += 0.0007
    animationId = requestAnimationFrame(animateEarth)
  }
const animateFlyLines = () => {
  const time = Date.now() * 0.0005
  
  flyLines.forEach(flyLine => {
    flyLine.material.uniforms.time.value = time
    if (flyLine.arrow) {
      flyLine.progress = (flyLine.progress + 0.0015) % 1
      const point = flyLine.curve.getPoint(flyLine.progress)
      const tangent = flyLine.curve.getTangent(flyLine.progress).normalize()
      flyLine.arrow.position.copy(point)
      flyLine.arrow.lookAt(point.clone().add(tangent))
      flyLine.arrow.rotateX(Math.PI / 2)
      const scale = 0.8 + Math.sin(flyLine.progress * Math.PI) * 0.5
      flyLine.arrow.scale.set(scale, scale, scale)
    }
  })
}

附上完整代码地址 gitee.com/TriF/3d_ear...

相关推荐
90后的晨仔11 分钟前
从传统 CSS 到 Vue 的 Class 与 Style 动态绑定全解
前端·vue.js
拾光拾趣录15 分钟前
前端灵魂拷问:10道题
前端·面试
独泪了无痕2 小时前
Vite 模块动态导入之Glob导入
前端·vue.js·vite
xdpcxq10292 小时前
Docker用Web应用实例深入容器
前端·docker·容器
超浪的晨2 小时前
JavaWeb 入门:JavaScript 基础与实战详解(Java 开发者视角)
java·开发语言·前端·javascript·后端·学习·个人开发
tianchang2 小时前
React Hook 解析(二):`useEffect` 与 `useLayoutEffect`
前端·react.js
codervibe2 小时前
使用 Vue3 + Axios 与 Spring Boot 高效对接(含 token 处理)
前端·vue.js
云边散步2 小时前
《校园生活平台从 0 到 1 的搭建》第五篇:商品后端
前端·后端
圆心角2 小时前
vue keep-alive 原理
前端·vue.js
P7Dreamer2 小时前
Tailwind CSS 日常使用
前端·css