Explorar o código

Merge pull request #1368 from sebavan/HighlightLayer

Highlight Layer First step
David Catuhe %!s(int64=9) %!d(string=hai) anos
pai
achega
a27c582619

+ 2 - 1
Tools/Gulp/config.json

@@ -125,7 +125,8 @@
       "../../src/Tools/babylon.loadingScreen.js",
       "../../src/Audio/babylon.audioEngine.js",
       "../../src/Audio/babylon.sound.js",
-      "../../src/Audio/babylon.soundtrack.js"
+      "../../src/Audio/babylon.soundtrack.js",
+      "../../src/Layer/babylon.highlightlayer.js"
     ]
   },
   "shadersDirectories": [

+ 480 - 0
src/Layer/babylon.highlightlayer.js

@@ -0,0 +1,480 @@
+var __extends = (this && this.__extends) || function (d, b) {
+    for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p];
+    function __() { this.constructor = d; }
+    d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
+};
+var BABYLON;
+(function (BABYLON) {
+    /**
+     * Special Glow Blur post process only blurring the alpha channel
+     * It enforces keeping the most luminous color in the color channel.
+     */
+    var GlowBlurPostProcess = (function (_super) {
+        __extends(GlowBlurPostProcess, _super);
+        function GlowBlurPostProcess(name, direction, blurWidth, options, camera, samplingMode, engine, reusable) {
+            var _this = this;
+            if (samplingMode === void 0) { samplingMode = BABYLON.Texture.BILINEAR_SAMPLINGMODE; }
+            _super.call(this, name, "glowBlurPostProcess", ["screenSize", "direction", "blurWidth"], null, options, camera, samplingMode, engine, reusable);
+            this.direction = direction;
+            this.blurWidth = blurWidth;
+            this.onApplyObservable.add(function (effect) {
+                effect.setFloat2("screenSize", _this.width, _this.height);
+                effect.setVector2("direction", _this.direction);
+                effect.setFloat("blurWidth", _this.blurWidth);
+            });
+        }
+        return GlowBlurPostProcess;
+    }(BABYLON.PostProcess));
+    /**
+     * The highlight layer Helps adding a glow effect around a mesh.
+     *
+     * Once instantiated in a scene, simply use the pushMesh or removeMesh method to add or remove
+     * glowy meshes to your scene.
+     *
+     * !!! THIS REQUIRES AN ACTIVE STENCIL BUFFER ON THE CANVAS !!!
+     */
+    var HighlightLayer = (function () {
+        /**
+         * Instantiates a new highlight Layer and references it to the scene..
+         * @param name The name of the layer
+         * @param scene The scene to use the layer in
+         * @param options Sets of none mandatory options to use with the layer (see IHighlightLayerOptions for more information)
+         */
+        function HighlightLayer(name, scene, options) {
+            this._vertexBuffers = {};
+            this._mainTextureDesiredSize = { width: 0, height: 0 };
+            this._meshes = {};
+            this._maxSize = 0;
+            this._shouldRender = false;
+            /**
+             * Specifies whether or not the inner glow is ACTIVE in the layer.
+             */
+            this.innerGlow = true;
+            /**
+             * Specifies whether or not the outer glow is ACTIVE in the layer.
+             */
+            this.outerGlow = true;
+            /**
+             * An event triggered when the highlight layer has been disposed.
+             * @type {BABYLON.Observable}
+             */
+            this.onDisposeObservable = new BABYLON.Observable();
+            /**
+             * An event triggered when the highlight layer is about rendering the main texture with the glowy parts.
+             * @type {BABYLON.Observable}
+             */
+            this.onBeforeRenderMainTextureObservable = new BABYLON.Observable();
+            /**
+             * An event triggered when the highlight layer is being blurred.
+             * @type {BABYLON.Observable}
+             */
+            this.onBeforeBlurObservable = new BABYLON.Observable();
+            /**
+             * An event triggered when the highlight layer has been blurred.
+             * @type {BABYLON.Observable}
+             */
+            this.onAfterBlurObservable = new BABYLON.Observable();
+            /**
+             * An event triggered when the glowing blurred texture is being merged in the scene.
+             * @type {BABYLON.Observable}
+             */
+            this.onBeforeComposeObservable = new BABYLON.Observable();
+            /**
+             * An event triggered when the glowing blurred texture has been merged in the scene.
+             * @type {BABYLON.Observable}
+             */
+            this.onAfterComposeObservable = new BABYLON.Observable();
+            /**
+             * An event triggered when the highlight layer changes its size.
+             * @type {BABYLON.Observable}
+             */
+            this.onSizeChangedObservable = new BABYLON.Observable();
+            this._scene = scene;
+            var engine = scene.getEngine();
+            this._engine = engine;
+            this._maxSize = this._engine.getCaps().maxTextureSize;
+            this._scene.highlightLayer = this;
+            // Warn on stencil.
+            if (!this._engine.isStencilEnable) {
+                BABYLON.Tools.Warn("Rendering the Highlight Layer requires the stencil to be active on the canvas. var engine = new BABYLON.Engine(canvas, antialias, { stencil: true }");
+            }
+            // Adapt options
+            this._options = options || {
+                mainTextureRatio: 0.25,
+                blurTextureSizeRatio: 0.5,
+                blurHorizontalSize: 1,
+                blurVerticalSize: 1,
+                alphaBlendingMode: BABYLON.Engine.ALPHA_COMBINE
+            };
+            this._options.mainTextureRatio = this._options.mainTextureRatio || 0.25;
+            this._options.blurTextureSizeRatio = this._options.blurTextureSizeRatio || 0.5;
+            this._options.blurHorizontalSize = this._options.blurHorizontalSize || 1;
+            this._options.blurVerticalSize = this._options.blurVerticalSize || 1;
+            this._options.alphaBlendingMode = this._options.alphaBlendingMode || BABYLON.Engine.ALPHA_COMBINE;
+            // VBO
+            var vertices = [];
+            vertices.push(1, 1);
+            vertices.push(-1, 1);
+            vertices.push(-1, -1);
+            vertices.push(1, -1);
+            var vertexBuffer = new BABYLON.VertexBuffer(engine, vertices, BABYLON.VertexBuffer.PositionKind, false, false, 2);
+            this._vertexBuffers[BABYLON.VertexBuffer.PositionKind] = vertexBuffer;
+            // Indices
+            var indices = [];
+            indices.push(0);
+            indices.push(1);
+            indices.push(2);
+            indices.push(0);
+            indices.push(2);
+            indices.push(3);
+            this._indexBuffer = engine.createIndexBuffer(indices);
+            // Effect
+            this._glowMapMergeEffect = engine.createEffect("glowMapMerge", [BABYLON.VertexBuffer.PositionKind], ["offset"], ["textureSampler"], "");
+            // Render target
+            this.setMainTextureSize();
+            // Create Textures and post processes
+            this.createTextureAndPostProcesses();
+        }
+        /**
+         * Creates the render target textures and post processes used in the highlight layer.
+         */
+        HighlightLayer.prototype.createTextureAndPostProcesses = function () {
+            var _this = this;
+            var blurTextureWidth = this._mainTextureDesiredSize.width * this._options.blurTextureSizeRatio;
+            var blurTextureHeight = this._mainTextureDesiredSize.height * this._options.blurTextureSizeRatio;
+            blurTextureWidth = BABYLON.Tools.GetExponentOfTwo(blurTextureWidth, this._maxSize);
+            blurTextureHeight = BABYLON.Tools.GetExponentOfTwo(blurTextureHeight, this._maxSize);
+            this._mainTexture = new BABYLON.RenderTargetTexture("HighlightLayerMainRTT", {
+                width: this._mainTextureDesiredSize.width,
+                height: this._mainTextureDesiredSize.height
+            }, this._scene, false, true, BABYLON.Engine.TEXTURETYPE_UNSIGNED_INT);
+            this._mainTexture.wrapU = BABYLON.Texture.CLAMP_ADDRESSMODE;
+            this._mainTexture.wrapV = BABYLON.Texture.CLAMP_ADDRESSMODE;
+            this._mainTexture.anisotropicFilteringLevel = 1;
+            this._mainTexture.updateSamplingMode(BABYLON.Texture.BILINEAR_SAMPLINGMODE);
+            this._mainTexture.renderParticles = false;
+            this._mainTexture.renderList = null;
+            this._blurTexture = new BABYLON.RenderTargetTexture("HighlightLayerBlurRTT", {
+                width: blurTextureWidth,
+                height: blurTextureHeight
+            }, this._scene, false, true, BABYLON.Engine.TEXTURETYPE_UNSIGNED_INT);
+            this._blurTexture.wrapU = BABYLON.Texture.CLAMP_ADDRESSMODE;
+            this._blurTexture.wrapV = BABYLON.Texture.CLAMP_ADDRESSMODE;
+            this._blurTexture.anisotropicFilteringLevel = 16;
+            this._blurTexture.updateSamplingMode(BABYLON.Texture.TRILINEAR_SAMPLINGMODE);
+            this._blurTexture.renderParticles = false;
+            this._downSamplePostprocess = new BABYLON.PassPostProcess("HighlightLayerPPP", this._options.blurTextureSizeRatio, null, BABYLON.Texture.BILINEAR_SAMPLINGMODE, this._scene.getEngine());
+            this._downSamplePostprocess.onApplyObservable.add(function (effect) {
+                effect.setTexture("textureSampler", _this._mainTexture);
+            });
+            this._horizontalBlurPostprocess = new GlowBlurPostProcess("HighlightLayerHBP", new BABYLON.Vector2(1.0, 0), this._options.blurHorizontalSize, 1, null, BABYLON.Texture.BILINEAR_SAMPLINGMODE, this._scene.getEngine());
+            this._horizontalBlurPostprocess.onApplyObservable.add(function (effect) {
+                effect.setFloat2("screenSize", blurTextureWidth, blurTextureHeight);
+            });
+            this._verticalBlurPostprocess = new GlowBlurPostProcess("HighlightLayerVBP", new BABYLON.Vector2(0, 1.0), this._options.blurVerticalSize, 1, null, BABYLON.Texture.BILINEAR_SAMPLINGMODE, this._scene.getEngine());
+            this._verticalBlurPostprocess.onApplyObservable.add(function (effect) {
+                effect.setFloat2("screenSize", blurTextureWidth, blurTextureHeight);
+            });
+            this._mainTexture.onAfterUnbindObservable.add(function () {
+                _this.onBeforeBlurObservable.notifyObservers(_this);
+                _this._scene.postProcessManager.directRender([_this._downSamplePostprocess, _this._horizontalBlurPostprocess, _this._verticalBlurPostprocess], _this._blurTexture.getInternalTexture());
+                _this.onAfterBlurObservable.notifyObservers(_this);
+            });
+            // Custom render function
+            var renderSubMesh = function (subMesh) {
+                var mesh = subMesh.getRenderingMesh();
+                var scene = _this._scene;
+                var engine = scene.getEngine();
+                // Culling
+                engine.setState(subMesh.getMaterial().backFaceCulling);
+                // Managing instances
+                var batch = mesh._getInstancesRenderList(subMesh._id);
+                if (batch.mustReturn) {
+                    return;
+                }
+                var hardwareInstancedRendering = (engine.getCaps().instancedArrays !== null) && (batch.visibleInstances[subMesh._id] !== null) && (batch.visibleInstances[subMesh._id] !== undefined);
+                if (_this.isReady(subMesh, hardwareInstancedRendering)) {
+                    engine.enableEffect(_this._glowMapGenerationEffect);
+                    mesh._bind(subMesh, _this._glowMapGenerationEffect, BABYLON.Material.TriangleFillMode);
+                    var material = subMesh.getMaterial();
+                    _this._glowMapGenerationEffect.setMatrix("viewProjection", scene.getTransformMatrix());
+                    var highlightLayerMesh = _this._meshes[mesh.id];
+                    if (highlightLayerMesh) {
+                        _this._glowMapGenerationEffect.setFloat4("color", highlightLayerMesh.color.r, highlightLayerMesh.color.g, highlightLayerMesh.color.b, 1.0);
+                    }
+                    else {
+                        _this._glowMapGenerationEffect.setFloat4("color", HighlightLayer.neutralColor.r, HighlightLayer.neutralColor.g, HighlightLayer.neutralColor.b, HighlightLayer.neutralColor.a);
+                    }
+                    // Alpha test
+                    if (material && material.needAlphaTesting()) {
+                        var alphaTexture = material.getAlphaTestTexture();
+                        _this._glowMapGenerationEffect.setTexture("diffuseSampler", alphaTexture);
+                        _this._glowMapGenerationEffect.setMatrix("diffuseMatrix", alphaTexture.getTextureMatrix());
+                    }
+                    // Bones
+                    if (mesh.useBones && mesh.computeBonesUsingShaders) {
+                        _this._glowMapGenerationEffect.setMatrices("mBones", mesh.skeleton.getTransformMatrices(mesh));
+                    }
+                    // Draw
+                    mesh._processRendering(subMesh, _this._glowMapGenerationEffect, BABYLON.Material.TriangleFillMode, batch, hardwareInstancedRendering, function (isInstance, world) { return _this._glowMapGenerationEffect.setMatrix("world", world); });
+                }
+                else {
+                    // Need to reset refresh rate of the shadowMap
+                    _this._mainTexture.resetRefreshCounter();
+                }
+            };
+            this._mainTexture.customRenderFunction = function (opaqueSubMeshes, alphaTestSubMeshes, transparentSubMeshes) {
+                _this.onBeforeRenderMainTextureObservable.notifyObservers(_this);
+                var index;
+                for (index = 0; index < opaqueSubMeshes.length; index++) {
+                    renderSubMesh(opaqueSubMeshes.data[index]);
+                }
+                for (index = 0; index < alphaTestSubMeshes.length; index++) {
+                    renderSubMesh(alphaTestSubMeshes.data[index]);
+                }
+                for (index = 0; index < transparentSubMeshes.length; index++) {
+                    renderSubMesh(transparentSubMeshes.data[index]);
+                }
+            };
+            this._mainTexture.onClearObservable.add(function (engine) {
+                engine.clear(HighlightLayer.neutralColor, true, true, true);
+            });
+        };
+        /**
+         * Checks for the readiness of the element composing the layer.
+         * @param subMesh the mesh to check for
+         * @param useInstances specify wether or not to use instances to render the mesh
+         * @return true if ready otherwise, false
+         */
+        HighlightLayer.prototype.isReady = function (subMesh, useInstances) {
+            var defines = [];
+            var attribs = [BABYLON.VertexBuffer.PositionKind];
+            var mesh = subMesh.getMesh();
+            var material = subMesh.getMaterial();
+            // Alpha test
+            if (material && material.needAlphaTesting()) {
+                defines.push("#define ALPHATEST");
+                if (mesh.isVerticesDataPresent(BABYLON.VertexBuffer.UVKind)) {
+                    attribs.push(BABYLON.VertexBuffer.UVKind);
+                    defines.push("#define UV1");
+                }
+                if (mesh.isVerticesDataPresent(BABYLON.VertexBuffer.UV2Kind)) {
+                    var alphaTexture = material.getAlphaTestTexture();
+                    if (alphaTexture.coordinatesIndex === 1) {
+                        attribs.push(BABYLON.VertexBuffer.UV2Kind);
+                        defines.push("#define UV2");
+                    }
+                }
+            }
+            // Bones
+            if (mesh.useBones && mesh.computeBonesUsingShaders) {
+                attribs.push(BABYLON.VertexBuffer.MatricesIndicesKind);
+                attribs.push(BABYLON.VertexBuffer.MatricesWeightsKind);
+                if (mesh.numBoneInfluencers > 4) {
+                    attribs.push(BABYLON.VertexBuffer.MatricesIndicesExtraKind);
+                    attribs.push(BABYLON.VertexBuffer.MatricesWeightsExtraKind);
+                }
+                defines.push("#define NUM_BONE_INFLUENCERS " + mesh.numBoneInfluencers);
+                defines.push("#define BonesPerMesh " + (mesh.skeleton.bones.length + 1));
+            }
+            else {
+                defines.push("#define NUM_BONE_INFLUENCERS 0");
+            }
+            // Instances
+            if (useInstances) {
+                defines.push("#define INSTANCES");
+                attribs.push("world0");
+                attribs.push("world1");
+                attribs.push("world2");
+                attribs.push("world3");
+            }
+            // Get correct effect      
+            var join = defines.join("\n");
+            if (this._cachedDefines !== join) {
+                this._cachedDefines = join;
+                this._glowMapGenerationEffect = this._scene.getEngine().createEffect("glowMapGeneration", attribs, ["world", "mBones", "viewProjection", "diffuseMatrix", "color"], ["diffuseSampler"], join);
+            }
+            return this._glowMapGenerationEffect.isReady();
+        };
+        /**
+         * Renders the glowing part of the scene by blending the blurred glowing meshes on top of the rendered scene.
+         */
+        HighlightLayer.prototype.render = function () {
+            var currentEffect = this._glowMapMergeEffect;
+            // Check
+            if (!currentEffect.isReady() || !this._blurTexture.isReady())
+                return;
+            var engine = this._scene.getEngine();
+            this.onBeforeComposeObservable.notifyObservers(this);
+            // Render
+            engine.enableEffect(currentEffect);
+            engine.setState(false);
+            // Cache
+            var previousStencilBuffer = engine.getStencilBuffer();
+            var previousStencilFunction = engine.getStencilFunction();
+            var previousStencilMask = engine.getStencilMask();
+            var previousAlphaMode = engine.getAlphaMode();
+            // Texture
+            currentEffect.setTexture("textureSampler", this._blurTexture);
+            // VBOs
+            engine.bindBuffers(this._vertexBuffers, this._indexBuffer, currentEffect);
+            // Draw order
+            engine.setAlphaMode(this._options.alphaBlendingMode);
+            engine.setStencilMask(0x00);
+            engine.setStencilBuffer(true);
+            engine.setStencilFunctionReference(HighlightLayer.glowingMeshStencilReference);
+            if (this.outerGlow) {
+                currentEffect.setFloat("offset", 0);
+                engine.setStencilFunction(WebGLRenderingContext.NOTEQUAL);
+                engine.draw(true, 0, 6);
+            }
+            if (this.innerGlow) {
+                currentEffect.setFloat("offset", 1);
+                engine.setStencilFunction(WebGLRenderingContext.EQUAL);
+                engine.draw(true, 0, 6);
+            }
+            // Restore Cache
+            engine.setStencilFunction(previousStencilFunction);
+            engine.setStencilMask(previousStencilMask);
+            engine.setAlphaMode(previousAlphaMode);
+            engine.setStencilBuffer(previousStencilBuffer);
+            this.onAfterComposeObservable.notifyObservers(this);
+            // Handle size changes.
+            var size = this._mainTexture.getSize();
+            this.setMainTextureSize();
+            if (size.width !== this._mainTextureDesiredSize.width || size.height !== this._mainTextureDesiredSize.height) {
+                // Recreate RTT and post processes on size change.
+                this.onSizeChangedObservable.notifyObservers(this);
+                this.disposeTextureAndPostProcesses();
+                this.createTextureAndPostProcesses();
+            }
+        };
+        /**
+         * Add a mesh in the highlight layer in order to make it glow with the chosen color.
+         * @param mesh The mesh to highlight
+         * @param color The color of the highlight
+         */
+        HighlightLayer.prototype.pushMesh = function (mesh, color) {
+            var meshHighlight = this._meshes[mesh.id];
+            if (meshHighlight) {
+                meshHighlight.color = color;
+            }
+            else {
+                this._meshes[mesh.id] = {
+                    mesh: mesh,
+                    color: color,
+                    observerHighlight: mesh.onBeforeRenderObservable.add(this.highligtStencilReference),
+                    observerDefault: mesh.onAfterRenderObservable.add(this.defaultStencilReference)
+                };
+            }
+            this._shouldRender = true;
+        };
+        /**
+         * Remove a mesh from the highlight layer in order to make it stop glowing.
+         * @param mesh The mesh to highlight
+         */
+        HighlightLayer.prototype.removeMesh = function (mesh) {
+            var meshHighlight = this._meshes[mesh.id];
+            if (meshHighlight) {
+                mesh.onBeforeRenderObservable.remove(meshHighlight.observerHighlight);
+                mesh.onAfterRenderObservable.remove(meshHighlight.observerDefault);
+            }
+            this._meshes[mesh.id] = undefined;
+            this._shouldRender = false;
+            for (var meshHighlightToCheck in this._meshes) {
+                if (meshHighlightToCheck) {
+                    this._shouldRender = true;
+                    break;
+                }
+            }
+        };
+        /**
+         * Returns true if the layer contains information to display, otherwise false.
+         */
+        HighlightLayer.prototype.shouldRender = function () {
+            return this._shouldRender;
+        };
+        /**
+         * Sets the main texture desired size which is the closest power of two
+         * of the engine canvas size.
+         */
+        HighlightLayer.prototype.setMainTextureSize = function () {
+            this._mainTextureDesiredSize.width = this._engine.getRenderingCanvas().width * this._options.mainTextureRatio;
+            this._mainTextureDesiredSize.height = this._engine.getRenderingCanvas().height * this._options.mainTextureRatio;
+            this._mainTextureDesiredSize.width = BABYLON.Tools.GetExponentOfTwo(this._mainTextureDesiredSize.width, this._maxSize);
+            this._mainTextureDesiredSize.height = BABYLON.Tools.GetExponentOfTwo(this._mainTextureDesiredSize.height, this._maxSize);
+        };
+        /**
+         * Force the stencil to the glowing expected value for glowing parts
+         */
+        HighlightLayer.prototype.highligtStencilReference = function (mesh) {
+            mesh.getScene().getEngine().setStencilFunctionReference(HighlightLayer.glowingMeshStencilReference);
+        };
+        /**
+         * Force the stencil to the normal expected value for none glowing parts
+         */
+        HighlightLayer.prototype.defaultStencilReference = function (mesh) {
+            mesh.getScene().getEngine().setStencilFunctionReference(HighlightLayer.normalMeshStencilReference);
+        };
+        /**
+         * Dispose only the render target textures and post process.
+         */
+        HighlightLayer.prototype.disposeTextureAndPostProcesses = function () {
+            this._blurTexture.dispose();
+            this._mainTexture.dispose();
+            this._downSamplePostprocess.dispose();
+            this._horizontalBlurPostprocess.dispose();
+            this._verticalBlurPostprocess.dispose();
+        };
+        /**
+         * Dispose the highlight layer and free resources.
+         */
+        HighlightLayer.prototype.dispose = function () {
+            var vertexBuffer = this._vertexBuffers[BABYLON.VertexBuffer.PositionKind];
+            if (vertexBuffer) {
+                vertexBuffer.dispose();
+                this._vertexBuffers[BABYLON.VertexBuffer.PositionKind] = null;
+            }
+            if (this._indexBuffer) {
+                this._scene.getEngine()._releaseBuffer(this._indexBuffer);
+                this._indexBuffer = null;
+            }
+            // Clean textures and post processes
+            this.disposeTextureAndPostProcesses();
+            // Clean mesh references 
+            for (var id in this._meshes) {
+                var meshHighlight = this._meshes[id];
+                if (meshHighlight && meshHighlight.mesh) {
+                    meshHighlight.mesh.onBeforeRenderObservable.remove(meshHighlight.observerHighlight);
+                    meshHighlight.mesh.onAfterRenderObservable.remove(meshHighlight.observerDefault);
+                }
+            }
+            this._meshes = null;
+            // Remove from scene
+            this._scene.highlightLayer = null;
+            // Callback
+            this.onDisposeObservable.notifyObservers(this);
+            this.onDisposeObservable.clear();
+            this.onBeforeRenderMainTextureObservable.clear();
+            this.onBeforeBlurObservable.clear();
+            this.onBeforeComposeObservable.clear();
+            this.onAfterComposeObservable.clear();
+            this.onSizeChangedObservable.clear();
+        };
+        /**
+         * The neutral color used during the preparation of the glow effect.
+         * This is black by default as the blend operation is a blend operation.
+         */
+        HighlightLayer.neutralColor = new BABYLON.Color4(0, 0, 0, 0);
+        /**
+         * Stencil value used for glowing meshes.
+         */
+        HighlightLayer.glowingMeshStencilReference = 0x02;
+        /**
+         * Stencil value used for the other meshes in the scene.
+         */
+        HighlightLayer.normalMeshStencilReference = 0x01;
+        return HighlightLayer;
+    }());
+    BABYLON.HighlightLayer = HighlightLayer;
+})(BABYLON || (BABYLON = {}));

