three.js 消防模拟火焰烟雾效果


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

修改后的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)

import * as THREE from '../../build/three.module.js';

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

let 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 ParticleTween(timeArray, valueArray) {
    this.times = timeArray || [];
    this.values = valueArray || [];
}

ParticleTween.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 //
///////////////////////////

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

let scene;

function ParticleEngine(scene_) {
    /////////////////////////
    // PARTICLE PROPERTIES //
    /////////////////////////

    scene = scene_;

    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 ParticleTween();

    // 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 ParticleTween();

    this.opacityBase = 1.0;
    this.opacitySpread = 0.0;
    this.opacityTween = new ParticleTween();

    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.BufferGeometry();
    this.particleTexture = null;
    this.particleMaterial = new THREE.ShaderMaterial(
        {
            uniforms:
            {
                texture: { type: "t", value: this.particleTexture },
            },
            vertexShader: particleVertexShader,
            fragmentShader: particleFragmentShader,
            blending: THREE.NormalBlending,
            depthTest: false,
            side: THREE.DoubleSide,
            transparent: true,
            // alphaTest: 0.5,  // if having transparency issues, try including: alphaTest: 0.5, 
            opacity: 1.0
        });
    this.particleMesh = new THREE.Mesh();
}

ParticleEngine.prototype.setValues = function (parameters) {
    if (parameters === undefined) return;

    // clear any previous tweens that might exist
    this.sizeTween = new ParticleTween();
    this.colorTween = new ParticleTween();
    this.opacityTween = new ParticleTween();

    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 },
            },
            vertexShader: particleVertexShader,
            fragmentShader: particleFragmentShader,
            blending: THREE.NormalBlending,
            depthTest: false,
            side: THREE.DoubleSide,
            transparent: true,
            // alphaTest: 0.5, // if having transparency issues, try including: alphaTest: 0.5, 
            opacity: 1.0
        });
    this.particleMesh = new THREE.Points();
}

// 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 () {

    let customVisible = [];
    let customColor = [];
    let customOpacity = [];
    let customSize = [];
    let customAngle = [];

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

        customVisible.push(this.particleArray[i].alive);
        customColor.push(this.particleArray[i].color);
        customOpacity.push(this.particleArray[i].opacity);
        customSize.push(this.particleArray[i].size);
        customAngle.push(this.particleArray[i].angle);
    }

    // 原ParticleEngine.js依赖的是旧版本的three.js,新版本的three.js的ShaderMaterial不支持attributes
    // 此处使用BufferGeometry作了修改
    for (var i = 0; i < this.particleCount - 2; i += 3) {
        let face = new THREE.Face3(i, i + 1, i + 2);
        this.particleGeometry.faces.push(face);
    }

    this.bufferGeometry = new THREE.BufferGeometry();
    this.bufferGeometry.fromGeometry(this.particleGeometry);

    this.bufferGeometry.setAttribute('customVisible', new THREE.BufferAttribute(new Float32Array(customVisible), 1));
    this.bufferGeometry.setAttribute('customColor', new THREE.BufferAttribute(new Float32Array(customColor.length * 3), 3).copyColorsArray(customColor));
    this.bufferGeometry.setAttribute('customOpacity', new THREE.BufferAttribute(new Float32Array(customOpacity), 1));
    this.bufferGeometry.setAttribute('customSize', new THREE.BufferAttribute(new Float32Array(customSize), 1));
    this.bufferGeometry.setAttribute('customAngle', new THREE.BufferAttribute(new Float32Array(customAngle), 1));

    this.particleMaterial.blending = this.blendStyle;
    if (this.blendStyle != THREE.NormalBlending)
        this.particleMaterial.depthTest = false;

    this.particleMesh = new THREE.Points(this.bufferGeometry, this.particleMaterial);
    this.particleMesh.dynamic = true;
    this.particleMesh.sortParticles = true;
    scene.add(this.particleMesh);
    this.isShow = true;
}

