Browse Source

Large CPU particle optimization

David Catuhe 7 years ago
parent
commit
2ef712e1c0

+ 5 - 2
dist/preview release/what's new.md

@@ -9,6 +9,10 @@
 - Added [Environment Texture Tools](https://doc.babylonjs.com/how_to/physically_based_rendering#creating-a-compressed-environment-texture) to reduce the size of the usual .DDS file ([sebavan](http://www.github.com/sebavan))
 - Added [Environment Texture Tools](https://doc.babylonjs.com/how_to/physically_based_rendering#creating-a-compressed-environment-texture) to reduce the size of the usual .DDS file ([sebavan](http://www.github.com/sebavan))
 - New GUI control: the [Grid](http://doc.babylonjs.com/how_to/gui#grid) ([Deltakosh](https://github.com/deltakosh))
 - New GUI control: the [Grid](http://doc.babylonjs.com/how_to/gui#grid) ([Deltakosh](https://github.com/deltakosh))
 - New `serialize` and `Parse` functions to serialize and parse all procedural textures from the Procedural Textures Library ([julien-moreau](https://github.com/julien-moreau))
 - New `serialize` and `Parse` functions to serialize and parse all procedural textures from the Procedural Textures Library ([julien-moreau](https://github.com/julien-moreau))
+- Particle system improvements ([Deltakosh](https://github.com/deltakosh))
+  - Improved rendering performance (up to x2 on low end devices)
+  - Added support for `minScaleX`, `minScaleY`, `maxScaleX`, `maxScaleY`
+  - Added support for `radiusRange` for sphere emitter
 
 
 ## Updates
 ## Updates
 
 
@@ -32,8 +36,7 @@
 - Added a new `mesh.ignoreNonUniformScaling` to turn off non uniform scaling compensation ([Deltakosh](https://github.com/deltakosh))
 - Added a new `mesh.ignoreNonUniformScaling` to turn off non uniform scaling compensation ([Deltakosh](https://github.com/deltakosh))
 - AssetsManager tasks will only run when their state is INIT. It is now possible to remove a task from the assets manager ([RaananW](https://github.com/RaananW))
 - AssetsManager tasks will only run when their state is INIT. It is now possible to remove a task from the assets manager ([RaananW](https://github.com/RaananW))
 - Added sprite isVisible field ([TrevorDev](https://github.com/TrevorDev))
 - Added sprite isVisible field ([TrevorDev](https://github.com/TrevorDev))
-- Added support for `minScaleX`, `minScaleY`, `maxScaleX`, `maxScaleY` for particles ([Deltakosh](https://github.com/deltakosh))
-- Added support for `radiusRange` for sphere particle emitter ([Deltakosh](https://github.com/deltakosh))
+
 
 
 ### glTF Loader
 ### glTF Loader
 
 

+ 172 - 109
src/Particles/babylon.particleSystem.ts

@@ -242,20 +242,20 @@
         }
         }
 
 
         /**
         /**
-         * Random color of each particle after it has been emitted, between color1 and color2 vectors.
+         * Random color of each particle after it has been emitted, between color1 and color2 vectors
          */
          */
         public color1 = new Color4(1.0, 1.0, 1.0, 1.0);
         public color1 = new Color4(1.0, 1.0, 1.0, 1.0);
         /**
         /**
-         * Random color of each particle after it has been emitted, between color1 and color2 vectors.
+         * Random color of each particle after it has been emitted, between color1 and color2 vectors
          */
          */
         public color2 = new Color4(1.0, 1.0, 1.0, 1.0);
         public color2 = new Color4(1.0, 1.0, 1.0, 1.0);
         /**
         /**
-         * Color the particle will have at the end of its lifetime.
+         * Color the particle will have at the end of its lifetime
          */
          */
         public colorDead = new Color4(0, 0, 0, 1.0);
         public colorDead = new Color4(0, 0, 0, 1.0);
 
 
         /**
         /**
-         * An optional mask to filter some colors out of the texture, or filter a part of the alpha channel.
+         * An optional mask to filter some colors out of the texture, or filter a part of the alpha channel
          */
          */
         public textureMask = new Color4(1.0, 1.0, 1.0, 1.0);
         public textureMask = new Color4(1.0, 1.0, 1.0, 1.0);
 
 
@@ -267,48 +267,48 @@
 
 
         /**
         /**
          * This function can be defined to specify initial direction for every new particle.
          * This function can be defined to specify initial direction for every new particle.
-         * It by default use the emitterType defined function.
+         * It by default use the emitterType defined function
          */
          */
         public startDirectionFunction: (emitPower: number, worldMatrix: Matrix, directionToUpdate: Vector3, particle: Particle) => void;
         public startDirectionFunction: (emitPower: number, worldMatrix: Matrix, directionToUpdate: Vector3, particle: Particle) => void;
         /**
         /**
          * This function can be defined to specify initial position for every new particle.
          * This function can be defined to specify initial position for every new particle.
-         * It by default use the emitterType defined function.
+         * It by default use the emitterType defined function
          */
          */
         public startPositionFunction: (worldMatrix: Matrix, positionToUpdate: Vector3, particle: Particle) => void;
         public startPositionFunction: (worldMatrix: Matrix, positionToUpdate: Vector3, particle: Particle) => void;
 
 
         /**
         /**
-         * If using a spritesheet (isAnimationSheetEnabled), defines if the sprite animation should loop between startSpriteCellID and endSpriteCellID or not.
+         * If using a spritesheet (isAnimationSheetEnabled), defines if the sprite animation should loop between startSpriteCellID and endSpriteCellID or not
          */
          */
         public spriteCellLoop = true;
         public spriteCellLoop = true;
         /**
         /**
-         * If using a spritesheet (isAnimationSheetEnabled) and spriteCellLoop defines the speed of the sprite loop.
+         * If using a spritesheet (isAnimationSheetEnabled) and spriteCellLoop defines the speed of the sprite loop
          */
          */
         public spriteCellChangeSpeed = 0;
         public spriteCellChangeSpeed = 0;
         /**
         /**
-         * If using a spritesheet (isAnimationSheetEnabled) and spriteCellLoop defines the first sprite cell to display.
+         * If using a spritesheet (isAnimationSheetEnabled) and spriteCellLoop defines the first sprite cell to display
          */
          */
         public startSpriteCellID = 0;
         public startSpriteCellID = 0;
         /**
         /**
-         * If using a spritesheet (isAnimationSheetEnabled) and spriteCellLoop defines the last sprite cell to display.
+         * If using a spritesheet (isAnimationSheetEnabled) and spriteCellLoop defines the last sprite cell to display
          */
          */
         public endSpriteCellID = 0;
         public endSpriteCellID = 0;
         /**
         /**
-         * If using a spritesheet (isAnimationSheetEnabled), defines the sprite cell width to use.
+         * If using a spritesheet (isAnimationSheetEnabled), defines the sprite cell width to use
          */
          */
         public spriteCellWidth = 0;
         public spriteCellWidth = 0;
         /**
         /**
-         * If using a spritesheet (isAnimationSheetEnabled), defines the sprite cell height to use.
+         * If using a spritesheet (isAnimationSheetEnabled), defines the sprite cell height to use
          */
          */
         public spriteCellHeight = 0;
         public spriteCellHeight = 0;
 
 
         /**
         /**
-        * An event triggered when the system is disposed.
+        * An event triggered when the system is disposed
         */
         */
         public onDisposeObservable = new Observable<ParticleSystem>();
         public onDisposeObservable = new Observable<ParticleSystem>();
 
 
         private _onDisposeObserver: Nullable<Observer<ParticleSystem>>;
         private _onDisposeObserver: Nullable<Observer<ParticleSystem>>;
         /**
         /**
-         * Sets a callback that will be triggered when the system is disposed.
+         * Sets a callback that will be triggered when the system is disposed
          */
          */
         public set onDispose(callback: () => void) {
         public set onDispose(callback: () => void) {
             if (this._onDisposeObserver) {
             if (this._onDisposeObserver) {
@@ -318,12 +318,28 @@
         }
         }
 
 
         /**
         /**
-         * Gets wether an animation sprite sheet is enabled or not on the particle system.
+         * Gets whether an animation sprite sheet is enabled or not on the particle system
          */
          */
-        public get isAnimationSheetEnabled(): Boolean {
+        public get isAnimationSheetEnabled(): boolean {
             return this._isAnimationSheetEnabled;
             return this._isAnimationSheetEnabled;
         }
         }
 
 
+        /**
+         * Gets or sets a boolean indicating if the particles must be rendered as billboard or aligned with the direction
+         */
+        public get isBillboardBased(): boolean {
+            return this._isBillboardBased;
+        }      
+        
+        public set isBillboardBased(value: boolean) {
+            if (this._isBillboardBased === value) {
+                return;
+            }
+
+            this._isBillboardBased = value;
+            this._resetEffect();
+        }            
+
         private _particles = new Array<Particle>();
         private _particles = new Array<Particle>();
         private _epsilon: number;
         private _epsilon: number;
         private _capacity: number;
         private _capacity: number;
@@ -333,6 +349,7 @@
         private _vertexData: Float32Array;
         private _vertexData: Float32Array;
         private _vertexBuffer: Nullable<Buffer>;
         private _vertexBuffer: Nullable<Buffer>;
         private _vertexBuffers: { [key: string]: VertexBuffer } = {};
         private _vertexBuffers: { [key: string]: VertexBuffer } = {};
+        private _spriteBuffer: Nullable<Buffer>;
         private _indexBuffer: Nullable<WebGLBuffer>;
         private _indexBuffer: Nullable<WebGLBuffer>;
         private _effect: Effect;
         private _effect: Effect;
         private _customEffect: Nullable<Effect>;
         private _customEffect: Nullable<Effect>;
@@ -343,6 +360,7 @@
         private _scaledGravity = Vector3.Zero();
         private _scaledGravity = Vector3.Zero();
         private _currentRenderId = -1;
         private _currentRenderId = -1;
         private _alive: boolean;
         private _alive: boolean;
+        private _useInstancing = false;
 
 
         private _started = false;
         private _started = false;
         private _stopped = false;
         private _stopped = false;
@@ -350,6 +368,7 @@
         private _scaledUpdateSpeed: number;
         private _scaledUpdateSpeed: number;
         private _vertexBufferSize = 12;
         private _vertexBufferSize = 12;
         private _isAnimationSheetEnabled: boolean;
         private _isAnimationSheetEnabled: boolean;
+        private _isBillboardBased = true;
 
 
         // end of sheet animation
         // end of sheet animation
 
 
@@ -399,9 +418,6 @@
 
 
             this._epsilon = epsilon;
             this._epsilon = epsilon;
             this._isAnimationSheetEnabled = isAnimationSheetEnabled;
             this._isAnimationSheetEnabled = isAnimationSheetEnabled;
-            if (isAnimationSheetEnabled) {
-                this._vertexBufferSize = 13;
-            }
 
 
             this._scene = scene || Engine.LastCreatedScene;
             this._scene = scene || Engine.LastCreatedScene;
 
 
@@ -409,26 +425,10 @@
 
 
             scene.particleSystems.push(this);
             scene.particleSystems.push(this);
 
 
-            this._createIndexBuffer();
-
-            // 13 floats per particle (x, y, z, r, g, b, a, angle, scaleX, scaleY, offsetX, offsetY) + 1 filler
-            this._vertexData = new Float32Array(capacity * this._vertexBufferSize * 4);
-            this._vertexBuffer = new Buffer(scene.getEngine(), this._vertexData, true, this._vertexBufferSize);
-
-            var positions = this._vertexBuffer.createVertexBuffer(VertexBuffer.PositionKind, 0, 3);
-            var colors = this._vertexBuffer.createVertexBuffer(VertexBuffer.ColorKind, 3, 4);
-            var options = this._vertexBuffer.createVertexBuffer("options", 7, 3);
-            var size = this._vertexBuffer.createVertexBuffer("size", 10, 2);
+            this._useInstancing = this._scene.getEngine().getCaps().instancedArrays;
 
 
-            if (this._isAnimationSheetEnabled) {
-                var cellIndexBuffer = this._vertexBuffer.createVertexBuffer("cellIndex", 12, 1);
-                this._vertexBuffers["cellIndex"] = cellIndexBuffer;
-            }
-
-            this._vertexBuffers[VertexBuffer.PositionKind] = positions;
-            this._vertexBuffers[VertexBuffer.ColorKind] = colors;
-            this._vertexBuffers["options"] = options;
-            this._vertexBuffers["size"] = size;
+            this._createIndexBuffer();
+            this._createVertexBuffers();
 
 
             // Default emitter type
             // Default emitter type
             this.particleEmitterType = new BoxParticleEmitter();
             this.particleEmitterType = new BoxParticleEmitter();
@@ -467,7 +467,76 @@
             }
             }
         }
         }
 
 
+        private _resetEffect() {
+            if (this._vertexBuffer) {
+                this._vertexBuffer.dispose();
+                this._vertexBuffer = null;
+            }
+
+            if (this._spriteBuffer) {
+                this._spriteBuffer.dispose();
+                this._spriteBuffer = null;
+            }            
+
+            this._createVertexBuffers();           
+        }
+
+        private _createVertexBuffers() {
+            this._vertexBufferSize = this._useInstancing ? 10 : 12;
+            if (this._isAnimationSheetEnabled) {
+                this._vertexBufferSize += 1;
+            }
+
+            if (!this._isBillboardBased) {
+                this._vertexBufferSize += 3;
+            }
+
+            let engine = this._scene.getEngine();
+            this._vertexData = new Float32Array(this._capacity * this._vertexBufferSize * (this._useInstancing ? 1 : 4));
+            this._vertexBuffer = new Buffer(engine, this._vertexData, true, this._vertexBufferSize);
+
+            var positions = this._vertexBuffer.createVertexBuffer(VertexBuffer.PositionKind, 0, 3, this._vertexBufferSize, this._useInstancing);
+            this._vertexBuffers[VertexBuffer.PositionKind] = positions;
+
+            var colors = this._vertexBuffer.createVertexBuffer(VertexBuffer.ColorKind, 3, 4, this._vertexBufferSize, this._useInstancing);
+            this._vertexBuffers[VertexBuffer.ColorKind] = colors;
+
+            var options = this._vertexBuffer.createVertexBuffer("angle", 7, 1, this._vertexBufferSize, this._useInstancing);
+            this._vertexBuffers["angle"] = options;
+            
+            var offsets: VertexBuffer;
+            let dataOffset = 8;
+            if (this._useInstancing) {
+                var spriteData = new Float32Array([0, 0, 1, 0, 1, 1, 0, 1]);  
+                this._spriteBuffer = new Buffer(engine, spriteData, false, 2);  
+                offsets = this._spriteBuffer.createVertexBuffer("offset", 0, 2);
+            } else {
+                offsets = this._vertexBuffer.createVertexBuffer("offset", 8, 2, this._vertexBufferSize, this._useInstancing);
+                dataOffset += 2;
+            }
+            this._vertexBuffers["offset"] = offsets;            
+
+            var size = this._vertexBuffer.createVertexBuffer("size", dataOffset, 2, this._vertexBufferSize, this._useInstancing);
+            this._vertexBuffers["size"] = size;
+            dataOffset += 2;
+
+            if (this._isAnimationSheetEnabled) {
+                var cellIndexBuffer = this._vertexBuffer.createVertexBuffer("cellIndex", dataOffset, 1, this._vertexBufferSize, this._useInstancing);
+                this._vertexBuffers["cellIndex"] = cellIndexBuffer;
+                dataOffset += 1;
+            }
+
+            if (!this._isBillboardBased) {
+                var directionBuffer = this._vertexBuffer.createVertexBuffer("direction", dataOffset, 3, this._vertexBufferSize, this._useInstancing);
+                this._vertexBuffers["direction"] = directionBuffer;
+                dataOffset += 3;
+            }
+        }
+
         private _createIndexBuffer() {
         private _createIndexBuffer() {
+            if (this._useInstancing) {
+                return;
+            }
             var indices = [];
             var indices = [];
             var index = 0;
             var index = 0;
             for (var count = 0; count < this._capacity; count++) {
             for (var count = 0; count < this._capacity; count++) {
@@ -546,48 +615,45 @@
          */
          */
         public _appendParticleVertex(index: number, particle: Particle, offsetX: number, offsetY: number): void {
         public _appendParticleVertex(index: number, particle: Particle, offsetX: number, offsetY: number): void {
             var offset = index * this._vertexBufferSize;
             var offset = index * this._vertexBufferSize;
-            this._vertexData[offset] = particle.position.x;
-            this._vertexData[offset + 1] = particle.position.y;
-            this._vertexData[offset + 2] = particle.position.z;
-            this._vertexData[offset + 3] = particle.color.r;
-            this._vertexData[offset + 4] = particle.color.g;
-            this._vertexData[offset + 5] = particle.color.b;
-            this._vertexData[offset + 6] = particle.color.a;
-            this._vertexData[offset + 7] = particle.angle;
-            this._vertexData[offset + 8] = offsetX;
-            this._vertexData[offset + 9] = offsetY;   
-            this._vertexData[offset + 10] = particle.scale.x * particle.size;
-            this._vertexData[offset + 11] = particle.scale.y * particle.size;      
-        }
 
 
-        /**
-         * @hidden (for internal use only)
-         */
-        public _appendParticleVertexWithAnimation(index: number, particle: Particle, offsetX: number, offsetY: number): void {
-            if (offsetX === 0)
-                offsetX = this._epsilon;
-            else if (offsetX === 1)
-                offsetX = 1 - this._epsilon;
+            this._vertexData[offset++] = particle.position.x;
+            this._vertexData[offset++] = particle.position.y;
+            this._vertexData[offset++] = particle.position.z;
+            this._vertexData[offset++] = particle.color.r;
+            this._vertexData[offset++] = particle.color.g;
+            this._vertexData[offset++] = particle.color.b;
+            this._vertexData[offset++] = particle.color.a;
+            this._vertexData[offset++] = particle.angle;
+
+            if (!this._useInstancing) {
+                if (this._isAnimationSheetEnabled) {
+                    if (offsetX === 0)
+                        offsetX = this._epsilon;
+                    else if (offsetX === 1)
+                        offsetX = 1 - this._epsilon;
+    
+                    if (offsetY === 0)
+                        offsetY = this._epsilon;
+                    else if (offsetY === 1)
+                        offsetY = 1 - this._epsilon;
+                }
 
 
-            if (offsetY === 0)
-                offsetY = this._epsilon;
-            else if (offsetY === 1)
-                offsetY = 1 - this._epsilon;
+                this._vertexData[offset++] = offsetX;
+                this._vertexData[offset++] = offsetY;   
+            }
 
 
-            var offset = index * this._vertexBufferSize;
-            this._vertexData[offset] = particle.position.x;
-            this._vertexData[offset + 1] = particle.position.y;
-            this._vertexData[offset + 2] = particle.position.z;
-            this._vertexData[offset + 3] = particle.color.r;
-            this._vertexData[offset + 4] = particle.color.g;
-            this._vertexData[offset + 5] = particle.color.b;
-            this._vertexData[offset + 6] = particle.color.a;
-            this._vertexData[offset + 7] = particle.angle;
-            this._vertexData[offset + 8] = offsetX;
-            this._vertexData[offset + 9] = offsetY;   
-            this._vertexData[offset + 10] = particle.scale.x * particle.size;
-            this._vertexData[offset + 11] = particle.scale.y * particle.size;
-            this._vertexData[offset + 12] = particle.cellIndex;
+            this._vertexData[offset++] = particle.scale.x * particle.size;
+            this._vertexData[offset++] = particle.scale.y * particle.size;
+            
+            if (this._isAnimationSheetEnabled) {
+                this._vertexData[offset++] = particle.cellIndex;
+            }
+
+            if (!this._isBillboardBased) {
+                this._vertexData[offset++] = particle.direction.x;
+                this._vertexData[offset++] = particle.direction.y;
+                this._vertexData[offset++] = particle.direction.z;
+            }
         }
         }
 
 
         // start of sub system methods
         // start of sub system methods
@@ -726,21 +792,25 @@
                 defines.push("#define ANIMATESHEET");
                 defines.push("#define ANIMATESHEET");
             }
             }
 
 
+            if (this._isBillboardBased) {
+                defines.push("#define BILLBOARD");
+            }
+
             // Effect
             // Effect
             var join = defines.join("\n");
             var join = defines.join("\n");
             if (this._cachedDefines !== join) {
             if (this._cachedDefines !== join) {
                 this._cachedDefines = join;
                 this._cachedDefines = join;
 
 
-                var attributesNamesOrOptions: any;
-                var effectCreationOption: any;
+                var attributesNamesOrOptions = [VertexBuffer.PositionKind, VertexBuffer.ColorKind, "angle", "offset", "size"];
+                var effectCreationOption = ["invView", "view", "projection", "vClipPlane", "textureMask"];
 
 
                 if (this._isAnimationSheetEnabled) {
                 if (this._isAnimationSheetEnabled) {
-                    attributesNamesOrOptions = [VertexBuffer.PositionKind, VertexBuffer.ColorKind, "options", "size", "cellIndex"];
-                    effectCreationOption = ["invView", "view", "projection", "particlesInfos", "vClipPlane", "textureMask"];
+                    attributesNamesOrOptions.push("cellIndex");
+                    effectCreationOption.push("particlesInfos")
                 }
                 }
-                else {
-                    attributesNamesOrOptions = [VertexBuffer.PositionKind, VertexBuffer.ColorKind, "options", "size", ];
-                    effectCreationOption = ["invView", "view", "projection", "vClipPlane", "textureMask"]
+
+                if (!this._isBillboardBased) {
+                    attributesNamesOrOptions.push("direction");
                 }
                 }
 
 
                 this._effect = this._scene.getEngine().createEffect(
                 this._effect = this._scene.getEngine().createEffect(
@@ -817,20 +887,12 @@
                 }
                 }
             }
             }
 
 
-            // Animation sheet
-            if (this._isAnimationSheetEnabled) {
-                this._appendParticleVertexes = this._appenedParticleVertexesWithSheet;
-            }
-            else {
-                this._appendParticleVertexes = this._appenedParticleVertexesNoSheet;
-            }
-
             // Update VBO
             // Update VBO
             var offset = 0;
             var offset = 0;
             for (var index = 0; index < this._particles.length; index++) {
             for (var index = 0; index < this._particles.length; index++) {
                 var particle = this._particles[index];
                 var particle = this._particles[index];
-                this._appendParticleVertexes(offset, particle);
-                offset += 4;
+                this._appendParticleVertices(offset, particle);                
+                offset += this._useInstancing ? 1 : 4;
             }
             }
 
 
             if (this._vertexBuffer) {
             if (this._vertexBuffer) {
@@ -842,20 +904,13 @@
             }
             }
         }
         }
 
 
-        private _appendParticleVertexes: Nullable<(offset: number, particle: Particle) => void> = null;
-
-        private _appenedParticleVertexesWithSheet(offset: number, particle: Particle) {
-            this._appendParticleVertexWithAnimation(offset++, particle, 0, 0);
-            this._appendParticleVertexWithAnimation(offset++, particle, 1, 0);
-            this._appendParticleVertexWithAnimation(offset++, particle, 1, 1);
-            this._appendParticleVertexWithAnimation(offset++, particle, 0, 1);
-        }
-
-        private _appenedParticleVertexesNoSheet(offset: number, particle: Particle) {
+        private _appendParticleVertices(offset: number, particle: Particle) {
             this._appendParticleVertex(offset++, particle, 0, 0);
             this._appendParticleVertex(offset++, particle, 0, 0);
-            this._appendParticleVertex(offset++, particle, 1, 0);
-            this._appendParticleVertex(offset++, particle, 1, 1);
-            this._appendParticleVertex(offset++, particle, 0, 1);
+            if (!this._useInstancing) {
+                this._appendParticleVertex(offset++, particle, 1, 0);
+                this._appendParticleVertex(offset++, particle, 1, 1);
+                this._appendParticleVertex(offset++, particle, 0, 1);
+            }
         }
         }
 
 
         /**
         /**
@@ -920,7 +975,6 @@
                 effect.setFloat4("vClipPlane", clipPlane.normal.x, clipPlane.normal.y, clipPlane.normal.z, clipPlane.d);
                 effect.setFloat4("vClipPlane", clipPlane.normal.x, clipPlane.normal.y, clipPlane.normal.z, clipPlane.d);
             }
             }
 
 
-            // VBOs
             engine.bindBuffers(this._vertexBuffers, this._indexBuffer, effect);
             engine.bindBuffers(this._vertexBuffers, this._indexBuffer, effect);
 
 
             // Draw order
             // Draw order
@@ -934,7 +988,11 @@
                 engine.setDepthWrite(true);
                 engine.setDepthWrite(true);
             }
             }
 
 
-            engine.drawElementsType(Material.TriangleFillMode, 0, this._particles.length * 6);
+            if (this._useInstancing) {
+                engine.drawArraysType(Material.TriangleFanDrawMode, 0, 4, this._particles.length);  
+            } else {
+                engine.drawElementsType(Material.TriangleFillMode, 0, this._particles.length * 6);
+            }
             engine.setAlphaMode(Engine.ALPHA_DISABLE);
             engine.setAlphaMode(Engine.ALPHA_DISABLE);
 
 
             return this._particles.length;
             return this._particles.length;
@@ -950,6 +1008,11 @@
                 this._vertexBuffer = null;
                 this._vertexBuffer = null;
             }
             }
 
 
+            if (this._spriteBuffer) {
+                this._spriteBuffer.dispose();
+                this._spriteBuffer = null;
+            }
+
             if (this._indexBuffer) {
             if (this._indexBuffer) {
                 this._scene.getEngine()._releaseBuffer(this._indexBuffer);
                 this._scene.getEngine()._releaseBuffer(this._indexBuffer);
                 this._indexBuffer = null;
                 this._indexBuffer = null;

+ 46 - 12
src/Shaders/particles.vertex.fx

@@ -1,14 +1,23 @@
 // Attributes
 // Attributes
 attribute vec3 position;
 attribute vec3 position;
 attribute vec4 color;
 attribute vec4 color;
-attribute vec3 options;
+attribute float angle;
+attribute vec2 offset;
 attribute vec2 size;
 attribute vec2 size;
+#ifdef ANIMATESHEET	
 attribute float cellIndex;
 attribute float cellIndex;
+#endif
+#ifndef BILLBOARD	
+attribute vec3 direction;
+#endif
 
 
 // Uniforms
 // Uniforms
 uniform mat4 view;
 uniform mat4 view;
 uniform mat4 projection;
 uniform mat4 projection;
+
+#ifdef ANIMATESHEET	
 uniform vec3 particlesInfos; // x (number of rows) y(number of columns) z(rowSize)
 uniform vec3 particlesInfos; // x (number of rows) y(number of columns) z(rowSize)
+#endif
 
 
 // Output
 // Output
 varying vec2 vUV;
 varying vec2 vUV;
@@ -21,34 +30,59 @@ varying float fClipDistance;
 #endif
 #endif
 
 
 void main(void) {	
 void main(void) {	
-	vec3 viewPos = (view * vec4(position, 1.0)).xyz; 
 	vec2 cornerPos;
 	vec2 cornerPos;
-	float angle = options.x;
-	vec2 offset = options.yz;
 	
 	
 	cornerPos = vec2(offset.x - 0.5, offset.y  - 0.5) * size;
 	cornerPos = vec2(offset.x - 0.5, offset.y  - 0.5) * size;
 
 
+#ifdef BILLBOARD	
 	// Rotate
 	// Rotate
 	vec3 rotatedCorner;
 	vec3 rotatedCorner;
 	rotatedCorner.x = cornerPos.x * cos(angle) - cornerPos.y * sin(angle);
 	rotatedCorner.x = cornerPos.x * cos(angle) - cornerPos.y * sin(angle);
 	rotatedCorner.y = cornerPos.x * sin(angle) + cornerPos.y * cos(angle);
 	rotatedCorner.y = cornerPos.x * sin(angle) + cornerPos.y * cos(angle);
 	rotatedCorner.z = 0.;
 	rotatedCorner.z = 0.;
 
 
+	vec3 viewPos = (view * vec4(position, 1.0)).xyz + rotatedCorner; 
+
 	// Position
 	// Position
-	viewPos += rotatedCorner;
 	gl_Position = projection * vec4(viewPos, 1.0);   
 	gl_Position = projection * vec4(viewPos, 1.0);   
-	
+#else
+	// Rotate
+	vec3 rotatedCorner;
+	rotatedCorner.x = cornerPos.x * cos(angle) - cornerPos.y * sin(angle);
+	rotatedCorner.z = cornerPos.x * sin(angle) + cornerPos.y * cos(angle);
+	rotatedCorner.y = 0.;
+
+	vec3 worldPos = position + rotatedCorner; 
+
+	vec3 yaxis = normalize(direction);
+	vec3 xaxis = normalize(cross(vec3(0., 1.0, 0.), yaxis));
+	vec3 zaxis = normalize(cross(yaxis, xaxis));
+
+	vec4 row0 = vec4(xaxis.x, xaxis.y, xaxis.z, 0.);
+	vec4 row1 = vec4(yaxis.x, yaxis.y, yaxis.z, 0.);
+	vec4 row2 = vec4(zaxis.x, zaxis.y, zaxis.z, 0.);
+	// vec4 row0 = vec4(1., 0., 0., 0.);
+	// vec4 row1 = vec4(0., 1., 0., 0.);
+	// vec4 row2 = vec4(0., 0., 1., 0.);
+	vec4 row3 = vec4(0., 0., 0., 1.0);
+
+	mat4 rotMatrix =  mat4(row0, row1, row2, row3);
+
+	vec4 alignedWorld = rotMatrix * vec4(worldPos, 0.0);
+
+	gl_Position = projection * view * vec4(alignedWorld.xyz, 1.0);  
+#endif	
 	vColor = color;
 	vColor = color;
 
 
 	#ifdef ANIMATESHEET
 	#ifdef ANIMATESHEET
-	float rowOffset = floor(cellIndex / particlesInfos.z);
-    float columnOffset = cellIndex - rowOffset * particlesInfos.z;
+		float rowOffset = floor(cellIndex / particlesInfos.z);
+		float columnOffset = cellIndex - rowOffset * particlesInfos.z;
 
 
-	vec2 uvScale = particlesInfos.xy;
-	vec2 uvOffset = vec2(offset.x , 1.0 - offset.y);
-	vUV = (uvOffset + vec2(columnOffset, rowOffset)) * uvScale;
+		vec2 uvScale = particlesInfos.xy;
+		vec2 uvOffset = vec2(offset.x , 1.0 - offset.y);
+		vUV = (uvOffset + vec2(columnOffset, rowOffset)) * uvScale;
 	#else
 	#else
-	vUV = offset;
+		vUV = offset;
 	#endif
 	#endif
 
 
 	// Clip plane
 	// Clip plane