+ 652 - 0
src/Layer/babylon.highlightlayer.ts

@@ -0,0 +1,652 @@
+module BABYLON {
+    /**
+     * Special Glow Blur post process only blurring the alpha channel
+     * It enforces keeping the most luminous color in the color channel.
+     */
+    class GlowBlurPostProcess extends PostProcess {
+        constructor(name: string, public direction: Vector2, public blurWidth: number, options: number | PostProcessOptions, camera: Camera, samplingMode: number = Texture.BILINEAR_SAMPLINGMODE, engine?: Engine, reusable?: boolean) {
+            super(name, "glowBlurPostProcess", ["screenSize", "direction", "blurWidth"], null, options, camera, samplingMode, engine, reusable);
+            this.onApplyObservable.add((effect: Effect) => {
+                effect.setFloat2("screenSize", this.width, this.height);
+                effect.setVector2("direction", this.direction);
+                effect.setFloat("blurWidth", this.blurWidth);
+            });
+        }
+    }
+
+    /**
+     * Highlight layer options. This helps customizing the behaviour
+     * of the highlight layer.
+     */
+    export interface IHighlightLayerOptions {
+        /**
+         * Multiplication factor apply to the canvas size to compute the render target size
+         * used to generated the glowing objects (the smaller the faster).
+         */
+        mainTextureRatio?: number;
+
+        /**
+         * Multiplication factor apply to the main texture size in the first step of the blur to reduce the size 
+         * of the picture to blur (the smaller the faster).
+         */
+        blurTextureSizeRatio?: number;
+
+        /**
+         * How big in texel of the blur texture is the vertical blur.
+         */
+        blurVerticalSize?: number;
+
+        /**
+         * How big in texel of the blur texture is the horizontal blur.
+         */
+        blurHorizontalSize?: number;
+
+        /**
+         * Alpha blending mode used to apply the blur. Default is combine.
+         */
+        alphaBlendingMode?: number
+    }
+
+    /**
+     * Storage interface grouping all the information required for glowing a mesh.
+     */
+    interface IHighlightLayerMesh {
+        /** 
+         * The glowy mesh
+         */
+        mesh: Mesh;
+        /**
+         * The color of the glow
+         */
+        color: Color3;
+        /**
+         * The mesh render callback use to insert stencil information
+         */
+        observerHighlight: Observer<Mesh>;
+        /**
+         * The mesh render callback use to come to the default behavior
+         */
+        observerDefault: Observer<Mesh>;
+
+        // TODO.
+        // glowEmissiveOnly: boolean = false;
+    }
+
+    /**
+     * The highlight layer Helps adding a glow effect around a mesh.
+     * 
+     * Once instantiated in a scene, simply use the pushMesh or removeMesh method to add or remove
+     * glowy meshes to your scene.
+     * 
+     * !!! THIS REQUIRES AN ACTIVE STENCIL BUFFER ON THE CANVAS !!!
+     */
+    export class HighlightLayer {
+        /**
+         * The neutral color used during the preparation of the glow effect.
+         * This is black by default as the blend operation is a blend operation. 
+         */
+        public static neutralColor:Color4 = new Color4(0, 0, 0, 0);
+
+        /**
+         * Stencil value used for glowing meshes.
+         */
+        public static glowingMeshStencilReference = 0x02;
+
+        /**
+         * Stencil value used for the other meshes in the scene.
+         */
+        public static normalMeshStencilReference = 0x01;
+
+        private _scene: Scene;
+        private _engine: Engine;
+        private _options: IHighlightLayerOptions;
+        private _vertexBuffers: { [key: string]: VertexBuffer } = {};
+        private _indexBuffer: WebGLBuffer;
+        private _downSamplePostprocess: PassPostProcess;
+        private _horizontalBlurPostprocess: GlowBlurPostProcess;
+        private _verticalBlurPostprocess: GlowBlurPostProcess;
+        private _cachedDefines: string;
+        private _glowMapGenerationEffect: Effect;
+        private _glowMapMergeEffect: Effect;      
+        private _blurTexture: RenderTargetTexture;
+        private _mainTexture: RenderTargetTexture;
+        private _mainTextureDesiredSize: ISize = { width:0, height:0 };
+        private _meshes: {[id: string]: IHighlightLayerMesh} = {};
+        private _maxSize:number = 0;
+        private _shouldRender = false;
+        private _instanceGlowingMeshStencilReference = HighlightLayer.glowingMeshStencilReference++;
+
+        /**
+         * Specifies whether or not the inner glow is ACTIVE in the layer.
+         */
+        public innerGlow: boolean = true;
+        
+        /**
+         * Specifies whether or not the outer glow is ACTIVE in the layer.
+         */
+        public outerGlow: boolean = true;
+
+        /**
+         * An event triggered when the highlight layer has been disposed.
+         * @type {BABYLON.Observable}
+         */
+        public onDisposeObservable = new Observable<HighlightLayer>();
+
+        /**
+         * An event triggered when the highlight layer is about rendering the main texture with the glowy parts.
+         * @type {BABYLON.Observable}
+         */
+        public onBeforeRenderMainTextureObservable = new Observable<HighlightLayer>();
+
+        /**
+         * An event triggered when the highlight layer is being blurred.
+         * @type {BABYLON.Observable}
+         */
+        public onBeforeBlurObservable = new Observable<HighlightLayer>();
+        
+        /**
+         * An event triggered when the highlight layer has been blurred.
+         * @type {BABYLON.Observable}
+         */
+        public onAfterBlurObservable = new Observable<HighlightLayer>();
+
+        /**
+         * An event triggered when the glowing blurred texture is being merged in the scene.
+         * @type {BABYLON.Observable}
+         */
+        public onBeforeComposeObservable = new Observable<HighlightLayer>();
+
+        /**
+         * An event triggered when the glowing blurred texture has been merged in the scene.
+         * @type {BABYLON.Observable}
+         */
+        public onAfterComposeObservable = new Observable<HighlightLayer>();
+
+        /**
+         * An event triggered when the highlight layer changes its size.
+         * @type {BABYLON.Observable}
+         */
+        public onSizeChangedObservable = new Observable<HighlightLayer>();
+
+        /**
+         * Instantiates a new highlight Layer and references it to the scene..
+         * @param name The name of the layer
+         * @param scene The scene to use the layer in
+         * @param options Sets of none mandatory options to use with the layer (see IHighlightLayerOptions for more information)
+         */
+        constructor(name: string, scene: Scene, options?: IHighlightLayerOptions) {
+            this._scene = scene;
+            var engine = scene.getEngine();
+            this._engine = engine;
+            this._maxSize = this._engine.getCaps().maxTextureSize;
+            this._scene.highlightLayers.push(this);
+
+            // Warn on stencil.
+            if (!this._engine.isStencilEnable) {
+                Tools.Warn("Rendering the Highlight Layer requires the stencil to be active on the canvas. var engine = new BABYLON.Engine(canvas, antialias, { stencil: true }");
+            }
+
+            // Adapt options
+            this._options = options || {
+                mainTextureRatio: 0.25,
+                blurTextureSizeRatio: 0.5,
+                blurHorizontalSize: 1,
+                blurVerticalSize: 1,
+                alphaBlendingMode: Engine.ALPHA_COMBINE
+            };
+            this._options.mainTextureRatio = this._options.mainTextureRatio || 0.25; 
+            this._options.blurTextureSizeRatio = this._options.blurTextureSizeRatio || 0.5;
+            this._options.blurHorizontalSize = this._options.blurHorizontalSize || 1;
+            this._options.blurVerticalSize = this._options.blurVerticalSize || 1;
+            this._options.alphaBlendingMode = this._options.alphaBlendingMode || Engine.ALPHA_COMBINE;
+
+            // VBO
+            var vertices = [];
+            vertices.push(1, 1);
+            vertices.push(-1, 1);
+            vertices.push(-1, -1);
+            vertices.push(1, -1);
+
+            var vertexBuffer = new VertexBuffer(engine, vertices, VertexBuffer.PositionKind, false, false, 2);
+            this._vertexBuffers[VertexBuffer.PositionKind] = vertexBuffer;
+
+            // Indices
+            var indices = [];
+            indices.push(0);
+            indices.push(1);
+            indices.push(2);
+            indices.push(0);
+            indices.push(2);
+            indices.push(3);
+
+            this._indexBuffer = engine.createIndexBuffer(indices);
+            
+            // Effect
+            this._glowMapMergeEffect = engine.createEffect("glowMapMerge",
+                [VertexBuffer.PositionKind],
+                ["offset"],
+                ["textureSampler"], "");
+            
+            // Render target
+            this.setMainTextureSize();
+
+            // Create Textures and post processes
+            this.createTextureAndPostProcesses();
+        }
+
+        /**
+         * Creates the render target textures and post processes used in the highlight layer.
+         */
+        private createTextureAndPostProcesses(): void {
+            var blurTextureWidth = this._mainTextureDesiredSize.width * this._options.blurTextureSizeRatio;
+            var blurTextureHeight = this._mainTextureDesiredSize.height * this._options.blurTextureSizeRatio;
+            blurTextureWidth = Tools.GetExponentOfTwo(blurTextureWidth, this._maxSize);
+            blurTextureHeight = Tools.GetExponentOfTwo(blurTextureHeight, this._maxSize);
+
+            this._mainTexture = new RenderTargetTexture("HighlightLayerMainRTT", 
+                {
+                    width: this._mainTextureDesiredSize.width,
+                    height: this._mainTextureDesiredSize.height
+                }, 
+                this._scene, 
+                false, 
+                true,
+                Engine.TEXTURETYPE_UNSIGNED_INT);
+            this._mainTexture.wrapU = Texture.CLAMP_ADDRESSMODE;
+            this._mainTexture.wrapV = Texture.CLAMP_ADDRESSMODE;
+            this._mainTexture.anisotropicFilteringLevel = 1;
+            this._mainTexture.updateSamplingMode(Texture.BILINEAR_SAMPLINGMODE);
+            this._mainTexture.renderParticles = false;
+            this._mainTexture.renderList = null;
+
+            this._blurTexture = new RenderTargetTexture("HighlightLayerBlurRTT",
+                {
+                    width: blurTextureWidth,
+                    height: blurTextureHeight
+                }, 
+                this._scene, 
+                false, 
+                true,
+                Engine.TEXTURETYPE_UNSIGNED_INT);
+            this._blurTexture.wrapU = Texture.CLAMP_ADDRESSMODE;
+            this._blurTexture.wrapV = Texture.CLAMP_ADDRESSMODE;
+            this._blurTexture.anisotropicFilteringLevel = 16;
+            this._blurTexture.updateSamplingMode(Texture.TRILINEAR_SAMPLINGMODE);
+            this._blurTexture.renderParticles = false;
+
+            this._downSamplePostprocess = new PassPostProcess("HighlightLayerPPP", this._options.blurTextureSizeRatio, 
+                null, Texture.BILINEAR_SAMPLINGMODE, this._scene.getEngine());
+            this._downSamplePostprocess.onApplyObservable.add(effect => {
+                effect.setTexture("textureSampler", this._mainTexture);
+            });
+
+            this._horizontalBlurPostprocess = new GlowBlurPostProcess("HighlightLayerHBP", new BABYLON.Vector2(1.0, 0), this._options.blurHorizontalSize, 1, 
+                null, Texture.BILINEAR_SAMPLINGMODE, this._scene.getEngine());
+            this._horizontalBlurPostprocess.onApplyObservable.add(effect => {
+                effect.setFloat2("screenSize", blurTextureWidth, blurTextureHeight);
+            });
+
+            this._verticalBlurPostprocess = new GlowBlurPostProcess("HighlightLayerVBP", new BABYLON.Vector2(0, 1.0), this._options.blurVerticalSize, 1, 
+                null, Texture.BILINEAR_SAMPLINGMODE, this._scene.getEngine());
+            this._verticalBlurPostprocess.onApplyObservable.add(effect => {
+                effect.setFloat2("screenSize", blurTextureWidth, blurTextureHeight);
+            });
+
+            this._mainTexture.onAfterUnbindObservable.add(() => {
+                this.onBeforeBlurObservable.notifyObservers(this);
+
+                this._scene.postProcessManager.directRender(
+                    [this._downSamplePostprocess, this._horizontalBlurPostprocess, this._verticalBlurPostprocess], 
+                    this._blurTexture.getInternalTexture());
+                
+                this.onAfterBlurObservable.notifyObservers(this);
+            });
+
+            // Custom render function
+            var renderSubMesh = (subMesh: SubMesh): void => {
+                var mesh = subMesh.getRenderingMesh();
+                var scene = this._scene;
+                var engine = scene.getEngine();
+
+                // Culling
+                engine.setState(subMesh.getMaterial().backFaceCulling);
+
+                // Managing instances
+                var batch = mesh._getInstancesRenderList(subMesh._id);
+                if (batch.mustReturn) {
+                    return;
+                }
+
+                var hardwareInstancedRendering = (engine.getCaps().instancedArrays !== null) && (batch.visibleInstances[subMesh._id] !== null) && (batch.visibleInstances[subMesh._id] !== undefined);
+
+                if (this.isReady(subMesh, hardwareInstancedRendering)) {
+                    engine.enableEffect(this._glowMapGenerationEffect);
+                    mesh._bind(subMesh, this._glowMapGenerationEffect, Material.TriangleFillMode);
+                    var material = subMesh.getMaterial();
+
+                    this._glowMapGenerationEffect.setMatrix("viewProjection", scene.getTransformMatrix());
+
+                    var highlightLayerMesh = this._meshes[mesh.id];
+                    if (highlightLayerMesh) {
+                        this._glowMapGenerationEffect.setFloat4("color", 
+                            highlightLayerMesh.color.r,
+                            highlightLayerMesh.color.g,
+                            highlightLayerMesh.color.b,
+                            1.0);
+                    }
+                    else {
+                        this._glowMapGenerationEffect.setFloat4("color", 
+                            HighlightLayer.neutralColor.r,
+                            HighlightLayer.neutralColor.g,
+                            HighlightLayer.neutralColor.b,
+                            HighlightLayer.neutralColor.a);
+                    }
+
+                    // Alpha test
+                    if (material && material.needAlphaTesting()) {
+                        var alphaTexture = material.getAlphaTestTexture();
+                        this._glowMapGenerationEffect.setTexture("diffuseSampler", alphaTexture);
+                        this._glowMapGenerationEffect.setMatrix("diffuseMatrix", alphaTexture.getTextureMatrix());
+                    }
+
+                    // Bones
+                    if (mesh.useBones && mesh.computeBonesUsingShaders) {
+                        this._glowMapGenerationEffect.setMatrices("mBones", mesh.skeleton.getTransformMatrices(mesh));
+                    }
+
+                    // Draw
+                    mesh._processRendering(subMesh, this._glowMapGenerationEffect, Material.TriangleFillMode, batch, hardwareInstancedRendering,
+                        (isInstance, world) => this._glowMapGenerationEffect.setMatrix("world", world));
+                } else {
+                    // Need to reset refresh rate of the shadowMap
+                    this._mainTexture.resetRefreshCounter();
+                }
+            };
+
+            this._mainTexture.customRenderFunction = (opaqueSubMeshes: SmartArray<SubMesh>, alphaTestSubMeshes: SmartArray<SubMesh>, transparentSubMeshes: SmartArray<SubMesh>): void => {
+                this.onBeforeRenderMainTextureObservable.notifyObservers(this);
+
+                var index: number;
+
+                for (index = 0; index < opaqueSubMeshes.length; index++) {
+                    renderSubMesh(opaqueSubMeshes.data[index]);
+                }
+
+                for (index = 0; index < alphaTestSubMeshes.length; index++) {
+                    renderSubMesh(alphaTestSubMeshes.data[index]);
+                }
+
+                for (index = 0; index < transparentSubMeshes.length; index++) {
+                    renderSubMesh(transparentSubMeshes.data[index]);
+                }
+            };
+
+            this._mainTexture.onClearObservable.add((engine: Engine) => {
+                engine.clear(HighlightLayer.neutralColor, true, true, true);
+            });
+        }
+
+        /**
+         * Checks for the readiness of the element composing the layer.
+         * @param subMesh the mesh to check for
+         * @param useInstances specify wether or not to use instances to render the mesh
+         * @return true if ready otherwise, false
+         */
+        private isReady(subMesh: SubMesh, useInstances: boolean): boolean {
+            var defines = [];
+
+            var attribs = [VertexBuffer.PositionKind];
+
+            var mesh = subMesh.getMesh();
+            var material = subMesh.getMaterial();
+
+            // Alpha test
+            if (material && material.needAlphaTesting()) {
+                defines.push("#define ALPHATEST");
+                if (mesh.isVerticesDataPresent(VertexBuffer.UVKind)) {
+                    attribs.push(VertexBuffer.UVKind);
+                    defines.push("#define UV1");
+                }
+                if (mesh.isVerticesDataPresent(VertexBuffer.UV2Kind)) {
+                    var alphaTexture = material.getAlphaTestTexture();
+
+                    if (alphaTexture.coordinatesIndex === 1) {
+                        attribs.push(VertexBuffer.UV2Kind);
+                        defines.push("#define UV2");
+                    }
+                }
+            }
+
+            // Bones
+            if (mesh.useBones && mesh.computeBonesUsingShaders) {
+                attribs.push(VertexBuffer.MatricesIndicesKind);
+                attribs.push(VertexBuffer.MatricesWeightsKind);
+                if (mesh.numBoneInfluencers > 4) {
+                    attribs.push(VertexBuffer.MatricesIndicesExtraKind);
+                    attribs.push(VertexBuffer.MatricesWeightsExtraKind);
+                }
+                defines.push("#define NUM_BONE_INFLUENCERS " + mesh.numBoneInfluencers);
+                defines.push("#define BonesPerMesh " + (mesh.skeleton.bones.length + 1));
+            } else {
+                defines.push("#define NUM_BONE_INFLUENCERS 0");
+            }
+
+            // Instances
+            if (useInstances) {
+                defines.push("#define INSTANCES");
+                attribs.push("world0");
+                attribs.push("world1");
+                attribs.push("world2");
+                attribs.push("world3");
+            }
+
+            // Get correct effect      
+            var join = defines.join("\n");
+            if (this._cachedDefines !== join) {
+                this._cachedDefines = join;
+                this._glowMapGenerationEffect = this._scene.getEngine().createEffect("glowMapGeneration",
+                    attribs,
+                    ["world", "mBones", "viewProjection", "diffuseMatrix", "color"],
+                    ["diffuseSampler"], join);
+            }
+
+            return this._glowMapGenerationEffect.isReady();
+        }
+
+        /**
+         * Renders the glowing part of the scene by blending the blurred glowing meshes on top of the rendered scene.
+         */
+        public render(): void {
+            var currentEffect = this._glowMapMergeEffect;
+
+            // Check
+            if (!currentEffect.isReady() || !this._blurTexture.isReady())
+                return;
+
+            var engine = this._scene.getEngine();
+
+            this.onBeforeComposeObservable.notifyObservers(this);
+
+            // Render
+            engine.enableEffect(currentEffect);
+            engine.setState(false);
+
+            // Cache
+            var previousStencilBuffer = engine.getStencilBuffer();
+            var previousStencilFunction = engine.getStencilFunction();
+            var previousStencilMask = engine.getStencilMask();
+            var previousAlphaMode = engine.getAlphaMode();
+
+            // Texture
+            currentEffect.setTexture("textureSampler", this._blurTexture);
+
+            // VBOs
+            engine.bindBuffers(this._vertexBuffers, this._indexBuffer, currentEffect);
+
+            // Draw order
+            engine.setAlphaMode(this._options.alphaBlendingMode);
+            engine.setStencilMask(0x00);
+            engine.setStencilBuffer(true);
+            engine.setStencilFunctionReference(this._instanceGlowingMeshStencilReference);
+
+            if (this.outerGlow) {
+                currentEffect.setFloat("offset", 0);
+                engine.setStencilFunction(WebGLRenderingContext.NOTEQUAL);
+                engine.draw(true, 0, 6);
+            }
+            if (this.innerGlow) {
+                currentEffect.setFloat("offset", 1);
+                engine.setStencilFunction(WebGLRenderingContext.EQUAL);
+                engine.draw(true, 0, 6);
+            }
+
+            // Restore Cache
+            engine.setStencilFunction(previousStencilFunction);
+            engine.setStencilMask(previousStencilMask);
+            engine.setAlphaMode(previousAlphaMode);
+            engine.setStencilBuffer(previousStencilBuffer);
+
+            this.onAfterComposeObservable.notifyObservers(this);
+
+            // Handle size changes.
+            var size = this._mainTexture.getSize();
+            this.setMainTextureSize();
+            if (size.width !== this._mainTextureDesiredSize.width || size.height !== this._mainTextureDesiredSize.height) {
+                // Recreate RTT and post processes on size change.
+                this.onSizeChangedObservable.notifyObservers(this);
+                this.disposeTextureAndPostProcesses();
+                this.createTextureAndPostProcesses();
+            }
+        }
+
+        /**
+         * Add a mesh in the highlight layer in order to make it glow with the chosen color.
+         * @param mesh The mesh to highlight
+         * @param color The color of the highlight
+         */
+        public pushMesh(mesh: Mesh, color: Color3) {
+            var meshHighlight = this._meshes[mesh.id];
+            if (meshHighlight) {
+                meshHighlight.color = color;
+            }
+            else {
+                this._meshes[mesh.id] = {
+                    mesh: mesh,
+                    color: color,
+                    // Lambda required for capture due to Observable this context
+                    observerHighlight: mesh.onBeforeRenderObservable.add((mesh: Mesh) => { 
+                        mesh.getScene().getEngine().setStencilFunctionReference(this._instanceGlowingMeshStencilReference);
+                    }),
+                    observerDefault: mesh.onAfterRenderObservable.add(this.defaultStencilReference)
+                };
+            }
+
+            this._shouldRender = true;
+        }
+
+        /**
+         * Remove a mesh from the highlight layer in order to make it stop glowing.
+         * @param mesh The mesh to highlight
+         */
+        public removeMesh(mesh: Mesh) {
+            var meshHighlight = this._meshes[mesh.id];
+            if (meshHighlight) {
+                mesh.onBeforeRenderObservable.remove(meshHighlight.observerHighlight);
+                mesh.onAfterRenderObservable.remove(meshHighlight.observerDefault); 
+            }
+
+            this._meshes[mesh.id] = undefined;
+
+            this._shouldRender = false;
+            for (var meshHighlightToCheck in this._meshes) {
+                if (meshHighlightToCheck) {
+                    this._shouldRender = true;
+                    break;
+                }
+            }
+        }
+
+        /**
+         * Returns true if the layer contains information to display, otherwise false.
+         */
+        public shouldRender(): boolean {
+            return this._shouldRender;
+        }
+
+        /**
+         * Sets the main texture desired size which is the closest power of two
+         * of the engine canvas size.
+         */
+        private setMainTextureSize() : void {
+            this._mainTextureDesiredSize.width = this._engine.getRenderingCanvas().width * this._options.mainTextureRatio;
+            this._mainTextureDesiredSize.height = this._engine.getRenderingCanvas().height * this._options.mainTextureRatio;
+
+            this._mainTextureDesiredSize.width = Tools.GetExponentOfTwo(this._mainTextureDesiredSize.width, this._maxSize);
+            this._mainTextureDesiredSize.height = Tools.GetExponentOfTwo(this._mainTextureDesiredSize.height, this._maxSize);
+        }
+
+        /**
+         * Force the stencil to the normal expected value for none glowing parts
+         */
+        private defaultStencilReference(mesh: Mesh) {
+            mesh.getScene().getEngine().setStencilFunctionReference(HighlightLayer.normalMeshStencilReference);
+        }
+
+        /**
+         * Dispose only the render target textures and post process.
+         */
+        private disposeTextureAndPostProcesses(): void {
+            this._blurTexture.dispose();
+            this._mainTexture.dispose();
+            
+            this._downSamplePostprocess.dispose();
+            this._horizontalBlurPostprocess.dispose();
+            this._verticalBlurPostprocess.dispose();
+        }
+
+        /**
+         * Dispose the highlight layer and free resources.
+         */
+        public dispose(): void {
+            var vertexBuffer = this._vertexBuffers[VertexBuffer.PositionKind];
+            if (vertexBuffer) {
+                vertexBuffer.dispose();
+                this._vertexBuffers[VertexBuffer.PositionKind] = null;
+            }
+
+            if (this._indexBuffer) {
+                this._scene.getEngine()._releaseBuffer(this._indexBuffer);
+                this._indexBuffer = null;
+            }
+
+            // Clean textures and post processes
+            this.disposeTextureAndPostProcesses();
+
+            // Clean mesh references 
+            for (var id in this._meshes) {
+                var meshHighlight = this._meshes[id];
+                if (meshHighlight && meshHighlight.mesh) {
+                    meshHighlight.mesh.onBeforeRenderObservable.remove(meshHighlight.observerHighlight);
+                    meshHighlight.mesh.onAfterRenderObservable.remove(meshHighlight.observerDefault);
+                } 
+            }
+            this._meshes = null;
+
+            // Remove from scene
+            var index = this._scene.highlightLayers.indexOf(this, 0);
+            if (index > -1) {
+                this._scene.highlightLayers.splice(index, 1);
+            }
+
+            // Callback
+            this.onDisposeObservable.notifyObservers(this);
+
+            this.onDisposeObservable.clear();
+            this.onBeforeRenderMainTextureObservable.clear();
+            this.onBeforeBlurObservable.clear();
+            this.onBeforeComposeObservable.clear();
+            this.onAfterComposeObservable.clear();
+            this.onSizeChangedObservable.clear();
+        }
+    }
+} 

