Three.js - 实现一个3D地球可视化

这篇具有很好参考价值的文章主要介绍了Three.js - 实现一个3D地球可视化。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

3D地球可视化效果
threejs三维地图可视化,javascript,3d,开发语言
3D地球的开发并不复杂,对球形物体进行贴图操作,完成球体自转和月球公转,太阳场景设置等即可

上代码

<template>
  <div class="earth_page">
    <div v-if="loadingProcess !== 100" class='loading'>
      <span class='progress'>{{loadingProcess}} %</span>
    </div>
    <div class="scene" id="viewer-container"></div>
  </div>
</template>

<script setup>
import { onBeforeUnmount, onMounted, nextTick, ref } from "vue"
import modules from "./modules/index.js";
import Animations from './utils/animations';
import * as THREE from "three";
import { TWEEN } from 'three/examples/jsm/libs/tween.module.min.js'; // tween 动画效果渲染 效果同 gsap
import { Lensflare, LensflareElement } from 'three/examples/jsm/objects/Lensflare.js';

let loadingProcess = ref(100) // loading加载数据 0 25 50 75 100
let sceneReady = false // 场景加载完毕标志,程序进行label展示,镜头拉进等效果

let viewer = null // 基础类,包含场景、相机、控制器等实例
let tiemen = null // 水面动画 函数
let allTiemen = null // 全局动画 函数

const sizes = { // 存储全局宽度 高度
  width: window.innerWidth,
  height: window.innerHeight
}

const lensflareTexture0 = 'images/lensflare0.png' // 太阳光贴图
const lensflareTexture1 = 'images/lensflare1.png' // 黑色描边贴图
const earth = new THREE.Object3D(); // 地球存储
// 地球3D层
// const earthObject = new THREE.Object3D()
// 地球半径
const globeRadius = 10

