“This is the 29th day of my participation in the First Challenge 2022. For details: First Challenge 2022”

Introduction to the

In this section, multiple particles are mainly used to emit light at different positions according to time on the map boundary, and a luminous line is realized to move on the boundary.

Implementation approach

  1. Get GeoJSON data for map boundaries.
  2. Create map wireframes based on the data and create fully transparent particles at each data point.
  3. throughrequestAnimationFrame()Function to modify the transparency of particles at different positions.

implementation

Based on the template

<! DOCTYPE html><html lang="en">
  <head>
    <meta charset="UTF-8" />
    <title>learning</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-dev/build/three.module.js'
      import { OrbitControls } from './file/three.js-dev/examples/jsm/controls/OrbitControls.js'

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

      const fov = 40 // Scope of view
      const aspect = 2 // The camera defaults to the width ratio of the canvas
      const near = 0.1 / / nearly flat
      const far = 10000 / / far plane
      // Perspective projection camera
      const camera = new THREE.PerspectiveCamera(fov, aspect, near, far)
      camera.position.set(0.100.500)
      camera.lookAt(0.0.0)
      // Control the camera
      const controls = new OrbitControls(camera, canvas)
      controls.update()

      / / the scene
      const scene = new THREE.Scene()

      / / rendering
      function render() {
        renderer.render(scene, camera)
        requestAnimationFrame(render)
      }
      requestAnimationFrame(render)
    </script>
  </body>
</html>
Copy the code

Load map data, draw maps and customize geometry

  • Mapping China
  const lines = []
  const geometry = new THREE.BufferGeometry()
  let positions = null
  let opacitys = null
  // Change the coordinates around Beijing
  const projection = d3.geoMercator().center([116.412318.39.909843]).translate([0.0])
Copy the code
  1. Create a variablelinesSave boundary point data.
  2. createBufferGeometry()Geometry, draw transparent points.
  • Common method for drawing lines and putting boundary point information intolinesIn the.
  let indexBol = true
  /** * Border graphic drawing *@param Polygon Polygon number group *@param Color Material color * */
  function lineDraw(polygon, color) {
    const lineGeometry = new THREE.BufferGeometry()
    const pointsArray = new Array()
    polygon.forEach((row) = > {
      const [x, y] = projection(row)
      // Create a 3d point
      pointsArray.push(new THREE.Vector3(x, -y, 0))

      if (indexBol) {
        lines.push([x, -y, 0])
      }
    })
    indexBol = false
    // Add multiple points
    lineGeometry.setFromPoints(pointsArray)

    const lineMaterial = new THREE.LineBasicMaterial({
      color: color
    })
    return new THREE.Line(lineGeometry, lineMaterial)
  }
Copy the code
  1. Used hereindexBolVariable, because we only need the boundary point data in the first array of map data.
  • Load map data and create custom geometry.
  const loader = new THREE.FileLoader()
  loader.load('./file/100000.json'.(data) = > {
    const jsondata = JSON.parse(data)

    // The Chinese border
    const feature = jsondata.features[0]
    const province = new THREE.Object3D()
    province.properties = feature.properties.name
    / / data
    const coordinates = feature.geometry.coordinates

    coordinates.forEach((coordinate) = > {
      // coordinate polygon data
      coordinate.forEach((rows) = > {
        const line = lineDraw(rows, 0xffffff)
        province.add(line)
      })
    })

    positions = new Float32Array(lines.flat(1))
    // Set the vertex
    geometry.setAttribute('position'.new THREE.BufferAttribute(positions, 3))
    // Set particle transparency to 0
    opacitys = new Float32Array(positions.length).map(() = > 0)
    geometry.setAttribute('aOpacity'.new THREE.BufferAttribute(opacitys, 1))

    scene.add(province)
  })
Copy the code
  1. Parse map data usinglineDraw()Function to draw lines and save boundary information.
  2. Add geometry vertex information (positions) and transparency information for each vertex (opacitys)

Draw transparent points using a shading material and a dot grid

  • Create control variables.
  // Control the color and particle size
  const params = {
    pointSize: 2.0.pointColor: '#4ec0e9'
  }
Copy the code
  • Define vertex shaders
  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; } `
Copy the code
  1. three.jsThe variable in.projectionMatrixIs the projection transformation matrix,modelViewMatrixIs the transformation matrix of camera coordinate system,positionVertex coordinates.
  2. throughuniformGlobal variables get external SettingsuSize, set the particle size.
  3. throughvaryingThe variable puts the vertex corresponding to transparencyaOpacityPass in the slice shader.
  • Defines the slice shader
  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; } `
Copy the code
  1. throughuniformGlobal variables get external SettingsuColor, base color.
  2. throughvaryingTransparency of variable acquisitionvOpacity.
  3. Set transparency to less than0.2Is not executed.
  4. According to the algorithm to obtain the current vertex to show the color, this algorithm is found on the Internet, to achieve the luminous effect.
  • Create a shader material and place it in the point material to draw a transparent point.
  const material = new THREE.ShaderMaterial({
    vertexShader: vertexShader,
    fragmentShader: fragmentShader,
    transparent: true.// Set transparency
    uniforms: {
      uSize: {
        value: params.pointSize
      },
      uColor: {
        value: new THREE.Color(params.pointColor)
      }
    }
  })
  const points = new THREE.Points(geometry, material)
  scene.add(points)
Copy the code
  1. Place vertex and slice shader, setuniformsGlobal variables.
  2. createPoints()Mesh, draw transparent points, add scene.
  • Now all the points are transparent just like the last picture.

Modified particle transparency to create glare effect

  let currentPos = 0
  let pointSpeed = 20 / / speed
  / / rendering
  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)
  }
Copy the code
  1. Two loops, the first clearing the last particle transparency modification. The second set the glare length and modify the particle transparency.
  2. .needsUpdateMust be set before the vertex information in the shader can be modified.