Explorar el Código

Merge pull request #1293 from sebavan/Stencil

Stencil
David Catuhe hace 9 años
padre
commit
2a0c505951

+ 1 - 0
Tools/Gulp/config.json

@@ -25,6 +25,7 @@
       "../../src/Tools/babylon.tools.js",
       "../../src/States/babylon.alphaCullingState.js",
       "../../src/States/babylon.depthCullingState.js",
+      "../../src/States/babylon.stencilState.js",
       "../../src/babylon.engine.js",
       "../../src/babylon.node.js",
       "../../src/Tools/babylon.filesInput.js",

+ 1 - 1
src/Canvas2d/babylon.canvas2d.ts

@@ -157,7 +157,7 @@
 
             if (this._isScreenSpace) {
                 this._afterRenderObserver = this._scene.onAfterRenderObservable.add((d, s) => {
-                    this._engine.clear(null, false, true);
+                    this._engine.clear(null, false, true, true);
                     this._render();
                 });
             } else {

+ 2 - 2
src/Lights/Shadows/babylon.shadowGenerator.ts

@@ -266,9 +266,9 @@
 
             this._shadowMap.onClearObservable.add((engine: Engine) => {
                 if (this.useBlurVarianceShadowMap || this.useVarianceShadowMap) {
-                    engine.clear(new Color4(0, 0, 0, 0), true, true);
+                    engine.clear(new Color4(0, 0, 0, 0), true, true, true);
                 } else {
-                    engine.clear(new Color4(1.0, 1.0, 1.0, 1.0), true, true);
+                    engine.clear(new Color4(1.0, 1.0, 1.0, 1.0), true, true, true);
                 }
             });
         }

+ 2 - 2
src/Materials/Textures/Procedurals/babylon.proceduralTexture.ts

@@ -299,7 +299,7 @@
                     this._effect.setFloat("face", face);
 
                     // Clear
-                    engine.clear(scene.clearColor, true, true);
+                    engine.clear(scene.clearColor, true, true, true);
 
                     // Draw order
                     engine.draw(true, 0, 6);
@@ -313,7 +313,7 @@
                 engine.bindFramebuffer(this._texture);
 
                 // Clear
-                engine.clear(scene.clearColor, true, true);
+                engine.clear(scene.clearColor, true, true, true);
 
                 // Draw order
                 engine.draw(true, 0, 6);

+ 31 - 1
src/Materials/Textures/babylon.renderTargetTexture.ts

@@ -300,7 +300,7 @@
             if (this.onClearObservable.hasObservers()) {
                 this.onClearObservable.notifyObservers(engine);
             } else {
-                engine.clear(scene.clearColor, true, true);
+                engine.clear(scene.clearColor, true, true, true);
             }
 
             if (!this._doNotChangeAspectRatio) {
@@ -338,6 +338,36 @@
             }
         }
 
+        /**
+         * Overrides the default sort function applied in the renderging group to prepare the meshes.
+         * This allowed control for front to back rendering or reversly depending of the special needs.
+         * 
+         * @param renderingGroupId The rendering group id corresponding to its index
+         * @param opaqueSortCompareFn The opaque queue comparison function use to sort.
+         * @param alphaTestSortCompareFn The alpha test queue comparison function use to sort.
+         * @param transparentSortCompareFn The transparent queue comparison function use to sort.
+         */
+        public setRenderingOrder(renderingGroupId: number,
+            opaqueSortCompareFn: (a: SubMesh, b: SubMesh) => number = null,
+            alphaTestSortCompareFn: (a: SubMesh, b: SubMesh) => number = null,
+            transparentSortCompareFn: (a: SubMesh, b: SubMesh) => number = null): void {
+            
+            this._renderingManager.setRenderingOrder(renderingGroupId,
+                opaqueSortCompareFn,
+                alphaTestSortCompareFn,
+                transparentSortCompareFn);
+        }
+
+        /**
+         * Specifies whether or not the stencil and depth buffer are cleared between two rendering groups.
+         * 
+         * @param renderingGroupId The rendering group id corresponding to its index
+         * @param autoClearDepthStencil Automatically clears depth and stencil between groups if true.
+         */
+        public setRenderingAutoClearDepthStencil(renderingGroupId: number, autoClearDepthStencil: boolean): void {            
+            this._renderingManager.setRenderingAutoClearDepthStencil(renderingGroupId, autoClearDepthStencil);
+        }
+
         public clone(): RenderTargetTexture {
             var textureSize = this.getSize();
             var newTexture = new RenderTargetTexture(this.name, textureSize.width, this.getScene(), this._generateMipMaps);

+ 9 - 1
src/Materials/babylon.material.ts

@@ -135,7 +135,7 @@
         }
 
         /**
-        * An event triggered when the material is compiled.
+        * An event triggered when the material is bound.
         * @type {BABYLON.Observable}
         */
         public onBindObservable = new Observable<AbstractMesh>();
@@ -148,6 +148,11 @@
             this._onBindObserver = this.onBindObservable.add(callback);
         }
 
+        /**
+        * An event triggered when the material is unbound.
+        * @type {BABYLON.Observable}
+        */
+        public onUnBindObservable = new Observable<Material>();
 
 
         @serialize()
