threejs粒子系统

threejs粒子系统

粒子系统

一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第4天,点击查看活动详情

粒子系统:
    本文列举一些常见的场景如:雪天、雨天、萤火虫、星空、烟雾、爆炸、火焰、多云、雾霾
实现思路:
    - 点粒子
    - 自定义着色器
    - 贴图
    - 运动动画与粒子存活时间
复制代码

通过点粒子携带贴图配合自定义着色器材质,控制粒子运动轨迹以及存活时间,在存活时间内控制粒子的透明度以及位置 及颜色、大小等~

Three.js将粒子系统视为一个基本的几何体,因为它就像基本几何体一样,即有形状,又有位置、缩放因子、旋转属性。粒子系统将geometry对象里的每一个点视为一个单独的粒子。为什么这样做?首先我们了解一下geometry,他是一个BufferGeometry的实例,用于储存面片、线、点、顶点位置、向量、颜色等信息。points通过这些信息去生成粒子集合,渲染到场景中。当然本文中,每一个BufferGeometry的动画都各自独立,如果采用points整体执行动画或许可能性能会比较好,但是动画会显得非常的生硬,这里可以提一下cesium中的粒子系统就完全不同于threejs,如果大家感兴趣,后续我将更新cesium相关内容。所以讲各自动画分离,这样动画就不那么生硬,但是有个缺点就是粒子数量过多可能会卡顿,当然这个配置有关~

粒子效果

这里将粒子做了统一的封装,简单介绍一下相关属性:
positionBase:粒子生成初始位置 new THREE.Vector3
positionRadius: 圆角
positionSpread: 粒子存在范围 new THREE.Vector3
velocityBase: 运动速度 三维坐标方向上的速度 new THREE.Vector3
velocityBase: 运动范围  new THREE.Vector3
accelerationBase: 运动加速度 三维坐标方向上的加速度 new THREE.Vector3
particleTexture: 贴图
sizeTween: 粒子大小
opacityTween: 粒子透明度
colorTween: 粒子颜色
particlesPerSecond:粒子数量
particleDeathAge: 单粒子存活时间
emitterDeathAge: 动画持续时间
复制代码

snow_.gif

雪粒子对象:相关属性在上方做了介绍,主要通过贴图及粒子动画实现
复制代码
snow :
  {
    positionStyle    : Type.CUBE,
    positionBase     : new THREE.Vector3( 0, 200, 0 ),
    positionSpread   : new THREE.Vector3( 400, 0, 400 ),

    velocityStyle    : Type.CUBE,
    velocityBase     : new THREE.Vector3( 50, -200, 0 ),
    velocitySpread   : new THREE.Vector3( 50, 200, 50 ),
    accelerationBase : new THREE.Vector3( 0, -10,0 ),

    angleBase               : 0,
    angleSpread             : 720,
    angleVelocityBase       :  0,
    angleVelocitySpread     : 60,

    particleTexture : textureLoader.load(gAppPath+'images/snowflake.png' ),

    sizeTween    : new Tween( [0, 0.25], [4, 5] ),
    colorBase   : new THREE.Vector3(0.66, 1.0, 0.9), // H,S,L
    opacityTween : new Tween( [2, 3], [0.8, 0] ),

    particlesPerSecond : 500,
    particleDeathAge   : 1.0,
    emitterDeathAge    : 60
  },
复制代码

萤火虫

yhc_.gif

