瀏覽代碼

gpuparticles: prewarm, initial rotation, color gradients and size gradients

David Catuhe 7 年之前
父節點
當前提交
fca3d953a1

+ 4 - 0
src/Materials/Textures/babylon.rawTexture.ts

@@ -34,5 +34,9 @@
         public static CreateRGBATexture(data: ArrayBufferView, width: number, height: number, scene: Scene, generateMipMaps: boolean = true, invertY: boolean = false, samplingMode: number = Texture.TRILINEAR_SAMPLINGMODE, type: number = Engine.TEXTURETYPE_UNSIGNED_INT): RawTexture {
             return new RawTexture(data, width, height, Engine.TEXTUREFORMAT_RGBA, scene, generateMipMaps, invertY, samplingMode, type);
         }
+
+        public static CreateRTexture(data: ArrayBufferView, width: number, height: number, scene: Scene, generateMipMaps: boolean = true, invertY: boolean = false, samplingMode: number = Texture.TRILINEAR_SAMPLINGMODE, type: number = Engine.TEXTURETYPE_FLOAT): RawTexture {
+            return new RawTexture(data, width, height, Engine.TEXTUREFORMAT_R, scene, generateMipMaps, invertY, samplingMode, type);
+        }        
     }
 }

+ 1 - 0
src/Math/babylon.math.ts

@@ -6663,6 +6663,7 @@
     // There's a Tmp array per object type : int, float, Vector2, Vector3, Vector4, Quaternion, Matrix
     export class Tmp {
         public static Color3: Color3[] = [Color3.Black(), Color3.Black(), Color3.Black()];
+        public static Color4: Color4[] = [new Color4(0, 0, 0, 0)];
         public static Vector2: Vector2[] = [Vector2.Zero(), Vector2.Zero(), Vector2.Zero()];  // 3 temp Vector2 at once should be enough
         public static Vector3: Vector3[] = [Vector3.Zero(), Vector3.Zero(), Vector3.Zero(),
         Vector3.Zero(), Vector3.Zero(), Vector3.Zero(), Vector3.Zero(), Vector3.Zero(), Vector3.Zero()];    // 9 temp Vector3 at once should be enough

+ 65 - 4
src/Particles/babylon.IParticleSystem.ts

@@ -6,6 +6,10 @@ module BABYLON {
      */
     export interface IParticleSystem {
         /**
+         * List of animations used by the particle system.
+         */
+        animations: Animation[];
+        /**
          * The id of the Particle system.
          */
         id: string;
@@ -114,12 +118,35 @@ module BABYLON {
          * Maximum power of emitting particles.
          */
         maxEmitPower: number;        
-
+        /**
+         * Minimum angular speed of emitting particles (Z-axis rotation for each particle).
+         */
+        minAngularSpeed: number;
+        /**
+         * Maximum angular speed of emitting particles (Z-axis rotation for each particle).
+         */
+        maxAngularSpeed: number;
+        /**
+         * Gets or sets the minimal initial rotation in radians.         
+         */
+        minInitialRotation: number;
+        /**
+         * Gets or sets the maximal initial rotation in radians.         
+         */
+        maxInitialRotation: number;         
         /**
          * The particle emitter type defines the emitter used by the particle system.
          * It can be for example box, sphere, or cone...
          */
-        particleEmitterType: Nullable<IParticleEmitterType>;        
+        particleEmitterType: Nullable<IParticleEmitterType>;  
+        /** 
+         * Gets or sets a value indicating how many cycles (or frames) must be executed before first rendering (this value has to be set before starting the system). Default is 0 
+         */
+        preWarmCycles: number;   
+        /** 
+         * Gets or sets a value indicating the time step multiplier to use in pre-warm mode (default is 1) 
+         */
+        preWarmStepOffset: number;               
 
         /**
          * Gets the maximum number of particles active at the same time.
@@ -188,6 +215,40 @@ module BABYLON {
          * Is this system ready to be used/rendered
          * @return true if the system is ready
          */
-        isReady(): boolean;        
-    }
+        isReady(): boolean; 
+        /**
+         * Adds a new color gradient
+         * @param gradient defines the gradient to use (between 0 and 1)
+         * @param color defines the color to affect to the specified gradient
+         */
+        addColorGradient(gradient: number, color: Color4): IParticleSystem;   
+        /**
+         * Remove a specific color gradient
+         * @param gradient defines the gradient to remove
+         */
+        removeColorGradient(gradient: number): IParticleSystem;
+        /**
+         * Adds a new size gradient
+         * @param gradient defines the gradient to use (between 0 and 1)
+         * @param factor defines the size factor to affect to the specified gradient
+         */
+        addSizeGradient(gradient: number, factor: number): IParticleSystem;
+        /**
+         * Remove a specific size gradient
+         * @param gradient defines the gradient to remove
+         */
+        removeSizeGradient(gradient: number): IParticleSystem;
+        /**
+         * Gets the current list of color gradients.
+         * You must use addColorGradient and removeColorGradient to udpate this list
+         * @returns the list of color gradients
+         */
+        getColorGradients(): Nullable<Array<ColorGradient>>;
+        /**
+         * Gets the current list of size gradients.
+         * You must use addSizeGradient and removeSizeGradient to udpate this list
+         * @returns the list of size gradients
+         */
+        getSizeGradients(): Nullable<Array<FactorGradient>>;
+    }  
 }

+ 419 - 185
src/Particles/babylon.gpuParticleSystem.ts

@@ -33,6 +33,7 @@
         private _capacity: number;
         private _activeCount: number;
         private _currentActiveCount: number;
+        private _accumulatedCount = 0;
         private _renderEffect: Effect;
         private _updateEffect: Effect;
 
@@ -56,8 +57,9 @@
         private _timeDelta = 0;
 
         private _randomTexture: RawTexture;
+        private _randomTexture2: RawTexture;
 
-        private _attributesStrideSize = 18;
+        private _attributesStrideSize = 21;
         private _updateEffectOptions: EffectCreationOptions;
 
         private _randomTextureSize: number;
@@ -284,6 +286,23 @@
             this._activeCount = Math.min(value, this._capacity);
         }
 
