当前位置:   article > 正文

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

threejs粒子光路

大厂技术  高级前端  Node进阶

点击上方 程序员成长指北,关注公众号

回复1,加入高级Node交流群

简介

本节主要使用多个粒子在地图边界,根据时间在不同的位置发光,实现一条发光的线在边界上移动。大家可以先看下效果:

918097f6b4019b9221e3385b584a9649.gif
light

实现思路

  1. 获取地图边界「GeoJSON数据」

  2. 根据数据创建地图线框,在每个数据点上创建全透明粒子。

  3. 通过requestAnimationFrame()函数,修改不同位置粒子的透明度。

实现

基础模版

  1. <!DOCTYPE html>
  2. <html lang="en">
  3.   <head>
  4.     <meta charset="UTF-8" />
  5.     <title>学习</title>
  6.   </head>
  7.   <body>
  8.     <canvas id="c2d" class="c2d" width="1000" height="500"></canvas>
  9.     <script src="https://d3js.org/d3.v5.min.js"></script>
  10.     <script type="module">
  11.       import * as THREE from './file/three.js-dev/build/three.module.js'
  12.       import { OrbitControls } from './file/three.js-dev/examples/jsm/controls/OrbitControls.js'
  13.       const canvas = document.querySelector('#c2d')
  14.       // 渲染器
  15.       const renderer = new THREE.WebGLRenderer({ canvas })
  16.       const fov = 40 // 视野范围
  17.       const aspect = 2 // 相机默认值 画布的宽高比
  18.       const near = 0.1 // 近平面
  19.       const far = 10000 // 远平面
  20.       // 透视投影相机
  21.       const camera = new THREE.PerspectiveCamera(fov, aspect, near, far)
  22.       camera.position.set(0100500)
  23.       camera.lookAt(000)
  24.       // 控制相机
  25.       const controls = new OrbitControls(camera, canvas)
  26.       controls.update()
  27.       // 场景
  28.       const scene = new THREE.Scene()
  29.       // 渲染
  30.       function render() {
  31.         renderer.render(scene, camera)
  32.         requestAnimationFrame(render)
  33.       }
  34.       requestAnimationFrame(render)
  35.     </script>
  36.   </body>
  37. </html>

加载地图数据,绘制地图、自定义几何体

  1. const lines = []
  2.   const geometry = new THREE.BufferGeometry()
  3.   let positions = null
  4.   let opacitys = null
  5.   // 以北京为中心 修改坐标
  6.   const projection = d3.geoMercator().center([116.41231839.909843]).translate([00])
  1. 创建变量lines保存边界点数据。

  2. 创建BufferGeometry()几何体,绘制透明点。

  • 绘制线条公用方法,并把边界点信息放入lines中。

  1. let indexBol = true
  2.   /**
  3.    * 边框 图形绘制
  4.    * @param polygon 多边形 点数组
  5.    * @param color 材质颜色
  6.    * */
  7.   function lineDraw(polygon, color) {
  8.     const lineGeometry = new THREE.BufferGeometry()
  9.     const pointsArray = new Array()
  10.     polygon.forEach((row) => {
  11.       const [x, y] = projection(row)
  12.       // 创建三维点
  13.       pointsArray.push(new THREE.Vector3(x, -y, 0))
  14.       if (indexBol) {
  15.         lines.push([x, -y, 0])
  16.       }
  17.     })
  18.     indexBol = false
  19.     // 放入多个点
  20.     lineGeometry.setFromPoints(pointsArray)
  21.     const lineMaterial = new THREE.LineBasicMaterial({
  22.       color: color
  23.     })
  24.     return new THREE.Line(lineGeometry, lineMaterial)
  25.   }
  1. 这里使用indexBol变量,是因为我们只需要地图数据第一个数组中的边界点数据。

  • 加载地图数据,并创建自定义几何体。

  1. const loader = new THREE.FileLoader()
  2.   loader.load('./file/100000.json', (data) => {
  3.     const jsondata = JSON.parse(data)
  4.     // 中国边界
  5.     const feature = jsondata.features[0]
  6.     const province = new THREE.Object3D()
  7.     province.properties = feature.properties.name
  8.     // 点数据
  9.     const coordinates = feature.geometry.coordinates
  10.     coordinates.forEach((coordinate) => {
  11.       // coordinate 多边形数据
  12.       coordinate.forEach((rows) => {
  13.         const line = lineDraw(rows, 0xffffff)
  14.         province.add(line)
  15.       })
  16.     })
  17.     positions = new Float32Array(lines.flat(1))
  18.     // 设置顶点
  19.     geometry.setAttribute('position'new THREE.BufferAttribute(positions, 3))
  20.     // 设置 粒子透明度为 0
  21.     opacitys = new Float32Array(positions.length).map(() => 0)
  22.     geometry.setAttribute('aOpacity'new THREE.BufferAttribute(opacitys, 1))
  23.     scene.add(province)
  24.   })
  1. 解析地图数据,使用lineDraw()函数绘制线条和保存边界信息。

  2. 添加几何体顶点信息(positions)和每个顶点的透明度信息(opacitys)