萤火虫粒子对象:相关属性在上方做了介绍,主要通过贴图及粒子动画实现
复制代码
fireflies :
  {
    positionStyle  : Type.CUBE,
    positionBase   : new THREE.Vector3( 0, 100, 0 ),
    positionSpread : new THREE.Vector3( 400, 200, 400 ),

    velocityStyle  : Type.CUBE,
    velocityBase   : new THREE.Vector3( 0, 0, 0 ),
    velocitySpread : new THREE.Vector3( 60, 20, 60 ),



    particleTexture : textureLoader.load(gAppPath+'images/spark.png' ),

    sizeBase   : 30.0,
    sizeSpread : 10.0,
    opacityTween : new Tween([0.0, 1.0, 1.1, 2.0, 2.1, 3.0, 3.1, 4.0, 4.1, 5.0, 5.1, 6.0, 6.1],
      [0.2, 0.2, 1.0, 1.0, 0.2, 0.2, 1.0, 1.0, 0.2, 0.2, 1.0, 1.0, 0.2] ),
    colorBase   : new THREE.Vector3(0.30, 1.0, 0.6), // H,S,L
    colorSpread : new THREE.Vector3(0.3, 0.0, 0.0),

    particlesPerSecond : 20,
    particleDeathAge   : 6.1,
    emitterDeathAge    : 600
  },
复制代码

烟雾

smoke_.gif

烟雾粒子对象:相关属性在上方做了介绍,主要通过贴图及粒子动画实现
复制代码
smoke :
  {
    positionStyle    : Type.CUBE,
    positionBase     : new THREE.Vector3( 0, 0, 0 ),
    positionSpread   : new THREE.Vector3( 2, 0, 2 ),

    velocityStyle    : Type.CUBE,
    velocityBase     : new THREE.Vector3( 0, 40, 0 ),
    velocitySpread   : new THREE.Vector3( 20, 40, 20 ),
    accelerationBase : new THREE.Vector3( 0,-10,0 ),

    particleTexture : textureLoader.load(gAppPath+'images/smokeparticle.png'),
    speedBase     : 10,
    speedSpread   : 10,
    angleBase               : 0,
    angleSpread             : 720,
    angleVelocityBase       : 0,
    angleVelocitySpread     : 720,

    sizeTween    : new Tween( [0, 1], [32, 128] ),
    opacityTween : new Tween( [0.8, 2], [0.5, 0] ),
    colorTween   : new Tween( [0.4, 1], [ new THREE.Vector3(0,0,0.2), new THREE.Vector3(0, 0, 0.5) ] ),

    particlesPerSecond : 6,
    particleDeathAge   : 2.0,
    emitterDeathAge    : 5
  },
复制代码

爆炸

ball_.gif

爆炸粒子对象:相关属性在上方做了介绍,主要通过贴图及粒子动画实现
复制代码
fireball :
  {
    positionStyle  : Type.SPHERE,
    positionBase   : new THREE.Vector3( 0, 50, 0 ),
    positionRadius : 2,

    velocityStyle : Type.SPHERE,
    speedBase     : 10,
    speedSpread   : 50,

    particleTexture : textureLoader.load(gAppPath+'images/smokeparticle.png' ),

    sizeTween    : new Tween( [0, 0.1], [1, 50] ),
    opacityTween : new Tween( [0.7, 1], [1, 0] ),
    colorBase    : new THREE.Vector3(0.02, 1, 0.4),
    blendStyle   : THREE.AdditiveBlending,

    particlesPerSecond : 300,
    particleDeathAge   : 0.5,
    emitterDeathAge    : 60
  },
复制代码

星空

star_.gif

星星粒子对象:相关属性在上方做了介绍,主要通过贴图及粒子动画实现
复制代码
starfield :
  {
    positionStyle    : Type.CUBE,
    positionBase     : new THREE.Vector3( 0, 100, 0 ),
    positionSpread   : new THREE.Vector3( 400, 100, 400 ),

    velocityStyle    : Type.CUBE,
    velocityBase     : new THREE.Vector3( 0, 0, 0 ),
    velocitySpread   : new THREE.Vector3( 0.5, 0.5, 0.5 ),

    angleBase               : 0,
    angleSpread             : 720,
    angleVelocityBase       : 0,
    angleVelocitySpread     : 4,

    particleTexture : textureLoader.load(gAppPath+'images/spikey.png' ),

    sizeBase    : 10.0,
    sizeSpread  : 2.0,
    colorBase   : new THREE.Vector3(0.15, 1.0, 0.9), // H,S,L
    colorSpread : new THREE.Vector3(0.00, 0.0, 0.2),
    opacityBase : 1,

    particlesPerSecond : 1000,
    particleDeathAge   : 60.0,
    emitterDeathAge    : 0.1
  },