// 初始化three场景
const init = () => {
  viewer = new modules.Viewer('viewer-container') //初始化场景
  // 调整相机位置(相机位置在初始化的时候设置过一次,这里对其进行调整)
  viewer.camera.position.set(0, 600, 1600)
  // 限制controls的上下角度范围 (OrbitControls的范围)
  viewer.controls.maxPolarAngle = Math.PI / 2.1;

  // 增加灯光(初始化viewer的时候,对灯光也做了初始,这里进行灯光调整)
  let { lights } = viewer

  // 环境光会均匀的照亮场景中的所有物体。 环境光不能用来投射阴影,因为它没有方向。
  let ambientLight = lights.addAmbientLight() 
  ambientLight.setOption({color: 0xffffff, intensity: 0.8}) // 调用灯光内置方法,设置新的属性
  
  // 平行光是沿着特定方向发射的光。这种光的表现像是无限远,从它发出的光线都是平行的。常常用平行光来模拟太阳光的效果。 太阳足够远,因此我们可以认为太阳的位置是无限远,所以我们认为从太阳发出的光线也都是平行的。
  lights.addDirectionalLight([-1, 1.75, 1], { // 增加直射灯光方法 
    color: 'rgb(255,234,229)',
    // intensity: 3, // intensity属性是用来设置聚光灯的强度,默认值是1,如果设置成0那什么也看不到,该值越大,点光源看起来越亮
    // castShadow: true, // castShadow属性是用来控制光源是否产生阴影,取值为true或false
  })
  
  // 从一个点向各个方向发射的光源。一个常见的例子是模拟一个灯泡发出的光。
  const pointLight = lights.addPointLight([0, 45, -2000], { // 增加直射灯光方法 
    color: 'rgb(253,153,253)'
  })

  // 模拟太阳光效果
  const textureLoader = new THREE.TextureLoader(); // 加载texture的一个类。 内部使用ImageLoader来加载文件。
  const textureFlare0 = textureLoader.load(lensflareTexture0); // 加载太阳光 贴图
  const textureFlare1 = textureLoader.load(lensflareTexture1); // 加载黑色贴图
  // 镜头光晕
  const lensflare = new Lensflare(); // 创建一个模拟追踪着灯光的镜头光晕。 Lensflare can only be used when setting the alpha context parameter of WebGLRenderer to true.
  lensflare.addElement(new LensflareElement( textureFlare0, 600, 0, pointLight.color));
  // LensflareElement( texture : Texture, size : Float, distance : Float, color : Color )
  // texture - 用于光晕的THREE.Texture(贴图)
  // size - (可选)光晕尺寸(单位为像素)
  // distance - (可选)和光源的距离值在0到1之间(值为0时在光源的位置)
  // color - (可选)光晕的(Color)颜色
  lensflare.addElement(new LensflareElement( textureFlare1, 60, .6));
  lensflare.addElement(new LensflareElement( textureFlare1, 70, .7));
  lensflare.addElement(new LensflareElement( textureFlare1, 120, .9));
  lensflare.addElement(new LensflareElement( textureFlare1, 70, 1));
  pointLight.add(lensflare);

  // 地球
  const textLoader = new THREE.TextureLoader();
  const planet = new THREE.Mesh(new THREE.SphereGeometry(globeRadius, 64, 64), new THREE.MeshStandardMaterial({
    map: textLoader.load('images/earth_basic.jpeg'),
    normalMap: textLoader.load('images/earth_normal.jpeg'),
    roughnessMap: textLoader.load('images/earth_rough.jpeg'),
    normalScale: new THREE.Vector2(10, 10),
    metalness: .1
  }));
  planet.rotation.y = -Math.PI;
  
  // 云层
  const atmosphere = new THREE.Mesh(new THREE.SphereGeometry(globeRadius + 0.6, 64, 64), new THREE.MeshLambertMaterial({
    alphaMap: textLoader.load('images/clouds.jpeg'),
    transparent: true,
    opacity: .4,
    depthTest: true
  }))
  earth.add(planet);
  earth.add(atmosphere);
  earth.scale.set(6, 6, 6)
  earth.rotation.set(0.5, 2.9, 0.1)
  viewer.scene.add(earth);

  // 月亮
  const moon = new THREE.Mesh(new THREE.SphereGeometry(2, 32, 32), new THREE.MeshStandardMaterial({
    map: textLoader.load('images/moon_basic.jpeg'),
    normalMap: textLoader.load('images/moon_normal.jpeg'),
    roughnessMap: textLoader.load('images/moon_roughness.jpeg'),
    normalScale: new THREE.Vector2(10, 10),
    metalness: .1
  }));
  moon.position.set(-120, 0, -120);
  moon.scale.set(6, 6, 6);
  viewer.scene.add(moon);

  // Animations.animateCamera 利用tweenjs 完成的镜头切换动画工具函数,分别传入相机,控制器,相机最终位置,指向控制器位置,动作时间
  Animations.animateCamera(viewer.camera, viewer.controls, { x: 100, y: 20, z: 200 }, { x: 0, y: 0, z: 0 }, 4000, () => {
    sceneReady = true
  });

  /**
  * 创建canvas方形纹理,取代图片纹理,利用代码形式创建
  * */
  function generateSprite() {
    const canvas = document.createElement('canvas')
    canvas.width = 16
    canvas.height = 16

    const context = canvas.getContext('2d')
    // 创建颜色渐变
    const gradient = context.createRadialGradient(
      canvas.width / 2,
      canvas.height / 2,
      0,
      canvas.width / 2,
      canvas.height / 2,
      canvas.width / 2
    )
    gradient.addColorStop(0, 'rgba(255,255,255,1)')
    gradient.addColorStop(0.2, 'rgba(0,255,255,1)')
    gradient.addColorStop(0.4, 'rgba(0,0,64,1)')
    gradient.addColorStop(1, 'rgba(0,0,0,1)')

    // 绘制方形
    context.fillStyle = gradient
    context.fillRect(0, 0, canvas.width, canvas.height)
    // 转为纹理
    const texture = new THREE.Texture(canvas)
    texture.needsUpdate = true
    return texture
  }

  const positions = []
  const colors = []
  // 创建 几何体
  const geometry = new THREE.SphereGeometry()
  for (let i = 0; i < 10000; i++) {
    let 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)
    // const 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 ) );
  // 默认球体
  geometry.computeBoundingSphere()

  // ------------- 1 ----------
  // 星星资源图片
  // PointsMaterial 点基础材质
  const starsMaterial = new THREE.PointsMaterial({
    map: generateSprite(),
    size: 1,
    transparent: true,
    opacity: 1,
    //true:且该几何体的colors属性有值,则该粒子会舍弃第一个属性--color,而应用该几何体的colors属性的颜色
    // vertexColors: true,
    blending: THREE.AdditiveBlending,
    sizeAttenuation: true
  })
  // 粒子系统 网格
  let stars = new THREE.Points(geometry, starsMaterial)
  stars.scale.set(600, 600, 600)
  // viewer.scene.add(stars)

  const groupHalo = new THREE.Group();
  // 地球光圈
  // const geometryCircle = new THREE.PlaneGeometry( 200, 200 );
  // const materialCircle = new THREE.MeshLambertMaterial( {
  //   map: textureFlare0, 
  //   transparent: true,
  //   side: THREE.DoubleSide, 
  //   depthWrite: false
  // } );
  // const meshCircle = new THREE.Mesh( geometryCircle, materialCircle );
  // groupHalo.add( meshCircle );

  // 转动的球
  // const p1 = new THREE.Vector3( -200, 0, 0 );
  // const p2 = new THREE.Vector3( 200, 0, 0 );
  // const points = [p1,p2];
  // const geometryPoint = new THREE.BufferGeometry().setFromPoints( points );
  // const materialPoint = new THREE.PointsMaterial({
  //   map: textureFlare1,
  //   transparent: true,
  //   side: THREE.DoubleSide, 
  //   size: 1, 
  //   depthWrite: false
  // });
  // const earthPoints = new THREE.Points( geometryPoint, materialPoint );
  // groupHalo.add( earthPoints );
	// groupHalo.rotation.set( 1.9, 0.5, 1 );
  // viewer.scene.add(groupHalo)

  // 经纬度转标转成3D空间坐标
  /** 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),
  //   }
  // }

  /**
   * 经维度 转换坐标
   * THREE.Spherical 球类坐标
   * lng:经度
   * lat:维度
   * radius:地球半径
   */
   function lglt2xyz(lng, lat, radius) {
    // 以z轴正方向为起点的水平方向弧度值
    const theta = (90 + lng) * (Math.PI / 180)
    // 以y轴正方向为起点的垂直方向弧度值
    const phi = (90 - lat) * (Math.PI / 180)
    return new THREE.Vector3().setFromSpherical(new THREE.Spherical(radius, phi, theta))
  }

  // 移动 队列
  const moveArr = []
  function renders() {
    moveArr.forEach(function (mesh) {
      mesh._s += 0.01
      let tankPosition = new THREE.Vector3()
      tankPosition = mesh.curve.getPointAt(mesh._s % 1)
      mesh.position.set(tankPosition.x, tankPosition.y, tankPosition.z)
    })
  }

  /**
  * 绘制 目标点
  * */
  function spotCircle(spot) {
    // 圆
    const geometry1 = new THREE.CircleGeometry(0.12, 100)
    const material1 = new THREE.MeshBasicMaterial({ color: 0x4caf50, side: THREE.DoubleSide })
    const circle = new THREE.Mesh(geometry1, material1)
    circle.position.set(spot[0], spot[1], spot[2])
    // mesh在球面上的法线方向(球心和球面坐标构成的方向向量)
    var coordVec3 = new THREE.Vector3(spot[0], spot[1], spot[2]).normalize()
    // mesh默认在XOY平面上,法线方向沿着z轴new THREE.Vector3(0, 0, 1)
    var meshNormal = new THREE.Vector3(0, 0, 1)
    // 四元数属性.quaternion表示mesh的角度状态
    //.setFromUnitVectors();计算两个向量之间构成的四元数值
    circle.quaternion.setFromUnitVectors(meshNormal, coordVec3)
    earth.add(circle)

    // 圆环
    const geometry2 = new THREE.RingGeometry(0.03, 0.04, 100)
    // transparent 设置 true 开启透明
    const material2 = new THREE.MeshBasicMaterial({ color: 0xff0000, side: THREE.DoubleSide, transparent: true })
    const circleY = new THREE.Mesh(geometry2, material2)
    circleY.position.set(spot[0], spot[1], spot[2])

    // 指向圆心
    circleY.lookAt(new THREE.Vector3(0, 0, 0))
    earth.add(circleY)
    // 加入动画队列
    // bigByOpacityArr.push(circleY)
  }

  // 在线上移动的物体就简单了。根据三维三次贝塞尔曲线得到的点,绘制一个几何体。把点缓存下来,加入移动队列进行动画。
  /**
   * 线上移动物体
   * */
   function moveSpot(curve) {
    // 线上的移动物体
    const aGeo = new THREE.SphereGeometry(0.06, 64, 64)
    const aMater = new THREE.MeshPhongMaterial({ color: 0x9c27b0, side: THREE.DoubleSide })
    const aMesh = new THREE.Mesh(aGeo, aMater)
    // 保存曲线实例
    aMesh.curve = curve
    aMesh._s = 0

    moveArr.push(aMesh)
    earth.add(aMesh)
  }

  // 绘制飞线

  // 在3D中飞线,都是曲线且都是在球外部进行连接的。所以我们需要使用三维三次贝塞尔曲线。
  // 先获取要连线的两个坐标。计算出两点的夹角,根据夹角计算偏移。计算出放大后的终点位置。以这两个值计算出三维三次贝塞尔曲线的中间点。
  // 这是在网上随便找的算法,想优化的可以自己计算或者继续在网上找。
  // 然后就是根据三维三次贝塞尔曲线创建线几何体,加入地球场景中。
  /**
   * 绘制 两个目标点并连线
   * */
  function lineConnect(posStart, posEnd) {
    const v0 = lglt2xyz(posStart[0], posStart[1], globeRadius)
    const v3 = lglt2xyz(posEnd[0], posEnd[1], globeRadius)

    // angleTo() 计算向量的夹角
    const angle = v0.angleTo(v3)
    let vtop = v0.clone().add(v3)
    // multiplyScalar 将该向量与所传入的 标量进行相乘
    vtop = vtop.normalize().multiplyScalar(globeRadius)

    let n
    if (angle <= 1) {
      n = (globeRadius / 5) * angle
    } else if (angle > 1 && angle < 2) {
      n = (globeRadius / 5) * Math.pow(angle, 2)
    } else {
      n = (globeRadius / 5) * Math.pow(angle, 1.5)
    }

    const v1 = v0
      .clone()
      .add(vtop)
      .normalize()
      .multiplyScalar(globeRadius + n)
    const v2 = v3
      .clone()
      .add(vtop)
      .normalize()
      .multiplyScalar(globeRadius + n)
    // 三维三次贝塞尔曲线(v0起点,v1第一个控制点,v2第二个控制点,v3终点)
    const curve = new THREE.CubicBezierCurve3(v0, v1, v2, v3)

    // 绘制 目标位置
    spotCircle([v0.x, v0.y, v0.z])
    spotCircle([v3.x, v3.y, v3.z])
    moveSpot(curve)

    const lineGeometry = new THREE.BufferGeometry()
    // 获取曲线 上的50个点
    var points = curve.getPoints(50)
    var positions = []
    var colors = []
    var color = new THREE.Color()

    // 给每个顶点设置演示 实现渐变
    for (var j = 0; j < points.length; j++) {
      color.setHSL(0.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)
    }
    // 放入顶点 和 设置顶点颜色
    lineGeometry.setAttribute('position', new THREE.BufferAttribute(new Float32Array(positions), 3, true))
    lineGeometry.setAttribute('color', new THREE.BufferAttribute(new Float32Array(colors), 3, true))

    const material = new THREE.LineBasicMaterial({ vertexColors: THREE.VertexColors, side: THREE.DoubleSide })
    const line = new THREE.Line(lineGeometry, material)

    earth.add(line)
  }

  /**
  * 画图
  * */
  function drawChart() {
      lineConnect([-58.48, 36], [116.4, 39.91])
      lineConnect([-58.48, 36], [121.564136, 25.071558])
      lineConnect([-58.48, 36], [104.896185, 11.598253])
      lineConnect([-58.48, 36], [130.376441, -16.480708])
      lineConnect([-58.48, 36], [-71.940328, -13.5304])
      lineConnect([-58.48, 36], [-3.715707, 40.432926])
      lineConnect([-58.48, 36], [-78.940328, -23.5304])
      lineConnect([-58.48, 36], [-31.715707, 30.432926])
      lineConnect([-58.48, 36], [-34.940328, -73.5304])
      lineConnect([-58.48, 36], [-28.715707, 20.432926])
      lineConnect([-58.48, 36], [-51.940328, -83.5304])
      lineConnect([-58.48, 36], [-39.715707, 10.432926])
  }
  drawChart()
  // 全局动画逻辑
  const clock = new THREE.Clock();
  allTiemen = {
    fun: ({earth, moon}) => {
      renders();
      TWEEN && TWEEN.update();
      const elapsedTime = clock.getElapsedTime()
      earth && (earth.rotation.y += 0.002)
      atmosphere && (atmosphere.rotation.y += 0.004)
      atmosphere && (atmosphere.rotation.x += 0.002)
      // 公转
      moon && (moon.position.x = Math.sin(elapsedTime * .5) * -120);
      moon && (moon.position.z = Math.cos(elapsedTime * .5) * -120);
    },
    content: {earth, moon}
  }
  viewer.addAnimate(allTiemen)
}