+        private _preWarmDone = false;
+
+        /** Gets or sets a value indicating how many cycles (or frames) must be executed before first rendering (this value has to be set before starting the system). Default is 0 */
+        public preWarmCycles = 0;
+
+        /** Gets or sets a value indicating the time step multiplier to use in pre-warm mode (default is 1) */
+        public preWarmStepOffset = 1;        
+
+        /**
+         * Gets or sets the minimal initial rotation in radians.         
+         */
+        public minInitialRotation = 0;
+        /**
+         * Gets or sets the maximal initial rotation in radians.         
+         */
+        public maxInitialRotation = 0;            
+
         /**
          * Is this system ready to be used/rendered
          * @return true if the system is ready
@@ -317,6 +336,7 @@
         public start(): void {
             this._started = true;
             this._stopped = false;
+            this._preWarmDone = false;
         }
 
         /**
@@ -361,7 +381,157 @@
             this._isBillboardBased = value;
 
             this._releaseBuffers();
-        }          
+        }        
+        
+        private _colorGradients: Nullable<Array<ColorGradient>> = null;
+        private _colorGradientsTexture: RawTexture;
+
+        /**
+         * Gets the current list of color gradients.
+         * You must use addColorGradient and removeColorGradient to udpate this list
+         * @returns the list of color gradients
+         */
+        public getColorGradients(): Nullable<Array<ColorGradient>> {
+            return this._colorGradients;
+        }
+
+        /**
+         * Gets the current list of size gradients.
+         * You must use addSizeGradient and removeSizeGradient to udpate this list
+         * @returns the list of size gradients
+         */
+        public getSizeGradients(): Nullable<Array<FactorGradient>> {
+            return this._sizeGradients;
+        }               
+        
+        /**
+         * Adds a new color gradient
+         * @param gradient defines the gradient to use (between 0 and 1)
+         * @param color defines the color to affect to the specified gradient
+         */
+        public addColorGradient(gradient: number, color: Color4): GPUParticleSystem {
+            if (!this._colorGradients) {
+                this._colorGradients = [];
+            }
+
+            let colorGradient = new ColorGradient();
+            colorGradient.gradient = gradient;
+            colorGradient.color = color;
+            this._colorGradients.push(colorGradient);
+
+            this._colorGradients.sort((a, b) => {
+                if (a.gradient < b.gradient) {
+                    return -1;
+                } else if (a.gradient > b.gradient) {
+                    return 1;
+                }
+
+                return 0;
+            });
+
+            if (this._colorGradientsTexture) {
+                this._colorGradientsTexture.dispose();
+                (<any>this._colorGradientsTexture) = null;
+            }
+
+            this._releaseBuffers();            
+
+            return this;
+        }
+
+        /**
+         * Remove a specific color gradient
+         * @param gradient defines the gradient to remove
+         */
+        public removeColorGradient(gradient: number): GPUParticleSystem {
+            if (!this._colorGradients) {
+                return this;
+            }
+
+            let index = 0;
+            for (var colorGradient of this._colorGradients) {
+                if (colorGradient.gradient === gradient) {
+                    this._colorGradients.splice(index, 1);
+                    break;
+                }
+                index++;
+            }
+
+            if (this._colorGradientsTexture) {
+                this._colorGradientsTexture.dispose();
+                (<any>this._colorGradientsTexture) = null;
+            }            
+
+            this._releaseBuffers();
+
+            return this;
+        }    
+        
+        private _sizeGradients: Nullable<Array<FactorGradient>> = null;
+        private _sizeGradientsTexture: RawTexture;        
+        
+        /**
+         * Adds a new size gradient
+         * @param gradient defines the gradient to use (between 0 and 1)
+         * @param factor defines the size factor to affect to the specified gradient
+         */
+        public addSizeGradient(gradient: number, factor: number): GPUParticleSystem {
+            if (!this._sizeGradients) {
+                this._sizeGradients = [];
+            }
+
+            let sizeGradient = new FactorGradient();
+            sizeGradient.gradient = gradient;
+            sizeGradient.factor = factor;
+            this._sizeGradients.push(sizeGradient);
+
+            this._sizeGradients.sort((a, b) => {
+                if (a.gradient < b.gradient) {
+                    return -1;
+                } else if (a.gradient > b.gradient) {
+                    return 1;
+                }
+
+                return 0;
+            });
+
+            if (this._sizeGradientsTexture) {
+                this._sizeGradientsTexture.dispose();
+                (<any>this._sizeGradientsTexture) = null;
+            }
+
+            this._releaseBuffers();                 
+
+            return this;
+        }
+
+        /**
+         * Remove a specific size gradient
+         * @param gradient defines the gradient to remove
+         */
+        public removeSizeGradient(gradient: number): GPUParticleSystem {
+            if (!this._sizeGradients) {
+                return this;
+            }
+
+            let index = 0;
+            for (var sizeGradient of this._sizeGradients) {
+                if (sizeGradient.gradient === gradient) {
+                    this._sizeGradients.splice(index, 1);
+                    break;
+                }
+                index++;
+            }
+
+            if (this._sizeGradientsTexture) {
+                this._sizeGradientsTexture.dispose();
+                (<any>this._sizeGradientsTexture) = null;
+            }
+
+            this._releaseBuffers();               
+
+            return this;
+        }            
 
         /**
          * Instantiates a GPU particle system.
@@ -385,6 +555,11 @@
                 ...options
             };
 
+            var optionsAsNumber = <number>options;
+            if (isFinite(optionsAsNumber)) {
+                fullOptions.capacity = optionsAsNumber;
+            }
+
             this._capacity = fullOptions.capacity;
             this._activeCount = fullOptions.capacity;
             this._currentActiveCount = 0;
@@ -392,12 +567,12 @@
             this._scene.particleSystems.push(this);
 
             this._updateEffectOptions = {
-                attributes: ["position", "age", "life", "seed", "size", "color", "direction", "initialDirection", "angle"],
-                uniformsNames: ["currentCount", "timeDelta", "generalRandoms", "emitterWM", "lifeTime", "color1", "color2", "sizeRange", "scaleRange","gravity", "emitPower",
+                attributes: ["position", "age", "life", "seed", "size", "color", "direction", "initialDirection", "angle", "initialSize"],
+                uniformsNames: ["currentCount", "timeDelta", "emitterWM", "lifeTime", "color1", "color2", "sizeRange", "scaleRange","gravity", "emitPower",
                                 "direction1", "direction2", "minEmitBox", "maxEmitBox", "radius", "directionRandomizer", "height", "coneAngle", "stopFactor", 
                                 "angleRange", "radiusRange"],
                 uniformBuffersNames: [],
-                samplers:["randomSampler"],
+                samplers:["randomSampler", "randomSampler2", "sizeGradientSampler"],
                 defines: "",
                 fallbacks: null,  
                 onCompiled: null,
@@ -407,6 +582,8 @@
                 transformFeedbackVaryings: []
             };
 
+            this.particleEmitterType = new BoxParticleEmitter();
+
             // Random data
             var maxTextureSize = Math.min(this._engine.getCaps().maxTextureSize, fullOptions.randomTextureSize);
             var d = [];
@@ -416,12 +593,22 @@
                 d.push(Math.random());
                 d.push(Math.random());
             }
-            this._randomTexture = new RawTexture(new Float32Array(d), maxTextureSize, 1, Engine.TEXTUREFORMAT_RGBA, this._scene, false, false, Texture.NEAREST_SAMPLINGMODE, Engine.TEXTURETYPE_FLOAT)
+            this._randomTexture = new RawTexture(new Float32Array(d), maxTextureSize, 1, Engine.TEXTUREFORMAT_RGBA, this._scene, false, false, Texture.NEAREST_SAMPLINGMODE, Engine.TEXTURETYPE_FLOAT);
             this._randomTexture.wrapU = Texture.WRAP_ADDRESSMODE;
             this._randomTexture.wrapV = Texture.WRAP_ADDRESSMODE;
 
+            d = [];
+            for (var i = 0; i < maxTextureSize; ++i) {
+                d.push(Math.random());
+                d.push(Math.random());
+                d.push(Math.random());
+                d.push(Math.random());
+            }
+            this._randomTexture2 = new RawTexture(new Float32Array(d), maxTextureSize, 1, Engine.TEXTUREFORMAT_RGBA, this._scene, false, false, Texture.NEAREST_SAMPLINGMODE, Engine.TEXTURETYPE_FLOAT);
+            this._randomTexture2.wrapU = Texture.WRAP_ADDRESSMODE;
+            this._randomTexture2.wrapV = Texture.WRAP_ADDRESSMODE;
+
             this._randomTextureSize = maxTextureSize;
-            this.particleEmitterType = new BoxParticleEmitter();
         }
 
         private _createUpdateVAO(source: Buffer): WebGLVertexArrayObject {            
@@ -429,12 +616,22 @@
             updateVertexBuffers["position"] = source.createVertexBuffer("position", 0, 3);
             updateVertexBuffers["age"] = source.createVertexBuffer("age", 3, 1);
             updateVertexBuffers["life"] = source.createVertexBuffer("life", 4, 1);
-            updateVertexBuffers["seed"] = source.createVertexBuffer("seed", 5, 1);
-            updateVertexBuffers["size"] = source.createVertexBuffer("size", 6, 3);
-            updateVertexBuffers["color"] = source.createVertexBuffer("color", 9, 4);
-            updateVertexBuffers["direction"] = source.createVertexBuffer("direction", 13, 3);
+            updateVertexBuffers["seed"] = source.createVertexBuffer("seed", 5, 4);
+            updateVertexBuffers["size"] = source.createVertexBuffer("size", 9, 3);
+            let offset = 12;
+            if (this._sizeGradientsTexture) {
+                updateVertexBuffers["initialSize"] = source.createVertexBuffer("initialSize", offset, 3);
+                offset += 3;
+            }
+
+            if (!this._colorGradientsTexture) {
+                updateVertexBuffers["color"] = source.createVertexBuffer("color", offset, 4);
+                offset += 4;
+            }
+
+            updateVertexBuffers["direction"] = source.createVertexBuffer("direction", offset, 3);
+            offset += 3
 
-            let offset = 16;
             if (!this._isBillboardBased) {
                 updateVertexBuffers["initialDirection"] = source.createVertexBuffer("initialDirection", offset, 3);
                 offset += 3;
@@ -453,10 +650,20 @@
             renderVertexBuffers["position"] = source.createVertexBuffer("position", 0, 3, this._attributesStrideSize, true);
             renderVertexBuffers["age"] = source.createVertexBuffer("age", 3, 1, this._attributesStrideSize, true);
             renderVertexBuffers["life"] = source.createVertexBuffer("life", 4, 1, this._attributesStrideSize, true);
-            renderVertexBuffers["size"] = source.createVertexBuffer("size", 6, 3, this._attributesStrideSize, true);           
-            renderVertexBuffers["color"] = source.createVertexBuffer("color", 9, 4, this._attributesStrideSize, true);
+            renderVertexBuffers["size"] = source.createVertexBuffer("size", 9, 3, this._attributesStrideSize, true);      
+            
+            let offset = 12;
+            if (this._sizeGradientsTexture) {
+                offset += 3;
+            }
+
+            if (!this._colorGradientsTexture) {
+                renderVertexBuffers["color"] = source.createVertexBuffer("color", offset, 4, this._attributesStrideSize, true);
+                offset += 4;
+            }
+            
+            offset += 3; // Direction
 
-            let offset = 16;
             if (!this._isBillboardBased) {
                 renderVertexBuffers["initialDirection"] = source.createVertexBuffer("initialDirection", offset, 3, this._attributesStrideSize, true);
                 offset += 3;
@@ -481,7 +688,15 @@
             var data = new Array<float>();
 
             if (!this.isBillboardBased) {
-                this._attributesStrideSize = 21;
+                this._attributesStrideSize += 3;
+            }
+
+            if (this._colorGradientsTexture) {
+                this._attributesStrideSize -= 4;
+            }
+
+            if (this._sizeGradientsTexture) {
+                this._attributesStrideSize += 3;
             }
 
             for (var particleIndex = 0; particleIndex < this._capacity; particleIndex++) {
@@ -496,17 +711,28 @@
 
                 // Seed
                 data.push(Math.random());
+                data.push(Math.random());
+                data.push(Math.random());
+                data.push(Math.random());
 
                 // Size
                 data.push(0.0);
                 data.push(0.0);
                 data.push(0.0);
 
-                // color
-                data.push(0.0);
-                data.push(0.0);
-                data.push(0.0);                     
-                data.push(0.0); 
+                if (this._sizeGradientsTexture) {
+                    data.push(0.0);
+                    data.push(0.0);
+                    data.push(0.0);  
+                }                
+
+                if (!this._colorGradientsTexture) {
+                    // color
+                    data.push(0.0);
+                    data.push(0.0);
+                    data.push(0.0);                     
+                    data.push(0.0); 
+                }
 
                 // direction
                 data.push(0.0);
@@ -560,11 +786,29 @@
                 defines += "\n#define BILLBOARD";
             }   
 
+            if (this._colorGradientsTexture) {
+                defines += "\n#define COLORGRADIENTS";
+            }        
+            
+            if (this._sizeGradientsTexture) {
+                defines += "\n#define SIZEGRADIENTS";
+            }                 
+
             if (this._updateEffect && this._updateEffectOptions.defines === defines) {
                 return;
             }
 
-            this._updateEffectOptions.transformFeedbackVaryings = ["outPosition", "outAge", "outLife", "outSeed", "outSize", "outColor", "outDirection"];           
+            this._updateEffectOptions.transformFeedbackVaryings = ["outPosition", "outAge", "outLife", "outSeed", "outSize"];           
+
+            if (this._sizeGradientsTexture) {
+                this._updateEffectOptions.transformFeedbackVaryings.push("outInitialSize");
+            }
+
+            if (!this._colorGradientsTexture) {
+                this._updateEffectOptions.transformFeedbackVaryings.push("outColor");
+            }
+
+            this._updateEffectOptions.transformFeedbackVaryings.push("outDirection");
 
             if (!this._isBillboardBased) {
                 this._updateEffectOptions.transformFeedbackVaryings.push("outInitialDirection");
@@ -585,7 +829,11 @@
 
             if (this._isBillboardBased) {
                 defines += "\n#define BILLBOARD";
-            }            
+            }         
+            
+            if (this._colorGradientsTexture) {
+                defines += "\n#define COLORGRADIENTS";
+            }   
 
             if (this._renderEffect && this._renderEffect.defines === defines) {
                 return;
@@ -594,14 +842,15 @@
             this._renderEffect = new Effect("gpuRenderParticles", 
                                             ["position", "age", "life", "size", "color", "offset", "uv", "initialDirection", "angle"], 
                                             ["view", "projection", "colorDead", "invView", "vClipPlane"], 
-                                            ["textureSampler"], this._scene.getEngine(), defines);
+                                            ["textureSampler", "colorGradientSampler"], this._scene.getEngine(), defines);
         }        
 
         /**
          * Animates the particle system for the current frame by emitting new particles and or animating the living ones.
+         * @param preWarm defines if we are in the pre-warmimg phase
          */