复制代码

rain_.gif

雨粒子对象:相关属性在上方做了介绍,主要通过贴图及粒子动画实现
复制代码
rain :
  {
    positionStyle    : Type.CUBE,
    positionBase     : new THREE.Vector3( 0, 200, 0 ),
    positionSpread   : new THREE.Vector3( 400, 0, 400 ),

    velocityStyle    : Type.CUBE,
    velocityBase     : new THREE.Vector3( 0, -700, 0 ),
    velocitySpread   : new THREE.Vector3( 10, 50, 10 ),
    accelerationBase : new THREE.Vector3( 0, -10,0 ),

    particleTexture : textureLoader.load(gAppPath+'images/raindrop2flip.png' ),

    sizeBase    : 8,
    sizeSpread  : 4.0,
    colorBase   : new THREE.Vector3(0.66, 1.0, 0.75), // H,S,L
    colorSpread : new THREE.Vector3(0.0, 0, 0.1),
    opacityBase : 0.4,

    particlesPerSecond : 600,
    particleDeathAge   : 1.0,
    emitterDeathAge    : 60
  },
复制代码

火焰

candle_.gif

火焰粒子对象:相关属性在上方做了介绍,主要通过贴图及粒子动画实现
复制代码
candle :
  {
    positionStyle  : Type.SPHERE,
    positionBase   : new THREE.Vector3( 0, 50, 0 ),
    positionRadius : 2,

    velocityStyle  : Type.CUBE,
    velocityBase   : new THREE.Vector3(0,30,0),
    velocitySpread : new THREE.Vector3(20,0,20),
    speedBase     : 10,
    speedSpread   : 10,

    particleTexture : textureLoader.load(gAppPath+'images/smokeparticle.png' ),

    sizeTween    : new Tween( [0, 0.3, 1.2], [20, 15, 1] ),
    opacityTween : new Tween( [0.9, 1.5], [1, 0] ),
    colorTween   : new Tween( [0.5, 1.0], [ new THREE.Vector3(0.02, 1, 0.5), new THREE.Vector3(0.05, 1, 0) ] ),
    blendStyle : THREE.AdditiveBlending,

    particlesPerSecond : 600,
    particleDeathAge   : 104,
    emitterDeathAge    : 10
  }
复制代码

cloud_.gif

云粒子对象:相关属性在上方做了介绍,主要通过贴图及粒子动画实现
复制代码
clouds :
  {
    positionStyle  : Type.CUBE,
    positionBase   : new THREE.Vector3( -100, 100,  0 ),
    positionSpread : new THREE.Vector3( 0,  10, 60 ),

    velocityStyle  : Type.CUBE,
    velocityBase   : new THREE.Vector3( 100, 0, 0 ),
    velocitySpread : new THREE.Vector3( 100, 0, 0 ),

    particleTexture : textureLoader.load(gAppPath+'images/smokeparticle.png'),

    sizeBase     : 80.0,
    sizeSpread   : 100.0,
    colorBase    : new THREE.Vector3(0.0, 0.0, 1.0), // H,S,L
    opacityTween : new Tween([0,1,4,5],[0,1,1,0]),

    particlesPerSecond : 100,
    particleDeathAge   : 10.0,
    emitterDeathAge    : 60
  },
复制代码

雾霾

haze_.gif

