r3f喷雾火焰组件

发布时间 2023-05-29 15:03:40作者: 奥托
import { useTexture } from "@react-three/drei";
import { useFrame, useThree } from "@react-three/fiber";
import React, { useRef } from "react";
import * as THREE from "three";

const _VS = `
uniform float pointMultiplier;

attribute float size;
attribute float angle;
attribute vec4 colour;

varying vec4 vColour;
varying vec2 vAngle;

void main() {
  vec4 mvPosition = modelViewMatrix * vec4(position, 1.0);

  gl_Position = projectionMatrix * mvPosition;
  gl_PointSize = size * pointMultiplier / gl_Position.w;

  vAngle = vec2(cos(angle), sin(angle));
  vColour = colour;
}`;

const _FS = `

uniform sampler2D diffuseTexture;

varying vec4 vColour;
varying vec2 vAngle;

void main() {
  vec2 coords = (gl_PointCoord - 0.5) * mat2(vAngle.x, vAngle.y, -vAngle.y, vAngle.x) + 0.5;
  gl_FragColor = texture2D(diffuseTexture, coords) * vColour;
}`;

class LinearSpline {
  constructor(lerp) {
    this._points = [];
    this._lerp = lerp;
  }

  AddPoint(t, d) {
    this._points.push([t, d]);
  }

  Get(t) {
    let p1 = 0;

    for (let i = 0; i < this._points.length; i++) {
      if (this._points[i][0] >= t) {
        break;
      }
      p1 = i;
    }

    const p2 = Math.min(this._points.length - 1, p1 + 1);

    if (p1 == p2) {
      return this._points[p1][1];
    }

    return this._lerp(
      (t - this._points[p1][0]) / (this._points[p2][0] - this._points[p1][0]),
      this._points[p1][1],
      this._points[p2][1]
    );
  }
}

function Fire({ color = "red", speed = 0.016 }) {
  // let timeNum = 0;
  const geometryRef = useRef(null);
  const camear = useThree().camera;
  let particles = [];
  let gdfsghk;
  let alphaSpline = new LinearSpline((t, a, b) => {
    return a + t * (b - a);
  });
  alphaSpline.AddPoint(0.0, 0.0);
  alphaSpline.AddPoint(0.1, 1.0);
  alphaSpline.AddPoint(0.6, 1.0);
  alphaSpline.AddPoint(1.0, 0.0);

  let colourSpline = new LinearSpline((t, a, b) => {
    const c = a.clone();
    return c.lerp(b, t);
  });
  colourSpline.AddPoint(0.0, new THREE.Color(color));
  colourSpline.AddPoint(0.0, new THREE.Color(color));

  let sizeSpline = new LinearSpline((t, a, b) => {
    return a + t * b;
  });
  sizeSpline.AddPoint(0.0, 1.0);
  sizeSpline.AddPoint(0.5, 5.0);
  sizeSpline.AddPoint(1.0, 1.0);

  const fireImg = useTexture("/images/yun.png");
  const uniforms = {
    diffuseTexture: {
      value: fireImg,
    },
    pointMultiplier: {
      value:
        window.innerHeight / (3.0 * Math.tan((0.5 * 60.0 * Math.PI) / 180.0)),
    },
  };

  function addParticles(timeElapsed) {
    if (!gdfsghk) {
      gdfsghk = 0.0;
    }
    gdfsghk += timeElapsed;
    const n = Math.floor(gdfsghk * 75.0);
    gdfsghk -= n / 75.0;
    for (let i = 0; i < n; i++) {
      const life = Math.random() * 0.75 + 0.25;
      particles.push({
        position: new THREE.Vector3(
          (Math.random() * 2 - 1) * 1.0,
          (Math.random() * 2 - 1) * 1.0,
          (Math.random() * 2 - 1) * 1.0
        ),
        size: Math.random() * 0.5 + 0.5,
        colour: new THREE.Color(),
        alpha: 1.0,
        life: life,
        maxLife: life,
        rotation: Math.random() * 2.0 * Math.PI,
        velocity: new THREE.Vector3(0, -15, 0),
      });
    }
  }
  //更新形状
  function updateGeometry() {
    const positions = [];
    const sizes = [];
    const colours = [];
    const angles = [];

    for (let p of particles) {
      positions.push(p.position.x, p.position.y, p.position.z);
      colours.push(p.colour.r, p.colour.g, p.colour.b, p.alpha);
      sizes.push(p.currentSize);
      angles.push(p.rotation);
    }

    geometryRef.current.setAttribute(
      "position",
      new THREE.Float32BufferAttribute(positions, 3)
    );
    geometryRef.current.setAttribute(
      "size",
      new THREE.Float32BufferAttribute(sizes, 1)
    );
    geometryRef.current.setAttribute(
      "colour",
      new THREE.Float32BufferAttribute(colours, 4)
    );
    geometryRef.current.setAttribute(
      "angle",
      new THREE.Float32BufferAttribute(angles, 1)
    );

    geometryRef.current.attributes.position.needsUpdate = true;
    geometryRef.current.attributes.size.needsUpdate = true;
    geometryRef.current.attributes.colour.needsUpdate = true;
    geometryRef.current.attributes.angle.needsUpdate = true;
  }
  //更新粒子
  function updateParticles(timeElapsed) {
    for (let p of particles) {
      p.life -= timeElapsed;
    }
    particles = particles.filter((p) => {
      return p.life > 0.0;
    });
    for (let p of particles) {
      const t = 1.0 - p.life / p.maxLife;

      p.rotation += timeElapsed * 0.5;
      p.alpha = alphaSpline.Get(t);
      p.currentSize = p.size * sizeSpline.Get(t);
      p.colour.copy(colourSpline.Get(t));

      p.position.add(p.velocity.clone().multiplyScalar(timeElapsed));

      const drag = p.velocity.clone();
      drag.multiplyScalar(timeElapsed * 0.1);
      drag.x =
        Math.sign(p.velocity.x) *
        Math.min(Math.abs(drag.x), Math.abs(p.velocity.x));
      drag.y =
        Math.sign(p.velocity.y) *
        Math.min(Math.abs(drag.y), Math.abs(p.velocity.y));
      drag.z =
        Math.sign(p.velocity.z) *
        Math.min(Math.abs(drag.z), Math.abs(p.velocity.z));
      p.velocity.sub(drag);
    }

    particles.sort((a, b) => {
      const d1 = camear.position.distanceTo(a.position);
      const d2 = camear.position.distanceTo(b.position);
      if (d1 > d2) {
        return -1;
      }
      if (d1 < d2) {
        return 1;
      }
      return 0;
    });
  }

  //步长时间内重复调用
  function step(timeElapsed) {
    addParticles(timeElapsed);
    updateParticles(timeElapsed);
    updateGeometry();
  }

  useFrame((state, dealy, obj) => {
    step(speed);
  });

  return (
    <points name="fireShader">
      <bufferGeometry
        ref={geometryRef}
        position={new THREE.Float32BufferAttribute([], 5)}
        size={new THREE.Float32BufferAttribute([], 0.5)}
        colour={new THREE.Float32BufferAttribute([], 1)}
        angle={new THREE.Float32BufferAttribute([], 1)}
      />
      <shaderMaterial
        uniforms={uniforms}
        vertexShader={_VS}
        fragmentShader={_FS}
        blending={THREE.AdditiveBlending}
        depthTest={true}
        depthWrite={false}
        transparent={true}
        vertexColors={true}
      />
    </points>
  );
}

export default Fire;