-        public animate(): void {           
-            this._timeDelta = this.updateSpeed * this._scene.getAnimationRatio();   
+        public animate(preWarm = false): void {           
+            this._timeDelta = this.updateSpeed * (preWarm ? this.preWarmStepOffset : this._scene.getAnimationRatio());
             this._actualFrame += this._timeDelta;
 
             if (!this._stopped) {
@@ -609,17 +858,66 @@
                     this.stop();
                 }
             }             
+        }    
+
+        private _createSizeGradientTexture() {
+            if (!this._sizeGradients || !this._sizeGradients.length || this._sizeGradientsTexture) {
+                return;
+            }
+
+            let textureWidth = 256;
+            let data = new Float32Array(textureWidth);
+
+            for (var x = 0; x < textureWidth; x++) {
+                var ratio = x / textureWidth;
+
+                Tools.GetCurrentGradient(ratio, this._sizeGradients, (currentGradient, nextGradient, scale) => {
+                    data[x] = Scalar.Lerp((<FactorGradient>currentGradient).factor, (<FactorGradient>nextGradient).factor, scale);
+                });
+            }
+
+            this._sizeGradientsTexture = RawTexture.CreateRTexture(data, textureWidth, 1, this._scene, false, false, Texture.NEAREST_SAMPLINGMODE);
         }        
+            
+        private _createColorGradientTexture() {
+            if (!this._colorGradients || !this._colorGradients.length || this._colorGradientsTexture) {
+                return;
+            }
+
+            let textureWidth = 256;
+            let data = new Uint8Array(textureWidth * 4);
+            let tmpColor = Tmp.Color4[0];
+
+            for (var x = 0; x < textureWidth; x++) {
+                var ratio = x / textureWidth;
+
+                Tools.GetCurrentGradient(ratio, this._colorGradients, (currentGradient, nextGradient, scale) => {
+
+                    Color4.LerpToRef((<ColorGradient>currentGradient).color, (<ColorGradient>nextGradient).color, scale, tmpColor);
+                    data[x * 4] = tmpColor.r * 255;
+                    data[x * 4 + 1] = tmpColor.g * 255;
+                    data[x * 4 + 2] = tmpColor.b * 255;
+                    data[x * 4 + 3] = tmpColor.a * 255;
+                });
+
+            }
+
+            this._colorGradientsTexture = RawTexture.CreateRGBATexture(data, textureWidth, 1, this._scene, false, false, Texture.NEAREST_SAMPLINGMODE);
+        }
 
         /**
-         * Renders the particle system in its current state.
+         * Renders the particle system in its current state
+         * @param preWarm defines if the system should only update the particles but not render them
          * @returns the current number of particles
          */