雾霾粒子对象:相关属性在上方做了介绍,主要通过贴图及粒子动画实现
复制代码
haze: //雾霾
  {
    positionStyle: Type.CUBE,
    positionBase: new THREE.Vector3(0, 50, 0),
    positionSpread: new THREE.Vector3(400, 100, 400),

    velocityStyle: Type.CUBE,
    velocityBase: new THREE.Vector3(0, 0, 0),
    velocitySpread: new THREE.Vector3(100.5, 0.5, 100.5),

    angleBase: 0,
    angleSpread: 0,
    angleVelocityBase: 0,
    angleVelocitySpread: 4,

    particleTexture: textureLoader.load(gAppPath + 'images/smoke512.png'),

    sizeBase: 1000.0,
    sizeSpread: 2.0,
    colorBase: new THREE.Vector3(0.15, 1.0, 0), // H,S,L
    colorSpread: new THREE.Vector3(0.00, 0.0, 0.2),
    opacityBase: 0.15,

    particlesPerSecond: 1000,
    particleDeathAge: 60.0,
    emitterDeathAge: 0.1
  },
复制代码

粒子类

这里通过类的形式,对粒子系统做了一个整体的封装,我会将大部分思想进行解析。

类的封装分为:(具体下面会展开介绍)

- Tween 类 
(根据粒子对象中的sizeTween、opacityTween、colorBase传入的时间区间及数值区间,计算时间处于某
一刻时粒子对应的值)
- Particle 创建粒子类(控制粒子生成以及粒子位置、大小、透明度、颜色随时间变化的值)
- ParticleEngine 粒子类(根据粒子类型生成粒子初始位置,控制粒子运动以及销毁粒子)
复制代码

Tween

调用示例:new Tween( [0, 1], [1, 10] )

解析:简单描述思路就是通过传入某个时刻t计算在时间跨度(timeArray)中所对应的值(valueArray) 这个类的功能主要是通过传入t去获取size、color、opacity的值

这里补充一下这块代码的解析lerp-插值,作用是取v1,v2在插值因素[0,1]中所对应的点。 简单来说就是取v1可以看做起点,v2看做终点,lerp就是取v1,v2中的一个值-根据插值因素来决定

class Tween {
  constructor(timeArray, valueArray) {
    this.times = timeArray || []
    this.values = valueArray || []
  }

  lerp(t) {
    var i = 0
    var n = this.times.length //时间取值数组
    while (i < n && t > this.times[i]) { //判断当前时间所在的区间位置
      i++
    }
    if (i == 0) { //起点
      return this.values[0]
    }
    if (i == n) { //终点
      return this.values[n - 1]
    }
    //取时间中的一段,计算当前时间在这一小段时间中的插值因素
    var p = (t - this.times[i - 1]) / (this.times[i] - this.times[i - 1]) //计算插值因素
    if (this.values[0] instanceof THREE.Vector3) {
      return this.values[i - 1].clone().lerp(this.values[i], p) //取该插值因素所对应的点
    } else // its a float
    {
      return this.values[i - 1] + p * (this.values[i] - this.values[i - 1]) //不是v3的情况计算插值因素所对应的长度
    }
  }
}
复制代码

Particle

调用示例:var particle = new Particle()

解析:这个类是用于创建粒子,只是单个粒子。通过随机生成粒子在特定范围内的位置,不断累加向量标量, 向量标量不断变大模拟加速度,控制角度(在自定义着色器中提现),控制大小、颜色、透明度

class Particle {
  constructor() {
    this.position = new THREE.Vector3() //粒子位置
    this.velocity = new THREE.Vector3() //标量向量
    this.acceleration = new THREE.Vector3()
    this.angle = 0 //角度
    this.angleVelocity = 0 
    this.angleAcceleration = 0 
    this.size = 16.0
    this.color = new THREE.Color()
    this.opacity = 1.0
    this.age = 0 //存活时间
    this.alive = 0 //是否显示
  }

  update(dt) { //粒子属性更新函数
    this.position.add(this.velocity.clone().multiplyScalar(dt))//向量相加,粒子位置移动
    this.velocity.add(this.acceleration.clone().multiplyScalar(dt))//累加向量相加,模拟加速度

    this.angle += this.angleVelocity * 0.01745329251 * dt // 角度
    this.angleVelocity += this.angleAcceleration * 0.01745329251 * dt // 角速度

    this.age += dt //累加粒子存活时间

    if (this.sizeTween.times.length > 0) {
      this.size = this.sizeTween.lerp(this.age) //大小
    }

    if (this.colorTween.times.length > 0) {
      var colorHSL = this.colorTween.lerp(this.age)
      this.color = new THREE.Color().setHSL(colorHSL.x, colorHSL.y, colorHSL.z)//颜色
    }

    if (this.opacityTween.times.length > 0) {
      this.opacity = this.opacityTween.lerp(this.age)//透明度
    }
  }
}
复制代码