ParticleEngine.prototype.update = function (dt) {
    var recycleIndices = [];

    let customVisible = [];
    let customColor = [];
    let customOpacity = [];
    let customSize = [];
    let customAngle = [];
    // update particle data
    for (var i = 0; i < this.particleCount; i++) {
        if (this.particleArray[i].alive) {
            this.particleArray[i].update(dt);
            this.particleGeometry.vertices[i] = this.particleArray[i].position;

            // 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
            customVisible.push(this.particleArray[i].alive);
            customColor.push(this.particleArray[i].color);
            customOpacity.push(this.particleArray[i].opacity);
            customSize.push(this.particleArray[i].size);
            customAngle.push(this.particleArray[i].angle);
        }
    }

    // bufferGeometry需要更新
    this.bufferGeometry.fromGeometry(this.particleGeometry);

    this.bufferGeometry.getAttribute('customVisible').copyArray(customVisible);
    this.bufferGeometry.getAttribute('customColor').copyColorsArray(customColor);
    this.bufferGeometry.getAttribute('customOpacity').copyArray(customOpacity);
    this.bufferGeometry.getAttribute('customSize').copyArray(customSize);
    this.bufferGeometry.getAttribute('customAngle').copyArray(customAngle);

    this.bufferGeometry.getAttribute('customVisible').needsUpdate = true;
    this.bufferGeometry.getAttribute('customColor').needsUpdate = true;
    this.bufferGeometry.getAttribute('customOpacity').needsUpdate = true;
    this.bufferGeometry.getAttribute('customSize').needsUpdate = true;
    this.bufferGeometry.getAttribute('customAngle').needsUpdate = true;

    // 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);
    this.isShow = false;
}

ParticleEngine.prototype.show = function () {
    scene.add(this.particleMesh);
    this.isShow = true;
}

ParticleEngine.prototype.hide = function () {
    scene.remove(this.particleMesh);
    this.isShow = false;
}
///////////////////////////////////////////////////////////////////////////////

export { ParticleEngine, Type, ParticleTween }

MySmoke.js创建烟雾代码:

//消防烟雾效果

import * as THREE from '../build/three.module.js';
import { ParticleEngine, Type as ParticleType, ParticleTween } from '../js/particle-engine/ParticleEngine.js'

let particleEngine;
let clockForParticleEngine = new THREE.Clock();
let scene;
let position;

/** 创建烟雾 */
function createSmoke(scene_, position_) {
    scene = scene_;
    position = position_;

    if (particleEngine) {
        particleEngine.destroy();
    }

    particleEngine = new ParticleEngine(scene);
    particleEngine.setValues({
        positionStyle: ParticleType.CUBE,
        positionBase: new THREE.Vector3(position.x, position.y + 0, position.z),
        positionSpread: new THREE.Vector3(10, 0, 10),

        velocityStyle: ParticleType.CUBE,
        velocityBase: new THREE.Vector3(0, 200, 0),
        velocitySpread: new THREE.Vector3(200, 150, 200),
        accelerationBase: new THREE.Vector3(0, -100, 0),

        particleTexture: new THREE.TextureLoader().load('images/particle-engine/smokeparticle.png'),

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

        sizeTween: new ParticleTween([0, 1], [64, 256]),
        opacityTween: new ParticleTween([0.8, 2], [0.5, 0]),
        colorTween: new ParticleTween([0.4, 1], [new THREE.Vector3(0, 0, 0.2), new THREE.Vector3(0, 0, 0.5)]),

        particlesPerSecond: 200,
        particleDeathAge: 2.0,
        emitterDeathAge: 60
    });
    particleEngine.initialize();
}

/** 更新烟雾 */
function updateParticle() {
    let delta = clockForParticleEngine.getDelta();
    if (delta < 0.2) { // 1秒 / 5帧 = 0.2 秒/帧
        particleEngine && particleEngine.update(delta * 0.5);
    } else {
        if (scene && position) {
            let isShow = false;
            if (particleEngine && particleEngine.isShow) {
                isShow = true;
            }
            createSmoke(scene, position);
            if (!isShow) {
                particleEngine && particleEngine.hide();
            }
        }
    }
}

export { createSmoke, updateParticle, particleEngine }

火焰效果MyFire3.js代码:

/**
 * 火焰
 */

import * as THREE from '../build/three.module.js';

let MyFire3 = function () {

    let fireVertexShader = `
attribute vec4 orientation;
attribute vec3 offset;
attribute vec2 scale;
attribute float life;
attribute float random;

varying vec2 vUv;
varying float vRandom;
varying float vAlpha;

float range(float oldValue, float oldMin, float oldMax, float newMin, float newMax) {
    float oldRange = oldMax - oldMin;
    float newRange = newMax - newMin;
    return (((oldValue - oldMin) * newRange) / oldRange) + newMin;
}

// From Inigo Quilez http://www.iquilezles.org/www/articles/functions/functions.htm
float pcurve(float x, float a, float b) {
    float k = pow(a + b, a + b) / (pow(a, a) * pow(b, b));
    return k * pow(x, a) * pow(1.0 - x, b);
}

void main() {
    vUv = uv;
    vRandom = random;

    vAlpha = pcurve(life, 1.0, 2.0);

    vec3 pos = position;

    pos.xy *= scale * vec2(range(pow(life, 1.5), 0.0, 1.0, 1.0, 0.6), range(pow(life, 1.5), 0.0, 1.0, 0.6, 1.2));

    vec4 or = orientation;
    vec3 vcV = cross(or.xyz, pos);
    pos = vcV * (2.0 * or.w) + (cross(or.xyz, vcV) * 2.0 + pos);

    gl_Position = projectionMatrix * modelViewMatrix * vec4(pos, 1.0);
}
`;

    let fireFragmentShader = `
uniform sampler2D uMap;
uniform vec3 uColor1;
uniform vec3 uColor2;
uniform float uTime;

varying vec2 vUv;
varying float vAlpha;
varying float vRandom;

void main() {
    vec2 uv = vUv;

    float spriteLength = 10.0;
    uv.x /= spriteLength;
    float spriteIndex = mod(uTime * 0.1 + vRandom * 2.0, 1.0);
    uv.x += floor(spriteIndex * spriteLength) / spriteLength;

    vec4 map = texture2D(uMap, uv);

    gl_FragColor.rgb = mix(uColor2, uColor1, map.r);
    gl_FragColor.a = vAlpha * map.a;
}
`;

    let embersVertexShader = `
attribute float size;
attribute float life;
attribute vec3 offset;

varying float vAlpha;

// From Inigo Quilez http://www.iquilezles.org/www/articles/functions/functions.htm
float impulse(float k, float x) {
    float h = k * x;
    return h * exp(1.0 - h);
}

void main() {
    vAlpha = impulse(6.28, life);

    vec3 pos = position;
    pos += offset * vec3(life * 0.7 + 0.3, life * 0.9 + 0.1, life * 0.7 + 0.3);

    vec4 mvPosition = modelViewMatrix * vec4(pos, 1.0);
    gl_PointSize = size * (80.0 / length(mvPosition.xyz));
    gl_Position = projectionMatrix * mvPosition;
}
`;

    let embersFragmentShader = `
uniform sampler2D uMap;
uniform vec3 uColor;

varying float vAlpha;

void main() {
    vec2 uv = vec2(gl_PointCoord.x, 1.0 - gl_PointCoord.y);
    vec4 mask = texture2D(uMap, uv);

    gl_FragColor.rgb = uColor;
    gl_FragColor.a = mask.a * vAlpha * 0.8;
}
`;

    let hazeVertexShader = `
attribute vec3 base;
attribute vec3 offset;
attribute vec4 orientation;
attribute vec2 scale;
attribute float life;

varying float vAlpha;
varying vec2 vUv;

// From Inigo Quilez http://www.iquilezles.org/www/articles/functions/functions.htm
float impulse(float k, float x) {
    float h = k * x;
    return h * exp(1.0 - h);
}

float pcurve(float x, float a, float b) {
    float k = pow(a + b, a + b) / (pow(a, a) * pow(b, b));
    return k * pow(x, a) * pow(1.0 - x, b);
}

void main() {
    vUv = uv;
    vAlpha = pcurve(life, 1.0, 2.0);

    vec3 pos = position;

    pos.xy *= scale * (life * 0.7 + 0.3);

    vec4 or = orientation;
    vec3 vcV = cross(or.xyz, pos);
    pos = vcV * (2.0 * or.w) + (cross(or.xyz, vcV) * 2.0 + pos);

    pos += base;
    pos += offset * vec3(life * 0.7 + 0.3, life * 0.9 + 0.1, life * 0.7 + 0.3);

    gl_Position = projectionMatrix * modelViewMatrix * vec4(pos, 1.0);;
}
`;

    let hazeFragmentShader = `
uniform sampler2D uMap;
uniform sampler2D uMask;
uniform vec2 uResolution;

varying float vAlpha;
varying vec2 vUv;

void main() {
    vec2 uv = gl_FragCoord.xy / uResolution;
    vec2 mask = texture2D(uMask, vUv).ra - vec2(0.5);
    uv -= mask * 0.1;
    vec4 tex = texture2D(uMap, uv);

    gl_FragColor.rgb = tex.rgb;
    gl_FragColor.a = vAlpha * 0.5;
}
`;

    function random(min, max, precision) {
        var p = Math.pow(10, precision);
        return Math.round((min + Math.random() * (max - min)) * p) / p;
    }


    let _scene;
    let _renderer;
    let _camera;
    let _controls;
    let _rtt;
    let _fire;
    let _width;
    let _height;

    this.objs = [];

    let _self = this;

    this._isShow = false;

    let _pos_x;
    let _pos_y;
    let _pos_z;

    this.config = function (scene_, renderer_, camera_, controls_) {
        _width = 1920;
        _height = 1040;

        _renderer = renderer_;
        _scene = scene_;
        _camera = camera_;
        _controls = controls_;

        var _parameters = {
            minFilter: THREE.LinearFilter,
            magFilter: THREE.LinearFilter,
            format: THREE.RGBAFormat,
            stencilBuffer: false
        };
        _rtt = new THREE.WebGLRenderTarget(_width * 0.5, _height * 0.5, _parameters);
    }

    this.setPosition = function (x, y, z) {
        _pos_x = x;
        _pos_y = y;
        _pos_z = z;
    }

    this.showFire = function () {
        initFire();
        initEmbers();
        initHaze();
        this._isShow = true;
    }

    this.refresh = function () {
        _self.loop();
        _self.loop2();
        _self.loop3();
    }

    this.isShow = function () {
        return this._isShow;
    }

    this.hide = function () {
        _self.objs.map(obj => {
            _scene.remove(obj);
        });
        this._isShow = false;
    }

    this.show = function () {
        _self.objs.map(obj => {
            _scene.add(obj);
        });
        this._isShow = true;
    }

    //=====// Fire //========================================//     

    function initFire() {
        var _geometry, _shader, _mesh, _group;
        var _num = 50;

        var _x = new THREE.Vector3(1, 0, 0);
        var _y = new THREE.Vector3(0, 1, 0);
        var _z = new THREE.Vector3(0, 0, 1);

        var _tipTarget = new THREE.Vector3();
        var _tip = new THREE.Vector3();
        var _diff = new THREE.Vector3();

        var _quat = new THREE.Quaternion();
        var _quat2 = new THREE.Quaternion();

        (function () {
            initGeometry();
            initInstances();
            initShader();
            initMesh();
        })();

        function initGeometry() {
            _geometry = new THREE.InstancedBufferGeometry();
            _geometry.maxInstancedCount = _num;

            var shape = new THREE.PlaneBufferGeometry(200, 200);
            shape.translate(0, 0.4, 0);
            var data = shape.attributes;

            _geometry.addAttribute('position', new THREE.BufferAttribute(new Float32Array(data.position.array), 3));
            _geometry.addAttribute('uv', new THREE.BufferAttribute(new Float32Array(data.uv.array), 2));
            _geometry.addAttribute('normal', new THREE.BufferAttribute(new Float32Array(data.normal.array), 3));
            _geometry.setIndex(new THREE.BufferAttribute(new Uint16Array(shape.index.array), 1));
            shape.dispose();
        }

        function initInstances() {
            var orientation = new THREE.InstancedBufferAttribute(new Float32Array(_num * 4), 4);
            var randoms = new THREE.InstancedBufferAttribute(new Float32Array(_num), 1);
            var scale = new THREE.InstancedBufferAttribute(new Float32Array(_num * 2), 2);
            var life = new THREE.InstancedBufferAttribute(new Float32Array(_num), 1);

            for (let i = 0; i < _num; i++) {
                orientation.setXYZW(i, 0, 0, 0, 1);
                life.setX(i, i / _num + 1);
            }

            _geometry.addAttribute('orientation', orientation);
            _geometry.addAttribute('scale', scale);
            _geometry.addAttribute('life', life);
            _geometry.addAttribute('random', randoms);
        }

        function initShader() {
            var uniforms = {
                uMap: {
                    type: 't',
                    value: null
                },
                uColor1: {
                    type: 'c',
                    value: new THREE.Color(0x961800)
                }, // red
                uColor2: {
                    type: 'c',
                    value: new THREE.Color(0x4b5828)
                }, // yellow
                uTime: {
                    type: 'f',
                    value: 0
                },
            };

            _shader = new THREE.ShaderMaterial({
                uniforms: uniforms,
                vertexShader: fireVertexShader,
                fragmentShader: fireFragmentShader,
                blending: THREE.AdditiveBlending,
                transparent: true,
                depthTest: false,
                side: THREE.DoubleSide,
            });

            var textureLoader = new THREE.TextureLoader();
            textureLoader.load('images/myFire3/flame.png', t => _shader.uniforms.uMap.value = t);
        }

        function initMesh() {
            _group = new THREE.Group();
            _mesh = new THREE.Mesh(_geometry, _shader);
            _mesh.frustumCulled = false;
            _group.add(_mesh);
            _scene.add(_group);
            _self.objs.push(_group);
            _fire = _group;
        }

        _self.loop = function () {
            let e = 100;
            _shader.uniforms.uTime.value = e * 0.001;

            var life = _geometry.attributes.life;
            var orientation = _geometry.attributes.orientation;
            var scale = _geometry.attributes.scale;
            var randoms = _geometry.attributes.random;

            for (let i = 0; i < _num; i++) {
                var value = life.array[i];
                value += 0.04;

                if (value > 1) {
                    value -= 1;

                    _quat.setFromAxisAngle(_y, random(0, 3.14, 3));
                    _quat2.setFromAxisAngle(_x, random(-1, 1, 2) * 0.1);
                    _quat.multiply(_quat2);
                    _quat2.setFromAxisAngle(_z, random(-1, 1, 2) * 0.3);
                    _quat.multiply(_quat2);
                    orientation.setXYZW(i, _quat.x, _quat.y, _quat.z, _quat.w);

                    scale.setXY(i, random(0.8, 1.2, 3), random(0.8, 1.2, 3));
                    randoms.setX(i, random(0, 1, 3));
                }

                life.setX(i, value);
            }
            life.needsUpdate = true;
            orientation.needsUpdate = true;
            scale.needsUpdate = true;
            randoms.needsUpdate = true;

            _group.position.x = _pos_x; //Math.sin(e * 0.002) * 1.4;
            _group.position.y = _pos_y + 50; //Math.cos(e * 0.0014) * 0.2;
            _group.position.z = _pos_z; //Math.cos(e * 0.0014) * 0.5;

            let tipOffset = 0.4;
            _tipTarget.copy(_group.position);
            _tipTarget.y += tipOffset;
            _tip.lerp(_tipTarget, 0.1);

            _diff.copy(_tip);
            _diff.sub(_group.position);
            let length = _diff.length();
            //_group.scale.y = (length / tipOffset - 1) * 0.4 + 1;

            _group.quaternion.setFromUnitVectors(_y, _diff.normalize());
        }
    }

    //=====// Embers //========================================//     

    function initEmbers() {
        var _geometry, _shader, _points;
        var _num = 8;

        (function () {
            initGeometry();
            initShader();
            initMesh();
        })();

        function initGeometry() {
            _geometry = new THREE.BufferGeometry();
            _geometry.addAttribute('position', new THREE.BufferAttribute(new Float32Array(_num * 3), 3));
            _geometry.addAttribute('offset', new THREE.BufferAttribute(new Float32Array(_num * 3), 3));
            _geometry.addAttribute('size', new THREE.BufferAttribute(new Float32Array(_num), 1));
            _geometry.addAttribute('life', new THREE.BufferAttribute(new Float32Array(_num), 1));
            var scale = new THREE.InstancedBufferAttribute(new Float32Array(_num * 2), 2);
            _geometry.addAttribute('scale', scale);

            for (var i = 0; i < _num; i++) {
                _geometry.attributes.life.setX(i, random(0, 1, 3) + 1);
            }
        }

        function initShader() {
            var uniforms = {
                uMap: {
                    type: 't',
                    value: null
                },
                uColor: {
                    type: 'c',
                    value: new THREE.Color(0xffe61e)
                },
            };

            _shader = new THREE.ShaderMaterial({
                uniforms: uniforms,
                vertexShader: embersVertexShader,
                fragmentShader: embersFragmentShader,
                blending: THREE.AdditiveBlending,
                transparent: true,
                depthTest: false,
            });

            var textureLoader = new THREE.TextureLoader();
            textureLoader.load('images/myFire3/ember.png', t => _shader.uniforms.uMap.value = t);
        }

        function initMesh() {
            _points = new THREE.Points(_geometry, _shader);
            _points.frustumCulled = false;
            _scene.add(_points);
            _self.objs.push(_points);
        }

        _self.loop2 = function () {
            var life = _geometry.attributes.life;
            var position = _geometry.attributes.position;
            var size = _geometry.attributes.size;
            var offset = _geometry.attributes.offset;
            var scale = _geometry.attributes.scale;
            for (let i = 0; i < _num; i++) {
                var value = life.array[i];
                value += 0.02;

                if (value > 1) {
                    value -= 1;

                    position.setXYZ(i, _pos_x, _pos_y + 0.1, _pos_z);
                    offset.setXYZ(i,
                        random(-150, 150, 3),
                        random(100, 300, 3),
                        random(-100, 100, 3)
                    );
                    size.setX(i, random(20, 100, 3));

                    scale.setXY(i, 50, 50);
                }

                life.setX(i, value);
            }

            life.needsUpdate = true;
            position.needsUpdate = true;
            size.needsUpdate = true;
            offset.needsUpdate = true;
        }
    }

    //=====// Haze //========================================//     

    function initHaze() {
        var _geometry, _shader, _mesh;

        var _num = 4;

        var _z = new THREE.Vector3(0, 0, 1);
        var _quat = new THREE.Quaternion();
        var _quat2 = new THREE.Quaternion();

        (function () {
            initGeometry();
            initInstances();
            initShader();
            initMesh();
            window.addEventListener('resize', resizeHaze, false);
            resizeHaze();
        })();

        function initGeometry() {
            _geometry = new THREE.InstancedBufferGeometry();
            _geometry.maxInstancedCount = _num;

            var shape = new THREE.PlaneBufferGeometry(0.1, 0.1);
            var data = shape.attributes;

            _geometry.addAttribute('position', new THREE.BufferAttribute(new Float32Array(data.position.array), 3));
            _geometry.addAttribute('uv', new THREE.BufferAttribute(new Float32Array(data.uv.array), 2));
            _geometry.addAttribute('normal', new THREE.BufferAttribute(new Float32Array(data.normal.array), 3));
            _geometry.setIndex(new THREE.BufferAttribute(new Uint16Array(shape.index.array), 1));
            shape.dispose();
        }

        function initInstances() {
            var base = new THREE.InstancedBufferAttribute(new Float32Array(_num * 3), 3);
            var offset = new THREE.InstancedBufferAttribute(new Float32Array(_num * 3), 3);
            var orientation = new THREE.InstancedBufferAttribute(new Float32Array(_num * 4), 4);
            var scale = new THREE.InstancedBufferAttribute(new Float32Array(_num * 2), 2);
            var rotation = new THREE.InstancedBufferAttribute(new Float32Array(_num), 1);
            var life = new THREE.InstancedBufferAttribute(new Float32Array(_num), 1);

            for (let i = 0; i < _num; i++) {
                orientation.setXYZW(i, 0, 0, 0, 1);
                life.setX(i, i / _num + 1);
            }

            _geometry.addAttribute('base', base);
            _geometry.addAttribute('offset', offset);
            _geometry.addAttribute('orientation', orientation);
            _geometry.addAttribute('scale', scale);
            _geometry.addAttribute('rotation', rotation);
            _geometry.addAttribute('life', life);
        }

        function initShader() {
            let dpr = _renderer.getPixelRatio();
            var uniforms = {
                uMap: {
                    type: 't',
                    value: null
                },
                uMask: {
                    type: 't',
                    value: null
                },
                uResolution: {
                    type: 'v2',
                    value: new THREE.Vector2(_width * dpr, _height * dpr)
                },
            };

            _shader = new THREE.ShaderMaterial({
                uniforms: uniforms,
                vertexShader: hazeVertexShader,
                fragmentShader: hazeFragmentShader,
                transparent: true,
                depthTest: false,
            });

            var textureLoader = new THREE.TextureLoader();
            textureLoader.load('images/myFire3/haze.png', t => _shader.uniforms.uMask.value = t);
        }

        function initMesh() {
            _mesh = new THREE.Mesh(_geometry, _shader);
            _mesh.frustumCulled = false;
            _scene.add(_mesh);
            _self.objs.push(_mesh);
        }

        function resizeHaze() {
            let dpr = _renderer.getPixelRatio();
            _shader.uniforms.uMap.value = _rtt.texture;
            _shader.uniforms.uResolution.value.set(_width * dpr, _height * dpr);
        }

        _self.loop3 = function () {

            let e = 100;
            _mesh.visible = false;
            //_renderer.render(_scene, _camera, _rtt);
            _mesh.visible = true;

            var life = _geometry.attributes.life;
            var base = _geometry.attributes.base;
            var offset = _geometry.attributes.offset;
            var scale = _geometry.attributes.scale;
            var orientation = _geometry.attributes.orientation;
            var rotation = _geometry.attributes.rotation;
            for (let i = 0; i < _num; i++) {
                var value = life.array[i];
                value += 0.008;

                if (value > 1) {
                    value -= 1;

                    rotation.setX(i, random(0, 3.14, 3));

                    base.setXYZ(i, _pos_x, _pos_y + 0.1, _pos_z);
                    offset.setXYZ(i,
                        random(-150, 150, 3),
                        random(100, 300, 3),
                        0
                    );
                    //scale.setXY(i, random(0.6, 1.2, 3), random(0.6, 1.2, 3));
                    scale.setXY(i, 50, 50);
                }

                _quat.copy(_camera.quaternion);
                _quat2.setFromAxisAngle(_z, rotation.array[i]);
                _quat.multiply(_quat2);
                orientation.setXYZW(i, _quat.x, _quat.y, _quat.z, _quat.w);

                life.setX(i, value);
            }

            life.needsUpdate = true;
            base.needsUpdate = true;
            scale.needsUpdate = true;
            offset.needsUpdate = true;
            orientation.needsUpdate = true;
        }
    }


}

MyFire3.prototype.constructor = MyFire3;

export { MyFire3 }

在主文件中添加如下代码实现火焰烟雾效果:

import { createSmoke, updateParticle, particleEngine } from '../js.my/MySmoke.js'
import { MyFire3 } from '../js.my/MyFire3.js';

let showFire = function () {
    myFire3 = new MyFire3();
    myFire3.config(scene, renderer, camera, controls);
    myFire3.setPosition(417, 0, 1134);
    if (planSelect.getPosition()) {
        let pos = planSelect.getPosition();
        myFire3.setPosition(pos.x, pos.y, pos.z);
        createSmoke(scene, pos);
    }
    myFire3.showFire();
    planTypeSelect.setFire(myFire3);
    planTypeSelect.setSmoke(particleEngine);
}

if (!myFire3) {
    showFire();
} else {
    if (myFire3.isShow()) {
        myFire3.hide();
        particleEngine.hide();
    } else {
        showFire();
        particleEngine.show();
    }
}

说明:planSelect.getPosition()选择应急预案获取事件位置信息,planTypeSelect选择预案类型,当预案类型变更时隐藏火焰和烟雾,不是消防类预案时不显示火焰和烟雾,所以需要把相关变量传给planTypeSelect。

效果图: