Kaynağa Gözat

Merge pull request #4421 from sebavan/Env

Environment Texture
sebavan 7 yıl önce
ebeveyn
işleme
6f405c6e7c

+ 5 - 1
Tools/Gulp/config.json

@@ -425,7 +425,9 @@
             ],
             "shaders": [
                 "pbr.vertex",
-                "pbr.fragment"
+                "pbr.fragment",
+                "rgbdEncode.fragment",
+                "rgbdDecode.fragment"
             ],
             "shaderIncludes": [
                 "pbrVertexDeclaration",
@@ -956,6 +958,7 @@
                 "../../src/Tools/HDR/babylon.cubemapToSphericalPolynomial.js",
                 "../../src/Tools/HDR/babylon.panoramaToCubemap.js",
                 "../../src/Tools/HDR/babylon.hdr.js",
+                "../../src/Tools/babylon.environmentTextureTools.js",
                 "../../src/Materials/Textures/babylon.hdrCubeTexture.js"
             ],
             "dependUpon": [
@@ -1789,6 +1792,7 @@
                     "../../inspector/src/tabs/ConsoleTab.ts",
                     "../../inspector/src/tabs/StatsTab.ts",
                     "../../inspector/src/tabs/GLTFTab.ts",
+                    "../../inspector/src/tabs/ToolsTab.ts",
                     "../../inspector/src/tabs/TabBar.ts",
                     "../../inspector/src/tools/AbstractTool.ts",
                     "../../inspector/src/tools/PauseScheduleTool.ts",

+ 1 - 0
dist/preview release/what's new.md

@@ -6,6 +6,7 @@
 - Added new `MixMaterial` to the Materials Library allowing to mix up to 8 textures ([julien-moreau](https://github.com/julien-moreau))
 - Added new `PhotoDome` object to display 360 photos. [Demo](https://www.babylonjs-playground.com/#14KRGG#0) ([SzeyinLee](https://github.com/SzeyinLee))
 - New GUI 3D controls toolset. [Complete doc + demos](http://doc.babylonjs.com/how_to/gui3d) ([Deltakosh](https://github.com/deltakosh))
+- Added Environmnent Texture Tools to reduce the size of the usual .DDS file ([sebavan](http://www.github.com/sebavan))
 
 ## Updates
 

+ 3 - 2
inspector/sass/defines.scss

@@ -13,8 +13,9 @@ $font               : 'Inconsolata', sans-serif;
 $color              : #ccc;
 $background         : #242424;
 $background-active  : #2c2c2c;
-$color-top         : #f29766;
-$color-bot         : #5db0d7;
+$color-top          : #f29766;
+$color-bot          : #5db0d7;
+$color-error        : #fa371d;
 
 $background-lighter : lighten($color: $background, $amount         : 3%);
 $background-lighter2: lighten($color: $background-lighter, $amount : 5%);

+ 1 - 0
inspector/sass/main.scss

@@ -94,6 +94,7 @@
   @import "tabs/consoleTab";
   @import "tabs/statsTab";
   @import "tabs/gltfTab";
+  @import "tabs/toolsTab";
 
   @import "tree";
   @import "detailPanel";

+ 59 - 0
inspector/sass/tabs/_toolsTab.scss

@@ -0,0 +1,59 @@
+.tab-panel {
+
+    &.tools-panel {
+        overflow-y      : auto;
+    }
+
+    .tool-title1 {        
+        font-size       : 1.1em;
+        padding         : 10px;
+    }
+
+    .tool-title2 {
+        margin          : 10px 0 10px 0;
+        font-size       : 1.05em; 
+        border-bottom   : 1px solid $color-bot;
+        box-sizing      : border-box;
+    }
+
+    .tool-label {
+        display         : inline-block;
+        width           : 75%;
+        padding         : 2px;
+        background-color: $background-lighter;
+        border-bottom   : 1px solid $background;
+        border-top      : 1px solid $background;
+        height          : 30px;
+        line-height     : 30px;
+        box-sizing      : border-box;
+    }
+
+    .tool-label-line {
+        @extend .tool-label;
+        width           : 100%;
+    }
+
+    .tool-label-error {
+        @extend .tool-label;
+        color           : $color-error;
+        width           : 100%;
+        background-color: none;
+    }
+
+    .tool-value {
+        display         : inline-block;
+        width           : 25%;
+        padding         : 2px;
+        background-color: $background-lighter;
+        border-top      : 1px solid $background;
+        border-bottom   : 1px solid $background;
+        height          : 30px;
+        line-height     : 30px;
+        box-sizing      : border-box;
+    }
+
+    .tool-infos {
+        width           : 100%;
+        padding         : 4px;
+    }
+}

+ 1 - 0
inspector/src/tabs/TabBar.ts

@@ -41,6 +41,7 @@ module INSPECTOR {
             this._tabs.push(new PhysicsTab(this, this._inspector));
             this._tabs.push(new CameraTab(this, this._inspector));
             this._tabs.push(new SoundTab(this, this._inspector));
+            this._tabs.push(new ToolsTab(this, this._inspector));
             this._toolBar = new Toolbar(this._inspector);
 
             this._build();

+ 146 - 0
inspector/src/tabs/ToolsTab.ts

@@ -0,0 +1,146 @@
+module INSPECTOR {
+
+    export class ToolsTab extends Tab {
+
+        private _inspector: Inspector;
+
+        private _scene: BABYLON.Scene;
+
+        constructor(tabbar: TabBar, insp: Inspector) {
+            super(tabbar, 'Tools');
+
+            this._inspector = insp;
+
+            this._scene = this._inspector.scene;
+
+            // Build the tools panel: a div that will contains all tools
+            this._panel = Helpers.CreateDiv('tab-panel') as HTMLDivElement;
+            this._panel.classList.add("tools-panel")
+
+            let title = Helpers.CreateDiv('tool-title1', this._panel);
+            let versionSpan = Helpers.CreateElement('span');
+            versionSpan.textContent = `Babylon.js v${BABYLON.Engine.Version} - Tools`;
+            title.appendChild(versionSpan);
+
+            // Environment block
+            title = Helpers.CreateDiv('tool-title2', this._panel);
+            title.textContent = "Environment";
+            {
+                let elemLabel = this._createToolLabel("Load Environment Texture (.dds, .env) ", this._panel);
+                elemLabel.className = "tool-label-line";
+
+                let errorElemm = Inspector.DOCUMENT.createElement('div');
+                errorElemm.className = "tool-label-error";
+                errorElemm.style.display = "none";
+
+                let inputElement = Inspector.DOCUMENT.createElement('input');
+                inputElement.className = "tool-label-line";
+                inputElement.type = "file";
+                inputElement.onchange = (event: any) => {
+                    var files: File[] = event.target.files;
+                    let file: BABYLON.Nullable<File> = null;
+                    if (files && files.length) {
+                        file = files[0];
+                    }
+
+                    if (!file) {
+                        errorElemm.style.display = "block";
+                        errorElemm.textContent = "Please, select a file first."
+                        return;
+                    }
+
+                    let isFileDDS = file.name.toLowerCase().indexOf(".dds") > 0;
+                    let isFileEnv = file.name.toLowerCase().indexOf(".env") > 0;
+                    if (!isFileDDS && ! isFileEnv) {
+                        errorElemm.style.display = "block";
+                        errorElemm.textContent = "Please, select a dds or env file."
+                        return;
+                    }
+
+                    BABYLON.Tools.ReadFile(file, data => {
+                        var blob = new Blob([data], {type: "octet/stream"});
+                        var url = URL.createObjectURL(blob);
+                        if (isFileDDS) {
+                            this._scene.environmentTexture = BABYLON.CubeTexture.CreateFromPrefilteredData(url, this._scene, ".dds");
+                            errorElemm.style.display = "none";
+                        }
+                        else {
+                            this._scene.environmentTexture = new BABYLON.CubeTexture(url, this._scene, 
+                                undefined, undefined, undefined, 
+                                () => {
+                                    errorElemm.style.display = "none";
+                                }, 
+                                (message) => {
+                                    if (message) {
+                                        errorElemm.style.display = "block";
+                                        errorElemm.textContent = message;
+                                    }
+                                }, 
+                                undefined, undefined,
+                                ".env");
+                        }
+                    }, undefined, true);
+                };
+                this._panel.appendChild(inputElement);
+
+                this._createToolLabel("Compress to .env", this._panel);
+                let elemValue = Helpers.CreateDiv('tool-value', this._panel);
+                inputElement = Inspector.DOCUMENT.createElement('input');
+                inputElement.value = "Save";
+                inputElement.type = "button";
+                inputElement.onclick = () => {
+                    if (!this._scene.environmentTexture) {
+                        errorElemm.style.display = "block";
+                        errorElemm.textContent = "You must load an environment texture first.";
+                        return;
+                    }
+                    if (this._scene.activeCamera) {
+                        BABYLON.EnvironmentTextureTools.CreateEnvTextureAsync(this._scene.environmentTexture)
+                        .then((buffer: ArrayBuffer) => {
+                            var blob = new Blob([buffer], {type: "octet/stream"});
+                            BABYLON.Tools.Download(blob, "environment.env");
+                            errorElemm.style.display = "none";
+                        })
+                        .catch((error: any) => {
+                            errorElemm.style.display = "block";
+                            errorElemm.textContent = error;
+                        });
+                    }
+                    else {
+                        errorElemm.style.display = "block";
+                        errorElemm.textContent = "An active camera is required.";
+                    }
+                };
+                elemValue.appendChild(inputElement);
+                
+                this._panel.appendChild(errorElemm);
+            }
+
+            title = Helpers.CreateDiv('tool-title2', this._panel);
+            title.textContent = "Capture";
+            {
+                this._createToolLabel("Screenshot", this._panel);
+                let elemValue = Helpers.CreateDiv('tool-value', this._panel);
+                let inputElement = Inspector.DOCUMENT.createElement('input');
+                inputElement.value = "Capture";
+                inputElement.type = "button";
+                inputElement.onclick = () => {
+                    if (this._scene.activeCamera) {
+                        BABYLON.Tools.CreateScreenshot(this._scene.getEngine(), this._scene.activeCamera, {precision: 0.5});
+                    }
+                };
+                elemValue.appendChild(inputElement);
+            }
+        }
+
+        private _createToolLabel(content: string, parent: HTMLElement): HTMLElement {
+            let elem = Helpers.CreateDiv('tool-label', parent);
+            elem.textContent = content;
+            return elem;
+        }
+
+        public dispose() {
+            // Nothing to dispose
+        }
+    }
+}

+ 92 - 18
src/Engine/babylon.engine.ts

@@ -1129,6 +1129,11 @@
         }
 
         /**
+         * Defines whether the engine has been created with the premultipliedAlpha option on or not.
+         */
+        public readonly premultipliedAlpha: boolean = true;
+
+        /**
          * Creates a new engine
          * @param canvasOrContext defines the canvas or WebGL context to use for rendering
          * @param antialias defines enable antialiasing (default: false)
@@ -1177,6 +1182,13 @@
                     options.stencil = true;
                 }
 
+                if (options.premultipliedAlpha) {
+                    this.premultipliedAlpha = true;
+                }
+                else {
+                    this.premultipliedAlpha = false;
+                }
+
                 this._deterministicLockstep = options.deterministicLockstep;
                 this._lockstepMaxSteps = options.lockstepMaxSteps;
                 this._doNotHandleContextLost = options.doNotHandleContextLost ? true : false;
@@ -2415,8 +2427,9 @@
          * @param requiredHeight The height of the target to render to
          * @param forceFullscreenViewport Forces the viewport to be the entire texture/screen if true
          * @param depthStencilTexture The depth stencil texture to use to render
+         * @param lodLevel defines le lod level to bind to the frame buffer
          */
-        public bindFramebuffer(texture: InternalTexture, faceIndex?: number, requiredWidth?: number, requiredHeight?: number, forceFullscreenViewport?: boolean, depthStencilTexture?: InternalTexture): void {
+        public bindFramebuffer(texture: InternalTexture, faceIndex?: number, requiredWidth?: number, requiredHeight?: number, forceFullscreenViewport?: boolean, depthStencilTexture?: InternalTexture, lodLevel = 0): void {
             if (this._currentRenderTarget) {
                 this.unBindFramebuffer(this._currentRenderTarget);
             }
@@ -2427,14 +2440,14 @@
                 if (faceIndex === undefined) {
                     faceIndex = 0;
                 }
-                gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_CUBE_MAP_POSITIVE_X + faceIndex, texture._webGLTexture, 0);
+                gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_CUBE_MAP_POSITIVE_X + faceIndex, texture._webGLTexture, lodLevel);
 
                 if (depthStencilTexture) {
                     if (depthStencilTexture._generateStencilBuffer) {
-                        gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.DEPTH_STENCIL_ATTACHMENT, gl.TEXTURE_CUBE_MAP_POSITIVE_X + faceIndex, depthStencilTexture._webGLTexture, 0);
+                        gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.DEPTH_STENCIL_ATTACHMENT, gl.TEXTURE_CUBE_MAP_POSITIVE_X + faceIndex, depthStencilTexture._webGLTexture, lodLevel);
                     }
                     else {
-                        gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.DEPTH_ATTACHMENT, gl.TEXTURE_CUBE_MAP_POSITIVE_X + faceIndex, depthStencilTexture._webGLTexture, 0);
+                        gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.DEPTH_ATTACHMENT, gl.TEXTURE_CUBE_MAP_POSITIVE_X + faceIndex, depthStencilTexture._webGLTexture, lodLevel);
                     }
                 }
             }
@@ -2442,7 +2455,20 @@
             if (this._cachedViewport && !forceFullscreenViewport) {
                 this.setViewport(this._cachedViewport, requiredWidth, requiredHeight);
             } else {
-                gl.viewport(0, 0, requiredWidth || texture.width, requiredHeight || texture.height);
+                if (!requiredWidth) {
+                    requiredWidth = texture.width;
+                    if (lodLevel) {
+                        requiredWidth = requiredWidth / Math.pow(2, lodLevel);
+                    }
+                }
+                if (!requiredHeight) {
+                    requiredHeight = texture.height;
+                    if (lodLevel) {
+                        requiredHeight = requiredHeight / Math.pow(2, lodLevel);
+                    }
+                }
+
+                gl.viewport(0, 0, requiredWidth, requiredHeight);
             }
 
             this.wipeCaches();
@@ -5259,6 +5285,28 @@
             this._gl.compressedTexImage2D(target, lod, internalFormat, width, height, 0, <DataView>data);
         }
 
+        /** @hidden */
+        public _uploadImageToTexture(texture: InternalTexture, faceIndex: number, lod: number, image: HTMLImageElement) {
+            var gl = this._gl;
+
+            var textureType = this._getWebGLTextureType(texture.type);
+            var format = this._getInternalFormat(texture.format);
+            var internalFormat = this._getRGBABufferInternalSizedFormat(texture.type, format);
+
+            var bindTarget = texture.isCube ? gl.TEXTURE_CUBE_MAP : gl.TEXTURE_2D;
+
+            this._bindTextureDirectly(bindTarget, texture, true);
+            gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, texture.invertY ? 1 : 0);
+
+            var target = gl.TEXTURE_2D;
+            if (texture.isCube) {
+                var target = gl.TEXTURE_CUBE_MAP_POSITIVE_X + faceIndex;
+            }
+
+            gl.texImage2D(target, lod, internalFormat, format, textureType, image);
+            this._bindTextureDirectly(bindTarget, null, true);
+        }
+
         /**
          * Creates a new render target cube texture
          * @param size defines the size of the texture
@@ -5343,8 +5391,8 @@
          * Create a cube texture from prefiltered data (ie. the mipmaps contain ready to use data for PBR reflection)
          * @param rootUrl defines the url where the file to load is located
          * @param scene defines the current scene
-         * @param scale defines scale to apply to the mip map selection
-         * @param offset defines offset to apply to the mip map selection
+         * @param lodScale defines scale to apply to the mip map selection
+         * @param lodOffset defines offset to apply to the mip map selection
          * @param onLoad defines an optional callback raised when the texture is loaded
          * @param onError defines an optional callback raised if there is an issue to load the texture
          * @param format defines the format of the data
@@ -5352,7 +5400,7 @@
          * @param createPolynomials defines wheter or not to create polynomails harmonics for the texture
          * @returns the cube texture as an InternalTexture
          */
-        public createPrefilteredCubeTexture(rootUrl: string, scene: Nullable<Scene>, scale: number, offset: number,
+        public createPrefilteredCubeTexture(rootUrl: string, scene: Nullable<Scene>, lodScale: number, lodOffset: number,
             onLoad: Nullable<(internalTexture: Nullable<InternalTexture>) => void> = null,
             onError: Nullable<(message?: string, exception?: any) => void> = null, format?: number, forcedExtension: any = null,
             createPolynomials: boolean = true): InternalTexture {
@@ -5372,8 +5420,6 @@
                     texture._sphericalPolynomial = loadData.info.sphericalPolynomial;
                 }
                 texture._dataSource = InternalTexture.DATASOURCE_CUBEPREFILTERED;
-                texture._lodGenerationScale = scale;
-                texture._lodGenerationOffset = offset;
 
                 if (this._caps.textureLOD) {
                     // Do not add extra process if texture lod is supported.
@@ -5397,8 +5443,8 @@
                     let smoothness = i / (mipSlices - 1);
                     let roughness = 1 - smoothness;
 
-                    let minLODIndex = offset; // roughness = 0
-                    let maxLODIndex = Scalar.Log2(width) * scale + offset; // roughness = 1
+                    let minLODIndex = lodOffset; // roughness = 0
+                    let maxLODIndex = Scalar.Log2(width) * lodScale + lodOffset; // roughness = 1
 
                     let lodIndex = minLODIndex + (maxLODIndex - minLODIndex) * roughness;
                     let mipmapIndex = Math.round(Math.min(Math.max(lodIndex, 0), maxLODIndex));
@@ -5443,7 +5489,7 @@
                 }
             };
 
-            return this.createCubeTexture(rootUrl, scene, null, false, callback, onError, format, forcedExtension, createPolynomials);
+            return this.createCubeTexture(rootUrl, scene, null, false, callback, onError, format, forcedExtension, createPolynomials, lodScale, lodOffset);
         }
 
         /**
@@ -5457,15 +5503,19 @@
          * @param format defines the format of the data
          * @param forcedExtension defines the extension to use to pick the right loader
          * @param createPolynomials if a polynomial sphere should be created for the cube texture
+         * @param lodScale defines the scale applied to environment texture. This manages the range of LOD level used for IBL according to the roughness
+         * @param lodOffset defines the offset applied to environment texture. This manages first LOD level used for IBL according to the roughness
          * @returns the cube texture as an InternalTexture
          */