-        public render(): number {
+        public render(preWarm = false): number {
             if (!this._started) {
                 return 0;
             }
 
+            this._createColorGradientTexture();
+            this._createSizeGradientTexture();
+
             this._recreateUpdateEffect();
             this._recreateRenderEffect();
 
@@ -627,36 +925,61 @@
                 return 0;
             }
 
-            if (this._currentRenderId === this._scene.getRenderId()) {
-                return 0;
-            }
+            if (!preWarm) {
+                if (!this._preWarmDone && this.preWarmCycles) {                
+                    for (var index = 0; index < this.preWarmCycles; index++) {
+                        this.animate(true);
+                        this.render(true);
+                    }
+
+                    this._preWarmDone = true;
+                }
+
+                if (this._currentRenderId === this._scene.getRenderId()) {
+                    return 0;
+                }
 
-            this._currentRenderId = this._scene.getRenderId();      
+                this._currentRenderId = this._scene.getRenderId();      
+            }
             
             // Get everything ready to render
             this._initialize();
 
-            this._currentActiveCount = Math.min(this._activeCount, this._currentActiveCount + (this.emitRate * this._timeDelta) | 0);
+            this._accumulatedCount += this.emitRate * this._timeDelta;
+            if (this._accumulatedCount > 1) {
+                var intPart = this._accumulatedCount | 0;
+                this._accumulatedCount -= intPart;
+                this._currentActiveCount = Math.min(this._activeCount, this._currentActiveCount + intPart);
+            }
+
+            if (!this._currentActiveCount) {
+                return 0;
+            }
             
             // Enable update effect
-
             this._engine.enableEffect(this._updateEffect);
             this._engine.setState(false);    
             
             this._updateEffect.setFloat("currentCount", this._currentActiveCount);
             this._updateEffect.setFloat("timeDelta", this._timeDelta);
             this._updateEffect.setFloat("stopFactor", this._stopped ? 0 : 1);
-            this._updateEffect.setFloat3("generalRandoms", Math.random(), Math.random(), Math.random());
             this._updateEffect.setTexture("randomSampler", this._randomTexture);
+            this._updateEffect.setTexture("randomSampler2", this._randomTexture2);
             this._updateEffect.setFloat2("lifeTime", this.minLifeTime, this.maxLifeTime);
             this._updateEffect.setFloat2("emitPower", this.minEmitPower, this.maxEmitPower);
-            this._updateEffect.setDirectColor4("color1", this.color1);
-            this._updateEffect.setDirectColor4("color2", this.color2);
+            if (!this._colorGradientsTexture) {            
+                this._updateEffect.setDirectColor4("color1", this.color1);
+                this._updateEffect.setDirectColor4("color2", this.color2);
+            }
             this._updateEffect.setFloat2("sizeRange", this.minSize, this.maxSize);
             this._updateEffect.setFloat4("scaleRange", this.minScaleX, this.maxScaleX, this.minScaleY, this.maxScaleY);
-            this._updateEffect.setFloat2("angleRange", this.minAngularSpeed, this.maxAngularSpeed);
+            this._updateEffect.setFloat4("angleRange", this.minAngularSpeed, this.maxAngularSpeed, this.minInitialRotation, this.maxInitialRotation);
             this._updateEffect.setVector3("gravity", this.gravity);
 
+            if (this._sizeGradientsTexture) {      
+                this._updateEffect.setTexture("sizeGradientSampler", this._sizeGradientsTexture);      
+            }
+
             if (this.particleEmitterType) {
                 this.particleEmitterType.applyToShader(this._updateEffect);
             }
@@ -683,48 +1006,53 @@
             this._engine.setRasterizerState(true);
             this._engine.bindTransformFeedbackBuffer(null);
 
-            // Enable render effect
-            this._engine.enableEffect(this._renderEffect);
-            let viewMatrix = this._scene.getViewMatrix();
-            this._renderEffect.setMatrix("view", viewMatrix);
-            this._renderEffect.setMatrix("projection", this._scene.getProjectionMatrix());
-            this._renderEffect.setTexture("textureSampler", this.particleTexture);
-            this._renderEffect.setDirectColor4("colorDead", this.colorDead);
+            if (!preWarm) {
+                // Enable render effect
+                this._engine.enableEffect(this._renderEffect);
+                let viewMatrix = this._scene.getViewMatrix();
+                this._renderEffect.setMatrix("view", viewMatrix);
+                this._renderEffect.setMatrix("projection", this._scene.getProjectionMatrix());
+                this._renderEffect.setTexture("textureSampler", this.particleTexture);
+                if (this._colorGradientsTexture) {
+                    this._renderEffect.setTexture("colorGradientSampler", this._colorGradientsTexture);
+                } else {
+                    this._renderEffect.setDirectColor4("colorDead", this.colorDead);
+                }
 
 
-            if (this._scene.clipPlane) {
-                var clipPlane = this._scene.clipPlane;
-                var invView = viewMatrix.clone();
-                invView.invert();
-                this._renderEffect.setMatrix("invView", invView);
-                this._renderEffect.setFloat4("vClipPlane", clipPlane.normal.x, clipPlane.normal.y, clipPlane.normal.z, clipPlane.d);
-            }            
+                if (this._scene.clipPlane) {
+                    var clipPlane = this._scene.clipPlane;
+                    var invView = viewMatrix.clone();
+                    invView.invert();
+                    this._renderEffect.setMatrix("invView", invView);
+                    this._renderEffect.setFloat4("vClipPlane", clipPlane.normal.x, clipPlane.normal.y, clipPlane.normal.z, clipPlane.d);
+                }            
 
-            // Draw order
-            switch(this.blendMode)
-            {
-                case ParticleSystem.BLENDMODE_ADD:
-                    this._engine.setAlphaMode(Engine.ALPHA_ADD);
-                    break;
-                case ParticleSystem.BLENDMODE_ONEONE:
-                    this._engine.setAlphaMode(Engine.ALPHA_ONEONE);
-                    break;
-                case ParticleSystem.BLENDMODE_STANDARD:
-                    this._engine.setAlphaMode(Engine.ALPHA_COMBINE);
-                    break;
-            }      
-
-            if (this.forceDepthWrite) {
-                this._engine.setDepthWrite(true);
-            }
+                // Draw order
+                switch(this.blendMode)
+                {
+                    case ParticleSystem.BLENDMODE_ADD:
+                        this._engine.setAlphaMode(Engine.ALPHA_ADD);
+                        break;
+                    case ParticleSystem.BLENDMODE_ONEONE:
+                        this._engine.setAlphaMode(Engine.ALPHA_ONEONE);
+                        break;
+                    case ParticleSystem.BLENDMODE_STANDARD:
+                        this._engine.setAlphaMode(Engine.ALPHA_COMBINE);
+                        break;
+                }      
 
-            // Bind source VAO
-            this._engine.bindVertexArrayObject(this._renderVAO[this._targetIndex], null);
+                if (this.forceDepthWrite) {
+                    this._engine.setDepthWrite(true);
+                }
 
-            // Render
-            this._engine.drawArraysType(Material.TriangleFanDrawMode, 0, 4, this._currentActiveCount);   
-            this._engine.setAlphaMode(Engine.ALPHA_DISABLE);         
+                // Bind source VAO
+                this._engine.bindVertexArrayObject(this._renderVAO[this._targetIndex], null);
 
+                // Render
+                this._engine.drawArraysType(Material.TriangleFanDrawMode, 0, 4, this._currentActiveCount);   
+                this._engine.setAlphaMode(Engine.ALPHA_DISABLE);         
+            }
             // Switch VAOs
             this._targetIndex++;
             if (this._targetIndex === 2) {
@@ -789,13 +1117,27 @@
 
             this._releaseBuffers();
             this._releaseVAOs();
-         
 
+            if (this._colorGradientsTexture) {
+                this._colorGradientsTexture.dispose();
+                (<any>this._colorGradientsTexture) = null;
+            }
+
+            if (this._sizeGradientsTexture) {
+                this._sizeGradientsTexture.dispose();
+                (<any>this._sizeGradientsTexture) = null;
+            }            
+         
             if (this._randomTexture) {
                 this._randomTexture.dispose();
                 (<any>this._randomTexture) = null;
             }
 
+            if (this._randomTexture2) {
+                this._randomTexture2.dispose();
+                (<any>this._randomTexture2) = null;
+            }            
+
             if (disposeTexture && this.particleTexture) {
                 this.particleTexture.dispose();
                 this.particleTexture = null;
@@ -835,51 +1177,7 @@
         public serialize(): any {
             var serializationObject: any = {};
 
-            serializationObject.name = this.name;
-            serializationObject.id = this.id;
-
-            // Emitter
-            if ((<AbstractMesh>this.emitter).position) {
-                var emitterMesh = (<AbstractMesh>this.emitter);
-                serializationObject.emitterId = emitterMesh.id;
-            } else {
-                var emitterPosition = (<Vector3>this.emitter);
-                serializationObject.emitter = emitterPosition.asArray();
-            }
-
-            serializationObject.capacity = this.getCapacity();
-
-            if (this.particleTexture) {
-                serializationObject.textureName = this.particleTexture.name;
-            }
-
-            // Animations
-            Animation.AppendSerializedAnimations(this, serializationObject);
-
-            // Particle system
-            serializationObject.activeParticleCount = this.activeParticleCount;
-            serializationObject.randomTextureSize = this._randomTextureSize;
-            serializationObject.minSize = this.minSize;
-            serializationObject.maxSize = this.maxSize;
-            serializationObject.minEmitPower = this.minEmitPower;
-            serializationObject.maxEmitPower = this.maxEmitPower;
-            serializationObject.minLifeTime = this.minLifeTime;
-            serializationObject.maxLifeTime = this.maxLifeTime;            
-            serializationObject.minAngularSpeed = this.minAngularSpeed;
-            serializationObject.maxAngularSpeed = this.maxAngularSpeed;
-            serializationObject.emitRate = this.emitRate;
-            serializationObject.gravity = this.gravity.asArray();
-            serializationObject.color1 = this.color1.asArray();
-            serializationObject.color2 = this.color2.asArray();
-            serializationObject.colorDead = this.colorDead.asArray();
-            serializationObject.updateSpeed = this.updateSpeed;
-            serializationObject.targetStopDuration = this.targetStopDuration;
-            serializationObject.blendMode = this.blendMode;
-
-            // Emitter
-            if (this.particleEmitterType) {
-                serializationObject.particleEmitterType = this.particleEmitterType.serialize();
-            }
+            ParticleSystem._Serialize(serializationObject, this);
 
             return serializationObject;            
         }
@@ -895,72 +1193,8 @@
             var name = parsedParticleSystem.name;
             var particleSystem = new GPUParticleSystem(name, {capacity: parsedParticleSystem.capacity, randomTextureSize: parsedParticleSystem.randomTextureSize}, scene);
 
-            if (parsedParticleSystem.id) {
-                particleSystem.id = parsedParticleSystem.id;
-            }
-
-            // Texture
-            if (parsedParticleSystem.textureName) {
-                particleSystem.particleTexture = new Texture(rootUrl + parsedParticleSystem.textureName, scene);
-                particleSystem.particleTexture.name = parsedParticleSystem.textureName;
-            }
-
-            // Emitter
-            if (parsedParticleSystem.emitterId) {
-                particleSystem.emitter = scene.getLastMeshByID(parsedParticleSystem.emitterId);
-            } else {
-                particleSystem.emitter = Vector3.FromArray(parsedParticleSystem.emitter);
-            }
-
-            // Animations
-            if (parsedParticleSystem.animations) {
-                for (var animationIndex = 0; animationIndex < parsedParticleSystem.animations.length; animationIndex++) {
-                    var parsedAnimation = parsedParticleSystem.animations[animationIndex];
-                    particleSystem.animations.push(Animation.Parse(parsedAnimation));
-                }
-            }
-
-            // Particle system
             particleSystem.activeParticleCount = parsedParticleSystem.activeParticleCount;
-            particleSystem.minSize = parsedParticleSystem.minSize;
-            particleSystem.maxSize = parsedParticleSystem.maxSize;
-            particleSystem.minLifeTime = parsedParticleSystem.minLifeTime;
-            particleSystem.maxLifeTime = parsedParticleSystem.maxLifeTime;
-            particleSystem.minAngularSpeed = parsedParticleSystem.minAngularSpeed;
-            particleSystem.maxAngularSpeed = parsedParticleSystem.maxAngularSpeed;
-            particleSystem.minEmitPower = parsedParticleSystem.minEmitPower;
-            particleSystem.maxEmitPower = parsedParticleSystem.maxEmitPower;
-            particleSystem.emitRate = parsedParticleSystem.emitRate;
-            particleSystem.gravity = Vector3.FromArray(parsedParticleSystem.gravity);
-            particleSystem.color1 = Color4.FromArray(parsedParticleSystem.color1);
-            particleSystem.color2 = Color4.FromArray(parsedParticleSystem.color2);
-            particleSystem.colorDead = Color4.FromArray(parsedParticleSystem.colorDead);
-            particleSystem.updateSpeed = parsedParticleSystem.updateSpeed;
-            particleSystem.targetStopDuration = parsedParticleSystem.targetStopDuration;
-            particleSystem.blendMode = parsedParticleSystem.blendMode;
-
-            // Emitter
-            if (parsedParticleSystem.particleEmitterType) {
-                let emitterType: IParticleEmitterType;
-                switch (parsedParticleSystem.particleEmitterType.type) {
-                    case "SphereEmitter":
-                        emitterType = new SphereParticleEmitter();
-                        break;
-                    case "SphereDirectedParticleEmitter":
-                        emitterType = new SphereDirectedParticleEmitter();
-                        break;
-                    case "ConeEmitter":
-                        emitterType = new ConeParticleEmitter();
-                        break;
-                    case "BoxEmitter":
-                    default:
-                        emitterType = new BoxParticleEmitter();
-                        break;                                                
-                }
-
-                emitterType.parse(parsedParticleSystem.particleEmitterType);
-                particleSystem.particleEmitterType = emitterType;
-            }
+            ParticleSystem._Parse(parsedParticleSystem, particleSystem, scene, rootUrl);
 
             return particleSystem;
         }        

+ 171 - 115
src/Particles/babylon.particleSystem.ts

@@ -1,21 +1,5 @@
 module BABYLON {
 
-    interface IValueGradient {
-        gradient: number;
-    }
-
-    /** @hidden */
-    class ColorGradient implements IValueGradient {
-        public gradient: number;
-        public color: Color4;
-    }
-
-    /** @hidden */
-    class FactorGradient implements IValueGradient {
-        public gradient: number;
-        public factor: number;
-    }
-
     /**
      * This represents a particle system in Babylon.
      * Particles are often small sprites used to simulate hard-to-reproduce phenomena like fire, smoke, water, or abstract visual effects like magic glitter and faery dust.
@@ -203,7 +187,25 @@
         private _colorGradients: Nullable<Array<ColorGradient>> = null;
         private _sizeGradients: Nullable<Array<FactorGradient>> = null;
 
-       /**
+        /**
+         * Gets the current list of color gradients.
+         * You must use addColorGradient and removeColorGradient to udpate this list
+         * @returns the list of color gradients
+         */
+        public getColorGradients(): Nullable<Array<ColorGradient>> {
+            return this._colorGradients;
+        }
+
+        /**
+         * Gets the current list of size gradients.
+         * You must use addSizeGradient and removeSizeGradient to udpate this list
+         * @returns the list of size gradients
+         */
+        public getSizeGradients(): Nullable<Array<FactorGradient>> {
+            return this._sizeGradients;
+        }        
+
+        /**
          * Random direction of each particle after it has been emitted, between direction1 and direction2 vectors.
          * This only works when particleEmitterTyps is a BoxParticleEmitter
          */
@@ -489,7 +491,7 @@
 
                         // Color
                         if (this._colorGradients && this._colorGradients.length > 0) {
-                            this._getCurrentGradient(ratio, this._colorGradients, (currentGradient, nextGradient, scale) => {
+                            Tools.GetCurrentGradient(ratio, this._colorGradients, (currentGradient, nextGradient, scale) => {
                                 Color4.LerpToRef((<ColorGradient>currentGradient).color, (<ColorGradient>nextGradient).color, scale, particle.color);
                             });
                         }
@@ -511,12 +513,11 @@
 
                         // Gradient
                         if (this._sizeGradients && this._sizeGradients.length > 0) {
-                            this._getCurrentGradient(ratio, this._sizeGradients, (currentGradient, nextGradient, scale) => {
+                            Tools.GetCurrentGradient(ratio, this._sizeGradients, (currentGradient, nextGradient, scale) => {
                                 particle.size = particle._initialSize * Scalar.Lerp((<FactorGradient>currentGradient).factor, (<FactorGradient>nextGradient).factor, scale);
                             });
                         }
 
-
                         if (this._isAnimationSheetEnabled) {
                             particle.updateCellIndex(this._scaledUpdateSpeed);
                         }
@@ -525,18 +526,6 @@
             }
         }
 
-        private _getCurrentGradient(ratio: number, gradients: IValueGradient[], updateFunc: (current: IValueGradient, next: IValueGradient, scale: number) => void) {
-            for (var gradientIndex = 0; gradientIndex < gradients.length - 1; gradientIndex++) {
-                let currentGradient = gradients[gradientIndex];
-                let nextGradient = gradients[gradientIndex + 1];
-
-                if (ratio >= currentGradient.gradient && ratio <= nextGradient.gradient) {
-                    let scale =  (ratio - currentGradient.gradient) / (nextGradient.gradient - currentGradient.gradient);
-                    updateFunc(currentGradient, nextGradient, scale);
-               }
-            }
-        }
-
         /**
          * Adds a new size gradient
          * @param gradient defines the gradient to use (between 0 and 1)
@@ -560,7 +549,7 @@
                 }
 
                 return 0;
-            })
+            });
 
             return this;
         }
@@ -609,7 +598,7 @@
                 }
 
                 return 0;
-            })
+            });
 
             return this;
         }
@@ -1346,59 +1335,11 @@
         public serialize(): any {
             var serializationObject: any = {};
 
-            serializationObject.name = this.name;
-            serializationObject.id = this.id;
-
-            // Emitter
-            if ((<AbstractMesh>this.emitter).position) {
-                var emitterMesh = (<AbstractMesh>this.emitter);
-                serializationObject.emitterId = emitterMesh.id;
-            } else {
-                var emitterPosition = (<Vector3>this.emitter);
-                serializationObject.emitter = emitterPosition.asArray();
-            }
-
-            serializationObject.capacity = this.getCapacity();
-
-            if (this.particleTexture) {
-                serializationObject.textureName = this.particleTexture.name;
-            }
+            ParticleSystem._Serialize(serializationObject, this);
 
-            // Animations
-            Animation.AppendSerializedAnimations(this, serializationObject);
-
-            // Particle system
-            serializationObject.minAngularSpeed = this.minAngularSpeed;
-            serializationObject.maxAngularSpeed = this.maxAngularSpeed;
-            serializationObject.minSize = this.minSize;
-            serializationObject.maxSize = this.maxSize;
-            serializationObject.minScaleX = this.minScaleX;
-            serializationObject.maxScaleX = this.maxScaleX;
-            serializationObject.minScaleY = this.minScaleY;
-            serializationObject.maxScaleY = this.maxScaleY;            
-            serializationObject.minEmitPower = this.minEmitPower;
-            serializationObject.maxEmitPower = this.maxEmitPower;
-            serializationObject.minLifeTime = this.minLifeTime;
-            serializationObject.maxLifeTime = this.maxLifeTime;
-            serializationObject.emitRate = this.emitRate;
-            serializationObject.minEmitBox = this.minEmitBox.asArray();
-            serializationObject.maxEmitBox = this.maxEmitBox.asArray();
-            serializationObject.gravity = this.gravity.asArray();
-            serializationObject.direction1 = this.direction1.asArray();
-            serializationObject.direction2 = this.direction2.asArray();
-            serializationObject.color1 = this.color1.asArray();
-            serializationObject.color2 = this.color2.asArray();
-            serializationObject.colorDead = this.colorDead.asArray();
-            serializationObject.updateSpeed = this.updateSpeed;
-            serializationObject.targetStopDuration = this.targetStopDuration;
             serializationObject.textureMask = this.textureMask.asArray();
-            serializationObject.blendMode = this.blendMode;
             serializationObject.customShader = this.customShader;
             serializationObject.preventAutoStart = this.preventAutoStart;
-            serializationObject.preWarmCycles = this.preWarmCycles;
-            serializationObject.preWarmStepOffset = this.preWarmStepOffset;
-            serializationObject.minInitialRotation = this.minInitialRotation;
-            serializationObject.maxInitialRotation = this.maxInitialRotation;
 
             serializationObject.startSpriteCellID = this.startSpriteCellID;
             serializationObject.endSpriteCellID = this.endSpriteCellID;
@@ -1409,42 +1350,89 @@
 
             serializationObject.isAnimationSheetEnabled = this._isAnimationSheetEnabled;
 
-            // Emitter
-            if (this.particleEmitterType) {
-                serializationObject.particleEmitterType = this.particleEmitterType.serialize();
-            }            
-
             return serializationObject;
         }
 
-        /**
-         * Parses a JSON object to create a particle system.
-         * @param parsedParticleSystem The JSON object to parse
-         * @param scene The scene to create the particle system in
-         * @param rootUrl The root url to use to load external dependencies like texture
-         * @returns the Parsed particle system
-         */
-        public static Parse(parsedParticleSystem: any, scene: Scene, rootUrl: string): ParticleSystem {
-            var name = parsedParticleSystem.name;
-            var custom: Nullable<Effect> = null;
-            var program: any = null;
-            if (parsedParticleSystem.customShader) {
-                program = parsedParticleSystem.customShader;
-                var defines: string = (program.shaderOptions.defines.length > 0) ? program.shaderOptions.defines.join("\n") : "";
-                custom = scene.getEngine().createEffectForParticles(program.shaderPath.fragmentElement, program.shaderOptions.uniforms, program.shaderOptions.samplers, defines);
+        /** @hidden */
+        public static _Serialize(serializationObject: any, particleSystem: IParticleSystem) {
+            serializationObject.name = particleSystem.name;
+            serializationObject.id = particleSystem.id;
+
+            serializationObject.capacity = particleSystem.getCapacity();
+
+            // Emitter
+            if ((<AbstractMesh>particleSystem.emitter).position) {
+                var emitterMesh = (<AbstractMesh>particleSystem.emitter);
+                serializationObject.emitterId = emitterMesh.id;
+            } else {
+                var emitterPosition = (<Vector3>particleSystem.emitter);
+                serializationObject.emitter = emitterPosition.asArray();
             }
-            var particleSystem = new ParticleSystem(name, parsedParticleSystem.capacity, scene, custom, parsedParticleSystem.isAnimationSheetEnabled);
-            particleSystem.customShader = program;
 
-            if (parsedParticleSystem.id) {
-                particleSystem.id = parsedParticleSystem.id;
+            // Emitter
+            if (particleSystem.particleEmitterType) {
+                serializationObject.particleEmitterType = particleSystem.particleEmitterType.serialize();
+            }           
+            
+            if (particleSystem.particleTexture) {
+                serializationObject.textureName = particleSystem.particleTexture.name;
             }
+            
+            // Animations
+            Animation.AppendSerializedAnimations(particleSystem, serializationObject);
 
-            // Auto start
-            if (parsedParticleSystem.preventAutoStart) {
-                particleSystem.preventAutoStart = parsedParticleSystem.preventAutoStart;
+            // Particle system
+            serializationObject.minAngularSpeed = particleSystem.minAngularSpeed;
+            serializationObject.maxAngularSpeed = particleSystem.maxAngularSpeed;
+            serializationObject.minSize = particleSystem.minSize;
+            serializationObject.maxSize = particleSystem.maxSize;
+            serializationObject.minScaleX = particleSystem.minScaleX;
+            serializationObject.maxScaleX = particleSystem.maxScaleX;
+            serializationObject.minScaleY = particleSystem.minScaleY;
+            serializationObject.maxScaleY = particleSystem.maxScaleY;            
+            serializationObject.minEmitPower = particleSystem.minEmitPower;
+            serializationObject.maxEmitPower = particleSystem.maxEmitPower;
+            serializationObject.minLifeTime = particleSystem.minLifeTime;
+            serializationObject.maxLifeTime = particleSystem.maxLifeTime;
+            serializationObject.emitRate = particleSystem.emitRate;
+            serializationObject.gravity = particleSystem.gravity.asArray();
+            serializationObject.color1 = particleSystem.color1.asArray();
+            serializationObject.color2 = particleSystem.color2.asArray();
+            serializationObject.colorDead = particleSystem.colorDead.asArray();
+            serializationObject.updateSpeed = particleSystem.updateSpeed;
+            serializationObject.targetStopDuration = particleSystem.targetStopDuration;
+            serializationObject.blendMode = particleSystem.blendMode;
+            serializationObject.preWarmCycles = particleSystem.preWarmCycles;
+            serializationObject.preWarmStepOffset = particleSystem.preWarmStepOffset;
+            serializationObject.minInitialRotation = particleSystem.minInitialRotation;
+            serializationObject.maxInitialRotation = particleSystem.maxInitialRotation;
+
+            let colorGradients = particleSystem.getColorGradients();
+            if (colorGradients) {
+                serializationObject.colorGradients = [];
+                for (var colorGradient of colorGradients) {
+                    serializationObject.colorGradients.push({
+                        gradient: colorGradient.gradient,
+                        color: colorGradient.color.asArray()
+                    })
+                }
             }
 
+            let sizeGradients = particleSystem.getSizeGradients();
+            if (sizeGradients) {
+                serializationObject.sizeGradients = [];
+                for (var sizeGradient of sizeGradients) {
+                    serializationObject.sizeGradients.push({
+                        gradient: sizeGradient.gradient,
+                        factor: sizeGradient.factor
+                    })
+                }
+            }            
+
+        }
+
+        /** @hidden */
+        public static _Parse(parsedParticleSystem: any, particleSystem: IParticleSystem, scene: Scene, rootUrl: string) {
             // Texture
             if (parsedParticleSystem.textureName) {
                 particleSystem.particleTexture = new Texture(rootUrl + parsedParticleSystem.textureName, scene);
@@ -1498,19 +1486,87 @@
             particleSystem.minEmitPower = parsedParticleSystem.minEmitPower;
             particleSystem.maxEmitPower = parsedParticleSystem.maxEmitPower;
             particleSystem.emitRate = parsedParticleSystem.emitRate;
-            particleSystem.minEmitBox = Vector3.FromArray(parsedParticleSystem.minEmitBox);
-            particleSystem.maxEmitBox = Vector3.FromArray(parsedParticleSystem.maxEmitBox);
             particleSystem.gravity = Vector3.FromArray(parsedParticleSystem.gravity);
-            particleSystem.direction1 = Vector3.FromArray(parsedParticleSystem.direction1);
-            particleSystem.direction2 = Vector3.FromArray(parsedParticleSystem.direction2);
             particleSystem.color1 = Color4.FromArray(parsedParticleSystem.color1);
             particleSystem.color2 = Color4.FromArray(parsedParticleSystem.color2);
             particleSystem.colorDead = Color4.FromArray(parsedParticleSystem.colorDead);
             particleSystem.updateSpeed = parsedParticleSystem.updateSpeed;
             particleSystem.targetStopDuration = parsedParticleSystem.targetStopDuration;
-            particleSystem.textureMask = Color4.FromArray(parsedParticleSystem.textureMask);
             particleSystem.blendMode = parsedParticleSystem.blendMode;
 
+
+            if (parsedParticleSystem.colorGradients) {
+                for (var colorGradient of parsedParticleSystem.colorGradients) {
+                    particleSystem.addColorGradient(colorGradient.gradient, Color4.FromArray(colorGradient.color));
+                }
+            }
+
+            if (parsedParticleSystem.sizeGradients) {
+                for (var sizeGradient of parsedParticleSystem.sizeGradients) {
+                    particleSystem.addSizeGradient(sizeGradient.gradient, sizeGradient.factor);
+                }
+            }       
+            
+            // Emitter
+            if (parsedParticleSystem.particleEmitterType) {
+                let emitterType: IParticleEmitterType;
+                switch (parsedParticleSystem.particleEmitterType.type) {
+                    case "SphereEmitter":
+                        emitterType = new SphereParticleEmitter();
+                        break;
+                    case "SphereDirectedParticleEmitter":
+                        emitterType = new SphereDirectedParticleEmitter();
+                        break;
+                    case "ConeEmitter":
+                        emitterType = new ConeParticleEmitter();
+                        break;
+                    case "BoxEmitter":
+                    default:
+                        emitterType = new BoxParticleEmitter();
+                        break;                                                
+                }
+
+                emitterType.parse(parsedParticleSystem.particleEmitterType);
+                particleSystem.particleEmitterType = emitterType;
+            }
+        }
+
+        /**
+         * Parses a JSON object to create a particle system.
+         * @param parsedParticleSystem The JSON object to parse
+         * @param scene The scene to create the particle system in
+         * @param rootUrl The root url to use to load external dependencies like texture
+         * @returns the Parsed particle system
+         */
+        public static Parse(parsedParticleSystem: any, scene: Scene, rootUrl: string): ParticleSystem {
+            var name = parsedParticleSystem.name;
+            var custom: Nullable<Effect> = null;
+            var program: any = null;
+            if (parsedParticleSystem.customShader) {
+                program = parsedParticleSystem.customShader;
+                var defines: string = (program.shaderOptions.defines.length > 0) ? program.shaderOptions.defines.join("\n") : "";
+                custom = scene.getEngine().createEffectForParticles(program.shaderPath.fragmentElement, program.shaderOptions.uniforms, program.shaderOptions.samplers, defines);
+            }
+            var particleSystem = new ParticleSystem(name, parsedParticleSystem.capacity, scene, custom, parsedParticleSystem.isAnimationSheetEnabled);
+            particleSystem.customShader = program;
+
+            if (parsedParticleSystem.id) {
+                particleSystem.id = parsedParticleSystem.id;
+            }
+
+            // Auto start
+            if (parsedParticleSystem.preventAutoStart) {
+                particleSystem.preventAutoStart = parsedParticleSystem.preventAutoStart;
+            }
+
+            particleSystem.minEmitBox = Vector3.FromArray(parsedParticleSystem.minEmitBox);
+            particleSystem.maxEmitBox = Vector3.FromArray(parsedParticleSystem.maxEmitBox);
+            particleSystem.direction1 = Vector3.FromArray(parsedParticleSystem.direction1);
+            particleSystem.direction2 = Vector3.FromArray(parsedParticleSystem.direction2);
+
+            ParticleSystem._Parse(parsedParticleSystem, particleSystem, scene, rootUrl);
+
+            particleSystem.textureMask = Color4.FromArray(parsedParticleSystem.textureMask);
             particleSystem.startSpriteCellID = parsedParticleSystem.startSpriteCellID;
             particleSystem.endSpriteCellID = parsedParticleSystem.endSpriteCellID;
             particleSystem.spriteCellLoop = parsedParticleSystem.spriteCellLoop;

+ 13 - 3
src/Shaders/gpuRenderParticles.vertex.fx

@@ -1,6 +1,6 @@
 #version 300 es
 
-uniform vec4 colorDead;
+
 uniform mat4 view;
 uniform mat4 projection;
 
@@ -9,7 +9,6 @@ in vec3 position;
 in float age;
 in float life;
 in vec3 size;
-in vec4 color;
 #ifndef BILLBOARD
 in vec3 initialDirection;
 #endif
@@ -26,11 +25,22 @@ uniform mat4 invView;
 out float fClipDistance;
 #endif
 
+#ifdef COLORGRADIENTS
+uniform sampler2D colorGradientSampler;
+#else
+uniform vec4 colorDead;
+in vec4 color;
+#endif
 
 void main() {
   vUV = uv;
   float ratio = age / life;
-  vColor = color * vec4(1.0 - ratio) + colorDead * vec4(ratio);
+#ifdef COLORGRADIENTS
+	vColor = texture(colorGradientSampler, vec2(ratio, 0));
+#else
+	vColor = color * vec4(1.0 - ratio) + colorDead * vec4(ratio);
+#endif
+  
 
   vec2 cornerPos = offset * size.yz * size.x;
 

+ 46 - 16
src/Shaders/gpuUpdateParticles.vertex.fx

@@ -5,17 +5,19 @@
 uniform float currentCount;
 uniform float timeDelta;
 uniform float stopFactor;
-uniform vec3 generalRandoms;
 uniform mat4 emitterWM;
 uniform vec2 lifeTime;
 uniform vec2 emitPower;
 uniform vec2 sizeRange;
 uniform vec4 scaleRange;
+#ifndef COLORGRADIENTS
 uniform vec4 color1;
 uniform vec4 color2;
+#endif
 uniform vec3 gravity;
 uniform sampler2D randomSampler;
-uniform vec2 angleRange;
+uniform sampler2D randomSampler2;
+uniform vec4 angleRange;
 
 #ifdef BOXEMITTER
 uniform vec3 direction1;
@@ -46,9 +48,11 @@ uniform float directionRandomizer;
 in vec3 position;
 in float age;
 in float life;
-in float seed;
+in vec4 seed;
 in vec3 size;
+#ifndef COLORGRADIENTS
 in vec4 color;
+#endif
 in vec3 direction;
 #ifndef BILLBOARD
 in vec3 initialDirection;
@@ -59,24 +63,33 @@ in vec2 angle;
 out vec3 outPosition;
 out float outAge;
 out float outLife;
-out float outSeed;
+out vec4 outSeed;
 out vec3 outSize;
+#ifdef SIZEGRADIENTS
+out vec3 outInitialSize;
+#endif
+#ifndef COLORGRADIENTS
 out vec4 outColor;
+#endif
 out vec3 outDirection;
 #ifndef BILLBOARD
 out vec3 outInitialDirection;
 #endif
 out vec2 outAngle;
 
+#ifdef SIZEGRADIENTS
+uniform sampler2D sizeGradientSampler;
+in vec3 initialSize;
+#endif 
+
 vec3 getRandomVec3(float offset) {
-  return texture(randomSampler, vec2(float(gl_VertexID) * offset / currentCount, 0)).rgb;
+  return texture(randomSampler2, vec2(float(gl_VertexID) * offset / currentCount, 0)).rgb;
 }
 
 vec4 getRandomVec4(float offset) {
   return texture(randomSampler, vec2(float(gl_VertexID) * offset / currentCount, 0));
 }
 
-
 void main() {
   if (age >= life) {
     if (stopFactor == 0.) {
@@ -84,7 +97,9 @@ void main() {
       outAge = life;
       outLife = life;
       outSeed = seed;
+#ifndef COLORGRADIENTS      
       outColor = vec4(0.,0.,0.,0.);
+#endif
       outSize = vec3(0., 0., 0.);
 #ifndef BILLBOARD        
       outInitialDirection = initialDirection;
@@ -97,7 +112,7 @@ void main() {
     vec3 direction;
 
     // Let's get some random values
-    vec4 randoms = getRandomVec4(generalRandoms.x);
+    vec4 randoms = getRandomVec4(seed.x);
 
     // Age and life
     outAge = 0.0;
@@ -109,26 +124,32 @@ void main() {
     // Size
     outSize.x = sizeRange.x + (sizeRange.y - sizeRange.x) * randoms.g;
     outSize.y = scaleRange.x + (scaleRange.y - scaleRange.x) * randoms.b;
-    outSize.z = scaleRange.z + (scaleRange.w - scaleRange.z) * randoms.a;
+    outSize.z = scaleRange.z + (scaleRange.w - scaleRange.z) * randoms.a; 
 
+#ifdef SIZEGRADIENTS
+    outInitialSize = outSize;
+#endif    
+
+#ifndef COLORGRADIENTS
     // Color
     outColor = color1 + (color2 - color1) * randoms.b;
+#endif
 
     // Angular speed
     outAngle.y = angleRange.x + (angleRange.y - angleRange.x) * randoms.a;
-    outAngle.x = 0.;
+    outAngle.x = angleRange.z + (angleRange.w - angleRange.z) * randoms.r;
 
     // Position / Direction (based on emitter type)
 #ifdef BOXEMITTER
-    vec3 randoms2 = getRandomVec3(generalRandoms.y);
-    vec3 randoms3 = getRandomVec3(generalRandoms.z);
+    vec3 randoms2 = getRandomVec3(seed.y);
+    vec3 randoms3 = getRandomVec3(seed.z);
 
     position = minEmitBox + (maxEmitBox - minEmitBox) * randoms2;
 
     direction = direction1 + (direction2 - direction1) * randoms3;
 #elif defined(SPHEREEMITTER)
-    vec3 randoms2 = getRandomVec3(generalRandoms.y);
-    vec3 randoms3 = getRandomVec3(generalRandoms.z);
+    vec3 randoms2 = getRandomVec3(seed.y);
+    vec3 randoms3 = getRandomVec3(seed.z);
 
     // Position on the sphere surface
     float phi = 2.0 * PI * randoms2.x;
@@ -146,7 +167,7 @@ void main() {
       direction = position + directionRandomizer * randoms3;
     #endif
 #elif defined(CONEEMITTER)
-    vec3 randoms2 = getRandomVec3(generalRandoms.y);
+    vec3 randoms2 = getRandomVec3(seed.y);
 
     float s = 2.0 * PI * randoms2.x;
     float h = randoms2.y;
@@ -166,7 +187,7 @@ void main() {
     if (coneAngle == 0.) {
         direction = vec3(0., 1.0, 0.);
     } else {
-        vec3 randoms3 = getRandomVec3(generalRandoms.z);
+        vec3 randoms3 = getRandomVec3(seed.z);
         direction = position + directionRandomizer * randoms3;
     }
 #else    
@@ -174,7 +195,7 @@ void main() {
     position = vec3(0., 0., 0.);
 
     // Spread in all directions
-    direction = 2.0 * (getRandomVec3(seed) - vec3(0.5, 0.5, 0.5));
+    direction = 2.0 * (getRandomVec3(seed.w) - vec3(0.5, 0.5, 0.5));
 #endif
 
     float power = emitPower.x + (emitPower.y - emitPower.x) * randoms.a;
@@ -191,8 +212,17 @@ void main() {
     outAge = age + timeDelta;
     outLife = life;
     outSeed = seed;
+#ifndef COLORGRADIENTS    
     outColor = color;
+#endif
+
+#ifdef SIZEGRADIENTS
+    outInitialSize = initialSize;
+	outSize = initialSize * texture(sizeGradientSampler, vec2(age / life, 0)).r;
+#else
     outSize = size;
+#endif 
+
 #ifndef BILLBOARD    
     outInitialDirection = initialDirection;
 #endif

+ 52 - 0
src/Tools/babylon.tools.ts

@@ -3,6 +3,39 @@
         animations: Array<Animation>;
     }
 
+    
+    /** Interface used by value gradients (color, factor, ...) */
+    export interface IValueGradient {
+        /**
+         * Gets or sets the gradient value (between 0 and 1)
+         */        
+        gradient: number;
+    }
+
+    /** Class used to store color gradient */
+    export class ColorGradient implements IValueGradient {
+        /**
+         * Gets or sets the gradient value (between 0 and 1)
+         */
+        public gradient: number;
+        /**
+         * Gets or sets associated color
+         */
+        public color: Color4;
+    }
+
+    /** Class used to store factor gradient */
+    export class FactorGradient implements IValueGradient {
+        /**
+         * Gets or sets the gradient value (between 0 and 1)
+         */
+        public gradient: number;
+        /**
+         * Gets or sets associated factor
+         */        
+        public factor: number;
+    }  
+
     // See https://stackoverflow.com/questions/12915412/how-do-i-extend-a-host-object-e-g-error-in-typescript
     // and https://github.com/Microsoft/TypeScript/wiki/Breaking-Changes#extending-built-ins-like-error-array-and-map-may-no-longer-work
     export class LoadFileError extends Error {
@@ -1607,6 +1640,25 @@
                 }, delay);
             });
         }
+
+
+        /**
+         * Gets the current gradient from an array of IValueGradient
+         * @param ratio defines the current ratio to get
+         * @param gradients defines the array of IValueGradient
+         * @param updateFunc defines the callback function used to get the final value from the selected gradients
+         */
+        public static GetCurrentGradient(ratio: number, gradients: IValueGradient[], updateFunc: (current: IValueGradient, next: IValueGradient, scale: number) => void) {
+            for (var gradientIndex = 0; gradientIndex < gradients.length - 1; gradientIndex++) {
+                let currentGradient = gradients[gradientIndex];
+                let nextGradient = gradients[gradientIndex + 1];
+
+                if (ratio >= currentGradient.gradient && ratio <= nextGradient.gradient) {
+                    let scale =  (ratio - currentGradient.gradient) / (nextGradient.gradient - currentGradient.gradient);
+                    updateFunc(currentGradient, nextGradient, scale);
+               }
+            }
+        }
     }
 
     /**