ParticleEngine

粒子类实现的功能:

    - setValues 通过粒子类型初始化粒子类的属性
    - createParticle 创建粒子,随机生成粒子位置、颜色、角度、大小、透明度等属性的值
    - initialize 初始化粒子场景,通过粒子数量生成所有粒子并添加与场景中
    - update 粒子属性更新函数,粒子更新循环系统
    - destroy 销毁粒子
    - randomVector3 根据特定范围以及初始位置随机生成一个三维坐标
    - randomValue 根据初始角度以及角度范围生成一个随机角度
复制代码
整体粒子调用示例:
import {ParticleEngine} from 'zhdPartice'
import ParticleEngineExamples from 'zhdParticleExamples'

initParticle = function () {
  this.particle = new ParticleEngine()
  this.particle.setValues(ParticleEngineExamples.candle, {
    positionBase: new THREE.Vector3(0, 0, 0),
  })
  this.particle.initialize(this.scene)
  this.renderFunction.push(() => {
    if (this.particle) this.particle.update(0.01)
  })
}
复制代码

着色器

着色器相关代码如下,这里不做展开。不懂的可以具体去了解着色器相关实现,这个比较复杂,这里就不作展开~

const particleVertexShader =
  [
    'attribute vec3  customColor;',
    'attribute float customOpacity;',
    'attribute float customSize;',
    'attribute float customAngle;',
    'attribute float customVisible;',  // float used as boolean (0 = false, 1 = true)
    'varying vec4  vColor;',
    'varying float vAngle;',
    'void main()',
    '{',
    'if ( customVisible > 0.5 )',     // true
    'vColor = vec4( customColor, customOpacity );', //     set color associated to vertex; use later in fragment shader.
    'else',        // false
    'vColor = vec4(0,0,0,0);',    //     make particle invisible.

    'vAngle = customAngle;',

    'vec4 mvPosition = modelViewMatrix * vec4( position, 1.0 );',
    'gl_PointSize = customSize * ( 300.0 / length( mvPosition.xyz ) );',     // scale particles as objects in 3D space
    'gl_Position = projectionMatrix * mvPosition;',
    '}'
  ].join('\n')

const particleFragmentShader =
  [
    'uniform sampler2D mytexture;',
    'varying vec4 vColor;',
    'varying float vAngle;',
    'void main()',
    '{',
    'gl_FragColor = vColor;',

    'float c = cos(vAngle);',
    'float s = sin(vAngle);',
    'vec2 rotatedUV = vec2(c * (gl_PointCoord.x - 0.5) + s * (gl_PointCoord.y - 0.5) + 0.5,',
    'c * (gl_PointCoord.y - 0.5) - s * (gl_PointCoord.x - 0.5) + 0.5);',  // rotate UV coordinates to rotate texture
    'vec4 rotatedTexture = texture2D( mytexture,  rotatedUV );',
    'gl_FragColor = gl_FragColor * rotatedTexture;',    // sets an otherwise white particle texture to desired color
    '}'
  ].join('\n')
复制代码

setValues

通过类型和新对象,生成粒子的初始化属性。