@@ -283,6 +288,9 @@
         }
 
         public unbind(): void {
+
+            this.onUnBindObservable.notifyObservers(this);
+
             if (this.disableDepthWrite) {
                 var engine = this._scene.getEngine();
                 engine.setDepthWrite(this._cachedDepthWriteState);

+ 14 - 4
src/PostProcess/babylon.postProcess.ts

@@ -174,10 +174,20 @@
                 }         
                 this.width = desiredWidth;
                 this.height = desiredHeight;
-                this._textures.push(this._engine.createRenderTargetTexture({ width: this.width, height: this.height }, { generateMipMaps: false, generateDepthBuffer: camera._postProcesses.indexOf(this) === 0, samplingMode: this.renderTargetSamplingMode, type: this._textureType }));
+
+                let textureSize = { width: this.width, height: this.height };
+                let textureOptions = { 
+                    generateMipMaps: false, 
+                    generateDepthBuffer: camera._postProcesses.indexOf(this) === 0, 
+                    generateStencilBuffer: camera._postProcesses.indexOf(this) === 0 && this._engine.isStencilEnable,
+                    samplingMode: this.renderTargetSamplingMode, 
+                    type: this._textureType 
+                };
+
+                this._textures.push(this._engine.createRenderTargetTexture(textureSize, textureOptions));
 
                 if (this._reusable) {
-                    this._textures.push(this._engine.createRenderTargetTexture({ width: this.width, height: this.height }, { generateMipMaps: false, generateDepthBuffer: camera._postProcesses.indexOf(this) === 0, samplingMode: this.renderTargetSamplingMode, type: this._textureType }));
+                    this._textures.push(this._engine.createRenderTargetTexture(textureSize, textureOptions));
                 }
 
                 this.onSizeChangedObservable.notifyObservers(this);
@@ -196,9 +206,9 @@
 
             // Clear
             if (this.clearColor) {
-                this._engine.clear(this.clearColor, true, true);
+                this._engine.clear(this.clearColor, true, true, true);
             } else {
-                this._engine.clear(scene.clearColor, scene.autoClear || scene.forceWireframe, true);
+                this._engine.clear(scene.clearColor, scene.autoClear || scene.forceWireframe, true, true);
             }
 
             if (this._reusable) {

+ 1 - 1
src/Rendering/babylon.depthRenderer.ts

@@ -25,7 +25,7 @@
             
             // set default depth value to 1.0 (far away)
             this._depthMap.onClearObservable.add((engine: Engine) => {
-                engine.clear(new Color4(1.0, 1.0, 1.0, 1.0), true, true);
+                engine.clear(new Color4(1.0, 1.0, 1.0, 1.0), true, true, true);
             });
 
             // Custom render function

+ 200 - 51
src/Rendering/babylon.renderingGroup.ts

@@ -6,12 +6,81 @@
         private _alphaTestSubMeshes = new SmartArray<SubMesh>(256);
         private _activeVertices: number;
 
+        private _opaqueSortCompareFn: (a: SubMesh, b: SubMesh) => number;
+        private _alphaTestSortCompareFn: (a: SubMesh, b: SubMesh) => number;
+        private _transparentSortCompareFn: (a: SubMesh, b: SubMesh) => number;
+        
+        private _renderOpaque: (subMeshes: SmartArray<SubMesh>) => void;
+        private _renderAlphaTest: (subMeshes: SmartArray<SubMesh>) => void;
+        private _renderTransparent: (subMeshes: SmartArray<SubMesh>) => void;
+
         public onBeforeTransparentRendering: () => void;
 
-        constructor(public index: number, scene: Scene) {
+        /**
+         * Set the opaque sort comparison function.
+         * If null the sub meshes will be render in the order they were created 
+         */
+        public set opaqueSortCompareFn(value: (a: SubMesh, b: SubMesh) => number) {
+            this._opaqueSortCompareFn = value;
+            if (value) {
+                this._renderOpaque = this.renderOpaqueSorted;
+            }
+            else {
+                this._renderOpaque = RenderingGroup.renderUnsorted;
+            }
+        }
+
+        /**
+         * Set the alpha test sort comparison function.
+         * If null the sub meshes will be render in the order they were created 
+         */
+        public set alphaTestSortCompareFn(value: (a: SubMesh, b: SubMesh) => number) {
+            this._alphaTestSortCompareFn = value;
+            if (value) {
+                this._renderAlphaTest = this.renderAlphaTestSorted;
+            }
+            else {
+                this._renderAlphaTest = RenderingGroup.renderUnsorted;
+            }
+        }
+
+        /**
+         * Set the transparent sort comparison function.
+         * If null the sub meshes will be render in the order they were created 
+         */
+        public set transparentSortCompareFn(value: (a: SubMesh, b: SubMesh) => number) {
+            if (value) {
+                this._transparentSortCompareFn = value;
+            }
+            else {
+                this._transparentSortCompareFn = RenderingGroup.defaultTransparentSortCompare;
+            }
+            this._renderTransparent = this.renderTransparentSorted;
+        }
+
+        /**
+         * Creates a new rendering group.
+         * @param index The rendering group index
+         * @param opaqueSortCompareFn The opaque sort comparison function. If null no order is applied
+         * @param alphaTestSortCompareFn The alpha test sort comparison function. If null no order is applied
+         * @param transparentSortCompareFn The transparent sort comparison function. If null back to front + alpha index sort is applied
+         */
+        constructor(public index: number, scene: Scene,
+            opaqueSortCompareFn: (a: SubMesh, b: SubMesh) => number = null,
+            alphaTestSortCompareFn: (a: SubMesh, b: SubMesh) => number = null,
+            transparentSortCompareFn: (a: SubMesh, b: SubMesh) => number = null) {
             this._scene = scene;
+
+            this.opaqueSortCompareFn = opaqueSortCompareFn;
+            this.alphaTestSortCompareFn = alphaTestSortCompareFn;
+            this.transparentSortCompareFn = transparentSortCompareFn;            
         }
 
+        /**
+         * Render all the sub meshes contained in the group.
+         * @param customRenderFunction Used to override the default render behaviour of the group.
+         * @returns true if rendered some submeshes.
+         */
         public render(customRenderFunction: (opaqueSubMeshes: SmartArray<SubMesh>, transparentSubMeshes: SmartArray<SubMesh>, alphaTestSubMeshes: SmartArray<SubMesh>) => void): boolean {
             if (customRenderFunction) {
                 customRenderFunction(this._opaqueSubMeshes, this._alphaTestSubMeshes, this._transparentSubMeshes);
@@ -25,23 +94,13 @@
                 return false;
             }
             var engine = this._scene.getEngine();
+            
             // Opaque
-            var subIndex: number;
-            var submesh: SubMesh;
-
-            for (subIndex = 0; subIndex < this._opaqueSubMeshes.length; subIndex++) {
-                submesh = this._opaqueSubMeshes.data[subIndex];
-
-                submesh.render(false);
-            }
+            this._renderOpaque(this._opaqueSubMeshes);
 
             // Alpha test
             engine.setAlphaTesting(true);
-            for (subIndex = 0; subIndex < this._alphaTestSubMeshes.length; subIndex++) {
-                submesh = this._alphaTestSubMeshes.data[subIndex];
-
-                submesh.render(false);
-            }
+            this._renderAlphaTest(this._alphaTestSubMeshes);
             engine.setAlphaTesting(false);
 
             if (this.onBeforeTransparentRendering) {
@@ -49,54 +108,144 @@
             }
 
             // Transparent
-            if (this._transparentSubMeshes.length) {
-                // Sorting
-                for (subIndex = 0; subIndex < this._transparentSubMeshes.length; subIndex++) {
-                    submesh = this._transparentSubMeshes.data[subIndex];
-                    submesh._alphaIndex = submesh.getMesh().alphaIndex;
-                    submesh._distanceToCamera = submesh.getBoundingInfo().boundingSphere.centerWorld.subtract(this._scene.activeCamera.globalPosition).length();
-                }
+            this._renderTransparent(this._transparentSubMeshes);
+            return true;
+        }
 
-                var sortedArray = this._transparentSubMeshes.data.slice(0, this._transparentSubMeshes.length);
-
-                sortedArray.sort((a, b) => {
-                    // Alpha index first
-                    if (a._alphaIndex > b._alphaIndex) {
-                        return 1;
-                    }
-                    if (a._alphaIndex < b._alphaIndex) {
-                        return -1;
-                    }
-
-                    // Then distance to camera
-                    if (a._distanceToCamera < b._distanceToCamera) {
-                        return 1;
-                    }
-                    if (a._distanceToCamera > b._distanceToCamera) {
-                        return -1;
-                    }
-
-                    return 0;
-                });
-
-                // Rendering                
-                for (subIndex = 0; subIndex < sortedArray.length; subIndex++) {
-                    submesh = sortedArray[subIndex];
-
-                    submesh.render(true);
-                }
-                engine.setAlphaMode(Engine.ALPHA_DISABLE);
+        /**
+         * Renders the opaque submeshes in the order from the opaqueSortCompareFn.
+         * @param subMeshes The submeshes to render
+         */
+        private renderOpaqueSorted(subMeshes: SmartArray<SubMesh>): void {
+            return RenderingGroup.renderSorted(subMeshes, this._opaqueSortCompareFn, this._scene.activeCamera.globalPosition, false);
+        }
+
+        /**
+         * Renders the opaque submeshes in the order from the alphatestSortCompareFn.
+         * @param subMeshes The submeshes to render
+         */
+        private renderAlphaTestSorted(subMeshes: SmartArray<SubMesh>): void {
+            return RenderingGroup.renderSorted(subMeshes, this._alphaTestSortCompareFn, this._scene.activeCamera.globalPosition, false);
+        }
+
+        /**
+         * Renders the opaque submeshes in the order from the transparentSortCompareFn.
+         * @param subMeshes The submeshes to render
+         */
+        private renderTransparentSorted(subMeshes: SmartArray<SubMesh>): void {
+            return RenderingGroup.renderSorted(subMeshes, this._transparentSortCompareFn, this._scene.activeCamera.globalPosition, true);
+        }
+
+        /**
+         * Renders the submeshes in a specified order.
+         * @param subMeshes The submeshes to sort before render
+         * @param sortCompareFn The comparison function use to sort
+         * @param cameraPosition The camera position use to preprocess the submeshes to help sorting
+         * @param transparent Specifies to activate blending if true
+         */
+        private static renderSorted(subMeshes: SmartArray<SubMesh>, sortCompareFn: (a: SubMesh, b: SubMesh) => number, cameraPosition: Vector3, transparent: boolean): void {
+            let subIndex = 0;
+            let subMesh;
+            for (; subIndex < subMeshes.length; subIndex++) {
+                subMesh = subMeshes.data[subIndex];
+                subMesh._alphaIndex = subMesh.getMesh().alphaIndex;
+                subMesh._distanceToCamera = subMesh.getBoundingInfo().boundingSphere.centerWorld.subtract(cameraPosition).length();
             }
-            return true;
+
+            let sortedArray = subMeshes.data.slice(0, subMeshes.length);
+            sortedArray.sort(sortCompareFn);
+
+            for (subIndex = 0; subIndex < sortedArray.length; subIndex++) {
+                subMesh = sortedArray[subIndex];
+                subMesh.render(transparent);
+            }
+        }
+
+        /**
+         * Renders the submeshes in the order they were dispatched (no sort applied).
+         * @param subMeshes The submeshes to render
+         */
+        private static renderUnsorted(subMeshes: SmartArray<SubMesh>): void {
+            for (var subIndex = 0; subIndex < subMeshes.length; subIndex++) {
+                let submesh = subMeshes.data[subIndex];
+                submesh.render(false);
+            }
+        }
+
+        /**
+         * Build in function which can be applied to ensure meshes of a special queue (opaque, alpha test, transparent)
+         * are rendered back to front if in the same alpha index.
+         * 
+         * @param a The first submesh
+         * @param b The second submesh
+         * @returns The result of the comparison
+         */
+        public static defaultTransparentSortCompare(a: SubMesh, b:SubMesh) : number {
+            // Alpha index first
+            if (a._alphaIndex > b._alphaIndex) {
+                return 1;
+            }
+            if (a._alphaIndex < b._alphaIndex) {
+                return -1;
+            }
+
+            // Then distance to camera
+            return RenderingGroup.backToFrontSortCompare(a, b);
         }
 
+        /**
+         * Build in function which can be applied to ensure meshes of a special queue (opaque, alpha test, transparent)
+         * are rendered back to front.
+         * 
+         * @param a The first submesh
+         * @param b The second submesh
+         * @returns The result of the comparison
+         */
+        public static backToFrontSortCompare(a: SubMesh, b:SubMesh) : number {
+            // Then distance to camera
+            if (a._distanceToCamera < b._distanceToCamera) {
+                return 1;
+            }
+            if (a._distanceToCamera > b._distanceToCamera) {
+                return -1;
+            }
+
+            return 0;
+        }
+
+        /**
+         * Build in function which can be applied to ensure meshes of a special queue (opaque, alpha test, transparent)
+         * are rendered front to back (prevent overdraw).
+         * 
+         * @param a The first submesh
+         * @param b The second submesh
+         * @returns The result of the comparison
+         */
+        public static frontToBackSortCompare(a: SubMesh, b:SubMesh) : number {
+            // Then distance to camera
+            if (a._distanceToCamera < b._distanceToCamera) {
+                return -1;
+            }
+            if (a._distanceToCamera > b._distanceToCamera) {
+                return 1;
+            }
+
+            return 0;
+        }
 
+        /**
+         * Resets the different lists of submeshes to prepare a new frame.
+         */
         public prepare(): void {
             this._opaqueSubMeshes.reset();
             this._transparentSubMeshes.reset();
             this._alphaTestSubMeshes.reset();
         }
 
+        /**
+         * Inserts the submesh in its correct queue depending on its material.
+         * @param subMesh The submesh to dispatch
+         */
         public dispatch(subMesh: SubMesh): void {
             var material = subMesh.getMaterial();
             var mesh = subMesh.getMesh();

+ 72 - 13
src/Rendering/babylon.renderingManager.ts

@@ -1,18 +1,35 @@
 module BABYLON {
     export class RenderingManager {
+        /**
+         * The max id used for rendering groups (not included)
+         */
         public static MAX_RENDERINGGROUPS = 4;
 
+        /**
+         * The min id used for rendering groups (included)
+         */
+        public static MIN_RENDERINGGROUPS = 0;
+
         private _scene: Scene;
         private _renderingGroups = new Array<RenderingGroup>();
-        private _depthBufferAlreadyCleaned: boolean;
+        private _depthStencilBufferAlreadyCleaned: boolean;
 
         private _currentIndex: number;
         private _currentActiveMeshes: AbstractMesh[];
         private _currentRenderParticles: boolean;
         private _currentRenderSprites: boolean;
 
+        private _autoClearDepthStencil: { [id:number]: boolean } = {};
+        private _customOpaqueSortCompareFn: { [id:number]: (a: SubMesh, b: SubMesh) => number } = {};
+        private _customAlphaTestSortCompareFn: { [id:number]: (a: SubMesh, b: SubMesh) => number } = {};
+        private _customTransparentSortCompareFn: { [id:number]: (a: SubMesh, b: SubMesh) => number } = {};
+
         constructor(scene: Scene) {
             this._scene = scene;
+
+            for (let i = RenderingManager.MIN_RENDERINGGROUPS; i < RenderingManager.MAX_RENDERINGGROUPS; i++) {
+                this._autoClearDepthStencil[i] = true;
+            }
         }
 
         private _renderParticles(index: number, activeMeshes: AbstractMesh[]): void {
@@ -34,7 +51,7 @@
                     continue;
                 }
 
-                this._clearDepthBuffer();
+                this._clearDepthStencilBuffer();
 
                 if (!particleSystem.emitter.position || !activeMeshes || activeMeshes.indexOf(particleSystem.emitter) !== -1) {
                     this._scene._activeParticles.addCount(particleSystem.render(), false);
@@ -55,20 +72,20 @@
                 var spriteManager = this._scene.spriteManagers[id];
 
                 if (spriteManager.renderingGroupId === index && ((activeCamera.layerMask & spriteManager.layerMask) !== 0)) {
-                    this._clearDepthBuffer();
+                    this._clearDepthStencilBuffer();
                     spriteManager.render();
                 }
             }
             this._scene._spritesDuration.endMonitoring(false);
         }
 
-        private _clearDepthBuffer(): void {
-            if (this._depthBufferAlreadyCleaned) {
+        private _clearDepthStencilBuffer(): void {
+            if (this._depthStencilBufferAlreadyCleaned) {
                 return;
             }
 
-            this._scene.getEngine().clear(0, false, true);
-            this._depthBufferAlreadyCleaned = true;
+            this._scene.getEngine().clear(0, false, true, true);
+            this._depthStencilBufferAlreadyCleaned = true;
         }
 
         private _renderSpritesAndParticles() {
@@ -88,15 +105,17 @@
             this._currentRenderParticles = renderParticles;
             this._currentRenderSprites = renderSprites;
 
-            for (var index = 0; index < RenderingManager.MAX_RENDERINGGROUPS; index++) {
-                this._depthBufferAlreadyCleaned = index === 0;
+            for (var index = RenderingManager.MIN_RENDERINGGROUPS; index < RenderingManager.MAX_RENDERINGGROUPS; index++) {
+                this._depthStencilBufferAlreadyCleaned = index === RenderingManager.MIN_RENDERINGGROUPS;
                 var renderingGroup = this._renderingGroups[index];
                 var needToStepBack = false;
 
                 this._currentIndex = index;
 
                 if (renderingGroup) {
-                    this._clearDepthBuffer();
+                    if (this._autoClearDepthStencil[index]) {
+                        this._clearDepthStencilBuffer();
+                    }
 
                     if (!renderingGroup.onBeforeTransparentRendering) {
                         renderingGroup.onBeforeTransparentRendering = this._renderSpritesAndParticles.bind(this);
@@ -118,11 +137,12 @@
         }
 
         public reset(): void {
-            this._renderingGroups.forEach((renderingGroup, index, array) => {
+            for (var index = RenderingManager.MIN_RENDERINGGROUPS; index < RenderingManager.MAX_RENDERINGGROUPS; index++) {
+                var renderingGroup = this._renderingGroups[index];
                 if (renderingGroup) {
                     renderingGroup.prepare();
                 }
-            });
+            }
         }
 
         public dispatch(subMesh: SubMesh): void {
@@ -130,11 +150,50 @@
             var renderingGroupId = mesh.renderingGroupId || 0;
 
             if (!this._renderingGroups[renderingGroupId]) {
-                this._renderingGroups[renderingGroupId] = new RenderingGroup(renderingGroupId, this._scene);
+                this._renderingGroups[renderingGroupId] = new RenderingGroup(renderingGroupId, this._scene,
+                    this._customOpaqueSortCompareFn[renderingGroupId],
+                    this._customAlphaTestSortCompareFn[renderingGroupId],
+                    this._customTransparentSortCompareFn[renderingGroupId]
+                );
             }
 
             this._renderingGroups[renderingGroupId].dispatch(subMesh);
         }
 
+        /**
+         * Overrides the default sort function applied in the renderging group to prepare the meshes.
+         * This allowed control for front to back rendering or reversly depending of the special needs.
+         * 
+         * @param renderingGroupId The rendering group id corresponding to its index
+         * @param opaqueSortCompareFn The opaque queue comparison function use to sort.
+         * @param alphaTestSortCompareFn The alpha test queue comparison function use to sort.
+         * @param transparentSortCompareFn The transparent queue comparison function use to sort.
+         */
+        public setRenderingOrder(renderingGroupId: number,
+            opaqueSortCompareFn: (a: SubMesh, b: SubMesh) => number = null,
+            alphaTestSortCompareFn: (a: SubMesh, b: SubMesh) => number = null,
+            transparentSortCompareFn: (a: SubMesh, b: SubMesh) => number = null) {
+            
+            if (this._renderingGroups[renderingGroupId]) {
+                var group = this._renderingGroups[renderingGroupId];
+                group.opaqueSortCompareFn = this._customOpaqueSortCompareFn[renderingGroupId];
+                group.alphaTestSortCompareFn = this._customAlphaTestSortCompareFn[renderingGroupId];
+                group.transparentSortCompareFn = this._customTransparentSortCompareFn[renderingGroupId];
+            }
+
+            this._customOpaqueSortCompareFn[renderingGroupId] = opaqueSortCompareFn;
+            this._customAlphaTestSortCompareFn[renderingGroupId] = alphaTestSortCompareFn;
+            this._customTransparentSortCompareFn[renderingGroupId] = transparentSortCompareFn;
+        }
+
+        /**
+         * Specifies whether or not the stencil and depth buffer are cleared between two rendering groups.
+         * 
+         * @param renderingGroupId The rendering group id corresponding to its index
+         * @param autoClearDepthStencil Automatically clears depth and stencil between groups if true.
+         */
+        public setRenderingAutoClearDepthStencil(renderingGroupId: number, autoClearDepthStencil: boolean): void {            
+            this._autoClearDepthStencil[renderingGroupId] = autoClearDepthStencil;
+        }
     }
 } 

+ 180 - 0
src/States/babylon.stencilState.js

@@ -0,0 +1,180 @@
+var BABYLON;
+(function (BABYLON) {
+    var Internals;
+    (function (Internals) {
+        var _StencilState = (function () {
+            function _StencilState() {
+                this._isStencilTestDirty = false;
+                this._isStencilMaskDirty = false;
+                this._isStencilFuncDirty = false;
+                this._isStencilOpDirty = false;
+                this.reset();
+            }
+            Object.defineProperty(_StencilState.prototype, "isDirty", {
+                get: function () {
+                    return this._isStencilTestDirty || this._isStencilMaskDirty || this._isStencilFuncDirty || this._isStencilOpDirty;
+                },
+                enumerable: true,
+                configurable: true
+            });
+            Object.defineProperty(_StencilState.prototype, "stencilFunc", {
+                get: function () {
+                    return this._stencilFunc;
+                },
+                set: function (value) {
+                    if (this._stencilFunc === value) {
+                        return;
+                    }
+                    this._stencilFunc = value;
+                    this._isStencilFuncDirty = true;
+                },
+                enumerable: true,
+                configurable: true
+            });
+            Object.defineProperty(_StencilState.prototype, "stencilFuncRef", {
+                get: function () {
+                    return this._stencilFuncRef;
+                },
+                set: function (value) {
+                    if (this._stencilFuncRef === value) {
+                        return;
+                    }
+                    this._stencilFuncRef = value;
+                    this._isStencilFuncDirty = true;
+                },
+                enumerable: true,
+                configurable: true
+            });
+            Object.defineProperty(_StencilState.prototype, "stencilFuncMask", {
+                get: function () {
+                    return this._stencilFuncMask;
+                },
+                set: function (value) {
+                    if (this._stencilFuncMask === value) {
+                        return;
+                    }
+                    this._stencilFuncMask = value;
+                    this._isStencilFuncDirty = true;
+                },
+                enumerable: true,
+                configurable: true
+            });
+            Object.defineProperty(_StencilState.prototype, "stencilOpStencilFail", {
+                get: function () {
+                    return this._stencilOpStencilFail;
+                },
+                set: function (value) {
+                    if (this._stencilOpStencilFail === value) {
+                        return;
+                    }
+                    this._stencilOpStencilFail = value;
+                    this._isStencilOpDirty = true;
+                },
+                enumerable: true,
+                configurable: true
+            });
+            Object.defineProperty(_StencilState.prototype, "stencilOpDepthFail", {
+                get: function () {
+                    return this._stencilOpDepthFail;
+                },
+                set: function (value) {
+                    if (this._stencilOpDepthFail === value) {
+                        return;
+                    }
+                    this._stencilOpDepthFail = value;
+                    this._isStencilOpDirty = true;
+                },
+                enumerable: true,
+                configurable: true
+            });
+            Object.defineProperty(_StencilState.prototype, "stencilOpStencilDepthPass", {
+                get: function () {
+                    return this._stencilOpStencilDepthPass;
+                },
+                set: function (value) {
+                    if (this._stencilOpStencilDepthPass === value) {
+                        return;
+                    }
+                    this._stencilOpStencilDepthPass = value;
+                    this._isStencilOpDirty = true;
+                },
+                enumerable: true,
+                configurable: true
+            });
+            Object.defineProperty(_StencilState.prototype, "stencilMask", {
+                get: function () {
+                    return this._stencilMask;
+                },
+                set: function (value) {
+                    if (this._stencilMask === value) {
+                        return;
+                    }
+                    this._stencilMask = value;
+                    this._isStencilMaskDirty = true;
+                },
+                enumerable: true,
+                configurable: true
+            });
+            Object.defineProperty(_StencilState.prototype, "stencilTest", {
+                get: function () {
+                    return this._stencilTest;
+                },
+                set: function (value) {
+                    if (this._stencilTest === value) {
+                        return;
+                    }
+                    this._stencilTest = value;
+                    this._isStencilTestDirty = true;
+                },
+                enumerable: true,
+                configurable: true
+            });
+            _StencilState.prototype.reset = function () {
+                this._stencilTest = false;
+                this._stencilMask = 0xFF;
+                this._stencilFunc = WebGLRenderingContext.ALWAYS;
+                this._stencilFuncRef = 1;
+                this._stencilFuncMask = 0xFF;
+                this._stencilOpStencilFail = WebGLRenderingContext.KEEP;
+                this._stencilOpDepthFail = WebGLRenderingContext.KEEP;
+                this._stencilOpStencilDepthPass = WebGLRenderingContext.REPLACE;
+                this._isStencilTestDirty = true;
+                this._isStencilMaskDirty = true;
+                this._isStencilFuncDirty = true;
+                this._isStencilOpDirty = true;
+            };
+            _StencilState.prototype.apply = function (gl) {
+                if (!this.isDirty) {
+                    return;
+                }
+                // Stencil test
+                if (this._isStencilTestDirty) {
+                    if (this.stencilTest) {
+                        gl.enable(gl.STENCIL_TEST);
+                    }
+                    else {
+                        gl.disable(gl.STENCIL_TEST);
+                    }
+                    this._isStencilTestDirty = false;
+                }
+                // Stencil mask
+                if (this._isStencilMaskDirty) {
+                    gl.stencilMask(this.stencilMask);
+                    this._isStencilMaskDirty = false;
+                }
+                // Stencil func
+                if (this._isStencilFuncDirty) {
+                    gl.stencilFunc(this.stencilFunc, this.stencilFuncRef, this.stencilFuncMask);
+                    this._isStencilFuncDirty = false;
+                }
+                // Stencil op
+                if (this._isStencilOpDirty) {
+                    gl.stencilOp(this.stencilOpStencilFail, this.stencilOpDepthFail, this.stencilOpStencilDepthPass);
+                    this._isStencilOpDirty = false;
+                }
+            };
+            return _StencilState;
+        }());
+        Internals._StencilState = _StencilState;
+    })(Internals = BABYLON.Internals || (BABYLON.Internals = {}));
+})(BABYLON || (BABYLON = {}));

+ 184 - 0
src/States/babylon.stencilState.ts

@@ -0,0 +1,184 @@
+module BABYLON.Internals {
+    export class _StencilState {
+        private _isStencilTestDirty = false;
+        private _isStencilMaskDirty = false;
+        private _isStencilFuncDirty = false;
+        private _isStencilOpDirty = false;
+
+        private _stencilTest: boolean;
+
+        private _stencilMask: number;
+
+        private _stencilFunc: number;
+        private _stencilFuncRef: number;
+        private _stencilFuncMask: number;
+
+        private _stencilOpStencilFail: number;
+        private _stencilOpDepthFail: number;
+        private _stencilOpStencilDepthPass: number;
+
+        public get isDirty(): boolean {
+            return this._isStencilTestDirty || this._isStencilMaskDirty || this._isStencilFuncDirty || this._isStencilOpDirty;
+        }
+
+        public get stencilFunc(): number {
+            return this._stencilFunc;
+        }
+
+        public set stencilFunc(value: number) {
+            if (this._stencilFunc === value) {
+                return;
+            }
+
+            this._stencilFunc = value;
+            this._isStencilFuncDirty = true;
+        }
+
+        public get stencilFuncRef(): number {
+            return this._stencilFuncRef;
+        }
+
+        public set stencilFuncRef(value: number) {
+            if (this._stencilFuncRef === value) {
+                return;
+            }
+
+            this._stencilFuncRef = value;
+            this._isStencilFuncDirty = true;
+        }
+
+        public get stencilFuncMask(): number {
+            return this._stencilFuncMask;
+        }
+
+        public set stencilFuncMask(value: number) {
+            if (this._stencilFuncMask === value) {
+                return;
+            }
+
+            this._stencilFuncMask = value;
+            this._isStencilFuncDirty = true;
+        }
+
+        public get stencilOpStencilFail(): number {
+            return this._stencilOpStencilFail;
+        }
+
+        public set stencilOpStencilFail(value: number) {
+            if (this._stencilOpStencilFail === value) {
+                return;
+            }
+
+            this._stencilOpStencilFail = value;
+            this._isStencilOpDirty = true;
+        }
+
+        public get stencilOpDepthFail(): number {
+            return this._stencilOpDepthFail;
+        }
+
+        public set stencilOpDepthFail(value: number) {
+            if (this._stencilOpDepthFail === value) {
+                return;
+            }
+
+            this._stencilOpDepthFail = value;
+            this._isStencilOpDirty = true;
+        }
+
+        public get stencilOpStencilDepthPass(): number {
+            return this._stencilOpStencilDepthPass;
+        }
+
+        public set stencilOpStencilDepthPass(value: number) {
+            if (this._stencilOpStencilDepthPass === value) {
+                return;
+            }
+
+            this._stencilOpStencilDepthPass = value;
+            this._isStencilOpDirty = true;
+        }
+
+        public get stencilMask(): number {
+            return this._stencilMask;
+        }
+
+        public set stencilMask(value: number) {
+            if (this._stencilMask === value) {
+                return;
+            }
+
+            this._stencilMask = value;
+            this._isStencilMaskDirty = true;
+        }
+
+        public get stencilTest(): boolean {
+            return this._stencilTest;
+        }
+
+        public set stencilTest(value: boolean) {
+            if (this._stencilTest === value) {
+                return;
+            }
+
+            this._stencilTest = value;
+            this._isStencilTestDirty = true;
+        }
+
+        public constructor() {
+            this.reset();
+        }
+
+        public reset() {
+            this._stencilTest = false;
+            this._stencilMask = 0xFF;
+
+            this._stencilFunc = WebGLRenderingContext.ALWAYS;
+            this._stencilFuncRef = 1;
+            this._stencilFuncMask = 0xFF;
+
+            this._stencilOpStencilFail = WebGLRenderingContext.KEEP;
+            this._stencilOpDepthFail = WebGLRenderingContext.KEEP;
+            this._stencilOpStencilDepthPass = WebGLRenderingContext.REPLACE;
+
+            this._isStencilTestDirty = true;
+            this._isStencilMaskDirty = true;
+            this._isStencilFuncDirty = true;
+            this._isStencilOpDirty = true;
+        }
+
+        public apply(gl: WebGLRenderingContext) {
+            if (!this.isDirty) {
+                return;
+            }
+
+            // Stencil test
+            if (this._isStencilTestDirty) {
+                if (this.stencilTest) {
+                    gl.enable(gl.STENCIL_TEST);
+                } else {
+                    gl.disable(gl.STENCIL_TEST);
+                }
+                this._isStencilTestDirty = false;
+            }
+
+            // Stencil mask
+            if (this._isStencilMaskDirty) {
+                gl.stencilMask(this.stencilMask);
+                this._isStencilMaskDirty = false;
+            }
+
+            // Stencil func
+            if (this._isStencilFuncDirty) {
+                gl.stencilFunc(this.stencilFunc, this.stencilFuncRef, this.stencilFuncMask);
+                this._isStencilFuncDirty = false;
+            }
+
+            // Stencil op
+            if (this._isStencilOpDirty) {
+                gl.stencilOp(this.stencilOpStencilFail, this.stencilOpDepthFail, this.stencilOpStencilDepthPass);
+                this._isStencilOpDirty = false;
+            }
+        }
+    }
+}

+ 150 - 37
src/babylon.engine.ts

@@ -327,6 +327,7 @@
         private _caps: EngineCapabilities;
         private _pointerLockRequested: boolean;
         private _alphaTest: boolean;
+        private _isStencilEnable: boolean; 
 
         private _loadingScreen: ILoadingScreen;
 
@@ -349,6 +350,7 @@
 
         // States
         private _depthCullingState = new Internals._DepthCullingState();
+        private _stencilState = new Internals._StencilState();
         private _alphaState = new Internals._AlphaState();
         private _alphaMode = Engine.ALPHA_DISABLE;
 
@@ -380,7 +382,7 @@
 
         private _externalData: StringDictionary<Object>;
         private _bindedRenderFunction: any;
-
+        
         /**
          * @constructor
          * @param {HTMLCanvasElement} canvas - the canvas to be used for rendering
@@ -445,6 +447,7 @@
             this.resize();
 
             // Caps
+            this._isStencilEnable = options.stencil;
             this._caps = new EngineCapabilities();
             this._caps.maxTexturesImageUnits = this._gl.getParameter(this._gl.MAX_TEXTURE_IMAGE_UNITS);
             this._caps.maxTextureSize = this._gl.getParameter(this._gl.MAX_TEXTURE_SIZE);
@@ -555,6 +558,13 @@
             return this._webGLVersion;
         }
 
+        /**
+         * Returns true if the stencil buffer has been enabled through the creation option of the context.
+         */
+        public get isStencilEnable(): boolean {
+            return this._isStencilEnable;
+        }
+
         private _prepareWorkingCanvas(): void {
             if (this._workingCanvas) {
                 return;
@@ -656,6 +666,70 @@
             this._depthCullingState.depthFunc = this._gl.LEQUAL;
         }
 
+        public getStencilBuffer(): boolean {
+            return this._stencilState.stencilTest;
+        }
+
+        public setStencilBuffer(enable: boolean): void {
+            this._stencilState.stencilTest = enable;
+        }
+
+        public getStencilMask(): number {
+            return this._stencilState.stencilMask;
+        }
+
+        public setStencilMask(mask: number): void {
+            this._stencilState.stencilMask = mask;
+        }
+
+        public getStencilFunction(): number {
+            return this._stencilState.stencilFunc;
+        }
+
+        public getStencilFunctionReference(): number {
+            return this._stencilState.stencilFuncRef;
+        }
+
+        public getStencilFunctionMask(): number {
+            return this._stencilState.stencilFuncMask;
+        }
+
+        public setStencilFunction(stencilFunc: number) {
+            this._stencilState.stencilFunc = stencilFunc;
+        }
+        
+        public setStencilFunctionReference(reference: number) {
+            this._stencilState.stencilFuncRef = reference;
+        }
+        
+        public setStencilFunctionMask(mask: number) {
+            this._stencilState.stencilFuncMask = mask;
+        }
+
+        public getStencilOperationFail(): number {
+            return this._stencilState.stencilOpStencilFail;
+        }
+
+        public getStencilOperationDepthFail(): number {
+            return this._stencilState.stencilOpDepthFail;
+        }
+
+        public getStencilOperationPass(): number {
+            return this._stencilState.stencilOpStencilDepthPass;
+        }
+
+        public setStencilOperationFail(operation: number): void {
+            this._stencilState.stencilOpStencilFail = operation;
+        }
+
+        public setStencilOperationDepthFail(operation: number): void {
+            this._stencilState.stencilOpDepthFail = operation;
+        }
+
+        public setStencilOperationPass(operation: number): void {
+            this._stencilState.stencilOpStencilDepthPass = operation;
+        }
+
         /**
          * stop executing a render loop function and remove it from the execution array
          * @param {Function} [renderFunction] the function to be removed. If not provided all functions will be removed.
@@ -737,26 +811,22 @@
             }
         }
 
-        public clear(color: any, backBuffer: boolean, depthStencil: boolean): void {
+        public clear(color: any, backBuffer: boolean, depth: boolean, stencil:boolean = false): void {
             this.applyStates();
 
+            var mode = 0;            
             if (backBuffer) {
                 this._gl.clearColor(color.r, color.g, color.b, color.a !== undefined ? color.a : 1.0);
-            }
-
-            if (depthStencil && this._depthCullingState.depthMask) {
-                this._gl.clearDepth(1.0);
-            }
-            var mode = 0;
-
-            if (backBuffer) {
                 mode |= this._gl.COLOR_BUFFER_BIT;
             }
-
-            if (depthStencil && this._depthCullingState.depthMask) {
+            if (depth) {
+                this._gl.clearDepth(1.0);
                 mode |= this._gl.DEPTH_BUFFER_BIT;
             }
-
+            if (stencil) {
+                this._gl.clearStencil(0);
+                mode |= this._gl.STENCIL_BUFFER_BIT;
+            }
             this._gl.clear(mode);
         }
 
@@ -772,7 +842,7 @@
             gl.scissor(x, y, width, height);
 
             // Clear
-            this.clear(clearColor, true, true);
+            this.clear(clearColor, true, true, true);
 
             // Restore state
             gl.scissor(curScissorBox[0], curScissorBox[1], curScissorBox[2], curScissorBox[3]);
@@ -866,17 +936,14 @@
 
         public bindFramebuffer(texture: WebGLTexture, faceIndex?: number, requiredWidth?: number, requiredHeight?: number): void {
             this._currentRenderTarget = texture;
-
-            var gl = this._gl;
             this.bindUnboundFramebuffer(texture._framebuffer);
-
+            var gl = this._gl;
             if (texture.isCube) {
+                
                 gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_CUBE_MAP_POSITIVE_X + faceIndex, texture, 0);
-            } else {
-                gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, texture, 0);
-            }
+            } 
 
-            this._gl.viewport(0, 0, requiredWidth || texture._width, requiredHeight || texture._height);
+            gl.viewport(0, 0, requiredWidth || texture._width, requiredHeight || texture._height);
 
             this.wipeCaches();
         }
@@ -1187,6 +1254,7 @@
 
         public applyStates() {
             this._depthCullingState.apply(this._gl);
+            this._stencilState.apply(this._gl);
             this._alphaState.apply(this._gl);
         }
 
@@ -1630,6 +1698,7 @@
             this.resetTextureCache();
             this._currentEffect = null;
 
+            this._stencilState.reset();
             this._depthCullingState.reset();
             this.setDepthFunctionToLessOrEqual();
             this._alphaState.reset();
@@ -1962,11 +2031,15 @@
             // in the same way, generateDepthBuffer is defaulted to true
             var generateMipMaps = false;
             var generateDepthBuffer = true;
+            var generateStencilBuffer = false;
+
             var type = Engine.TEXTURETYPE_UNSIGNED_INT;
             var samplingMode = Texture.TRILINEAR_SAMPLINGMODE;
             if (options !== undefined) {
                 generateMipMaps = options.generateMipMaps === undefined ? options : options.generateMipMaps;
                 generateDepthBuffer = options.generateDepthBuffer === undefined ? true : options.generateDepthBuffer;
+                generateStencilBuffer = generateDepthBuffer && options.generateStencilBuffer;
+
                 type = options.type === undefined ? type : options.type;
                 if (options.samplingMode !== undefined) {
                     samplingMode = options.samplingMode;
@@ -2002,19 +2075,32 @@
 
             gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, width, height, 0, gl.RGBA, getWebGLTextureType(gl, type), null);
 
-            var depthBuffer: WebGLRenderbuffer;
-            // Create the depth buffer
-            if (generateDepthBuffer) {
-                depthBuffer = gl.createRenderbuffer();
-                gl.bindRenderbuffer(gl.RENDERBUFFER, depthBuffer);
+            var depthStencilBuffer: WebGLRenderbuffer;
+
+            // Create the depth/stencil buffer
+            if (generateStencilBuffer) {
+                depthStencilBuffer = gl.createRenderbuffer();
+                gl.bindRenderbuffer(gl.RENDERBUFFER, depthStencilBuffer);
+                gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_STENCIL, width, height);
+            }
+            else if (generateDepthBuffer) {
+                depthStencilBuffer = gl.createRenderbuffer();
+                gl.bindRenderbuffer(gl.RENDERBUFFER, depthStencilBuffer);
                 gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_COMPONENT16, width, height);
             }
+
             // Create the framebuffer
             var framebuffer = gl.createFramebuffer();
             this.bindUnboundFramebuffer(framebuffer);
-            if (generateDepthBuffer) {
-                gl.framebufferRenderbuffer(gl.FRAMEBUFFER, gl.DEPTH_ATTACHMENT, gl.RENDERBUFFER, depthBuffer);
+
+            // Manage attachments
+            if (generateStencilBuffer) {                
+                gl.framebufferRenderbuffer(gl.FRAMEBUFFER, gl.DEPTH_STENCIL_ATTACHMENT, gl.RENDERBUFFER, depthStencilBuffer);
             }
+            else if (generateDepthBuffer) {
+                gl.framebufferRenderbuffer(gl.FRAMEBUFFER, gl.DEPTH_ATTACHMENT, gl.RENDERBUFFER, depthStencilBuffer);
+            }
+            gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, texture, 0);
 
             if (generateMipMaps) {
                 this._gl.generateMipmap(this._gl.TEXTURE_2D);
@@ -2027,7 +2113,7 @@
 
             texture._framebuffer = framebuffer;
             if (generateDepthBuffer) {
-                texture._depthBuffer = depthBuffer;
+                texture._depthBuffer = depthStencilBuffer;
             }
             texture._baseWidth = width;
             texture._baseHeight = height;
@@ -2038,6 +2124,7 @@
             texture.references = 1;
             texture.samplingMode = samplingMode;
             texture.type = type;
+            
             this.resetTextureCache();
 
             this._loadedTexturesCache.push(texture);
@@ -2051,9 +2138,15 @@
             var texture = gl.createTexture();
 
             var generateMipMaps = true;
+            var generateDepthBuffer = true;
+            var generateStencilBuffer = false;
+
             var samplingMode = Texture.TRILINEAR_SAMPLINGMODE;
             if (options !== undefined) {
                 generateMipMaps = options.generateMipMaps === undefined ? options : options.generateMipMaps;
+                generateDepthBuffer = options.generateDepthBuffer === undefined ? true : options.generateDepthBuffer;
+                generateStencilBuffer = generateDepthBuffer && options.generateStencilBuffer;
+
                 if (options.samplingMode !== undefined) {
                     samplingMode = options.samplingMode;
                 }
@@ -2079,14 +2172,31 @@
             gl.texParameteri(gl.TEXTURE_CUBE_MAP, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
 
             // Create the depth buffer
-            var depthBuffer = gl.createRenderbuffer();
-            gl.bindRenderbuffer(gl.RENDERBUFFER, depthBuffer);
-            gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_COMPONENT16, size, size);
+            var depthStencilBuffer: WebGLRenderbuffer;
+
+            // Create the depth/stencil buffer
+            if (generateStencilBuffer) {
+                depthStencilBuffer = gl.createRenderbuffer();
+                gl.bindRenderbuffer(gl.RENDERBUFFER, depthStencilBuffer);
+                gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_STENCIL, size, size);
+            }
+            else if (generateDepthBuffer) {
+                depthStencilBuffer = gl.createRenderbuffer();
+                gl.bindRenderbuffer(gl.RENDERBUFFER, depthStencilBuffer);
+                gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_COMPONENT16, size, size);
+            }
 
             // Create the framebuffer
             var framebuffer = gl.createFramebuffer();
             this.bindUnboundFramebuffer(framebuffer);
-            gl.framebufferRenderbuffer(gl.FRAMEBUFFER, gl.DEPTH_ATTACHMENT, gl.RENDERBUFFER, depthBuffer);
+
+            // Manage attachments
+            if (generateStencilBuffer) {                
+                gl.framebufferRenderbuffer(gl.FRAMEBUFFER, gl.DEPTH_STENCIL_ATTACHMENT, gl.RENDERBUFFER, depthStencilBuffer);
+            }
+            else if (generateDepthBuffer) {
+                gl.framebufferRenderbuffer(gl.FRAMEBUFFER, gl.DEPTH_ATTACHMENT, gl.RENDERBUFFER, depthStencilBuffer);
+            }
 
             // Mipmaps
             if (texture.generateMipMaps) {
@@ -2100,14 +2210,17 @@
             this.bindUnboundFramebuffer(null);
 
             texture._framebuffer = framebuffer;
-            texture._depthBuffer = depthBuffer;
-
-            this.resetTextureCache();
-
+            if (generateDepthBuffer) {
+                texture._depthBuffer = depthStencilBuffer;
+            }
             texture._width = size;
             texture._height = size;
             texture.isReady = true;
 
+            this.resetTextureCache();
+
+            this._loadedTexturesCache.push(texture);
+
             return texture;
         }
 

+ 32 - 2
src/babylon.scene.ts

@@ -2238,7 +2238,7 @@
             }
 
             // Clear
-            this._engine.clear(this.clearColor, this.autoClear || this.forceWireframe || this.forcePointsCloud, true);
+            this._engine.clear(this.clearColor, this.autoClear || this.forceWireframe || this.forcePointsCloud, true, true);
 
             // Shadows
             if (this.shadowsEnabled) {
@@ -2266,7 +2266,7 @@
                 for (var cameraIndex = 0; cameraIndex < this.activeCameras.length; cameraIndex++) {
                     this._renderId = currentRenderId;
                     if (cameraIndex > 0) {
-                        this._engine.clear(0, false, true);
+                        this._engine.clear(0, false, true, true);
                     }
 
                     this._processSubCameras(this.activeCameras[cameraIndex]);
@@ -2921,5 +2921,35 @@
         public getMaterialByTags(tagsQuery: string, forEach?: (material: Material) => void): Material[] {
             return this._getByTags(this.materials, tagsQuery, forEach).concat(this._getByTags(this.multiMaterials, tagsQuery, forEach));
         }
+
+        /**
+         * Overrides the default sort function applied in the renderging group to prepare the meshes.
+         * This allowed control for front to back rendering or reversly depending of the special needs.
+         * 
+         * @param renderingGroupId The rendering group id corresponding to its index
+         * @param opaqueSortCompareFn The opaque queue comparison function use to sort.
+         * @param alphaTestSortCompareFn The alpha test queue comparison function use to sort.
+         * @param transparentSortCompareFn The transparent queue comparison function use to sort.
+         */
+        public setRenderingOrder(renderingGroupId: number,
+            opaqueSortCompareFn: (a: SubMesh, b: SubMesh) => number = null,
+            alphaTestSortCompareFn: (a: SubMesh, b: SubMesh) => number = null,
+            transparentSortCompareFn: (a: SubMesh, b: SubMesh) => number = null): void {
+            
+            this._renderingManager.setRenderingOrder(renderingGroupId,
+                opaqueSortCompareFn,
+                alphaTestSortCompareFn,
+                transparentSortCompareFn);
+        }
+
+        /**
+         * Specifies whether or not the stencil and depth buffer are cleared between two rendering groups.
+         * 
+         * @param renderingGroupId The rendering group id corresponding to its index
+         * @param autoClearDepthStencil Automatically clears depth and stencil between groups if true.
+         */
+        public setRenderingAutoClearDepthStencil(renderingGroupId: number, autoClearDepthStencil: boolean): void {            
+            this._renderingManager.setRenderingAutoClearDepthStencil(renderingGroupId, autoClearDepthStencil);
+        }
     }
 }