+ 1 - 1
src/Layer/babylon.layer.ts

@@ -134,7 +134,7 @@
             engine.bindBuffers(this._vertexBuffers, this._indexBuffer, currentEffect);
 
             // Draw order
-            if (!this._alphaTestEffect) {
+            if (!this.alphaTest) {
                 engine.setAlphaMode(this.alphaBlendingMode);
                 engine.draw(true, 0, 6);
                 engine.setAlphaMode(Engine.ALPHA_DISABLE);

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

@@ -352,6 +352,7 @@
 
             this.onDisposeObservable.clear();
             this.onBindObservable.clear();
+            this.onUnBindObservable.clear();
         }
 
         public serialize(): any {

+ 4 - 0
src/Mesh/babylon.meshBuilder.ts

@@ -1,6 +1,10 @@
 module BABYLON {
     export class MeshBuilder {
         private static updateSideOrientationForRightHandedSystem(orientation: number, scene: Scene): number {
+            if (orientation == Mesh.DOUBLESIDE) {
+                return Mesh.DOUBLESIDE;
+            }
+
             if (!scene.useRightHandedSystem) {
                 if (orientation === undefined || orientation === null) {
                     return Mesh.FRONTSIDE;

+ 50 - 0
src/Shaders/glowBlurPostProcess.fragment.fx

@@ -0,0 +1,50 @@
+// Samplers
+varying vec2 vUV;
+uniform sampler2D textureSampler;
+
+// Parameters
+uniform vec2 screenSize;
+uniform vec2 direction;
+uniform float blurWidth;
+
+// Transform color to luminance.
+float getLuminance(vec3 color)
+{
+    return dot(color, vec3(0.2126, 0.7152, 0.0722));
+}
+
+void main(void)
+{
+	float weights[7];
+	weights[0] = 0.05;
+	weights[1] = 0.1;
+	weights[2] = 0.2;
+	weights[3] = 0.3;
+	weights[4] = 0.2;
+	weights[5] = 0.1;
+	weights[6] = 0.05;
+
+	vec2 texelSize = vec2(1.0 / screenSize.x, 1.0 / screenSize.y);
+	vec2 texelStep = texelSize * direction * blurWidth;
+	vec2 start = vUV - 3.0 * texelStep;
+
+	vec4 baseColor = vec4(0., 0., 0., 0.);
+	vec2 texelOffset = vec2(0., 0.);
+
+	for (int i = 0; i < 7; i++)
+	{
+		// alpha blur.
+		vec4 texel = texture2D(textureSampler, start + texelOffset);
+		baseColor.a += texel.a * weights[i];
+
+		// Highest Luma for outline.
+		float luminance = getLuminance(baseColor.rgb);
+		float luminanceTexel = getLuminance(texel.rgb);
+		float choice = step(luminanceTexel, luminance);
+		baseColor.rgb = choice * baseColor.rgb + (1.0 - choice) * texel.rgb;
+
+		texelOffset += texelStep;
+	}
+
+	gl_FragColor = baseColor;
+}

+ 16 - 0
src/Shaders/glowMapGeneration.fragment.fx

@@ -0,0 +1,16 @@
+#ifdef ALPHATEST
+varying vec2 vUV;
+uniform sampler2D diffuseSampler;
+#endif
+
+uniform vec4 color;
+
+void main(void)
+{
+#ifdef ALPHATEST
+	if (texture2D(diffuseSampler, vUV).a < 0.4)
+		discard;
+#endif
+
+	gl_FragColor = color;
+}

+ 45 - 0
src/Shaders/glowMapGeneration.vertex.fx

@@ -0,0 +1,45 @@
+// Attribute
+attribute vec3 position;
+
+#include<bonesDeclaration>
+
+// Uniforms
+#include<instancesDeclaration>
+
+uniform mat4 viewProjection;
+
+varying vec4 vPosition;
+
+#ifdef ALPHATEST
+varying vec2 vUV;
+uniform mat4 diffuseMatrix;
+#ifdef UV1
+attribute vec2 uv;
+#endif
+#ifdef UV2
+attribute vec2 uv2;
+#endif
+#endif
+
+void main(void)
+{
+#include<instancesVertex>
+#include<bonesVertex>
+
+#ifdef CUBEMAP
+	vPosition = finalWorld * vec4(position, 1.0);
+	gl_Position = viewProjection * finalWorld * vec4(position, 1.0);
+#else
+	vPosition = viewProjection * finalWorld * vec4(position, 1.0);
+	gl_Position = vPosition;
+#endif
+
+#ifdef ALPHATEST
+#ifdef UV1
+	vUV = vec2(diffuseMatrix * vec4(uv, 1.0, 0.0));
+#endif
+#ifdef UV2
+	vUV = vec2(diffuseMatrix * vec4(uv2, 1.0, 0.0));
+#endif
+#endif
+}

+ 14 - 0
src/Shaders/glowMapMerge.fragment.fx

@@ -0,0 +1,14 @@
+// Samplers
+varying vec2 vUV;
+uniform sampler2D textureSampler;
+
+// Offset
+uniform float offset;
+
+void main(void) {
+	vec4 baseColor = texture2D(textureSampler, vUV);
+
+	baseColor.a = abs(offset - baseColor.a);
+
+	gl_FragColor = baseColor;
+}

+ 12 - 0
src/Shaders/glowMapMerge.vertex.fx

@@ -0,0 +1,12 @@
+// Attributes
+attribute vec2 position;
+
+// Output
+varying vec2 vUV;
+
+const vec2 madd = vec2(0.5, 0.5);
+
+void main(void) {
+	vUV = position * madd + madd;
+	gl_Position = vec4(position, 0.0, 1.0);
+}

+ 46 - 2
src/babylon.scene.ts

@@ -104,7 +104,7 @@
         public animationsEnabled = true;
         public constantlyUpdateMeshUnderPointer = false;
         public useRightHandedSystem = false;
-
+        
         public hoverCursor = "pointer";
 
         // Events
@@ -360,6 +360,7 @@
 
         // Layers
         public layers = new Array<Layer>();
+        public highlightLayers = new Array<HighlightLayer>();
 
         // Skeletons
         public skeletonsEnabled = true;
@@ -2038,7 +2039,27 @@
 
             // Render
             Tools.StartPerformanceCounter("Main render");
+
+            // Activate HighlightLayer stencil
+            var stencilState = this._engine.getStencilBuffer();
+            var renderhighlights = false;
+            if (this.highlightLayers && this.highlightLayers.length > 0) {
+                for (let i = 0; i < this.highlightLayers.length; i++) {
+                    if (this.highlightLayers[i].shouldRender()) {
+                        renderhighlights = true;
+                        this._engine.setStencilBuffer(true);
+                        break;
+                    }
+                }
+            }
+
             this._renderingManager.render(null, null, true, true);
+
+            // Restore HighlightLayer stencil
+            if (renderhighlights) {
+                this._engine.setStencilBuffer(stencilState);
+            }
+
             Tools.EndPerformanceCounter("Main render");
 
             // Bounding boxes
@@ -2074,6 +2095,17 @@
                 engine.setDepthBuffer(true);
             }
 
+            // Highlight Layer
+            if (renderhighlights) {
+                engine.setDepthBuffer(false);
+                for (let i = 0; i < this.highlightLayers.length; i++) {
+                    if (this.highlightLayers[i].shouldRender()) {
+                        this.highlightLayers[i].render();
+                    }
+                }
+                engine.setDepthBuffer(true);
+            }
+
             this._renderDuration.endMonitoring(false);
 
             // Finalize frame
@@ -2188,7 +2220,7 @@
 
             // Before render
             this.onBeforeRenderObservable.notifyObservers(this);
-
+            
             // Customs render targets
             this._renderTargetsDuration.beginMonitoring();
             var beforeRenderTargetDate = Tools.Now;
@@ -2258,6 +2290,15 @@
                 this._renderTargets.push(this._depthRenderer.getDepthMap());
             }
 
+            // HighlightLayer
+            if (this.highlightLayers && this.highlightLayers.length > 0) {
+                for (let i = 0; i < this.highlightLayers.length; i++) {
+                    if (this.highlightLayers[i].shouldRender()) {
+                        this._renderTargets.push((<any>this.highlightLayers[i])._mainTexture);
+                    }
+                }
+            }
+
             // RenderPipeline
             this.postProcessRenderPipelineManager.update();
 
@@ -2536,6 +2577,9 @@
             while (this.layers.length) {
                 this.layers[0].dispose();
             }
+            while (this.highlightLayers.length) {
+                this.highlightLayers[0].dispose();
+            }
 
             // Release textures
             while (this.textures.length) {