ParticleEngine.js实现烟雾效果

参考网址:http://stemkoski.github.io/Three.js/Particle-Engine.html 

ParticleEngine.js源码依赖的three.js版本是60,而我使用的three.js的版本是112,新版本不支持为ShaderMaterial设置attributes,所以修改了ParticleEngine.js源码。

原始版本ParticleEngine.js代码:

/**
* @author Lee Stemkoski   http://www.adelphi.edu/~stemkoski/
*/

///////////////////////////////////////////////////////////////////////////////

/////////////
// SHADERS //
/////////////

// attribute: data that may be different for each particle (such as size and color);
//      can only be used in vertex shader
// varying: used to communicate data from vertex shader to fragment shader
// uniform: data that is the same for each particle (such as texture)

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, 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");

particleFragmentShader =
[
"uniform sampler2D texture;",
"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( texture,  rotatedUV );",
    "gl_FragColor = gl_FragColor * rotatedTexture;",    // sets an otherwise white particle texture to desired color
"}"
].join("\n");

///////////////////////////////////////////////////////////////////////////////

/////////////////
// TWEEN CLASS //
/////////////////

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

Tween.prototype.lerp = function(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]);
}

///////////////////////////////////////////////////////////////////////////////

////////////////////
// PARTICLE CLASS //
////////////////////

function Particle()
{
    this.position     = new THREE.Vector3();
    this.velocity     = new THREE.Vector3(); // units per second
    this.acceleration = new THREE.Vector3();

    this.angle             = 0;
    this.angleVelocity     = 0; // degrees per second
    this.angleAcceleration = 0; // degrees per second, per second
    
    this.size = 16.0;

    this.color   = new THREE.Color();
    this.opacity = 1.0;
            
    this.age   = 0;
    this.alive = 0; // use float instead of boolean for shader purposes    
}

Particle.prototype.update = function(dt)
{
    this.position.add( this.velocity.clone().multiplyScalar(dt) );
    this.velocity.add( this.acceleration.clone().multiplyScalar(dt) );
    
    // convert from degrees to radians: 0.01745329251 = Math.PI/180
    this.angle         += this.angleVelocity     * 0.01745329251 * dt;
    this.angleVelocity += this.angleAcceleration * 0.01745329251 * dt;

    this.age += dt;
    
    // if the tween for a given attribute is nonempty,
    //  then use it to update the attribute's value

    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 );
}
    
///////////////////////////////////////////////////////////////////////////////

///////////////////////////
// PARTICLE ENGINE CLASS //
///////////////////////////

var Type = Object.freeze({ "CUBE":1, "SPHERE":2 });

function ParticleEngine()
{
    /////////////////////////
    // PARTICLE PROPERTIES //
    /////////////////////////
    
    this.positionStyle = Type.CUBE;        
    this.positionBase   = new THREE.Vector3();
    // cube shape data
    this.positionSpread = new THREE.Vector3();
    // sphere shape data
    this.positionRadius = 0; // distance from base at which particles start
    
    this.velocityStyle = Type.CUBE;    
    // cube movement data
    this.velocityBase       = new THREE.Vector3();
    this.velocitySpread     = new THREE.Vector3(); 
    // sphere movement data
    //   direction vector calculated using initial position
    this.speedBase   = 0;
    this.speedSpread = 0;
    
    this.accelerationBase   = new THREE.Vector3();
    this.accelerationSpread = new THREE.Vector3();    
    
    this.angleBase               = 0;
    this.angleSpread             = 0;
    this.angleVelocityBase       = 0;
    this.angleVelocitySpread     = 0;
    this.angleAccelerationBase   = 0;
    this.angleAccelerationSpread = 0;
    
    this.sizeBase   = 0.0;
    this.sizeSpread = 0.0;
    this.sizeTween  = new Tween();
            
    // store colors in HSL format in a THREE.Vector3 object
    // http://en.wikipedia.org/wiki/HSL_and_HSV
    this.colorBase   = new THREE.Vector3(0.0, 1.0, 0.5); 
    this.colorSpread = new THREE.Vector3(0.0, 0.0, 0.0);
    this.colorTween  = new Tween();
    
    this.opacityBase   = 1.0;
    this.opacitySpread = 0.0;
    this.opacityTween  = new Tween();

    this.blendStyle = THREE.NormalBlending; // false;

    this.particleArray = [];
    this.particlesPerSecond = 100;
    this.particleDeathAge = 1.0;
    
    ////////////////////////
    // EMITTER PROPERTIES //
    ////////////////////////
    
    this.emitterAge      = 0.0;
    this.emitterAlive    = true;
    this.emitterDeathAge = 60; // time (seconds) at which to stop creating particles.
    
    // How many particles could be active at any time?
    this.particleCount = this.particlesPerSecond * Math.min( this.particleDeathAge, this.emitterDeathAge );

    //////////////
    // THREE.JS //
    //////////////
    
    this.particleGeometry = new THREE.Geometry();
    this.particleTexture  = null;
    this.particleMaterial = new THREE.ShaderMaterial( 
    {
        uniforms: 
        {
            texture:   { type: "t", value: this.particleTexture },
        },
        attributes:     
        {
            customVisible:    { type: 'f',  value: [] },
            customAngle:    { type: 'f',  value: [] },
            customSize:        { type: 'f',  value: [] },
            customColor:    { type: 'c',  value: [] },
            customOpacity:    { type: 'f',  value: [] }
        },
        vertexShader:   particleVertexShader,
        fragmentShader: particleFragmentShader,
        transparent: true, // alphaTest: 0.5,  // if having transparency issues, try including: alphaTest: 0.5, 
        blending: THREE.NormalBlending, depthTest: true,
        
    });
    this.particleMesh = new THREE.Mesh();
}
    
