Three.js - 实现地图边界炫光路径效果

简介

本节主要使用多个粒子在地图边界,根据时间在不同的位置发光,实现一条发光的线在边界上移动。

实现思路

  1. 获取地图边界GeoJSON数据。
  2. 根据数据创建地图线框,在每个数据点上创建全透明粒子。
  3. 通过requestAnimationFrame()函数,修改不同位置粒子的透明度。

完整代码

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <title>学习</title>
  </head>
  <body>
    <canvas id="c2d" class="c2d" width="1000" height="500"></canvas>
    <script src="https://d3js.org/d3.v5.min.js"></script>
    <script type="module">
      import * as THREE from './file/three.js-r123/build/three.module.js'
      import { OrbitControls } from './file/three.js-r123/examples/jsm/controls/OrbitControls.js'

      const canvas = document.querySelector('#c2d')
      // 渲染器
      const renderer = new THREE.WebGLRenderer({ canvas })

      const fov = 40 // 视野范围
      const aspect = 2 // 相机默认值 画布的宽高比
      const near = 0.1 // 近平面
      const far = 10000 // 远平面
      // 透视投影相机
      const camera = new THREE.PerspectiveCamera(fov, aspect, near, far)
      camera.position.set(0, 100, 500)
      camera.lookAt(0, 0, 0)
      // 控制相机
      const controls = new OrbitControls(camera, canvas)
      controls.update()

      // 场景
      const scene = new THREE.Scene()

      const lines = []
      let positions = null
      let opacitys = null
      const geometry = new THREE.BufferGeometry()
      // 以北京为中心 修改坐标
      const projection = d3.geoMercator().center([116.412318, 39.909843]).translate([0, 0])

      const loader = new THREE.FileLoader()
      loader.load('./file/100000.json', (data) => {
        const jsondata = JSON.parse(data)

        // 中国边界
        const feature = jsondata.features[0]
        const province = new THREE.Object3D()
        province.properties = feature.properties.name
        // 点数据
        const coordinates = feature.geometry.coordinates

        coordinates.forEach((coordinate) => {
          // coordinate 多边形数据
          coordinate.forEach((rows) => {
            const line = lineDraw(rows, 0xffffff)
            province.add(line)
          })
        })

        positions = new Float32Array(lines.flat(1))
        // 设置顶点
        geometry.setAttribute('position', new THREE.BufferAttribute(positions, 3))
        // 设置 粒子透明度为 0
        opacitys = new Float32Array(positions.length).map(() => 0)
        geometry.setAttribute('aOpacity', new THREE.BufferAttribute(opacitys, 1))

        scene.add(province)
      })

      let indexBol = true
      /**
       * 边框 图形绘制
       * @param polygon 多边形 点数组
       * @param color 材质颜色
       * */
      function lineDraw(polygon, color) {
        const lineGeometry = new THREE.BufferGeometry()
        const pointsArray = new Array()
        polygon.forEach((row) => {
          const [x, y] = projection(row)
          // 创建三维点
          pointsArray.push(new THREE.Vector3(x, -y, 0))

          if (indexBol) {
            lines.push([x, -y, 0])
          }
        })
        indexBol = false
        // 放入多个点
        lineGeometry.setFromPoints(pointsArray)

        const lineMaterial = new THREE.LineBasicMaterial({
          color: color
        })
        return new THREE.Line(lineGeometry, lineMaterial)
      }

      // 控制 颜色和粒子大小
      const params = {
        pointSize: 4.0,
        pointColor: '#4ec0e9'
      }

      const vertexShader = `
        attribute float aOpacity;
        uniform float uSize;
        varying float vOpacity;

        void main(){
            gl_Position = projectionMatrix*modelViewMatrix*vec4(position,1.0);
            gl_PointSize = uSize;

            vOpacity=aOpacity;
        }
        `

      const fragmentShader = `
        varying float vOpacity;
        uniform vec3 uColor;

        float invert(float n){
            return 1.-n;
        }

        void main(){
          if(vOpacity <=0.2){
              discard;
          }
          vec2 uv=vec2(gl_PointCoord.x,invert(gl_PointCoord.y));
          vec2 cUv=2.*uv-1.;
          vec4 color=vec4(1./length(cUv));
          color*=vOpacity;
          color.rgb*=uColor;
          gl_FragColor=color;
        }
        `
      const material = new THREE.ShaderMaterial({
        vertexShader: vertexShader,
        fragmentShader: fragmentShader,
        transparent: true, // 设置透明
        uniforms: {
          uSize: {
            value: params.pointSize
          },
          uColor: {
            value: new THREE.Color(params.pointColor)
          }
        }
      })
      const points = new THREE.Points(geometry, material)
      scene.add(points)

      let currentPos = 0
      let pointSpeed = 20 // 速度
      // 渲染
      function render() {
        if (points && geometry.attributes.position) {
          currentPos += pointSpeed
          for (let i = 0; i < pointSpeed; i++) {
            opacitys[(currentPos - i) % lines.length] = 0
          }

          for (let i = 0; i < 200; i++) {
            opacitys[(currentPos + i) % lines.length] = i / 50 > 2 ? 2 : i / 50
          }
          geometry.attributes.aOpacity.needsUpdate = true
        }

        renderer.render(scene, camera)
        requestAnimationFrame(render)
      }
      render()
      // requestAnimationFrame(render)
    </script>
  </body>
</html>

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值