onBeforeUnmount(()=>{
  window.removeEventListener('resize', () => {
    viewer._undateDom()
  })
})

onMounted(()=>{
  init()
  // 监听页面大小变动,自适应页面, 第一次直接触发执行
  window.addEventListener('resize', () => {
    viewer._undateDom()
  })
  // 初次页面变动执行不成功,主动延迟执行一次
  nextTick(()=>{
    viewer._undateDom()
  })
})  
</script>

<style lang="scss">
.earth_page {
  height: 100vh;
  overflow: hidden;
  .scene {
    height: 100vh;
    width: 100%;
    overflow: hidden;
  }
}
</style>

更多详细代码请关注公众号索取(备注:公众号):
threejs三维地图可视化,javascript,3d,开发语言文章来源地址https://www.toymoban.com/news/detail-757002.html

到了这里,关于Three.js - 实现一个3D地球可视化的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 使用Three.js创建令人惊叹的WebGL 3D可视化

    WebGL 可视化 3D 绘图是一项新兴技术,具有广阔的应用前景。它允许开发人员在 Web 浏览器中创建和渲染 3D 图形,而无需安装额外的插件或软件。 本博客将介绍 Three.js,Three.js 是一个功能强大的 WebGL 框架,提供了丰富的 API 用于创建和渲染 3D 图形,接下来让我们通

    2024年01月19日
    浏览(66)
  • Three.js学习项目--3D抗美援朝数据可视化

    部分场景 体验地址 https://kmyc.hongbin.xyz/ 操作说明 视频 操作说明 我做了哪些(功能) draco解析glb模型 同时处理部分纹理请求 减轻一次加载纹理压力 手动控制轨道控制器镜头动画 多音频拼接 控制 封装动画播放器 控制进度切换 动画进度控制器 同步音频 模拟视频体验 useCon

    2024年02月11日
    浏览(81)
  • DataGear 制作基于 three.js 的 3D 数据可视化看板

    DataGear 支持采用原生的HTML、JavaScript、CSS制作数据可视化看板,也支持导入由 npm 、 vite 等前端工具构建的前端程序包。得益于这一特性,可以很容易制作基于three.js的3D数据可视化看板。 首先,参考three.js的官方教程 https://threejs.org/docs/index.html#manual/en/introduction/Installation 编写

    2024年03月09日
    浏览(72)
  • 除了three.js,还有许多其他前端开发语言和库可以用于创建3D可视化大屏

    hello老铁们...本人熟悉html5,vue对bootsrap,uniapp,layui,element,vite,antd,echarts,jq响应式尤其擅长,ui设计等技能,如果ui前端工作中有遇到烦恼可私信关注评论我们共同交流进步!谢谢       随着前端技术的飞速发展,3D可视化已经成为许多应用场景中不可或缺的一部分。在

    2024年03月15日
    浏览(72)
  • 03.Three.js的入门教程(二)如何创建一个3D地球?

    前言:通过上节课 02.Three.js的入门课程(一),我们了解了Three.js的最小案例DEMO,熟悉了几个重要组成部分。这节课带领大家编写一个3D地球。 一、通过纹理图渲染一个地球 1.1. 创建一个纹理加载器对象TextureLoader,可以加载图片作为纹理贴图; 1.2.完整代码结构 二、小球标

    2024年02月04日
    浏览(69)
  • Three.js--》实现3d地球模型展示

    目录 项目搭建 实现网页简单布局 初始化three.js基础代码 创建环境背景 加载地球模型 实现光柱效果 添加月球模型 今天简单实现一个three.js的小Demo,加强自己对three知识的掌握与学习,只有在项目中才能灵活将所学知识运用起来,话不多说直接开始。 项目搭建 本案例还是借

    2024年02月08日
    浏览(70)
  • 基于threejs开发的3D地球大屏可视化,支持2D地图模式,飞线,涟漪,配置简单易上手

    基于threejs 封装的3D可视化地球组件,开箱即用 主要实现功能 根据geojson格式的json文件,渲染平面2D 和3D地图,地图可配置区域色,边界色 支持以贴图的方式实现用户设计的个性化地图图片的3D地球渲染(2D的支持正在开发) 通过本组件提供的方法可实现 新增飞线动画 和 标记

    2024年02月08日
    浏览(65)
  • 如何使用webgl(three.js)实现煤矿隧道、井下人员定位、掘进面、纵采面可视化解决方案——第十九课(一)

    序: 又是很久没有更新文章了,这次索性将之前做的三维煤矿项目拿出来讲讲,一是回顾技术,二是锻炼一下文笔。 随着科技的不断发展,越来越多的人开始关注煤矿采集的安全和效率问题。为了更好地展示煤矿采集的过程和效率,可视化展示系统成为了一个非常重要的工

    2024年02月08日
    浏览(71)
  • 【案例】3D地球(vue+three.js)

    需要下载插件 有人找不到合适的地球平面图的话,可直接地球平面图

    2024年02月06日
    浏览(42)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包