6ed438691eb55a4af81617a099788032.png
image.png

使用着色器材质和点网格绘制透明点

  • 创建控制变量。

  1. // 控制 颜色和粒子大小
  2.   const params = {
  3.     pointSize: 2.0,
  4.     pointColor: '#4ec0e9'
  5.   }
  • 定义顶点着色器

  1. const vertexShader = `
  2.     attribute float aOpacity;
  3.     uniform float uSize;
  4.     varying float vOpacity;
  5.     void main(){
  6.         gl_Position = projectionMatrix*modelViewMatrix*vec4(position,1.0);
  7.         gl_PointSize = uSize;
  8.         vOpacity=aOpacity;
  9.     }
  10.     `
  1. three.js中的变量。projectionMatrix是投影变换矩阵,modelViewMatrix是相机坐标系的变换矩阵,position顶点坐标。

  2. 通过uniform全局变量获取外部设置uSize,设置粒子大小。

  3. 通过varying变量把顶点对应的透明度aOpacity传入片元着色器。

  • 定义片元着色器

  1. const fragmentShader = `
  2.     varying float vOpacity;
  3.     uniform vec3 uColor;
  4.     float invert(float n){
  5.         return 1.-n;
  6.     }
  7.     void main(){
  8.       if(vOpacity <=0.2){
  9.           discard;
  10.       }
  11.       vec2 uv=vec2(gl_PointCoord.x,invert(gl_PointCoord.y));
  12.       vec2 cUv=2.*uv-1.;
  13.       vec4 color=vec4(1./length(cUv));
  14.       color*=vOpacity;
  15.       color.rgb*=uColor;
  16.       
  17.       gl_FragColor=color;
  18.     }
  19.     `
  1. 通过uniform全局变量获取外部设置uColor,基础颜色。

  2. 通过varying变量获取透明度vOpacity

  3. 设置透明度小于0.2的片元不执行。

  4. 根据算法获取当前顶点要展示的颜色,这个算法是网上找的,实现发光效果。

  • 创建着色器材质,放入点材质中绘制透明点。

  1. const material = new THREE.ShaderMaterial({
  2.     vertexShader: vertexShader,
  3.     fragmentShader: fragmentShader,
  4.     transparent: true// 设置透明
  5.     uniforms: {
  6.       uSize: {
  7.         value: params.pointSize
  8.       },
  9.       uColor: {
  10.         value: new THREE.Color(params.pointColor)
  11.       }
  12.     }
  13.   })
  14.   const points = new THREE.Points(geometry, material)
  15.   scene.add(points)
  1. 放入顶点和片元着色器,设置uniforms全局变量。

  2. 创建Points()网格,绘制透明点,加入场景。

  • 现在所有点都是透明的和上一张图无区别。

修改粒子透明度形成炫光效果

  1. let currentPos = 0
  2.   let pointSpeed = 20 // 速度
  3.   // 渲染
  4.   function render() {
  5.     if (points && geometry.attributes.position) {
  6.       currentPos += pointSpeed
  7.       for (let i = 0; i < pointSpeed; i++) {
  8.         opacitys[(currentPos - i) % lines.length] = 0
  9.       }
  10.       for (let i = 0; i < 200; i++) {
  11.         opacitys[(currentPos + i) % lines.length] = i / 50 > 2 ? 2 : i / 50
  12.       }
  13.       geometry.attributes.aOpacity.needsUpdate = true
  14.     }
  15.     renderer.render(scene, camera)
  16.     requestAnimationFrame(render)
  17.   }
  1. 两个循环,第一个清除上一次粒子透明度的修改。第二个设置炫光长度和修改粒子的透明度。

  2. .needsUpdate必须设置,设置后着色器中的顶点信息才会修改。

309268d326e997532f7d2e3cc4735551.png
1.gif

作者:那些年丶ny 链接:https://juejin.cn/post/7068254084792320037

  1. Node 社群
  2. 我组建了一个氛围特别好的 Node.js 社群,里面有很多 Node.js小伙伴,如果你对Node.js学习感兴趣的话(后续有计划也可以),我们可以一起进行Node.js相关的交流、学习、共建。下方加 考拉 好友回复「Node」即可。
  3. 如果你觉得这篇内容对你有帮助,我想请你帮我2个小忙:
  4. 1. 点个「在看」,让更多人也能看到这篇文章2. 订阅官方博客 www.inode.club 让我们一起成长
  5. 点赞和在看就是最大的支持❤️
声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/Gausst松鼠会/article/detail/109520
推荐阅读
相关标签
  

闽ICP备14008679号