-        public createCubeTexture(rootUrl: string, scene: Nullable<Scene>, files: Nullable<string[]>, noMipmap?: boolean, onLoad: Nullable<(data?: any) => void> = null, onError: Nullable<(message?: string, exception?: any) => void> = null, format?: number, forcedExtension: any = null, createPolynomials = false): InternalTexture {
+        public createCubeTexture(rootUrl: string, scene: Nullable<Scene>, files: Nullable<string[]>, noMipmap?: boolean, onLoad: Nullable<(data?: any) => void> = null, onError: Nullable<(message?: string, exception?: any) => void> = null, format?: number, forcedExtension: any = null, createPolynomials = false, lodScale: number = 0, lodOffset: number = 0): InternalTexture {
             var gl = this._gl;
 
             var texture = new InternalTexture(this, InternalTexture.DATASOURCE_CUBE);
             texture.isCube = true;
             texture.url = rootUrl;
             texture.generateMipMaps = !noMipmap;
+            texture._lodGenerationScale = lodScale;
+            texture._lodGenerationOffset = lodOffset;
 
             if (!this._doNotHandleContextLost) {
                 texture._extension = forcedExtension;
@@ -5474,6 +5524,7 @@
 
             var isKTX = false;
             var isDDS = false;
+            var isEnv = false;
             var lastDot = rootUrl.lastIndexOf('.');
             var extension = forcedExtension ? forcedExtension : (lastDot > -1 ? rootUrl.substring(lastDot).toLowerCase() : "");
             if (this._textureFormatInUse) {
@@ -5482,6 +5533,7 @@
                 isKTX = true;
             } else {
                 isDDS = (extension === ".dds");
+                isEnv = (extension === ".env");
             }
 
             let onerror = (request?: XMLHttpRequest, exception?: any) => {
@@ -5507,7 +5559,29 @@
                     texture.height = ktx.pixelHeight;
                     texture.isReady = true;
                 }, undefined, undefined, true, onerror);
-            } else if (isDDS) {
+            }
+            else if (isEnv) {
+                this._loadFile(rootUrl, (data) => {
+                    data = data as ArrayBuffer;
+                    var info = EnvironmentTextureTools.GetEnvInfo(data);
+                    if (info) {
+                        texture.width = info.width;
+                        texture.height = info.width;
+
+                        EnvironmentTextureTools.UploadPolynomials(texture, data, info!);
+                        EnvironmentTextureTools.UploadLevelsAsync(texture, data, info!).then(() => {
+                            texture.isReady = true;
+                            if (onLoad) {
+                                onLoad();
+                            }
+                        });
+                    }
+                    else if (onError) {
+                        onError("Can not parse the environment file", null);
+                    }
+                }, undefined, undefined, true, onerror);
+            }
+            else if (isDDS) {
                 if (files && files.length === 6) {
                     this._cascadeLoadFiles(
                         scene,
@@ -6939,7 +7013,7 @@
         }
 
         /** @hidden */
-        public _readTexturePixels(texture: InternalTexture, width: number, height: number, faceIndex = -1): ArrayBufferView {
+        public _readTexturePixels(texture: InternalTexture, width: number, height: number, faceIndex = -1, level = 0): ArrayBufferView {
             let gl = this._gl;
             if (!this._dummyFramebuffer) {
                 let dummy = gl.createFramebuffer();
@@ -6953,9 +7027,9 @@
             gl.bindFramebuffer(gl.FRAMEBUFFER, this._dummyFramebuffer);
 
             if (faceIndex > -1) {
-                gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_CUBE_MAP_POSITIVE_X + faceIndex, texture._webGLTexture, 0);
+                gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_CUBE_MAP_POSITIVE_X + faceIndex, texture._webGLTexture, level);
             } else {
-                gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, texture._webGLTexture, 0);
+                gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, texture._webGLTexture, level);
             }
 
             let readType = (texture.type !== undefined) ? this._getWebGLTextureType(texture.type) : gl.UNSIGNED_BYTE;

+ 3 - 0
src/Materials/Background/babylon.backgroundMaterial.ts

@@ -106,6 +106,7 @@
         public REFLECTIONMAP_OPPOSITEZ = false;
         public LODINREFLECTIONALPHA = false;
         public GAMMAREFLECTION = false;
+        public RGBDREFLECTION = false;
         public EQUIRECTANGULAR_RELFECTION_FOV = false;
 
         // Default BJS.
@@ -655,6 +656,7 @@
 
                         defines.REFLECTION = true;
                         defines.GAMMAREFLECTION = reflectionTexture.gammaSpace;
+                        defines.RGBDREFLECTION = reflectionTexture.isRGBD;
                         defines.REFLECTIONBLUR = this._reflectionBlur > 0;
                         defines.REFLECTIONMAP_OPPOSITEZ = this.getScene().useRightHandedSystem ? !reflectionTexture.invertZ : reflectionTexture.invertZ;
                         defines.LODINREFLECTIONALPHA = reflectionTexture.lodLevelInAlpha;
@@ -730,6 +732,7 @@
                         defines.REFLECTIONMAP_OPPOSITEZ = false;
                         defines.LODINREFLECTIONALPHA = false;
                         defines.GAMMAREFLECTION = false;
+                        defines.RGBDREFLECTION = false;
                     }
                 }
 

+ 5 - 0
src/Materials/PBR/babylon.pbrBaseMaterial.ts

@@ -86,6 +86,7 @@
         public REFLECTIONMAP_OPPOSITEZ = false;
         public LODINREFLECTIONALPHA = false;
         public GAMMAREFLECTION = false;
+        public RGBDREFLECTION = false;
         public RADIANCEOCCLUSION = false;
         public HORIZONOCCLUSION = false;
 
@@ -94,6 +95,7 @@
         public REFRACTIONMAP_OPPOSITEZ = false;
         public LODINREFRACTIONALPHA = false;
         public GAMMAREFRACTION = false;
+        public RGBDREFRACTION = false;
         public LINKREFRACTIONTOTRANSPARENCY = false;
 
         public INSTANCES = false;
@@ -1047,6 +1049,7 @@
                     if (reflectionTexture && StandardMaterial.ReflectionTextureEnabled) {
                         defines.REFLECTION = true;
                         defines.GAMMAREFLECTION = reflectionTexture.gammaSpace;
+                        defines.RGBDREFLECTION = reflectionTexture.isRGBD;
                         defines.REFLECTIONMAP_OPPOSITEZ = this.getScene().useRightHandedSystem ? !reflectionTexture.invertZ : reflectionTexture.invertZ;
                         defines.LODINREFLECTIONALPHA = reflectionTexture.lodLevelInAlpha;
 
@@ -1119,6 +1122,7 @@
                         defines.REFLECTIONMAP_OPPOSITEZ = false;
                         defines.LODINREFLECTIONALPHA = false;
                         defines.GAMMAREFLECTION = false;
+                        defines.RGBDREFLECTION = false;
                     }
 
                     if (this._lightmapTexture && StandardMaterial.LightmapTextureEnabled) {
@@ -1182,6 +1186,7 @@
                         defines.REFRACTION = true;
                         defines.REFRACTIONMAP_3D = refractionTexture.isCube;
                         defines.GAMMAREFRACTION = refractionTexture.gammaSpace;
+                        defines.RGBDREFRACTION = refractionTexture.isRGBD;
                         defines.REFRACTIONMAP_OPPOSITEZ = refractionTexture.invertZ;
                         defines.LODINREFRACTIONALPHA = refractionTexture.lodLevelInAlpha;
 

+ 44 - 5
src/Materials/Textures/babylon.baseTexture.ts

@@ -103,6 +103,13 @@
         @serialize()
         public gammaSpace = true;
 
+        /**
+         * Gets whether or not the texture contains RGBD data.
+         */
+        public get isRGBD(): boolean {
+            return this._texture != null && this._texture._isRGBD;
+        }
+
         @serialize()
         public invertZ = false;
 
@@ -110,10 +117,24 @@
         public lodLevelInAlpha = false;
 
         @serialize()
-        public lodGenerationOffset = 0.0;
+        public get lodGenerationOffset(): number {
+            if (this._texture) return this._texture._lodGenerationOffset;
+
+            return 0.0;
+        }
+        public set lodGenerationOffset(value: number) {
+            if (this._texture) this._texture._lodGenerationOffset = value;
+        }
 
         @serialize()
-        public lodGenerationScale = 0.8;
+        public get lodGenerationScale(): number {
+            if (this._texture) return this._texture._lodGenerationScale;
+
+            return 0.0;
+        }
+        public set lodGenerationScale(value: number) {
+            if (this._texture) this._texture._lodGenerationScale = value;
+        }
 
         @serialize()
         public isRenderTarget = false;
@@ -276,12 +297,22 @@
             return (this._texture.format !== undefined) ? this._texture.format : Engine.TEXTUREFORMAT_RGBA;
         }
 