setValues(parameters,params) {
  /**
   *
   * @param mugen 是否无限循环
   * @param particlesPerSecond 粒子数量
   * @param particleDeathAge 每颗粒子存活时间
   * @param emitterDeathAge 持续时间 mugen为false生效
   * @param positionBase obj 粒子系统初始位置 xyz
   * @param positionSpread obj 粒子范围 xyz
   * @param velocityBase 粒子沿着xzy方向移动速度 xyz
   * @param velocitySpread 粒子沿着xzy方向的运动范围
   */
  this.mugen = params.mugen||false // 无限循环
  if (parameters === undefined) {
    return
  }
  let obj=Object.assign(parameters,params) //覆盖原有的属性值
  this.sizeTween = new Tween() //大小
  this.colorTween = new Tween() //颜色
  this.opacityTween = new Tween()//透明度

  for (var key in obj) { //初始化参数赋值
    this[key] = obj[key]
  }

  Particle.prototype.sizeTween = this.sizeTween
  Particle.prototype.colorTween = this.colorTween
  Particle.prototype.opacityTween = this.opacityTween

  this.particleArray = [] //粒子合集
  this.emitterAge = 0.0
  this.emitterAlive = true
  this.particleCount = this.particlesPerSecond * Math.min(this.particleDeathAge, this.emitterDeathAge)//粒子数量
  //材质相关
  this.particleGeometry = new THREE.BufferGeometry()
  this.particleMaterial = new THREE.ShaderMaterial(
    {
      uniforms: {
        mytexture: {value: this.particleTexture},
      },
      vertexShader: particleVertexShader,
      fragmentShader: particleFragmentShader,
      blending: THREE.AdditiveBlending,
      depthTest: false,
      transparent: true,
      vertexColors: true
    })
}
复制代码

createParticle

创建粒子,根据粒子对象中的属性,初始化在特定范围内随机生成粒子的位置、角度、大小、透明度、颜色、存活时间、 是否显示等属性。代码中都做了相应的注释,由于前后关联密切,需结合前后的代码,便于理解这块代码~~

createParticle() {
  var particle = new Particle() //初始化创建粒子类
  //根据粒子类型,初始化随机生成对应属性值
  if (this.positionStyle == Type.CUBE) {
    particle.position = this.randomVector3(this.positionBase, this.positionSpread)//位置
  }
  if (this.positionStyle == Type.SPHERE) {
    var z = 2 * Math.random() - 1
    var t = 6.2832 * Math.random()
    var r = Math.sqrt(1 - z * z)
    var vec3 = new THREE.Vector3(r * Math.cos(t), r * Math.sin(t), z)
    particle.position = new THREE.Vector3().addVectors(this.positionBase, vec3.multiplyScalar(this.positionRadius))
  }

  if (this.velocityStyle == Type.CUBE) {
    particle.velocity = this.randomVector3(this.velocityBase, this.velocitySpread)//向量标量
  }
  if (this.velocityStyle == Type.SPHERE) {
    var direction = new THREE.Vector3().subVectors(particle.position, this.positionBase)
    var speed = this.randomValue(this.speedBase, this.speedSpread)
    particle.velocity = direction.normalize().multiplyScalar(speed)
  }

  particle.acceleration = this.randomVector3(this.accelerationBase, this.accelerationSpread)//标量的累加向量

  particle.angle = this.randomValue(this.angleBase, this.angleSpread)//角度
  particle.angleVelocity = this.randomValue(this.angleVelocityBase, this.angleVelocitySpread)
  particle.angleAcceleration = this.randomValue(this.angleAccelerationBase, this.angleAccelerationSpread)

  particle.size = this.randomValue(this.sizeBase, this.sizeSpread)//大小

  var color = this.randomVector3(this.colorBase, this.colorSpread)//颜色
  particle.color = new THREE.Color().setHSL(color.x, color.y, color.z)

  particle.opacity = this.randomValue(this.opacityBase, this.opacitySpread)//透明度

  particle.age = 0//存活时间
  particle.alive = 0 // 是否显示

  return particle
}
复制代码

initialize

初始化粒子场景,通过遍历生成BufferGeometry与材质生成点粒子集合,为其添加位置、颜色、透明度等属性,通过point将所有BufferGeometry添加到场景中。 记录下每个粒子的所有属性,通过setAttribute控制粒子相关属性的值,custom相关属性间接影响到了自定义着色器 中的粒子颜色、显隐、大小、透明度等属性