ParticleEngine.prototype.setValues = function( parameters )
{
    if ( parameters === undefined ) return;
    
    // clear any previous tweens that might exist
    this.sizeTween    = new Tween();
    this.colorTween   = new Tween();
    this.opacityTween = new Tween();
    
    for ( var key in parameters ) 
        this[ key ] = parameters[ key ];
    
    // attach tweens to particles
    Particle.prototype.sizeTween    = this.sizeTween;
    Particle.prototype.colorTween   = this.colorTween;
    Particle.prototype.opacityTween = this.opacityTween;    
    
    // calculate/set derived particle engine values
    this.particleArray = [];
    this.emitterAge      = 0.0;
    this.emitterAlive    = true;
    this.particleCount = this.particlesPerSecond * Math.min( this.particleDeathAge, this.emitterDeathAge );
    
    this.particleGeometry = new THREE.Geometry();
    this.particleMaterial = new THREE.ShaderMaterial( 
    {
        uniforms: 
        {
            texture:   { type: "t", value: this.particleTexture },
        },
        attributes:     
        {
            customVisible:    { type: 'f',  value: [] },
            customAngle:    { type: 'f',  value: [] },
            customSize:        { type: 'f',  value: [] },
            customColor:    { type: 'c',  value: [] },
            customOpacity:    { type: 'f',  value: [] }
        },
        vertexShader:   particleVertexShader,
        fragmentShader: particleFragmentShader,
        transparent: true,  alphaTest: 0.5, // if having transparency issues, try including: alphaTest: 0.5, 
        blending: THREE.NormalBlending, depthTest: true
    });
    this.particleMesh = new THREE.ParticleSystem();
}
    
// helper functions for randomization
ParticleEngine.prototype.randomValue = function(base, spread)
{
    return base + spread * (Math.random() - 0.5);
}
ParticleEngine.prototype.randomVector3 = function(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 ) );
}


ParticleEngine.prototype.createParticle = function()
{
    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; // particles initialize as inactive
    
    return particle;
}

ParticleEngine.prototype.initialize = function()
{
    // link particle data with geometry/material data
    for (var i = 0; i < this.particleCount; i++)
    {
        // remove duplicate code somehow, here and in update function below.
        this.particleArray[i] = this.createParticle();
        this.particleGeometry.vertices[i] = this.particleArray[i].position;
        this.particleMaterial.attributes.customVisible.value[i] = this.particleArray[i].alive;
        this.particleMaterial.attributes.customColor.value[i]   = this.particleArray[i].color;
        this.particleMaterial.attributes.customOpacity.value[i] = this.particleArray[i].opacity;
        this.particleMaterial.attributes.customSize.value[i]    = this.particleArray[i].size;
        this.particleMaterial.attributes.customAngle.value[i]   = this.particleArray[i].angle;
    }
    
    this.particleMaterial.blending = this.blendStyle;
    if ( this.blendStyle != THREE.NormalBlending) 
        this.particleMaterial.depthTest = false;
    
    this.particleMesh = new THREE.ParticleSystem( this.particleGeometry, this.particleMaterial );
    this.particleMesh.dynamic = true;
    this.particleMesh.sortParticles = true;
    scene.add( this.particleMesh );
}

ParticleEngine.prototype.update = function(dt)
{
    var recycleIndices = [];
    
    // update particle data
    for (var i = 0; i < this.particleCount; i++)
    {
        if ( this.particleArray[i].alive )
        {
            this.particleArray[i].update(dt);

            // check if particle should expire
            // could also use: death by size<0 or alpha<0.
            if ( this.particleArray[i].age > this.particleDeathAge ) 
            {
                this.particleArray[i].alive = 0.0;
                recycleIndices.push(i);
            }
            // update particle properties in shader
            this.particleMaterial.attributes.customVisible.value[i] = this.particleArray[i].alive;
            this.particleMaterial.attributes.customColor.value[i]   = this.particleArray[i].color;
            this.particleMaterial.attributes.customOpacity.value[i] = this.particleArray[i].opacity;
            this.particleMaterial.attributes.customSize.value[i]    = this.particleArray[i].size;
            this.particleMaterial.attributes.customAngle.value[i]   = this.particleArray[i].angle;
        }        
    }

    // check if particle emitter is still running
    if ( !this.emitterAlive ) return;

    // if no particles have died yet, then there are still particles to activate
    if ( this.emitterAge < this.particleDeathAge )
    {
        // determine indices of particles to activate
        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;        
    }

    // if any particles have died while the emitter is still running, we imediately recycle them
    for (var j = 0; j < recycleIndices.length; j++)
    {
        var i = recycleIndices[j];
        this.particleArray[i] = this.createParticle();
        this.particleArray[i].alive = 1.0; // activate right away
        this.particleGeometry.vertices[i] = this.particleArray[i].position;
    }

    // stop emitter?
    this.emitterAge += dt;
    if ( this.emitterAge > this.emitterDeathAge )  this.emitterAlive = false;
}

ParticleEngine.prototype.destroy = function()
{
    scene.remove( this.particleMesh );
}
///////////////////////////////////////////////////////////////////////////////
View Code

相关文章:

  • 2021-11-01
  • 2021-12-01
  • 2021-05-25
  • 2022-12-23
  • 2022-01-01
  • 2022-01-14
  • 2022-01-13
  • 2021-06-11
猜你喜欢
  • 2022-01-15
  • 2021-12-25
  • 2021-05-20
  • 2022-01-01
  • 2021-10-13
  • 2021-04-01
相关资源
相似解决方案