-        public readPixels(faceIndex = 0): Nullable<ArrayBufferView> {
+        /**
+         * Reads the pixels stored in the webgl texture and returns them as an ArrayBuffer.
+         * This will returns an RGBA array buffer containing either in values (0-255) or
+         * float values (0-1) depending of the underlying buffer type.
+         * @param faceIndex The face of the texture to read (in case of cube texture)
+         * @param level The LOD level of the texture to read (in case of Mip Maps)
+         * @returns The Array buffer containing the pixels data.
+         */
+        public readPixels(faceIndex = 0, level = 0): Nullable<ArrayBufferView> {
             if (!this._texture) {
                 return null;
             }
 
             var size = this.getSize();
+            var width = size.width;
+            var height = size.height;
             let scene = this.getScene();
 
             if (!scene) {
@@ -290,11 +321,19 @@
 
             var engine = scene.getEngine();
 
+            if (level != 0) {
+                width = width / Math.pow(2, level);
+                height = height / Math.pow(2, level);
+
+                width = Math.round(width);
+                height = Math.round(height);
+            }
+
             if (this._texture.isCube) {
-                return engine._readTexturePixels(this._texture, size.width, size.height, faceIndex);
+                return engine._readTexturePixels(this._texture, width, height, faceIndex, level);
             }
 
-            return engine._readTexturePixels(this._texture, size.width, size.height, -1);
+            return engine._readTexturePixels(this._texture, width, height, -1, level);
         }
 
         public releaseInternalTexture(): void {

+ 15 - 5
src/Materials/Textures/babylon.cubeTexture.ts

@@ -53,9 +53,11 @@
         private _extensions: string[];
         private _textureMatrix: Matrix;
         private _format: number;
-        private _prefiltered: boolean;
         private _createPolynomials: boolean;
 
+        /** @hidden */
+        public readonly _prefiltered: boolean = false;
+
         public static CreateFromImages(files: string[], scene: Scene, noMipmap?: boolean) {
             let rootUrlKey = "";
 
@@ -90,11 +92,14 @@
          * @param prefiltered defines whether or not the texture is created from prefiltered data
          * @param forcedExtension defines the extensions to use (force a special type of file to load) in case it is different from the file name
          * @param createPolynomials defines whether or not to create polynomial harmonics from the texture data if necessary
+         * @param lodScale defines the scale applied to environment texture. This manages the range of LOD level used for IBL according to the roughness
+         * @param lodOffset defines the offset applied to environment texture. This manages first LOD level used for IBL according to the roughness
          * @return the cube texture
          */
         constructor(rootUrl: string, scene: Scene, extensions: Nullable<string[]> = null, noMipmap: boolean = false, files: Nullable<string[]> = null,
             onLoad: Nullable<() => void> = null, onError: Nullable<(message?: string, exception?: any) => void> = null, format: number = Engine.TEXTUREFORMAT_RGBA, prefiltered = false, 
-            forcedExtension: any = null, createPolynomials: boolean = false) {
+            forcedExtension: any = null, createPolynomials: boolean = false,
+            lodScale: number = 0.8, lodOffset: number = 0) {
             super(scene);
 
             this.name = rootUrl;
@@ -119,9 +124,14 @@
             const lastDot = rootUrl.lastIndexOf(".");
             const extension = forcedExtension ? forcedExtension : (lastDot > -1 ? rootUrl.substring(lastDot).toLowerCase() : "");
             const isDDS = (extension === ".dds");
+            const isEnv = (extension === ".env");
+
+            if (isEnv) {
+                this.gammaSpace = false;
+            }
 
             if (!files) {
-                if (!isDDS && !extensions) {
+                if (!isEnv && !isDDS && !extensions) {
                     extensions = ["_px.jpg", "_py.jpg", "_pz.jpg", "_nx.jpg", "_ny.jpg", "_nz.jpg"];
                 }
 
@@ -140,10 +150,10 @@
             if (!this._texture) {
                 if (!scene.useDelayedTextureLoading) {
                     if (prefiltered) {
-                        this._texture = scene.getEngine().createPrefilteredCubeTexture(rootUrl, scene, this.lodGenerationScale, this.lodGenerationOffset, onLoad, onError, format, forcedExtension, this._createPolynomials);
+                        this._texture = scene.getEngine().createPrefilteredCubeTexture(rootUrl, scene, lodScale, lodOffset, onLoad, onError, format, forcedExtension, this._createPolynomials);
                     }
                     else {
-                        this._texture = scene.getEngine().createCubeTexture(rootUrl, scene, files, noMipmap, onLoad, onError, this._format, forcedExtension);
+                        this._texture = scene.getEngine().createCubeTexture(rootUrl, scene, files, noMipmap, onLoad, onError, this._format, forcedExtension, false, lodScale, lodOffset);
                     }
                 } else {
                     this.delayLoadState = Engine.DELAYLOADSTATE_NOTLOADED;

+ 12 - 1
src/Materials/Textures/babylon.internalTexture.ts

@@ -201,14 +201,25 @@ module BABYLON {
         public _lodTextureMid: BaseTexture;
         /** @hidden */
         public _lodTextureLow: BaseTexture;
+        /** @hidden */
+        public _isRGBD: boolean = false;
 
         /** @hidden */
         public _webGLTexture: Nullable<WebGLTexture>;
         /** @hidden */
         public _references: number = 1;
+
         private _engine: Engine;
 
         /**
+         * Gets the Engine the texture belongs to.
+         * @returns The babylon engine
+         */
+        public getEngine(): Engine {
+            return this._engine;
+        }
+
+        /**
          * Gets the data source type of the texture (can be one of the BABYLON.InternalTexture.DATASOURCE_XXXX)
          */
         public get dataSource(): number {
@@ -358,7 +369,7 @@ module BABYLON {
             }
         }
 
-        private _swapAndDie(target: InternalTexture): void {
+        public _swapAndDie(target: InternalTexture): void {
             target._webGLTexture = this._webGLTexture;
 
             if (this._framebuffer) {

+ 4 - 2
src/PostProcess/babylon.postProcessManager.ts

@@ -88,8 +88,10 @@
          * @param postProcesses An array of post processes to be run.
          * @param targetTexture The target texture to render to.
          * @param forceFullscreenViewport force gl.viewport to be full screen eg. 0,0,textureWidth,textureHeight
+         * @param faceIndex defines the face to render to if a cubemap is defined as the target
+         * @param lodLevel defines which lod of the texture to render to
          */
-        public directRender(postProcesses: PostProcess[], targetTexture: Nullable<InternalTexture> = null, forceFullscreenViewport = false): void {
+        public directRender(postProcesses: PostProcess[], targetTexture: Nullable<InternalTexture> = null, forceFullscreenViewport = false, faceIndex = 0, lodLevel = 0): void {
             var engine = this._scene.getEngine();
 
             for (var index = 0; index < postProcesses.length; index++) {
@@ -97,7 +99,7 @@
                     postProcesses[index + 1].activate(this._scene.activeCamera, targetTexture);
                 } else {
                     if (targetTexture) {
-                        engine.bindFramebuffer(targetTexture, 0, undefined, undefined, forceFullscreenViewport);
+                        engine.bindFramebuffer(targetTexture, faceIndex, undefined, undefined, forceFullscreenViewport, undefined, lodLevel);
                     } else {
                         engine.restoreDefaultFramebuffer();
                     }

+ 51 - 26
src/Shaders/ShadersInclude/helperFunctions.fx

@@ -5,49 +5,49 @@ const float GammaEncodePowerApprox = 1.0 / LinearEncodePowerApprox;
 const vec3 LuminanceEncodeApprox = vec3(0.2126, 0.7152, 0.0722);
 
 mat3 transposeMat3(mat3 inMatrix) {
-	vec3 i0 = inMatrix[0];
-	vec3 i1 = inMatrix[1];
-	vec3 i2 = inMatrix[2];
+    vec3 i0 = inMatrix[0];
+    vec3 i1 = inMatrix[1];
+    vec3 i2 = inMatrix[2];
 
-	mat3 outMatrix = mat3(
-		vec3(i0.x, i1.x, i2.x),
-		vec3(i0.y, i1.y, i2.y),
-		vec3(i0.z, i1.z, i2.z)
-		);
+    mat3 outMatrix = mat3(
+        vec3(i0.x, i1.x, i2.x),
+        vec3(i0.y, i1.y, i2.y),
+        vec3(i0.z, i1.z, i2.z)
+        );
 
-	return outMatrix;
+    return outMatrix;
 }
 
 // https://github.com/glslify/glsl-inverse/blob/master/index.glsl
 mat3 inverseMat3(mat3 inMatrix) {
-	float a00 = inMatrix[0][0], a01 = inMatrix[0][1], a02 = inMatrix[0][2];
-  	float a10 = inMatrix[1][0], a11 = inMatrix[1][1], a12 = inMatrix[1][2];
-  	float a20 = inMatrix[2][0], a21 = inMatrix[2][1], a22 = inMatrix[2][2];
+    float a00 = inMatrix[0][0], a01 = inMatrix[0][1], a02 = inMatrix[0][2];
+      float a10 = inMatrix[1][0], a11 = inMatrix[1][1], a12 = inMatrix[1][2];
+      float a20 = inMatrix[2][0], a21 = inMatrix[2][1], a22 = inMatrix[2][2];
 
-  	float b01 = a22 * a11 - a12 * a21;
-  	float b11 = -a22 * a10 + a12 * a20;
-  	float b21 = a21 * a10 - a11 * a20;
+      float b01 = a22 * a11 - a12 * a21;
+      float b11 = -a22 * a10 + a12 * a20;
+      float b21 = a21 * a10 - a11 * a20;
 
-  	float det = a00 * b01 + a01 * b11 + a02 * b21;
+      float det = a00 * b01 + a01 * b11 + a02 * b21;
 
-  	return mat3(b01, (-a22 * a01 + a02 * a21), (a12 * a01 - a02 * a11),
+      return mat3(b01, (-a22 * a01 + a02 * a21), (a12 * a01 - a02 * a11),
               b11, (a22 * a00 - a02 * a20), (-a12 * a00 + a02 * a10),
               b21, (-a21 * a00 + a01 * a20), (a11 * a00 - a01 * a10)) / det;
 }
 
 float computeFallOff(float value, vec2 clipSpace, float frustumEdgeFalloff)
 {
-	float mask = smoothstep(1.0 - frustumEdgeFalloff, 1.0, clamp(dot(clipSpace, clipSpace), 0., 1.));
-	return mix(value, 1.0, mask);
+    float mask = smoothstep(1.0 - frustumEdgeFalloff, 1.0, clamp(dot(clipSpace, clipSpace), 0., 1.));
+    return mix(value, 1.0, mask);
 }
 
 vec3 applyEaseInOut(vec3 x){
-	return x * x * (3.0 - 2.0 * x);
+    return x * x * (3.0 - 2.0 * x);
 }
 
 vec3 toLinearSpace(vec3 color)
 {
-	return pow(color, vec3(LinearEncodePowerApprox));
+    return pow(color, vec3(LinearEncodePowerApprox));
 }
 
 vec3 toGammaSpace(vec3 color)
@@ -67,12 +67,37 @@ float getLuminance(vec3 color)
 
 // https://stackoverflow.com/questions/4200224/random-noise-functions-for-glsl
 float getRand(vec2 seed) {
-	return fract(sin(dot(seed.xy ,vec2(12.9898,78.233))) * 43758.5453);
+    return fract(sin(dot(seed.xy ,vec2(12.9898,78.233))) * 43758.5453);
 }
 
 float dither(vec2 seed, float varianceAmount) {
-	float rand = getRand(seed);
-	float dither = mix(-varianceAmount/255.0, varianceAmount/255.0, rand);
-	
-	return dither;
+    float rand = getRand(seed);
+    float dither = mix(-varianceAmount/255.0, varianceAmount/255.0, rand);
+    
+    return dither;
+}
+
+// Check if configurable value is needed.
+const float rgbdMaxRange = 255.0;
+
+vec4 toRGBD(vec3 color) {
+    float maxRGB = max(0.0000001, max(color.r, max(color.g, color.b)));
+    float D      = max(rgbdMaxRange / maxRGB, 1.);
+    D            = clamp(floor(D) / 255.0, 0., 1.);
+    // vec3 rgb = color.rgb * (D * (255.0 / rgbdMaxRange));
+    vec3 rgb = color.rgb * D;
+    
+    // Helps with png quantization.
+    rgb = toGammaSpace(rgb);
+
+    return vec4(rgb, D); 
+}
+
+vec3 fromRGBD(vec4 rgbd) {
+    // Helps with png quantization.
+    rgbd.rgb = toLinearSpace(rgbd.rgb);
+
+    // return rgbd.rgb * ((rgbdMaxRange / 255.0) / rgbd.a);
+
+    return rgbd.rgb * rgbd.a;
 }

+ 15 - 11
src/Shaders/background.fragment.fx

@@ -137,7 +137,7 @@ void main(void) {
 #endif
 
 // _____________________________ REFLECTION ______________________________________
-vec3 reflectionColor = vec3(1., 1., 1.);
+vec4 reflectionColor = vec4(1., 1., 1., 1.);
 #ifdef REFLECTION
     vec3 reflectionVector = computeReflectionCoords(vec4(vPositionW, 1.0), normalW);
     #ifdef REFLECTIONMAP_OPPOSITEZ
@@ -161,41 +161,45 @@ vec3 reflectionColor = vec3(1., 1., 1.);
         #ifdef TEXTURELODSUPPORT
             // Apply environment convolution scale/offset filter tuning parameters to the mipmap LOD selection
             reflectionLOD = reflectionLOD * log2(vReflectionMicrosurfaceInfos.x) * vReflectionMicrosurfaceInfos.y + vReflectionMicrosurfaceInfos.z;
-            reflectionColor = sampleReflectionLod(reflectionSampler, reflectionCoords, reflectionLOD).rgb;
+            reflectionColor = sampleReflectionLod(reflectionSampler, reflectionCoords, reflectionLOD);
         #else
             float lodReflectionNormalized = clamp(reflectionLOD, 0., 1.);
             float lodReflectionNormalizedDoubled = lodReflectionNormalized * 2.0;
 
-            vec3 reflectionSpecularMid = sampleReflection(reflectionSampler, reflectionCoords).rgb;
+            vec4 reflectionSpecularMid = sampleReflection(reflectionSampler, reflectionCoords);
             if(lodReflectionNormalizedDoubled < 1.0){
                 reflectionColor = mix(
-                    sampleReflection(reflectionSamplerHigh, reflectionCoords).rgb,
+                    sampleReflection(reflectionSamplerHigh, reflectionCoords),
                     reflectionSpecularMid,
                     lodReflectionNormalizedDoubled
                 );
             } else {
                 reflectionColor = mix(
                     reflectionSpecularMid,
-                    sampleReflection(reflectionSamplerLow, reflectionCoords).rgb,
+                    sampleReflection(reflectionSamplerLow, reflectionCoords),
                     lodReflectionNormalizedDoubled - 1.0
                 );
             }
         #endif
     #else
         vec4 reflectionSample = sampleReflection(reflectionSampler, reflectionCoords);
-        reflectionColor = reflectionSample.rgb;
+        reflectionColor = reflectionSample;
+    #endif
+
+    #ifdef RGBDREFLECTION
+        reflectionColor.rgb = fromRGBD(reflectionColor);
     #endif
 
     #ifdef GAMMAREFLECTION
-        reflectionColor = toLinearSpace(reflectionColor.rgb);
+        reflectionColor.rgb = toLinearSpace(reflectionColor.rgb);
     #endif
 
     #ifdef REFLECTIONBGR
-        reflectionColor = reflectionColor.bgr;
+        reflectionColor.rgb = reflectionColor.bgr;
     #endif
 
     // _____________________________ Levels _____________________________________
-    reflectionColor *= vReflectionInfos.x;
+    reflectionColor.rgb *= vReflectionInfos.x;
 #endif
 
 // _____________________________ Diffuse Information _______________________________
@@ -221,7 +225,7 @@ float finalAlpha = alpha;
 #ifdef REFLECTIONFRESNEL
     vec3 colorBase = diffuseColor;
 #else
-    vec3 colorBase = reflectionColor * diffuseColor;
+    vec3 colorBase = reflectionColor.rgb * diffuseColor;
 #endif
     colorBase = max(colorBase, 0.0);
 
@@ -254,7 +258,7 @@ float finalAlpha = alpha;
         reflectionAmount *= reflectionDistanceFalloff;
     #endif
 
-    finalColor = mix(finalColor, reflectionColor, clamp(reflectionAmount, 0., 1.));
+    finalColor = mix(finalColor, reflectionColor.rgb, clamp(reflectionAmount, 0., 1.));
 #endif
 
 #ifdef OPACITYFRESNEL

+ 26 - 18
src/Shaders/pbr.fragment.fx

@@ -442,7 +442,7 @@ void main(void) {
 
     // _____________________________ Refraction Info _______________________________________
     #ifdef REFRACTION
-        vec3 environmentRefraction = vec3(0., 0., 0.);
+        vec4 environmentRefraction = vec4(0., 0., 0., 0.);
 
         vec3 refractionVector = refract(-viewDirectionW, normalW, vRefractionInfos.y);
         #ifdef REFRACTIONMAP_OPPOSITEZ
@@ -487,38 +487,42 @@ void main(void) {
                 float requestedRefractionLOD = refractionLOD;
             #endif
 
-            environmentRefraction = sampleRefractionLod(refractionSampler, refractionCoords, requestedRefractionLOD).rgb;
+            environmentRefraction = sampleRefractionLod(refractionSampler, refractionCoords, requestedRefractionLOD);
         #else
             float lodRefractionNormalized = clamp(refractionLOD / log2(vRefractionMicrosurfaceInfos.x), 0., 1.);
             float lodRefractionNormalizedDoubled = lodRefractionNormalized * 2.0;
 
-            vec3 environmentRefractionMid = sampleRefraction(refractionSampler, refractionCoords).rgb;
+            vec4 environmentRefractionMid = sampleRefraction(refractionSampler, refractionCoords);
             if(lodRefractionNormalizedDoubled < 1.0){
                 environmentRefraction = mix(
-                    sampleRefraction(refractionSamplerHigh, refractionCoords).rgb,
+                    sampleRefraction(refractionSamplerHigh, refractionCoords),
                     environmentRefractionMid,
                     lodRefractionNormalizedDoubled
                 );
             }else{
                 environmentRefraction = mix(
                     environmentRefractionMid,
-                    sampleRefraction(refractionSamplerLow, refractionCoords).rgb,
+                    sampleRefraction(refractionSamplerLow, refractionCoords),
                     lodRefractionNormalizedDoubled - 1.0
                 );
             }
         #endif
 
         #ifdef GAMMAREFRACTION
-            environmentRefraction = toLinearSpace(environmentRefraction.rgb);
+            environmentRefraction.rgb = fromRGBD(environmentRefraction);
+        #endif
+
+        #ifdef RGBDREFRACTION
+            environmentRefraction.rgb = toLinearSpace(environmentRefraction.rgb);
         #endif
 
         // _____________________________ Levels _____________________________________
-        environmentRefraction *= vRefractionInfos.x;
+        environmentRefraction.rgb *= vRefractionInfos.x;
     #endif
 
     // _____________________________ Reflection Info _______________________________________
     #ifdef REFLECTION
-        vec3 environmentRadiance = vec3(0., 0., 0.);
+        vec4 environmentRadiance = vec4(0., 0., 0., 0.);
         vec3 environmentIrradiance = vec3(0., 0., 0.);
 
         vec3 reflectionVector = computeReflectionCoords(vec4(vPositionW, 1.0), normalW);
@@ -564,29 +568,33 @@ void main(void) {
                 float requestedReflectionLOD = reflectionLOD;
             #endif
 
-            environmentRadiance = sampleReflectionLod(reflectionSampler, reflectionCoords, requestedReflectionLOD).rgb;
+            environmentRadiance = sampleReflectionLod(reflectionSampler, reflectionCoords, requestedReflectionLOD);
         #else
             float lodReflectionNormalized = clamp(reflectionLOD / log2(vReflectionMicrosurfaceInfos.x), 0., 1.);
             float lodReflectionNormalizedDoubled = lodReflectionNormalized * 2.0;
 
-            vec3 environmentSpecularMid = sampleReflection(reflectionSampler, reflectionCoords).rgb;
+            vec4 environmentSpecularMid = sampleReflection(reflectionSampler, reflectionCoords);
             if(lodReflectionNormalizedDoubled < 1.0){
                 environmentRadiance = mix(
-                    sampleReflection(reflectionSamplerHigh, reflectionCoords).rgb,
+                    sampleReflection(reflectionSamplerHigh, reflectionCoords),
                     environmentSpecularMid,
                     lodReflectionNormalizedDoubled
                 );
             }else{
                 environmentRadiance = mix(
                     environmentSpecularMid,
-                    sampleReflection(reflectionSamplerLow, reflectionCoords).rgb,
+                    sampleReflection(reflectionSamplerLow, reflectionCoords),
                     lodReflectionNormalizedDoubled - 1.0
                 );
             }
         #endif
 
+        #ifdef RGBDREFLECTION
+            environmentRadiance.rgb = fromRGBD(environmentRadiance);
+        #endif
+
         #ifdef GAMMAREFLECTION
-            environmentRadiance = toLinearSpace(environmentRadiance.rgb);
+            environmentRadiance.rgb = toLinearSpace(environmentRadiance.rgb);
         #endif
 
         // _____________________________ Irradiance ________________________________
@@ -603,8 +611,8 @@ void main(void) {
         #endif
 
         // _____________________________ Levels _____________________________________
-        environmentRadiance *= vReflectionInfos.x;
-        environmentRadiance *= vReflectionColor.rgb;
+        environmentRadiance.rgb *= vReflectionInfos.x;
+        environmentRadiance.rgb *= vReflectionColor.rgb;
         environmentIrradiance *= vReflectionColor.rgb;
     #endif
 
@@ -691,7 +699,7 @@ void main(void) {
             environmentIrradiance *= alpha;
 
             // Tint reflectance
-            environmentRefraction *= tint;
+            environmentRefraction.rgb *= tint;
 
             // Put alpha back to 1;
             alpha = 1.0;
@@ -731,7 +739,7 @@ void main(void) {
 
     // _____________________________ Radiance ________________________________________
     #ifdef REFLECTION
-        vec3 finalRadiance = environmentRadiance;
+        vec3 finalRadiance = environmentRadiance.rgb;
         finalRadiance *= specularEnvironmentReflectance;
 
         // Full value needed for alpha. 
@@ -740,7 +748,7 @@ void main(void) {
 
     // _____________________________ Refraction ______________________________________
     #ifdef REFRACTION
-        vec3 finalRefraction = environmentRefraction;
+        vec3 finalRefraction = environmentRefraction.rgb;
         finalRefraction *= refractance;
     #endif
 

+ 10 - 0
src/Shaders/rgbdDecode.fragment.fx

@@ -0,0 +1,10 @@
+// Samplers
+varying vec2 vUV;
+uniform sampler2D textureSampler;
+
+#include<helperFunctions>
+
+void main(void) 
+{
+	gl_FragColor = vec4(fromRGBD(texture2D(textureSampler, vUV)), 1.0);
+}

+ 10 - 0
src/Shaders/rgbdEncode.fragment.fx

@@ -0,0 +1,10 @@
+// Samplers
+varying vec2 vUV;
+uniform sampler2D textureSampler;
+
+#include<helperFunctions>
+
+void main(void) 
+{
+	gl_FragColor = toRGBD(texture2D(textureSampler, vUV).rgb);
+}

+ 686 - 0
src/Tools/babylon.environmentTextureTools.ts

@@ -0,0 +1,686 @@
+module BABYLON {
+    /**
+     * Raw texture data and descriptor sufficient for WebGL texture upload
+     */
+    export interface EnvironmentTextureInfo {
+        /**
+         * Version of the environment map
+         */
+        version: number;
+
+        /**
+         * Width of image
+         */
+        width: number;
+
+        /**
+         * Irradiance information stored in the file.
+         */
+        irradiance: any;
+
+        /**
+         * Specular information stored in the file.
+         */
+        specular: any;
+    }
+
+    /**
+     * Defines One Image in the file. It requires only the position in the file 
+     * as well as the length.
+     */
+    interface BufferImageData {
+        /**
+         * Length of the image data.
+         */
+        length: number;
+        /**
+         * Position of the data from the null terminator delimiting the end of the JSON.
+         */
+        position: number;
+    }
+
+    /**
+     * Defines the specular data enclosed in the file.
+     * This corresponds to the version 1 of the data.
+     */
+    interface EnvironmentTextureSpecularInfoV1 {
+        /**
+         * Defines where the specular Payload is located. It is a runtime value only not stored in the file.
+         */
+        specularDataPosition?: number;
+        /**
+         * This contains all the images data needed to reconstruct the cubemap.
+         */
+        mipmaps: Array<BufferImageData>
+    }
+
+    /**
+     * Defines the required storage to save the environment irradiance information.
+     */
+    interface EnvironmentTextureIrradianceInfoV1 {
+        polynomials: boolean;
+
+        l00: Array<number>;
+
+        l1_1: Array<number>;
+        l10: Array<number>;
+        l11: Array<number>;
+
+        l2_2: Array<number>;
+        l2_1: Array<number>;
+        l20: Array<number>;
+        l21: Array<number>;
+        l22: Array<number>;
+
+        x: Array<number>;
+        y: Array<number>;
+        z: Array<number>;
+
+        xx: Array<number>;
+        yy: Array<number>;
+        zz: Array<number>;
+
+        yz: Array<number>;
+        zx: Array<number>;
+        xy: Array<number>;
+    }
+
+    /**
+     * Sets of helpers addressing the serialization and deserialization of environment texture
+     * stored in a BabylonJS env file.
+     * Those files are usually stored as .env files.
+     */
+    export class EnvironmentTextureTools {
+
+        /**
+         * Magic number identifying the env file.
+         */
+        private static _MagicBytes = [0x86, 0x16, 0x87, 0x96, 0xf6, 0xd6, 0x96, 0x36];
+
+        /**
+         * Gets the environment info from an env file.
+         * @param data The array buffer containing the .env bytes.
+         * @returns the environment file info (the json header) if successfully parsed.
+         */
+        public static GetEnvInfo(data: ArrayBuffer): Nullable<EnvironmentTextureInfo> {
+            let dataView = new DataView(data);
+            let pos = 0;
+
+            for (let i = 0; i < EnvironmentTextureTools._MagicBytes.length; i++) {
+                if (dataView.getUint8(pos++) !== EnvironmentTextureTools._MagicBytes[i]) {
+                    Tools.Error('Not a babylon environment map');
+                    return null;
+                }
+            }
+            
+            // Read json manifest - collect characters up to null terminator
+            let manifestString = '';
+            let charCode = 0x00;
+            while ((charCode = dataView.getUint8(pos++))) {
+                manifestString += String.fromCharCode(charCode);
+            }
+
+            let manifest: EnvironmentTextureInfo = JSON.parse(manifestString);
+            if (manifest.specular) {
+                // Extend the header with the position of the payload.
+                manifest.specular.specularDataPosition = pos;
+            }
+
+            return manifest;
+        }
+
+        /**
+         * Creates an environment texture from a loaded cube texture.
+         * @param texture defines the cube texture to convert in env file
+         * @return a promise containing the environment data if succesfull.
+         */
+        public static CreateEnvTextureAsync(texture: CubeTexture): Promise<ArrayBuffer> {
+            let internalTexture = texture.getInternalTexture();
+            if (!internalTexture) {
+                return Promise.reject("The cube texture is invalid.");
+            }
+
+            if (!texture._prefiltered) {
+                return Promise.reject("The cube texture is invalid (not prefiltered).");
+            }
+
+            let engine = internalTexture.getEngine();
+            if (engine && engine.premultipliedAlpha) {
+                return Promise.reject("Env texture can only be created when the engine is created with the premultipliedAlpha option.");
+            }
+
+            let canvas = engine.getRenderingCanvas();
+            if (!canvas) {
+                return Promise.reject("Env texture can only be created when the engine is associated to a canvas.");
+            }
+
+            let textureType = Engine.TEXTURETYPE_FLOAT;
+            if (!engine.getCaps().textureFloatRender) {
+                textureType = Engine.TEXTURETYPE_HALF_FLOAT;
+                if (!engine.getCaps().textureHalfFloatRender) {
+                    return Promise.reject("Env texture can only be created when the browser supports half float or full float rendering.");
+                }
+            }
+
+            let cubeWidth = internalTexture.width;
+            let hostingScene = new Scene(engine);
+            let specularTextures: { [key: number]: ArrayBuffer } = { };
+            let promises: Promise<void>[] = [];
+
+            // Read and collect all mipmaps data from the cube.
+            let mipmapsCount = Scalar.Log2(internalTexture.width);
+            mipmapsCount = Math.round(mipmapsCount);
+            for (let i = 0; i <= mipmapsCount; i++) {
+                let faceWidth = Math.pow(2, mipmapsCount - i);
+
+                // All faces of the cube.
+                for (let face = 0; face < 6; face++) {
+                    let data = texture.readPixels(face, i);
+
+                    // Creates a temp texture with the face data.
+                    let tempTexture = engine.createRawTexture(data, faceWidth, faceWidth, Engine.TEXTUREFORMAT_RGBA, false, false, Texture.NEAREST_SAMPLINGMODE, null, textureType);
+                    // And rgbdEncode them. 
+                    let promise = new Promise<void>((resolve, reject) => {
+                        let rgbdPostProcess = new PostProcess("rgbdEncode", "rgbdEncode", null, null, 1, null, Texture.NEAREST_SAMPLINGMODE, engine, false, undefined, Engine.TEXTURETYPE_UNSIGNED_INT, undefined, null, false);
+                        rgbdPostProcess.getEffect().executeWhenCompiled(() => {
+                            rgbdPostProcess.onApply = (effect) => {
+                                effect._bindTexture("textureSampler", tempTexture);
+                            }
+            
+                            // As the process needs to happen on the main canvas, keep track of the current size
+                            let currentW = engine.getRenderWidth();
+                            let currentH = engine.getRenderHeight();
+
+                            // Set the desired size for the texture
+                            engine.setSize(faceWidth, faceWidth);
+                            hostingScene.postProcessManager.directRender([rgbdPostProcess], null);
+
+                            // Reading datas from WebGL
+                            canvas!.toBlob((blob) => {
+                                let fileReader = new FileReader();
+                                fileReader.onload = (event) => {
+                                    let arrayBuffer = event.target!.result as ArrayBuffer;
+                                    specularTextures[i * 6 + face] = arrayBuffer;
+                                    resolve();
+                                };
+                                fileReader.readAsArrayBuffer(blob!);
+                            });
+
+                            // Reapply the previous canvas size
+                            engine.setSize(currentW, currentH);
+                        });
+                    });
+                    promises.push(promise);
+                }
+            }
+
+            // Once all the textures haves been collected as RGBD stored in PNGs
+            return Promise.all(promises).then(() => {
+                // We can delete the hosting scene keeping track of all the creation objects
+                hostingScene.dispose();
+
+                // Creates the json header for the env texture
+                let info: EnvironmentTextureInfo = {
+                    version: 1,
+                    width: cubeWidth,
+                    irradiance: this._CreateEnvTextureIrradiance(texture),
+                    specular: {
+                        mipmaps: []
+                    }
+                };
+
+                // Sets the specular image data information
+                let position = 0;
+                for (let i = 0; i <= mipmapsCount; i++) {
+                    for (let face = 0; face < 6; face++) {
+                        let byteLength = specularTextures[i * 6 + face].byteLength;
+                        info.specular.mipmaps.push({
+                            length: byteLength,
+                            position: position
+                        });
+                        position += byteLength;
+                    }
+                }
+
+                // Encode the JSON as an array buffer
+                let infoString = JSON.stringify(info);
+                let infoBuffer = new ArrayBuffer(infoString.length + 1);
+                let infoView = new Uint8Array(infoBuffer); // Limited to ascii subset matching unicode.
+                for (let i= 0, strLen = infoString.length; i < strLen; i++) {
+                    infoView[i] = infoString.charCodeAt(i);
+                }
+                // Ends up with a null terminator for easier parsing
+                infoView[infoString.length] = 0x00;
+
+                // Computes the final required size and creates the storage
+                let totalSize = EnvironmentTextureTools._MagicBytes.length + position + infoBuffer.byteLength;
+                let finalBuffer = new ArrayBuffer(totalSize);
+                let finalBufferView = new Uint8Array(finalBuffer);
+                let dataView = new DataView(finalBuffer);
+
+                // Copy the magic bytes identifying the file in
+                let pos = 0;
+                for (let i = 0; i < EnvironmentTextureTools._MagicBytes.length; i++) {
+                    dataView.setUint8(pos++, EnvironmentTextureTools._MagicBytes[i]);
+                }
+
+                // Add the json info
+                finalBufferView.set(new Uint8Array(infoBuffer), pos);
+                pos += infoBuffer.byteLength;
+
+                // Finally inserts the texture data
+                for (let i = 0; i <= mipmapsCount; i++) {
+                    for (let face = 0; face < 6; face++) {
+                        let dataBuffer = specularTextures[i * 6 + face];
+                        finalBufferView.set(new Uint8Array(dataBuffer), pos);
+                        pos += dataBuffer.byteLength;
+                    }
+                }
+
+                // Voila
+                return finalBuffer;
+            });
+        }
+
+        /**
+         * Creates a JSON representation of the spherical data.
+         * @param texture defines the texture containing the polynomials
+         * @return the JSON representation of the spherical info
+         */
+        private static _CreateEnvTextureIrradiance(texture: CubeTexture) : Nullable<EnvironmentTextureIrradianceInfoV1> {
+            let polynmials = texture.sphericalPolynomial;
+            if (polynmials == null) {
+                return null;
+            }
+
+            return {
+                polynomials: true,
+
+                x: [polynmials.x.x, polynmials.x.y, polynmials.x.z],
+                y: [polynmials.y.x, polynmials.y.y, polynmials.y.z],
+                z: [polynmials.z.x, polynmials.z.y, polynmials.z.z],
+
+                xx: [polynmials.xx.x, polynmials.xx.y, polynmials.xx.z],
+                yy: [polynmials.yy.x, polynmials.yy.y, polynmials.yy.z],
+                zz: [polynmials.zz.x, polynmials.zz.y, polynmials.zz.z],
+
+                yz: [polynmials.yz.x, polynmials.yz.y, polynmials.yz.z],
+                zx: [polynmials.zx.x, polynmials.zx.y, polynmials.zx.z],
+                xy: [polynmials.xy.x, polynmials.xy.y, polynmials.xy.z]
+            } as any;
+        }
+
+        /**
+         * Uploads the texture info contained in the env file to te GPU.
+         * @param texture defines the internal texture to upload to
+         * @param arrayBuffer defines the buffer cotaining the data to load
+         * @param info defines the texture info retrieved through the GetEnvInfo method
+         * @returns a promise
+         */
+        public static UploadLevelsAsync(texture: InternalTexture, arrayBuffer: any, info: EnvironmentTextureInfo): Promise<void> {
+            if (info.version !== 1) {
+                Tools.Warn('Unsupported babylon environment map version "' + info.version + '"');
+            }
+
+            let specularInfo = info.specular as EnvironmentTextureSpecularInfoV1;
+            if (!specularInfo) {
+                // Nothing else parsed so far
+                return Promise.resolve();
+            }
+
+            // Double checks the enclosed info
+            let mipmapsCount = Scalar.Log2(info.width);
+            mipmapsCount = Math.round(mipmapsCount) + 1;
+            if (specularInfo.mipmaps.length !== 6 * mipmapsCount) {
+                Tools.Warn('Unsupported specular mipmaps number "' + specularInfo.mipmaps.length + '"');
+            }
+
+            // Gets everything ready.
+            let engine = texture.getEngine();
+            let expandTexture = false;
+            let generateNonLODTextures = false;
+            let rgbdPostProcess: Nullable<PostProcess> = null;
+            let cubeRtt: Nullable<InternalTexture> = null;
+            let lodTextures: Nullable<{ [lod: number]: BaseTexture}> = null;
+            let caps = engine.getCaps();
+
+            texture.format = Engine.TEXTUREFORMAT_RGBA;
+            texture.type = Engine.TEXTURETYPE_UNSIGNED_INT;
+            texture.samplingMode = Texture.TRILINEAR_SAMPLINGMODE;
+
+            // Add extra process if texture lod is not supported
+            if (!caps.textureLOD) {
+                expandTexture = false;
+                generateNonLODTextures = true;
+                lodTextures = { };
+            }
+            // in webgl 1 there are no ways to either render or copy lod level information for float textures.
+            else if (engine.webGLVersion < 2) {
+                expandTexture = false;
+            }
+            // If half float available we can uncompress the texture
+            else if (caps.textureHalfFloatRender && caps.textureHalfFloatLinearFiltering) {
+                expandTexture = true;
+                texture.type = Engine.TEXTURETYPE_HALF_FLOAT;
+            }
+            // If full float available we can uncompress the texture
+            else if (caps.textureFloatRender && caps.textureFloatLinearFiltering) {
+                expandTexture = true;
+                texture.type = Engine.TEXTURETYPE_FLOAT;
+            }
+
+            // Expand the texture if possible
+            if (expandTexture) {
+                // Simply run through the decode PP
+                rgbdPostProcess = new PostProcess("rgbdDecode", "rgbdDecode", null, null, 1, null, Texture.TRILINEAR_SAMPLINGMODE, engine, false, undefined, texture.type, undefined, null, false);
+                
+                texture._isRGBD = false;
+                texture.invertY = false;
+                cubeRtt = engine.createRenderTargetCubeTexture(texture.width, {
+                    generateDepthBuffer: false,
+                    generateMipMaps: true,
+                    generateStencilBuffer: false,
+                    samplingMode: Texture.TRILINEAR_SAMPLINGMODE,
+                    type: texture.type,
+                    format: Engine.TEXTUREFORMAT_RGBA
+                });
+            }
+            else {
+                texture._isRGBD = true;
+                texture.invertY = true;
+
+                // In case of missing support, applies the same patch than DDS files.
+                if (generateNonLODTextures) {
+                    let mipSlices = 3;
+                    let scale = texture._lodGenerationScale;
+                    let offset = texture._lodGenerationOffset;
+    
+                    for (let i = 0; i < mipSlices; i++) {
+                        //compute LOD from even spacing in smoothness (matching shader calculation)
+                        let smoothness = i / (mipSlices - 1);
+                        let roughness = 1 - smoothness;
+    
+                        let minLODIndex = offset; // roughness = 0
+                        let maxLODIndex = Scalar.Log2(info.width) * scale + offset; // roughness = 1
+    
+                        let lodIndex = minLODIndex + (maxLODIndex - minLODIndex) * roughness;
+                        let mipmapIndex = Math.round(Math.min(Math.max(lodIndex, 0), maxLODIndex));
+    
+                        let glTextureFromLod = new InternalTexture(engine, InternalTexture.DATASOURCE_TEMP);
+                        glTextureFromLod.isCube = true;
+                        glTextureFromLod.invertY = true;
+                        glTextureFromLod.generateMipMaps = false;
+                        engine.updateTextureSamplingMode(Texture.LINEAR_LINEAR, glTextureFromLod);
+    
+                        // Wrap in a base texture for easy binding.
+                        let lodTexture = new BaseTexture(null);
+                        lodTexture.isCube = true;
+                        lodTexture._texture = glTextureFromLod;
+                        lodTextures![mipmapIndex] = lodTexture;
+
+                        switch (i) {
+                            case 0:
+                            texture._lodTextureLow = lodTexture;
+                            break;
+                            case 1:
+                            texture._lodTextureMid = lodTexture;
+                            break;
+                            case 2:
+                            texture._lodTextureHigh = lodTexture;
+                            break;
+                        }
+                    }
+                }
+            }
+
+            let promises: Promise<void>[] = [];
+            // All mipmaps
+            for (let i = 0; i < mipmapsCount; i++) {
+                // All faces
+                for (let face = 0; face < 6; face++) {
+                    // Retrieves the face data
+                    let imageData = specularInfo.mipmaps[i * 6 + face];
+                    let bytes = new Uint8Array(arrayBuffer, specularInfo.specularDataPosition! + imageData.position, imageData.length);
+
+                    // Constructs an image element from bytes
+                    let blob = new Blob([bytes], { type: 'image/png' });
+                    let url = URL.createObjectURL(blob);
+                    let image = new Image();
+                    image.src = url;
+
+                    // Enqueue promise to upload to the texture.
+                    let promise = new Promise<void>((resolve, reject) => {;
+                        image.onload = () => {
+                            if (expandTexture) {
+                                let tempTexture = engine.createTexture(null, true, true, null, Texture.NEAREST_SAMPLINGMODE, null,
+                                (message) => {
+                                    reject(message);
+                                },
+                                image);
+
+                                rgbdPostProcess!.getEffect().executeWhenCompiled(() => {
+                                    // Uncompress the data to a RTT
+                                    rgbdPostProcess!.onApply = (effect) => {
+                                        effect._bindTexture("textureSampler", tempTexture);
+                                        effect.setFloat2("scale", 1, 1);
+                                    }
+                                    
+                                    engine.scenes[0].postProcessManager.directRender([rgbdPostProcess!], cubeRtt, true, face, i);
+
+                                    // Cleanup
+                                    engine.restoreDefaultFramebuffer();
+                                    tempTexture.dispose();
+                                    window.URL.revokeObjectURL(url);
+                                    resolve();
+                                });
+                            }
+                            else {
+                                engine._uploadImageToTexture(texture, face, i, image);
+
+                                // Upload the face to the none lod texture support
+                                if (generateNonLODTextures) {
+                                    let lodTexture = lodTextures![i];
+                                    if (lodTexture) {
+                                        engine._uploadImageToTexture(lodTexture._texture!, face, 0, image);
+                                    }
+                                }
+                                resolve();
+                            }
+                        };
+                        image.onerror = (error) => {
+                            reject(error);
+                        };
+                    });
+                    promises.push(promise);
+                }
+            }
+
+            // Once all done, finishes the cleanup and return
+            return Promise.all(promises).then(() => {
+                // Relase temp RTT.
+                if (cubeRtt) {
+                    engine._releaseFramebufferObjects(cubeRtt);
+                    cubeRtt._swapAndDie(texture);
+                }
+                // Relase temp Post Process.
+                if (rgbdPostProcess) {
+                    rgbdPostProcess.dispose();
+                }
+                // Flag internal texture as ready in case they are in use.
+                if (generateNonLODTextures) {
+                    if (texture._lodTextureHigh && texture._lodTextureHigh._texture) {
+                        texture._lodTextureHigh._texture.isReady = true;
+                    }
+                    if (texture._lodTextureMid && texture._lodTextureMid._texture) {
+                        texture._lodTextureMid._texture.isReady = true;
+                    }
+                    if (texture._lodTextureLow && texture._lodTextureLow._texture) {
+                        texture._lodTextureLow._texture.isReady = true;
+                    }
+                }
+            });
+        }
+
+        /**
+         * Uploads spherical polynomials information to the texture.
+         * @param texture defines the texture we are trying to upload the information to
+         * @param arrayBuffer defines the array buffer holding the data
+         * @param info defines the environment texture info retrieved through the GetEnvInfo method
+         */
+        public static UploadPolynomials(texture: InternalTexture, arrayBuffer: any, info: EnvironmentTextureInfo): void {
+            if (info.version !== 1) {
+                Tools.Warn('Unsupported babylon environment map version "' + info.version + '"');
+            }
+
+            let irradianceInfo = info.irradiance as EnvironmentTextureIrradianceInfoV1;
+            if (!irradianceInfo) {
+                return;
+            }
+            
+            //harmonics now represent radiance
+            texture._sphericalPolynomial = new SphericalPolynomial();
+
+            if (irradianceInfo.polynomials) {
+                EnvironmentTextureTools._UploadSP(irradianceInfo, texture._sphericalPolynomial);
+            }
+            else {
+                // convert From SH to SP.
+                EnvironmentTextureTools._ConvertSHIrradianceToLambertianRadiance(irradianceInfo);
+                EnvironmentTextureTools._ConvertSHToSP(irradianceInfo, texture._sphericalPolynomial);
+            }
+        }
+
+        /**
+         * Upload spherical polynomial coefficients to the texture
+         * @param polynmials Spherical polynmial coefficients (9)
+         * @param outPolynomialCoefficents Polynomial coefficients (9) object to store result
+         */
+        private static _UploadSP(polynmials: EnvironmentTextureIrradianceInfoV1, outPolynomialCoefficents: SphericalPolynomial) {
+            outPolynomialCoefficents.x.x = polynmials.x[0];
+            outPolynomialCoefficents.x.y = polynmials.x[1];
+            outPolynomialCoefficents.x.z = polynmials.x[2];
+
+            outPolynomialCoefficents.y.x = polynmials.y[0];
+            outPolynomialCoefficents.y.y = polynmials.y[1];
+            outPolynomialCoefficents.y.z = polynmials.y[2];
+
+            outPolynomialCoefficents.z.x = polynmials.z[0];
+            outPolynomialCoefficents.z.y = polynmials.z[1];
+            outPolynomialCoefficents.z.z = polynmials.z[2];
+
+            //xx
+            outPolynomialCoefficents.xx.x = polynmials.xx[0];
+            outPolynomialCoefficents.xx.y = polynmials.xx[1];
+            outPolynomialCoefficents.xx.z = polynmials.xx[2];
+
+            outPolynomialCoefficents.yy.x = polynmials.yy[0];
+            outPolynomialCoefficents.yy.y = polynmials.yy[1];
+            outPolynomialCoefficents.yy.z = polynmials.yy[2];
+
+            outPolynomialCoefficents.zz.x = polynmials.zz[0];
+            outPolynomialCoefficents.zz.y = polynmials.zz[1];
+            outPolynomialCoefficents.zz.z = polynmials.zz[2];
+
+            //yz
+            outPolynomialCoefficents.yz.x = polynmials.yz[0];
+            outPolynomialCoefficents.yz.y = polynmials.yz[1];
+            outPolynomialCoefficents.yz.z = polynmials.yz[2];
+
+            outPolynomialCoefficents.zx.x = polynmials.zx[0];
+            outPolynomialCoefficents.zx.y = polynmials.zx[1];
+            outPolynomialCoefficents.zx.z = polynmials.zx[2];
+
+            outPolynomialCoefficents.xy.x = polynmials.xy[0];
+            outPolynomialCoefficents.xy.y = polynmials.xy[1];
+            outPolynomialCoefficents.xy.z = polynmials.xy[2];
+        }
+
+        /**
+         * Convert from irradiance to outgoing radiance for Lambertian BDRF, suitable for efficient shader evaluation.
+         *	  L = (1/pi) * E * rho
+         * 
+         * This is done by an additional scale by 1/pi, so is a fairly trivial operation but important conceptually.
+         * @param harmonics Spherical harmonic coefficients (9)
+         */
+        private static _ConvertSHIrradianceToLambertianRadiance(harmonics: any): void {
+            let scaleFactor = 1 / Math.PI;
+            // The resultant SH now represents outgoing radiance, so includes the Lambert 1/pi normalisation factor but without albedo (rho) applied
+            // (The pixel shader must apply albedo after texture fetches, etc).
+            harmonics.l00[0] *= scaleFactor;
+            harmonics.l00[1] *= scaleFactor;
+            harmonics.l00[2] *= scaleFactor;
+            harmonics.l1_1[0] *= scaleFactor;
+            harmonics.l1_1[1] *= scaleFactor;
+            harmonics.l1_1[2] *= scaleFactor;
+            harmonics.l10[0] *= scaleFactor;
+            harmonics.l10[1] *= scaleFactor;
+            harmonics.l10[2] *= scaleFactor;
+            harmonics.l11[0] *= scaleFactor;
+            harmonics.l11[1] *= scaleFactor;
+            harmonics.l11[2] *= scaleFactor;
+            harmonics.l2_2[0] *= scaleFactor;
+            harmonics.l2_2[1] *= scaleFactor;
+            harmonics.l2_2[2] *= scaleFactor;
+            harmonics.l2_1[0] *= scaleFactor;
+            harmonics.l2_1[1] *= scaleFactor;
+            harmonics.l2_1[2] *= scaleFactor;
+            harmonics.l20[0] *= scaleFactor;
+            harmonics.l20[1] *= scaleFactor;
+            harmonics.l20[2] *= scaleFactor;
+            harmonics.l21[0] *= scaleFactor;
+            harmonics.l21[1] *= scaleFactor;
+            harmonics.l21[2] *= scaleFactor;
+            harmonics.l22[0] *= scaleFactor;
+            harmonics.l22[1] *= scaleFactor;
+            harmonics.l22[2] *= scaleFactor;
+        }
+
+        /**
+         * Convert spherical harmonics to spherical polynomial coefficients
+         * @param harmonics Spherical harmonic coefficients (9)
+         * @param outPolynomialCoefficents Polynomial coefficients (9) object to store result
+         */
+        private static _ConvertSHToSP(harmonics: any, outPolynomialCoefficents: SphericalPolynomial) {
+            let rPi = 1 / Math.PI;
+
+            //x
+            outPolynomialCoefficents.x.x = 1.02333 * harmonics.l11[0] * rPi;
+            outPolynomialCoefficents.x.y = 1.02333 * harmonics.l11[1] * rPi;
+            outPolynomialCoefficents.x.z = 1.02333 * harmonics.l11[2] * rPi;
+
+            outPolynomialCoefficents.y.x = 1.02333 * harmonics.l1_1[0] * rPi;
+            outPolynomialCoefficents.y.y = 1.02333 * harmonics.l1_1[1] * rPi;
+            outPolynomialCoefficents.y.z = 1.02333 * harmonics.l1_1[2] * rPi;
+
+            outPolynomialCoefficents.z.x = 1.02333 * harmonics.l10[0] * rPi;
+            outPolynomialCoefficents.z.y = 1.02333 * harmonics.l10[1] * rPi;
+            outPolynomialCoefficents.z.z = 1.02333 * harmonics.l10[2] * rPi;
+
+            //xx
+            outPolynomialCoefficents.xx.x = (0.886277 * harmonics.l00[0] - 0.247708 * harmonics.l20[0] + 0.429043 * harmonics.l22[0]) * rPi;
+            outPolynomialCoefficents.xx.y = (0.886277 * harmonics.l00[1] - 0.247708 * harmonics.l20[1] + 0.429043 * harmonics.l22[1]) * rPi;
+            outPolynomialCoefficents.xx.z = (0.886277 * harmonics.l00[2] - 0.247708 * harmonics.l20[2] + 0.429043 * harmonics.l22[2]) * rPi;
+
+            outPolynomialCoefficents.yy.x = (0.886277 * harmonics.l00[0] - 0.247708 * harmonics.l20[0] - 0.429043 * harmonics.l22[0]) * rPi;
+            outPolynomialCoefficents.yy.y = (0.886277 * harmonics.l00[1] - 0.247708 * harmonics.l20[1] - 0.429043 * harmonics.l22[1]) * rPi;
+            outPolynomialCoefficents.yy.z = (0.886277 * harmonics.l00[2] - 0.247708 * harmonics.l20[2] - 0.429043 * harmonics.l22[2]) * rPi;
+
+            outPolynomialCoefficents.zz.x = (0.886277 * harmonics.l00[0] + 0.495417 * harmonics.l20[0]) * rPi;
+            outPolynomialCoefficents.zz.y = (0.886277 * harmonics.l00[1] + 0.495417 * harmonics.l20[1]) * rPi;
+            outPolynomialCoefficents.zz.z = (0.886277 * harmonics.l00[2] + 0.495417 * harmonics.l20[2]) * rPi;
+
+            //yz
+            outPolynomialCoefficents.yz.x = 0.858086 * harmonics.l2_1[0] * rPi;
+            outPolynomialCoefficents.yz.y = 0.858086 * harmonics.l2_1[1] * rPi;
+            outPolynomialCoefficents.yz.z = 0.858086 * harmonics.l2_1[2] * rPi;
+
+            outPolynomialCoefficents.zx.x = 0.858086 * harmonics.l21[0] * rPi;
+            outPolynomialCoefficents.zx.y = 0.858086 * harmonics.l21[1] * rPi;
+            outPolynomialCoefficents.zx.z = 0.858086 * harmonics.l21[2] * rPi;
+
+            outPolynomialCoefficents.xy.x = 0.858086 * harmonics.l2_2[0] * rPi;
+            outPolynomialCoefficents.xy.y = 0.858086 * harmonics.l2_2[1] * rPi;
+            outPolynomialCoefficents.xy.z = 0.858086 * harmonics.l2_2[2] * rPi;
+        }
+    }
+}

+ 27 - 16
src/Tools/babylon.tools.ts

@@ -996,28 +996,18 @@
                     }
                 }
                 screenshotCanvas.toBlob(function (blob) {
-                    var url = URL.createObjectURL(blob);
                     //Creating a link if the browser have the download attribute on the a tag, to automatically start download generated image.
                     if (("download" in document.createElement("a"))) {
-                        var a = window.document.createElement("a");
-                        a.href = url;
-                        if (fileName) {
-                            a.setAttribute("download", fileName);
-                        }
-                        else {
+                        if (!fileName) {
                             var date = new Date();
-                            var stringDate = (date.getFullYear() + "-" + (date.getMonth() + 1)).slice(-2) + "-" + date.getDate() + "_" + date.getHours() + "-" + ('0' + date.getMinutes()).slice(-2);
-                            a.setAttribute("download", "screenshot_" + stringDate + ".png");
+                            var stringDate = (date.getFullYear() + "-" + (date.getMonth() + 1)).slice(2) + "-" + date.getDate() + "_" + date.getHours() + "-" + ('0' + date.getMinutes()).slice(-2);
+                            fileName = "screenshot_" + stringDate + ".png";
                         }
-                        window.document.body.appendChild(a);
-                        a.addEventListener("click", () => {
-                            if (a.parentElement) {
-                                a.parentElement.removeChild(a);
-                            }
-                        });
-                        a.click();
+                        Tools.Download(blob!, fileName);
                     }
                     else {
+                        var url = URL.createObjectURL(blob);
+                    
                         var newWindow = window.open("");
                         if (!newWindow) return;
                         var img = newWindow.document.createElement("img");
@@ -1033,6 +1023,27 @@
             }
         }
 
+        /**
+         * Downloads a blob in the browser
+         * @param blob defines the blob to download
+         * @param fileName defines the name of the downloaded file
+         */
+        public static Download(blob: Blob, fileName: string): void {
+            var url = window.URL.createObjectURL(blob);
+            var a = document.createElement("a");
+            document.body.appendChild(a);
+            a.style.display = "none";
+            a.href = url;
+            a.download = fileName;
+            a.addEventListener("click", () => {
+                if (a.parentElement) {
+                    a.parentElement.removeChild(a);
+                }
+            });
+            a.click();
+            window.URL.revokeObjectURL(url);
+        }
+
         public static CreateScreenshot(engine: Engine, camera: Camera, size: any, successCallback?: (data: string) => void, mimeType: string = "image/png"): void {
             var width: number;
             var height: number;