initialize(scene) {
  var positions = []
  var customVisibles = []
  var customColor = []
  var customOpacity = []
  var customSize = []
  var customAngle = []
  for (var i = 0; i < this.particleCount; i++) { //遍历生成对应数量的BufferGeometry
    this.particleArray[i] = this.createParticle()
    positions.push(this.particleArray[i].position.x)
    positions.push(this.particleArray[i].position.y)
    positions.push(this.particleArray[i].position.z)
    //自定义着色器相关
    customVisibles.push(this.particleArray[i].alive)
    customColor.push(this.particleArray[i].color.r)
    customColor.push(this.particleArray[i].color.g)
    customColor.push(this.particleArray[i].color.b)
    customOpacity.push(this.particleArray[i].opacity)
    customSize.push(this.particleArray[i].size)
    customAngle.push(this.particleArray[i].angle)
  }

  this.positions = positions //位置数组
  this.customVisibles = customVisibles //显隐数组
  this.customColor = customColor//颜色数组
  this.customOpacity = customOpacity//透明度数组
  this.customSize = customSize//大小数组
  this.customAngle = customAngle//角度数组
  //设置BufferGeometry的attribute属性
  this.particleGeometry.setAttribute('position', new THREE.Float32BufferAttribute(this.positions, 3))
  this.particleGeometry.setAttribute('customVisible', new THREE.Float32BufferAttribute(this.customVisibles, 1))
  this.particleGeometry.setAttribute('customColor', new THREE.Float32BufferAttribute(this.customColor, 3))
  this.particleGeometry.setAttribute('customOpacity', new THREE.Float32BufferAttribute(this.customOpacity, 1))
  this.particleGeometry.setAttribute('customSize', new THREE.Float32BufferAttribute(this.customSize, 1))
  this.particleGeometry.setAttribute('customAngle', new THREE.Float32BufferAttribute(this.customAngle, 1))


  this.particleMaterial.blending = this.blendStyle
  if (this.blendStyle !== THREE.NormalBlending) {
    this.particleMaterial.depthTest = false
  }
  //生成点
  this.particleMesh = new THREE.Points(this.particleGeometry, this.particleMaterial)
  this.particleMesh.dynamic = true
  this.particleMesh.sortParticles = true
  scene.add(this.particleMesh)
}
复制代码

update

粒子运动函数解析:通过遍历粒子更新对应粒子属性及着色器,记录下失效的粒子下标,通过下标去生成新的粒子。

从无到有的粒子过渡:根据粒子场景存在时间以及粒子场景结束时间去生成对应百分比数量的粒子,根据是否无限循环 来控制粒子动画是否在规定时间内停止或无限执行下去。

粒子位置更新:particleArray.update更新粒子的位置,通过下标以每三个一组的形式更新对应粒子的位置

