Skip to content

黑烟效果

示例

示例源码
tsx
import React, { useCallback, useEffect, useRef, useState } from 'react'
import ReactDOM from 'react-dom'
import * as THREE from 'three'
import { MbMap, MbTiandituLayer } from '@mapbox-react/core'

const App = () => {
  const [mapCenter] = useState([116.3466, 39.8704])
  const [zoom, setZoom] = useState(16)
  const [pitch, setPitch] = useState(45)
  const mapInst = useRef<any>()

  let map: any
  const altitude = 0

  const mapCreated = (mapbox: any) => {
    map = mapbox
  }
  const add3DLayer = () => {
    loaderModels()
  }

  const loaderModels = async () => {
    const promiseStar = new Promise((resolve) => {
      new THREE.TextureLoader().load(
        `https://mapbox-web.github.io/mapbox-react/images/smoke.png`,
        (texture) => {
          resolve(texture)
        }
      )
    })
    const [state, textureStar] = await promiseStar.then(
      (rs) => [true, rs, null],
      (err) => [false, null, err]
    )
    if (!state) return

    // 先创建一个空的缓冲几何体
    // First create an empty buffer geometry
    const geometry = new THREE.BufferGeometry()
    geometry.setAttribute(
      'position',
      new THREE.BufferAttribute(new Float32Array([]), 3)
    ) // 一个顶点由3个坐标构成 A vertex consists of 3 coordinates
    geometry.setAttribute(
      'a_opacity',
      new THREE.BufferAttribute(new Float32Array([]), 1)
    ) // 点的透明度,用1个浮点数表示 The transparency of the point, expressed as a floating point number
    geometry.setAttribute(
      'a_size',
      new THREE.BufferAttribute(new Float32Array([]), 1)
    ) // 点的初始大小,用1个浮点数表示 The initial size of the point, expressed as a floating point number
    geometry.setAttribute(
      'a_scale',
      new THREE.BufferAttribute(new Float32Array([]), 1)
    ) // 点的放大量,用1个浮点数表示 The amount of point magnification, expressed as a floating point number

    const material = new THREE.PointsMaterial({
      color: '#333',
      map: textureStar,
      transparent: true,
      depthWrite: false,
    })

    // 修正着色器 Correction shader
    material.onBeforeCompile = function (shader) {
      const vertexShaderAttribute = `
            attribute float a_opacity;
            attribute float a_size;
            attribute float a_scale;
            varying float v_opacity;
            void main() {
              v_opacity = a_opacity;
            `
      const vertexShaderSize = `
            gl_PointSize = a_size * a_scale;
            `
      shader.vertexShader = shader.vertexShader.replace(
        'void main() {',
        vertexShaderAttribute
      )
      shader.vertexShader = shader.vertexShader.replace(
        'gl_PointSize = size;',
        vertexShaderSize
      )

      const fragmentShaderVarying = `
            varying float v_opacity;
            void main() {
          `
      const fragmentShaderOpacity = `
            gl_FragColor = vec4( outgoingLight, diffuseColor.a * v_opacity );
          `
      shader.fragmentShader = shader.fragmentShader.replace(
        'void main() {',
        fragmentShaderVarying
      )
      shader.fragmentShader = shader.fragmentShader.replace(
        'gl_FragColor = vec4( outgoingLight, diffuseColor.a );',
        fragmentShaderOpacity
      )
    }

    // 创建点,并添加进场景 Create points and add them to the scene
    const points = new THREE.Points(geometry, material)
    const points2 = points.clone()

    points.rotation.z = Math.PI
    points2.rotation.z = Math.PI
    points2.rotation.x = -Math.PI / 4
    addThreeLayer(points, points2)

    // 定义Partical类 Defining Partial Classes
    class Partical {
      range: number
      center: Record<string, number>
      life: number
      createTime: number
      updateTime: number
      size: number
      opacityFactor: number
      opacity: number
      scaleFactor: number
      scale: number
      position: Record<string, number>
      speed: Record<string, number>
      constructor(range = 10, center = { x: 0, y: 0, z: 0 }) {
        this.range = range // 粒子的分布半径 Particle distribution radius
        this.center = center // 粒子的分布中心 The distribution center of particles
        this.life = 5000 // 粒子的存活时间,毫秒 The lifetime of the particle, in milliseconds
        this.createTime = Date.now() // 粒子创建时间 Particle creation time
        this.updateTime = Date.now() // 上次更新时间 Last updated
        this.size = 500 // 粒子大小 Particle size

        // 粒子透明度,及系数 粒子透明度,及系数
        this.opacityFactor = 1
        this.opacity = 1 * this.opacityFactor

        // 粒子放大量,及放大系数 Particle amplification and amplification factor
        this.scaleFactor = 3
        this.scale =
          1 +
          (this.scaleFactor * (this.updateTime - this.createTime)) / this.life // 初始1,到达生命周期时为3

        // 粒子位置 Particle Position
        this.position = {
          x: Math.random() * 2 * this.range + this.center.x - this.range,
          y: this.center.y,
          z: Math.random() * 2 * this.range + this.center.z - this.range,
        }

        // 水平方向的扩散 Horizontal diffusion
        let speedAround = Math.random() * 40
        if (speedAround < 20) speedAround -= 50
        if (speedAround > 20) speedAround += 10

        // 粒子的扩散速度 The diffusion rate of particles
        this.speed = {
          x: speedAround,
          y: Math.random() * 100 + 300,
          z: speedAround,
        }
      }

      update() {
        const now = Date.now()
        const time = now - this.updateTime

        this.position.x += (this.speed.x * time) / 1000
        this.position.y += (this.speed.y * time) / 1000
        this.position.z += (this.speed.z * time) / 1000

        // 计算粒子透明度 Calculating particle transparency
        this.opacity = 1 - (now - this.createTime) / this.life
        this.opacity *= this.opacityFactor
        if (this.opacity < 0) this.opacity = 0

        // 计算放大量 Calculation magnification
        this.scale =
          1 + (this.scaleFactor * (now - this.createTime)) / this.life
        if (this.scale > 1 + this.scaleFactor) this.scale = 1 + this.scaleFactor

        // 重置更新时间 Reset Update Time
        this.updateTime = now
      }
    }

    let particals: Partical[] = []
    setInterval(() => {
      particals.push(new Partical(10, { x: 0, y: 100, z: 0 }))
    }, 500)

    // 校验粒子,并更新粒子位置等数据
    // Verify particles and update particle position and other data
    setInterval(() => {
      particals = particals.filter((partical) => {
        partical.update()
        if (partical.updateTime - partical.createTime > partical.life) {
          return false
        } else {
          return true
        }
      })
      if (!particals.length) return

      // 遍历粒子,收集属性 Traverse particles and collect properties
      const positionList: number[] = []
      const opacityList: number[] = []
      const scaleList: number[] = []
      const sizeList: number[] = []
      particals.forEach((partical) => {
        const { x, y, z } = partical.position
        positionList.push(x, y, z)
        opacityList.push(partical.opacity)
        scaleList.push(partical.scale)
        sizeList.push(partical.size)
      })
      // 粒子属性写入 Particle attribute writing
      geometry.setAttribute(
        'position',
        new THREE.BufferAttribute(new Float32Array(positionList), 3)
      )
      geometry.setAttribute(
        'a_opacity',
        new THREE.BufferAttribute(new Float32Array(opacityList), 1)
      )
      geometry.setAttribute(
        'a_scale',
        new THREE.BufferAttribute(new Float32Array(scaleList), 1)
      )
      geometry.setAttribute(
        'a_size',
        new THREE.BufferAttribute(new Float32Array(sizeList), 1)
      )
    }, 20)
  }
  const addThreeLayer = (points, points2) => {
    const threeLayer = new map.mapboxgl.supermap.ThreeLayer('threeLayer')
    threeLayer.on('initialized', render)

    let light: THREE.PointLight
    function render() {
      const renderer = threeLayer.getThreeRenderer(),
        scene = threeLayer.getScene(),
        camera = threeLayer.getCamera()

      light = new THREE.PointLight(0xffffff, 0.8)
      light.position.copy(camera.position)
      scene.add(light)
      scene.add(new THREE.AmbientLight(0xffffff))

      threeLayer.setPosition(points, mapCenter)
      threeLayer.setPosition(points2, [mapCenter[0] + 0.002, mapCenter[1]])

      scene.add(points)
      scene.add(points2)
        ; (function animate() {
          renderer.render(scene, camera)
          requestAnimationFrame(animate)
        })()
    }

    threeLayer.on('render', () => {
      light && light.position.copy(threeLayer.renderer.camera.position)
    })
    map.addLayer(threeLayer)
  }

  return (
    <div className="map-wrapper">
      <MbMap
        ref={mapInst}
        center={mapCenter}
        zoom={zoom}
        pitch={pitch}
        onCreated={mapCreated}
      >
        <MbTiandituLayer types={['vec', 'cva']} onCreated={add3DLayer} />
      </MbMap>
    </div>
  )
}

ReactDOM.render(<App />, document.querySelector('#root'))