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

    修改后的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 }
    View Code

    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 }
    View Code

    火焰效果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 }
    View Code

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

    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();
        }
    }
    View Code

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

    效果图:

     

     

  • 相关阅读:
    redis集群登陆
    锁机制
    关系型数据库事务遵循ACID原则
    前端之Css
    Python之操作redis数据库
    前端之HTML
    Excel之批量改变特定字体颜色(转载)
    jmeter之批量修改请求路径
    Python之time模块
    Python之os模块
  • 原文地址:https://www.cnblogs.com/s0611163/p/15731822.html
Copyright © 2020-2023  润新知