update(dt) {
  var recycleIndices = []
  for (var i = 0; i < this.particleCount; i++) {//遍历场景中的粒子
    if (this.particleArray[i].alive) {
      this.particleArray[i].update(dt) //粒子属性更新
      if (this.particleArray[i].age > this.particleDeathAge) { //判断该粒子存活时间是否超过
        this.particleArray[i].alive = 0.0 //隐藏粒子
        recycleIndices.push(i)//失效粒子index数组
      }
      //更新着色器特定属性数组
      this.customVisibles[i] = this.particleArray[i].alive
      this.customColor[i * 3] = this.particleArray[i].color.r
      this.customColor[i * 3 + 1] = this.particleArray[i].color.g
      this.customColor[i * 3 + 2] = this.particleArray[i].color.b
      this.customOpacity[i] = this.particleArray[i].opacity
      this.customSize[i] = this.particleArray[i].size
      this.customAngle[i] = this.particleArray[i].angle
    }
  }
   //设置BufferGeometry的attribute属性,着色器相关,颜色、大小、位置、显隐效果
  this.particleGeometry.setAttribute('customVisible', new THREE.Float32BufferAttribute(this.customVisibles, 1))
  this.particleGeometry.setAttribute('customColor', new THREE.Float32BufferAttribute(this.customColor, 3))
  this.particleGeometry.setAttribute('customOpacity', new THREE.Float32BufferAttribute(this.customOpacity, 1))
  this.particleGeometry.setAttribute('customSize', new THREE.Float32BufferAttribute(this.customSize, 1))
  this.particleGeometry.setAttribute('customAngle', new THREE.Float32BufferAttribute(this.customAngle, 1))

  this.particleGeometry.attributes.customVisible.needsUpdate = true
  this.particleGeometry.attributes.customColor.needsUpdate = true
  this.particleGeometry.attributes.customOpacity.needsUpdate = true
  this.particleGeometry.attributes.customSize.needsUpdate = true
  this.particleGeometry.attributes.customAngle.needsUpdate = true

  if (!this.emitterAlive) return //整个粒子系统存活时间到了(粒子动画时间)
  //控制所有粒子中的一部份粒子显示,时间越长粒子数量越多,直到粒子全部显示
  if (this.emitterAge < this.particleDeathAge) { //如果时间还未到结束时间
    var startIndex = Math.round(this.particlesPerSecond * (this.emitterAge + 0))
    var endIndex = Math.round(this.particlesPerSecond * (this.emitterAge + dt))
    if (endIndex > this.particleCount) { //判断粒子数量
      endIndex = this.particleCount
    }

    for (var i = startIndex; i < endIndex; i++) { //
      this.particleArray[i].alive = 1.0
    }
  }

  for (var j = 0; j < recycleIndices.length; j++) //失效粒子重新生成
  {
    var i = recycleIndices[j] //获取index下标
    this.particleArray[i] = this.createParticle() //重新创建粒子
    this.particleArray[i].alive = 1.0 //显示粒子
  }
  //粒子位置改变
  for (var j = 0; j < this.particleCount; j++) {
    this.positions[j * 3] = this.particleArray[j].position.x
    this.positions[j * 3 + 1] = this.particleArray[j].position.y
    this.positions[j * 3 + 2] = this.particleArray[j].position.z
  }
  //每三个一组,更新整个BufferGeometry的粒子位置
  this.particleGeometry.setAttribute('position', new THREE.Float32BufferAttribute(this.positions, 3))
  //粒子动画时间累计
  this.emitterAge += dt
  if (!this.mugen) { //是否无限循环
    if (this.emitterAge > this.emitterDeathAge) this.emitterAlive = false
  }
}
复制代码

destroy

销毁粒子,在不需要时销毁粒子回收释放内存,以免照成卡顿问题。
复制代码
destroy(scene) {
  scene.remove(this.particleMesh)
}
复制代码

randomVector3

根据特定范围以及初始位置随机生成一个三维坐标,用来改变粒子的位置
复制代码
randomVector3(base, spread) {
  var rand3 = new THREE.Vector3(Math.random() - 0.5, Math.random() - 0.5, Math.random() - 0.5)
  return new THREE.Vector3().addVectors(base, new THREE.Vector3().multiplyVectors(spread, rand3))
}
复制代码

randomValue

根据初始角度以及角度范围生成一个随机角度,用来改变粒子的运动角度
复制代码
randomValue(base, spread) {
  return base + spread * (Math.random() - 0.5)
}
复制代码

结语

创作不易,猪猪叹气!这边文章写了我一整天的时间,我努力的将整个文章的思想都加上了注释,代码量有点多,但是满满的干活!希望有收获的同学能够轻轻的点一个赞~ 本人创建了threejs专栏threejs专栏感兴趣的可以关注下~ 本文代码量较大,主要是为了贴全代码,方便读者自己实现,相关代码解析都以模块抬头解析以及相关代码注释,有不懂的地方可以下文章下方留言~ 当然有什么意见以及不懂的地方也欢迎大家在评论区提出,我会吸取做出改正~~

分类:
前端
标签: