Browse Source

Initial support for experimental EXT_lights_imageBased

Gary Hsu 7 năm trước cách đây
mục cha
commit
73a4beb407

+ 5 - 2
Tools/Gulp/config.json

@@ -539,6 +539,7 @@
         "additionalTextures": {
             "files": [
                 "../../src/Materials/Textures/babylon.cubeTexture.js",
+                "../../src/Materials/Textures/babylon.rawCubeTexture.js",
                 "../../src/Materials/Textures/babylon.renderTargetTexture.js",
                 "../../src/Materials/Textures/babylon.multiRenderTarget.js",
                 "../../src/Materials/Textures/babylon.mirrorTexture.js",
@@ -1632,7 +1633,8 @@
                     "../../loaders/src/glTF/2.0/Extensions/KHR_materials_pbrSpecularGlossiness.ts",
                     "../../loaders/src/glTF/2.0/Extensions/KHR_materials_unlit.ts",
                     "../../loaders/src/glTF/2.0/Extensions/KHR_lights.ts",
-                    "../../loaders/src/glTF/2.0/Extensions/KHR_texture_transform.ts"
+                    "../../loaders/src/glTF/2.0/Extensions/KHR_texture_transform.ts",
+                    "../../loaders/src/glTF/2.0/Extensions/EXT_lights_imageBased.ts"
                 ],
                 "doNotIncludeInBundle": true,
                 "output": "babylon.glTF2FileLoader.js"
@@ -1656,7 +1658,8 @@
                     "../../loaders/src/glTF/2.0/Extensions/KHR_materials_pbrSpecularGlossiness.ts",
                     "../../loaders/src/glTF/2.0/Extensions/KHR_materials_unlit.ts",
                     "../../loaders/src/glTF/2.0/Extensions/KHR_lights.ts",
-                    "../../loaders/src/glTF/2.0/Extensions/KHR_texture_transform.ts"
+                    "../../loaders/src/glTF/2.0/Extensions/KHR_texture_transform.ts",
+                    "../../loaders/src/glTF/2.0/Extensions/EXT_lights_imageBased.ts"
                 ],
                 "output": "babylon.glTFFileLoader.js"
             }

+ 105 - 0
loaders/src/glTF/2.0/Extensions/EXT_lights_imageBased.ts

@@ -0,0 +1,105 @@
+/// <reference path="../../../../../dist/preview release/babylon.d.ts"/>
+
+module BABYLON.GLTF2.Extensions {
+    const NAME = "EXT_lights_imageBased";
+
+    interface ILightReference {
+        light: number;
+    }
+
+    interface ILight extends IChildRootProperty {
+        intensity: number;
+        rotation: number[];
+        specularImages: number[][];
+        irradianceCoefficients: number[][];
+
+        _babylonTexture?: BaseTexture;
+        _loaded?: Promise<void>;
+    }
+
+    interface ILights {
+        lights: ILight[];
+    }
+
+    /**
+     * [Specification](TODO) (Experimental)
+     */
+    export class EXT_lights_imageBased extends GLTFLoaderExtension {
+        public readonly name = NAME;
+
+        protected _loadSceneAsync(context: string, scene: _ILoaderScene): Nullable<Promise<void>> { 
+            return this._loadExtensionAsync<ILightReference>(context, scene, (extensionContext, extension) => {
+                const promises = new Array<Promise<void>>();
+
+                promises.push(this._loader._loadSceneAsync(context, scene));
+
+                this._loader._parent._logOpen(`${extensionContext}`);
+
+                const light = GLTFLoader._GetProperty(`${extensionContext}/light`, this._lights, extension.light);
+                promises.push(this._loadLightAsync(`#/extensions/${this.name}/lights/${extension.light}`, light).then(texture => {
+                    this._loader._babylonScene.environmentTexture = texture;
+                }));
+
+                this._loader._parent._logClose();
+
+                return Promise.all(promises).then(() => {});
+            });
+        }
+
+        private _loadLightAsync(context: string, light: ILight): Promise<BaseTexture> {
+            if (!light._loaded) {
+                const promises = new Array<Promise<void>>();
+
+                this._loader._parent._logOpen(`${context}`);
+
+                const imageData = new Array<Array<ArrayBufferView>>(light.specularImages.length);
+                for (let mipmap = 0; mipmap < light.specularImages.length; mipmap++) {
+                    const faces = light.specularImages[mipmap];
+                    imageData[mipmap] = new Array<ArrayBufferView>(faces.length);
+                    for (let face = 0; face < faces.length; face++) {
+                        const specularImageContext = `${context}/specularImages/${mipmap}/${face}`;
+                        this._loader._parent._logOpen(`${specularImageContext}`);
+
+                        const index = faces[face];
+                        const image = GLTFLoader._GetProperty(specularImageContext, this._loader._gltf.images, index);
+                        promises.push(this._loader._loadImageAsync(`#/images/${index}`, image).then(data => {
+                            imageData[mipmap][face] = data;
+                        }));
+
+                        this._loader._parent._logClose();
+                    }
+                }
+
+                this._loader._parent._logClose();
+
+                light._loaded = Promise.all(promises).then(() => {
+                    return new Promise<void>((resolve, reject) => {
+                        const size = Math.pow(2, imageData.length - 1);
+                        const sphericalPolynomial = SphericalPolynomial.FromHarmonics(SphericalHarmonics.FromArray(light.irradianceCoefficients));
+                        light._babylonTexture = new RawCubeTexture(this._loader._babylonScene, imageData, size, undefined, undefined, undefined, undefined, undefined, undefined, () => {
+                            resolve();
+                        }, (message, exception) => {
+                            reject(exception);
+                        }, sphericalPolynomial, true);
+                    });
+                });
+            }
+
+            return light._loaded.then(() => {
+                return light._babylonTexture!;
+            });
+        }
+
+        private get _lights(): Array<ILight> {
+            const extensions = this._loader._gltf.extensions;
+            if (!extensions || !extensions[this.name]) {
+                throw new Error(`#/extensions: '${this.name}' not found`);
+            }
+
+            const extension = extensions[this.name] as ILights;
+            return extension.lights;
+        }
+    }
+
+    GLTFLoader._Register(NAME, loader => new EXT_lights_imageBased(loader));
+}

+ 5 - 7
loaders/src/glTF/2.0/Extensions/KHR_lights.ts

@@ -18,11 +18,10 @@ module BABYLON.GLTF2.Extensions {
         type: LightType;
         color?: number[];
         intensity?: number;
-    }
-
-    interface ISpotLight extends ILight {
-        innerConeAngle?: number;
-        outerConeAngle?: number;
+        spot?: {
+            innerConeAngle?: number;
+            outerConeAngle?: number;
+        };
     }
 
     interface ILights {
@@ -71,10 +70,9 @@ module BABYLON.GLTF2.Extensions {
                         break;
                     }
                     case LightType.SPOT: {
-                        const spotLight = light as ISpotLight;
                         // TODO: support inner and outer cone angles
                         //const innerConeAngle = spotLight.innerConeAngle || 0;
-                        const outerConeAngle = spotLight.outerConeAngle || Math.PI / 4;
+                        const outerConeAngle = light.spot && light.spot.outerConeAngle || Math.PI / 4;
                         babylonLight = new SpotLight(name, Vector3.Zero(), Vector3.Forward(), outerConeAngle, 2, this._loader._babylonScene);
                         break;
                     }

+ 2 - 2
loaders/src/glTF/2.0/Extensions/KHR_materials_pbrSpecularGlossiness.ts

@@ -41,13 +41,13 @@ module BABYLON.GLTF2.Extensions {
             babylonMaterial.microSurface = properties.glossinessFactor == undefined ? 1 : properties.glossinessFactor;
 
             if (properties.diffuseTexture) {
-                promises.push(this._loader._loadTextureAsync(`${context}/diffuseTexture`, properties.diffuseTexture, texture => {
+                promises.push(this._loader._loadTextureInfoAsync(`${context}/diffuseTexture`, properties.diffuseTexture, texture => {
                     babylonMaterial.albedoTexture = texture;
                 }));
             }
 
             if (properties.specularGlossinessTexture) {
-                promises.push(this._loader._loadTextureAsync(`${context}/specularGlossinessTexture`, properties.specularGlossinessTexture, texture => {
+                promises.push(this._loader._loadTextureInfoAsync(`${context}/specularGlossinessTexture`, properties.specularGlossinessTexture, texture => {
                     babylonMaterial.reflectivityTexture = texture;
                 }));
 

+ 1 - 1
loaders/src/glTF/2.0/Extensions/KHR_materials_unlit.ts

@@ -35,7 +35,7 @@ module BABYLON.GLTF2.Extensions {
                 }
 
                 if (properties.baseColorTexture) {
-                    promises.push(this._loader._loadTextureAsync(`${context}/baseColorTexture`, properties.baseColorTexture, texture => {
+                    promises.push(this._loader._loadTextureInfoAsync(`${context}/baseColorTexture`, properties.baseColorTexture, texture => {
                         babylonMaterial.albedoTexture = texture;
                     }));
                 }

+ 2 - 2
loaders/src/glTF/2.0/Extensions/KHR_texture_transform.ts

@@ -16,9 +16,9 @@ module BABYLON.GLTF2.Extensions {
     export class KHR_texture_transform extends GLTFLoaderExtension {
         public readonly name = NAME;
 
-        protected _loadTextureAsync(context: string, textureInfo: ITextureInfo, assign: (texture: Texture) => void): Nullable<Promise<void>> {
+        protected _loadTextureInfoAsync(context: string, textureInfo: ITextureInfo, assign: (babylonTexture: Texture) => void): Nullable<Promise<void>> {
             return this._loadExtensionAsync<IKHRTextureTransform>(context, textureInfo, (extensionContext, extension) => {
-                return this._loader._loadTextureAsync(context, textureInfo, babylonTexture => {
+                return this._loader._loadTextureInfoAsync(context, textureInfo, babylonTexture => {
                     if (extension.offset) {
                         babylonTexture.uOffset = extension.offset[0];
                         babylonTexture.vOffset = extension.offset[1];

+ 51 - 43
loaders/src/glTF/2.0/babylon.glTFLoader.ts

@@ -10,7 +10,8 @@ module BABYLON.GLTF2 {
         _total?: number;
     }
 
-    class _ArrayItem {
+    /** @hidden */
+    export class _ArrayItem {
         public static Assign(values?: _IArrayItem[]): void {
             if (values) {
                 for (let index = 0; index < values.length; index++) {
@@ -320,9 +321,9 @@ module BABYLON.GLTF2 {
         }
 
         public _loadSceneAsync(context: string, scene: _ILoaderScene): Promise<void> {
-            const promise = GLTFLoaderExtension._LoadSceneAsync(this, context, scene);
-            if (promise) {
-                return promise;
+            const extensionPromise = GLTFLoaderExtension._LoadSceneAsync(this, context, scene);
+            if (extensionPromise) {
+                return extensionPromise;
             }
 
             const promises = new Array<Promise<void>>();
@@ -436,9 +437,9 @@ module BABYLON.GLTF2 {
         }
 
         public _loadNodeAsync(context: string, node: _ILoaderNode): Promise<void> {
-            const promise = GLTFLoaderExtension._LoadNodeAsync(this, context, node);
-            if (promise) {
-                return promise;
+            const extensionPromise = GLTFLoaderExtension._LoadNodeAsync(this, context, node);
+            if (extensionPromise) {
+                return extensionPromise;
             }
 
             if (node._babylonMesh) {
@@ -549,9 +550,9 @@ module BABYLON.GLTF2 {
         }
 
         private _loadVertexDataAsync(context: string, primitive: _ILoaderMeshPrimitive, babylonMesh: Mesh): Promise<Geometry> {
-            const promise = GLTFLoaderExtension._LoadVertexDataAsync(this, context, primitive, babylonMesh);
-            if (promise) {
-                return promise;
+            const extensionPromise = GLTFLoaderExtension._LoadVertexDataAsync(this, context, primitive, babylonMesh);
+            if (extensionPromise) {
+                return extensionPromise;
             }
 
             const attributes = primitive.attributes;
@@ -1254,13 +1255,13 @@ module BABYLON.GLTF2 {
                 babylonMaterial.roughness = properties.roughnessFactor == undefined ? 1 : properties.roughnessFactor;
 
                 if (properties.baseColorTexture) {
-                    promises.push(this._loadTextureAsync(`${context}/baseColorTexture`, properties.baseColorTexture, texture => {
+                    promises.push(this._loadTextureInfoAsync(`${context}/baseColorTexture`, properties.baseColorTexture, texture => {
                         babylonMaterial.albedoTexture = texture;
                     }));
                 }
 
                 if (properties.metallicRoughnessTexture) {
-                    promises.push(this._loadTextureAsync(`${context}/metallicRoughnessTexture`, properties.metallicRoughnessTexture, texture => {
+                    promises.push(this._loadTextureInfoAsync(`${context}/metallicRoughnessTexture`, properties.metallicRoughnessTexture, texture => {
                         babylonMaterial.metallicTexture = texture;
                     }));
 
@@ -1276,9 +1277,9 @@ module BABYLON.GLTF2 {
         }
 
         public _loadMaterialAsync(context: string, material: _ILoaderMaterial, mesh: _ILoaderMesh, babylonMesh: Mesh, babylonDrawMode: number, assign: (babylonMaterial: Material) => void): Promise<void> {
-            const promise = GLTFLoaderExtension._LoadMaterialAsync(this, context, material, mesh, babylonMesh, babylonDrawMode, assign);
-            if (promise) {
-                return promise;
+            const extensionPromise = GLTFLoaderExtension._LoadMaterialAsync(this, context, material, mesh, babylonMesh, babylonDrawMode, assign);
+            if (extensionPromise) {
+                return extensionPromise;
             }
 
             material._babylonData = material._babylonData || {};
@@ -1315,9 +1316,9 @@ module BABYLON.GLTF2 {
         }
 
         public _loadMaterialPropertiesAsync(context: string, material: _ILoaderMaterial, babylonMaterial: Material): Promise<void> {
-            const promise = GLTFLoaderExtension._LoadMaterialPropertiesAsync(this, context, material, babylonMaterial);
-            if (promise) {
-                return promise;
+            const extensionPromise = GLTFLoaderExtension._LoadMaterialPropertiesAsync(this, context, material, babylonMaterial);
+            if (extensionPromise) {
+                return extensionPromise;
             }
 
             const promises = new Array<Promise<void>>();
@@ -1346,7 +1347,7 @@ module BABYLON.GLTF2 {
             }
 
             if (material.normalTexture) {
-                promises.push(this._loadTextureAsync(`${context}/normalTexture`, material.normalTexture, texture => {
+                promises.push(this._loadTextureInfoAsync(`${context}/normalTexture`, material.normalTexture, texture => {
                     babylonMaterial.bumpTexture = texture;
                 }));
 
@@ -1358,7 +1359,7 @@ module BABYLON.GLTF2 {
             }
 
             if (material.occlusionTexture) {
-                promises.push(this._loadTextureAsync(`${context}/occlusionTexture`, material.occlusionTexture, texture => {
+                promises.push(this._loadTextureInfoAsync(`${context}/occlusionTexture`, material.occlusionTexture, texture => {
                     babylonMaterial.ambientTexture = texture;
                 }));
 
@@ -1369,7 +1370,7 @@ module BABYLON.GLTF2 {
             }
 
             if (material.emissiveTexture) {
-                promises.push(this._loadTextureAsync(`${context}/emissiveTexture`, material.emissiveTexture, texture => {
+                promises.push(this._loadTextureInfoAsync(`${context}/emissiveTexture`, material.emissiveTexture, texture => {
                     babylonMaterial.emissiveTexture = texture;
                 }));
             }
@@ -1406,16 +1407,30 @@ module BABYLON.GLTF2 {
             }
         }
 
-        public _loadTextureAsync(context: string, textureInfo: ITextureInfo, assign: (texture: Texture) => void): Promise<void> {
-            const promise = GLTFLoaderExtension._LoadTextureAsync(this, context, textureInfo, assign);
-            if (promise) {
-                return promise;
+        public _loadTextureInfoAsync(context: string, textureInfo: ITextureInfo, assign: (babylonTexture: Texture) => void): Promise<void> {
+            const extensionPromise = GLTFLoaderExtension._LoadTextureInfoAsync(this, context, textureInfo, assign);
+            if (extensionPromise) {
+                return extensionPromise;
             }
 
             this._parent._logOpen(`${context}`);
 
             const texture = GLTFLoader._GetProperty(`${context}/index`, this._gltf.textures, textureInfo.index);
-            context = `#/textures/${textureInfo.index}`;
+            const promise = this._loadTextureAsync(`#/textures/${textureInfo.index}`, texture, babylonTexture => {
+                babylonTexture.coordinatesIndex = textureInfo.texCoord || 0;
+                assign(babylonTexture);
+            });
+
+            this._parent._logClose();
+
+            return promise;
+        }
+
+        public _loadTextureAsync(context: string, texture: _ILoaderTexture, assign: (babylonTexture: Texture) => void): Promise<void> {
+            const extensionPromise = GLTFLoaderExtension._LoadTextureAsync(this, context, texture, assign);
+            if (extensionPromise) {
+                return extensionPromise;
+            }
 
             const promises = new Array<Promise<void>>();
 
@@ -1439,19 +1454,17 @@ module BABYLON.GLTF2 {
             babylonTexture.name = texture.name || `texture${texture._index}`;
             babylonTexture.wrapU = samplerData.wrapU;
             babylonTexture.wrapV = samplerData.wrapV;
-            babylonTexture.coordinatesIndex = textureInfo.texCoord || 0;
 
             const image = GLTFLoader._GetProperty(`${context}/source`, this._gltf.images, texture.source);
-            promises.push(this._loadImageAsync(`#/images/${image._index}`, image).then(blob => {
+            promises.push(this._loadImageAsync(`#/images/${image._index}`, image).then(data => {
                 const dataUrl = `data:${this._rootUrl}${image.uri || `image${image._index}`}`;
-                babylonTexture.updateURL(dataUrl, blob);
+                babylonTexture.updateURL(dataUrl, new Blob([data], { type: image.mimeType }));
             }));
 
             assign(babylonTexture);
             this._parent.onTextureLoadedObservable.notifyObservers(babylonTexture);
 
             this._parent._logClose();
-            this._parent._logClose();
 
             return Promise.all(promises).then(() => {});
         }
@@ -1469,33 +1482,28 @@ module BABYLON.GLTF2 {
             return sampler._data;
         }
 
-        private _loadImageAsync(context: string, image: _ILoaderImage): Promise<Blob> {
-            if (!image._blob) {
+        public _loadImageAsync(context: string, image: _ILoaderImage): Promise<ArrayBufferView> {
+            if (!image._data) {
                 this._parent._logOpen(`${context} ${image.name || ""}`);
 
-                let promise: Promise<ArrayBufferView>;
                 if (image.uri) {
-                    promise = this._loadUriAsync(context, image.uri);
+                    image._data = this._loadUriAsync(context, image.uri);
                 }
                 else {
                     const bufferView = GLTFLoader._GetProperty(`${context}/bufferView`, this._gltf.bufferViews, image.bufferView);
-                    promise = this._loadBufferViewAsync(`#/bufferViews/${bufferView._index}`, bufferView);
+                    image._data = this._loadBufferViewAsync(`#/bufferViews/${bufferView._index}`, bufferView);
                 }
 
-                image._blob = promise.then(data => {
-                    return new Blob([data], { type: image.mimeType });
-                });
-
                 this._parent._logClose();
             }
 
-            return image._blob;
+            return image._data;
         }
 
         public _loadUriAsync(context: string, uri: string): Promise<ArrayBufferView> {
-            const promise = GLTFLoaderExtension._LoadUriAsync(this, context, uri);
-            if (promise) {
-                return promise;
+            const extensionPromise = GLTFLoaderExtension._LoadUriAsync(this, context, uri);
+            if (extensionPromise) {
+                return extensionPromise;
             }
 
             if (!GLTFLoader._ValidateUri(uri)) {

+ 17 - 3
loaders/src/glTF/2.0/babylon.glTFLoaderExtension.ts

@@ -65,10 +65,16 @@ module BABYLON.GLTF2 {
         protected _loadMaterialPropertiesAsync(context: string, material: _ILoaderMaterial, babylonMaterial: Material): Nullable<Promise<void>> { return null; }
 
         /**
+         * Override this method to modify the default behavior for loading texture infos.
+         * @hidden
+         */
+        protected _loadTextureInfoAsync(context: string, textureInfo: ITextureInfo, assign: (babylonTexture: Texture) => void): Nullable<Promise<void>> { return null; }
+
+        /**
          * Override this method to modify the default behavior for loading textures.
          * @hidden
          */
-        protected _loadTextureAsync(context: string, textureInfo: ITextureInfo, assign: (texture: Texture) => void): Nullable<Promise<void>> { return null; }
+        protected _loadTextureAsync(context: string, texture: _ILoaderTexture, assign: (babylonTexture: Texture) => void): Nullable<Promise<void>> { return null; }
 
         /**
          * Override this method to modify the default behavior for loading uris.
@@ -175,11 +181,19 @@ module BABYLON.GLTF2 {
         }
 
         /**
+         * Helper method called by the loader to allow extensions to override loading texture infos.
+         * @hidden
+         */
+        public static _LoadTextureInfoAsync(loader: GLTFLoader, context: string, textureInfo: ITextureInfo, assign: (babylonTexture: Texture) => void): Nullable<Promise<void>> {
+            return loader._applyExtensions(extension => extension._loadTextureInfoAsync(context, textureInfo, assign));
+        }
+
+        /**
          * Helper method called by the loader to allow extensions to override loading textures.
          * @hidden
          */
-        public static _LoadTextureAsync(loader: GLTFLoader, context: string, textureInfo: ITextureInfo, assign: (texture: Texture) => void): Nullable<Promise<void>> {
-            return loader._applyExtensions(extension => extension._loadTextureAsync(context, textureInfo, assign));
+        public static _LoadTextureAsync(loader: GLTFLoader, context: string, texture: _ILoaderTexture, assign: (babylonTexture: Texture) => void): Nullable<Promise<void>> {
+            return loader._applyExtensions(extension => extension._loadTextureAsync(context, texture, assign));
         }
 
         /**

+ 1 - 1
loaders/src/glTF/2.0/babylon.glTFLoaderInterfaces.ts

@@ -54,7 +54,7 @@ module BABYLON.GLTF2 {
 
     /** @hidden */
     export interface _ILoaderImage extends IImage, _IArrayItem {
-        _blob?: Promise<Blob>;
+        _data?: Promise<ArrayBufferView>;
     }
 
     /** @hidden */

BIN
sandbox/Assets/environment.dds


+ 11 - 5
sandbox/index.js

@@ -53,7 +53,7 @@ if (BABYLON.Engine.isSupported()) {
     var currentScene;
     var currentSkybox;
     var currentPluginName;
-    var skyboxPath = "Assets/environment.dds";
+    var skyboxPath = "https://assets.babylonjs.com/environments/environmentSpecular.env";
     var debugLayerEnabled = false;
     var debugLayerLastActiveTab = 0;
 
@@ -126,7 +126,7 @@ if (BABYLON.Engine.isSupported()) {
 
         // Attach camera to canvas inputs
         if (!currentScene.activeCamera || currentScene.lights.length === 0) {
-            currentScene.createDefaultCameraOrLight(true);
+            currentScene.createDefaultCamera(true);
 
             if (cameraPosition) {
                 currentScene.activeCamera.setPosition(cameraPosition);
@@ -160,10 +160,16 @@ if (BABYLON.Engine.isSupported()) {
 
         currentScene.activeCamera.attachControl(canvas);
 
-        // Environment
+        // Lighting
         if (currentPluginName === "gltf") {
-            var hdrTexture = BABYLON.CubeTexture.CreateFromPrefilteredData(skyboxPath, currentScene);
-            currentSkybox = currentScene.createDefaultSkybox(hdrTexture, true, (currentScene.activeCamera.maxZ - currentScene.activeCamera.minZ) / 2, 0.3);
+            if (!currentScene.environmentTexture) {
+                currentScene.environmentTexture = BABYLON.CubeTexture.CreateFromPrefilteredData(skyboxPath, currentScene);
+            }
+
+            currentSkybox = currentScene.createDefaultSkybox(currentScene.environmentTexture, true, (currentScene.activeCamera.maxZ - currentScene.activeCamera.minZ) / 2, 0.3, false);
+        }
+        else {
+            currentScene.createDefaultLight();
         }
 
         // In case of error during loading, meshes will be empty and clearColor is set to red

+ 47 - 8
src/Engine/babylon.engine.ts

@@ -5645,8 +5645,8 @@
                         texture.width = info.width;
                         texture.height = info.width;
 
-                        EnvironmentTextureTools.UploadPolynomials(texture, data, info!);
-                        EnvironmentTextureTools.UploadLevelsAsync(texture, data, info!).then(() => {
+                        EnvironmentTextureTools.UploadEnvSpherical(texture, info);
+                        EnvironmentTextureTools.UploadEnvLevelsAsync(texture, data, info).then(() => {
                             texture.isReady = true;
                             if (onLoad) {
                                 onLoad();
@@ -5857,7 +5857,11 @@
             this._bindTextureDirectly(this._gl.TEXTURE_CUBE_MAP, null);
 
             // this.resetTextureCache();
+
             texture.isReady = true;
+
+            texture.onLoadedObservable.notifyObservers(texture);
+            texture.onLoadedObservable.clear();
         }
 
         /**
@@ -5865,22 +5869,33 @@
          * @param data defines the array of data to use to create each face
          * @param size defines the size of the textures
          * @param format defines the format of the data
-         * @param type defines the type fo the data (like BABYLON.Engine.TEXTURETYPE_UNSIGNED_INT)
+         * @param type defines the type of the data (like BABYLON.Engine.TEXTURETYPE_UNSIGNED_INT)
          * @param generateMipMaps  defines if the engine should generate the mip levels
          * @param invertY defines if data must be stored with Y axis inverted
          * @param samplingMode defines the required sampling mode (like BABYLON.Texture.NEAREST_SAMPLINGMODE)
          * @param compression defines the compression used (null by default)
+         * @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 sphericalPolynomial defines the spherical polynomial for irradiance
+         * @param rgbd defines if the data is stored as RGBD
+         * @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 createRawCubeTexture(data: Nullable<ArrayBufferView[]>, size: number, format: number, type: number, 
-                                    generateMipMaps: boolean, invertY: boolean, samplingMode: number, 
-                                    compression: Nullable<string> = null): InternalTexture {
+        public createRawCubeTexture(data: Nullable<ArrayBufferView[] | ArrayBufferView[][]>, size: number, format: number, type: number,
+                                    generateMipMaps: boolean, invertY: boolean, samplingMode: number, compression: Nullable<string> = null,
+                                    onLoad: Nullable<() => void> = null, onError: Nullable<(message?: string, exception?: any) => void> = null,
+                                    sphericalPolynomial: Nullable<SphericalPolynomial> = null, rgbd = false, lodScale: number = 0, lodOffset: number = 0): InternalTexture {
             var gl = this._gl;
             var texture = new InternalTexture(this, InternalTexture.DATASOURCE_CUBERAW);
             texture.isCube = true;
             texture.generateMipMaps = generateMipMaps;
             texture.format = format;
             texture.type = type;
+            texture._lodGenerationScale = lodScale;
+            texture._lodGenerationOffset = lodOffset;
+            texture._sourceIsRGBD = rgbd;
+            texture._sphericalPolynomial = sphericalPolynomial;
             if (!this._doNotHandleContextLost) {
                 texture._bufferViewArray = data;
             }
@@ -5905,8 +5920,32 @@
             }
 
             // Upload data if needed. The texture won't be ready until then.
-            if (data) {
-                this.updateRawCubeTexture(texture, data, format, type, invertY, compression);
+            if (data && data.length) {
+                if (rgbd) {
+                    EnvironmentTextureTools.UploadLevelsAsync(texture, data as ArrayBufferView[][]).then(() => {
+                        texture.isReady = true;
+
+                        texture.onLoadedObservable.notifyObservers(texture);
+                        texture.onLoadedObservable.clear();
+
+                        if (onLoad) {
+                            onLoad();
+                        }
+                    }, reason => {
+                        if (onError) {
+                            onError("Failed to upload raw cube texture data to the GPU", reason);
+                        }
+                    });
+                }
+                else {
+                    Tools.SetImmediate(() => {
+                        this.updateRawCubeTexture(texture, data as ArrayBufferView[], format, type, invertY, compression);
+
+                        if (onLoad) {
+                            onLoad();
+                        }
+                    });
+                }
             }
 
             this._bindTextureDirectly(this._gl.TEXTURE_CUBE_MAP, texture, true);

+ 1 - 1
src/Materials/Textures/babylon.cubeTexture.ts

@@ -1,7 +1,6 @@
 module BABYLON {
     export class CubeTexture extends BaseTexture {
         public url: string;
-        public coordinatesMode = Texture.CUBIC_MODE;
 
         /**
          * Gets or sets the center of the bounding box associated with the cube texture
@@ -110,6 +109,7 @@
             this.isCube = true;
             this._textureMatrix = Matrix.Identity();
             this._createPolynomials = createPolynomials;
+            this.coordinatesMode = Texture.CUBIC_MODE;
 
             if (!rootUrl && !files) {
                 return;

+ 7 - 5
src/Materials/Textures/babylon.internalTexture.ts

@@ -144,7 +144,7 @@ module BABYLON {
         /** @hidden */
         public _bufferView: Nullable<ArrayBufferView>;
         /** @hidden */
-        public _bufferViewArray: Nullable<ArrayBufferView[]>;
+        public _bufferViewArray: Nullable<ArrayBufferView[] | ArrayBufferView[][]>;
         /** @hidden */
         public _size: number;
         /** @hidden */
@@ -186,11 +186,13 @@ module BABYLON {
         /** @hidden */
         public _comparisonFunction: number = 0;
         /** @hidden */
-        public _sphericalPolynomial: Nullable<SphericalPolynomial>;
+        public _sphericalPolynomial: Nullable<SphericalPolynomial> = null;
         /** @hidden */
         public _lodGenerationScale: number = 0;
         /** @hidden */
         public _lodGenerationOffset: number = 0;
+        /** @hidden */
+        public _sourceIsRGBD: boolean = false;
 
         // The following three fields helps sharing generated fixed LODs for texture filtering
         // In environment not supporting the textureLOD extension like EDGE. They are for internal use only.
@@ -350,10 +352,10 @@ module BABYLON {
                     return;
 
                 case InternalTexture.DATASOURCE_CUBERAW:
-                    proxy = this._engine.createRawCubeTexture(this._bufferViewArray, this.width, this.format, this.type, this.generateMipMaps, this.invertY, this.samplingMode, this._compression);
+                    proxy = this._engine.createRawCubeTexture(this._bufferViewArray, this.width, this.format, this.type, this.generateMipMaps, this.invertY, this.samplingMode, this._compression, () => {
+                        this.isReady = true;
+                    }, null, this._sphericalPolynomial, this._sourceIsRGBD, this._lodGenerationScale, this._lodGenerationOffset);
                     proxy._swapAndDie(this);
-
-                    this.isReady = true;
                     return;
 
                 case InternalTexture.DATASOURCE_CUBEPREFILTERED:

+ 46 - 0
src/Materials/Textures/babylon.rawCubeTexture.ts

@@ -0,0 +1,46 @@
+module BABYLON {
+    export class RawCubeTexture extends CubeTexture {
+        /**
+         * Creates a cube texture where the raw buffers are passed in.
+         * @param scene defines the scene the texture is attached to
+         * @param data defines the array of data to use to create each face
+         * @param size defines the size of the textures
+         * @param format defines the format of the data
+         * @param type defines the type of the data (like BABYLON.Engine.TEXTURETYPE_UNSIGNED_INT)
+         * @param generateMipMaps  defines if the engine should generate the mip levels
+         * @param invertY defines if data must be stored with Y axis inverted
+         * @param samplingMode defines the required sampling mode (like BABYLON.Texture.NEAREST_SAMPLINGMODE)
+         * @param compression defines the compression used (null by default)
+         * @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 sphericalPolynomial defines the spherical polynomial for irradiance
+         * @param rgbd defines if the data is stored as RGBD
+         * @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
+         */
+        constructor(scene: Scene, data: ArrayBufferView[] | ArrayBufferView[][], size: number,
+                    format: number = Engine.TEXTUREFORMAT_RGBA, type: number = Engine.TEXTURETYPE_UNSIGNED_INT,
+                    generateMipMaps: boolean = false, invertY: boolean = false, samplingMode: number = Texture.TRILINEAR_SAMPLINGMODE,
+                    compression: Nullable<string> = null, onLoad: Nullable<() => void> = null, onError: Nullable<(message?: string, exception?: any) => void> = null,
+                    sphericalPolynomial: Nullable<SphericalPolynomial> = null, rgbd = false, lodScale: number = 0.8, lodOffset: number = 0) {
+            super("", scene);
+
+            this._texture = scene.getEngine().createRawCubeTexture(
+                data, size, format, type, generateMipMaps, invertY, samplingMode, compression,
+                onLoad, onError, sphericalPolynomial, rgbd, lodScale, lodOffset);
+        }
+
+        /**
+         * Clones the raw cube texture.
+         */
+        public clone(): CubeTexture {
+            return SerializationHelper.Clone(() => {
+                const scene = this.getScene()!;
+                const texture = this._texture!;
+                return new RawCubeTexture(scene, texture._bufferViewArray!, texture.width, texture.format, texture.type,
+                    texture.generateMipMaps, texture.invertY, texture.samplingMode, texture._compression, null, null,
+                    texture._sphericalPolynomial, texture._sourceIsRGBD, texture._lodGenerationScale, texture._lodGenerationOffset);
+            }, this);
+        }
+    }
+}

+ 49 - 13
src/Math/babylon.sphericalPolynomial.ts

@@ -17,7 +17,20 @@ module BABYLON {
             this.zz = this.zz.add(colorVector);
         }
 
-        public static getSphericalPolynomialFromHarmonics(harmonics: SphericalHarmonics): SphericalPolynomial {
+        public scale(scale: number)
+        {
+            this.x = this.x.scale(scale);
+            this.y = this.y.scale(scale);
+            this.z = this.z.scale(scale);
+            this.xx = this.xx.scale(scale);
+            this.yy = this.yy.scale(scale);
+            this.zz = this.zz.scale(scale);
+            this.yz = this.yz.scale(scale);
+            this.zx = this.zx.scale(scale);
+            this.xy = this.xy.scale(scale);
+        }
+
+        public static FromHarmonics(harmonics: SphericalHarmonics): SphericalPolynomial {
             var result = new SphericalPolynomial();
 
             result.x = harmonics.L11.scale(1.02333);
@@ -37,17 +50,22 @@ module BABYLON {
             return result;
         }
 
-        public scale(scale: number)
-        {
-            this.x = this.x.scale(scale);
-            this.y = this.y.scale(scale);
-            this.z = this.z.scale(scale);
-            this.xx = this.xx.scale(scale);
-            this.yy = this.yy.scale(scale);
-            this.zz = this.zz.scale(scale);
-            this.yz = this.yz.scale(scale);
-            this.zx = this.zx.scale(scale);
-            this.xy = this.xy.scale(scale);
+        /**
+         * Constructs a spherical polynomial from an array.
+         * @param data defines the 9x3 coefficients (x, y, z, xx, yy, zz, yz, zx, xy)
+         */
+        public static FromArray(data: ArrayLike<ArrayLike<number>>): SphericalPolynomial {
+            const sp = new SphericalPolynomial();
+            Vector3.FromArrayToRef(data[0], 0, sp.x);
+            Vector3.FromArrayToRef(data[1], 0, sp.y);
+            Vector3.FromArrayToRef(data[2], 0, sp.z);
+            Vector3.FromArrayToRef(data[3], 0, sp.xx);
+            Vector3.FromArrayToRef(data[4], 0, sp.yy);
+            Vector3.FromArrayToRef(data[5], 0, sp.zz);
+            Vector3.FromArrayToRef(data[6], 0, sp.yz);
+            Vector3.FromArrayToRef(data[7], 0, sp.zx);
+            Vector3.FromArrayToRef(data[8], 0, sp.xy);
+            return sp;
         }
     }
 
@@ -131,7 +149,7 @@ module BABYLON {
             // (The pixel shader must apply albedo after texture fetches, etc).
         }
 
-        public static getsphericalHarmonicsFromPolynomial(polynomial: SphericalPolynomial): SphericalHarmonics
+        public static FromPolynomial(polynomial: SphericalPolynomial): SphericalHarmonics
         {
             var result = new SphericalHarmonics();
 
@@ -149,5 +167,23 @@ module BABYLON {
 
             return result;
         }
+
+        /**
+         * Constructs a spherical harmonics from an array.
+         * @param data defines the 9x3 coefficients (l00, l1-1, l10, l11, l2-2, l2-1, l20, l21, l22)
+         */
+        public static FromArray(data: ArrayLike<ArrayLike<number>>): SphericalHarmonics {
+            const sh = new SphericalHarmonics();
+            Vector3.FromArrayToRef(data[0], 0, sh.L00);
+            Vector3.FromArrayToRef(data[1], 0, sh.L1_1);
+            Vector3.FromArrayToRef(data[2], 0, sh.L10);
+            Vector3.FromArrayToRef(data[3], 0, sh.L11);
+            Vector3.FromArrayToRef(data[4], 0, sh.L2_2);
+            Vector3.FromArrayToRef(data[5], 0, sh.L2_1);
+            Vector3.FromArrayToRef(data[6], 0, sh.L20);
+            Vector3.FromArrayToRef(data[7], 0, sh.L21);
+            Vector3.FromArrayToRef(data[8], 0, sh.L22);
+            return sh;
+        }
     }
 }

+ 1 - 1
src/Tools/HDR/babylon.cubemapToSphericalPolynomial.ts

@@ -172,7 +172,7 @@ module BABYLON {
             sphericalHarmonics.convertIncidentRadianceToIrradiance();
             sphericalHarmonics.convertIrradianceToLambertianRadiance();
 
-            return SphericalPolynomial.getSphericalPolynomialFromHarmonics(sphericalHarmonics);
+            return SphericalPolynomial.FromHarmonics(sphericalHarmonics);
         }
     }
 } 

+ 85 - 175
src/Tools/babylon.environmentTextureTools.ts

@@ -112,7 +112,7 @@ module BABYLON {
                     return null;
                 }
             }
-            
+
             // Read json manifest - collect characters up to null terminator
             let manifestString = '';
             let charCode = 0x00;
@@ -164,7 +164,7 @@ module BABYLON {
 
             let cubeWidth = internalTexture.width;
             let hostingScene = new Scene(engine);
-            let specularTextures: { [key: number]: ArrayBuffer } = { };
+            let specularTextures: { [key: number]: ArrayBuffer } = {};
             let promises: Promise<void>[] = [];
 
             // Read and collect all mipmaps data from the cube.
@@ -186,7 +186,7 @@ module BABYLON {
                             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();
@@ -246,7 +246,7 @@ module BABYLON {
                 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++) {
+                for (let i = 0, strLen = infoString.length; i < strLen; i++) {
                     infoView[i] = infoString.charCodeAt(i);
                 }
                 // Ends up with a null terminator for easier parsing
@@ -287,7 +287,7 @@ module BABYLON {
          * @param texture defines the texture containing the polynomials
          * @return the JSON representation of the spherical info
          */
-        private static _CreateEnvTextureIrradiance(texture: CubeTexture) : Nullable<EnvironmentTextureIrradianceInfoV1> {
+        private static _CreateEnvTextureIrradiance(texture: CubeTexture): Nullable<EnvironmentTextureIrradianceInfoV1> {
             let polynmials = texture.sphericalPolynomial;
             if (polynmials == null) {
                 return null;
@@ -311,13 +311,13 @@ module BABYLON {
         }
 
         /**
-         * Uploads the texture info contained in the env file to te GPU.
+         * Uploads the texture info contained in the env file to the 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> {
+        public static UploadEnvLevelsAsync(texture: InternalTexture, arrayBuffer: any, info: EnvironmentTextureInfo): Promise<void> {
             if (info.version !== 1) {
                 Tools.Warn('Unsupported babylon environment map version "' + info.version + '"');
             }
@@ -335,13 +335,45 @@ module BABYLON {
                 Tools.Warn('Unsupported specular mipmaps number "' + specularInfo.mipmaps.length + '"');
             }
 
+            const imageData = new Array<Array<ArrayBufferView>>(mipmapsCount);
+            for (let i = 0; i < mipmapsCount; i++) {
+                imageData[i] = new Array<ArrayBufferView>(6);
+                for (let face = 0; face < 6; face++) {
+                    const imageInfo = specularInfo.mipmaps[i * 6 + face];
+                    imageData[i][face] = new Uint8Array(arrayBuffer, specularInfo.specularDataPosition! + imageInfo.position, imageInfo.length);
+
+
+                    // {
+                    //     const link = document.createElement('a');
+                    //     document.body.appendChild(link);
+                    //     link.setAttribute("type", "hidden");
+                    //     link.download = `image${face}${i}`;
+                    //     const blob = new Blob([imageData[i][face]], { type: "image/png" });
+                    //     link.href = window.URL.createObjectURL(blob);
+                    //     link.click();
+                    // }
+                }
+            }
+
+            return EnvironmentTextureTools.UploadLevelsAsync(texture, imageData);
+        }
+
+        /**
+         * Uploads the levels of image data to the GPU.
+         * @param texture defines the internal texture to upload to
+         * @param imageData defines the array buffer views of image data [mipmap][face]
+         * @returns a promise
+         */
+        public static UploadLevelsAsync(texture: InternalTexture, imageData: ArrayBufferView[][]): Promise<void> {
+            const mipmapsCount = imageData.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 lodTextures: Nullable<{ [lod: number]: BaseTexture }> = null;
             let caps = engine.getCaps();
 
             texture.format = Engine.TEXTUREFORMAT_RGBA;
@@ -352,7 +384,7 @@ module BABYLON {
             if (!caps.textureLOD) {
                 expandTexture = false;
                 generateNonLODTextures = true;
-                lodTextures = { };
+                lodTextures = {};
             }
             // in webgl 1 there are no ways to either render or copy lod level information for float textures.
             else if (engine.webGLVersion < 2) {
@@ -373,7 +405,7 @@ module BABYLON {
             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, {
@@ -394,24 +426,24 @@ module BABYLON {
                     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 maxLODIndex = mipmapsCount * 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;
@@ -420,14 +452,14 @@ module BABYLON {
 
                         switch (i) {
                             case 0:
-                            texture._lodTextureLow = lodTexture;
-                            break;
+                                texture._lodTextureLow = lodTexture;
+                                break;
                             case 1:
-                            texture._lodTextureMid = lodTexture;
-                            break;
+                                texture._lodTextureMid = lodTexture;
+                                break;
                             case 2:
-                            texture._lodTextureHigh = lodTexture;
-                            break;
+                                texture._lodTextureHigh = lodTexture;
+                                break;
                         }
                     }
                 }
@@ -438,25 +470,22 @@ module BABYLON {
             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
+                    // Constructs an image element from image data
+                    let bytes = imageData[i][face];
                     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) => {;
+                    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);
+                                    (message) => {
+                                        reject(message);
+                                    },
+                                    image);
 
                                 rgbdPostProcess!.getEffect().executeWhenCompiled(() => {
                                     // Uncompress the data to a RTT
@@ -464,7 +493,7 @@ module BABYLON {
                                         effect._bindTexture("textureSampler", tempTexture);
                                         effect.setFloat2("scale", 1, 1);
                                     }
-                                    
+
                                     engine.scenes[0].postProcessManager.directRender([rgbdPostProcess!], cubeRtt, true, face, i);
 
                                     // Cleanup
@@ -524,10 +553,9 @@ module BABYLON {
         /**
          * 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 {
+        public static UploadEnvSpherical(texture: InternalTexture, info: EnvironmentTextureInfo): void {
             if (info.version !== 1) {
                 Tools.Warn('Unsupported babylon environment map version "' + info.version + '"');
             }
@@ -536,151 +564,33 @@ module BABYLON {
             if (!irradianceInfo) {
                 return;
             }
-            
-            //harmonics now represent radiance
-            texture._sphericalPolynomial = new SphericalPolynomial();
 
             if (irradianceInfo.polynomials) {
-                EnvironmentTextureTools._UploadSP(irradianceInfo, texture._sphericalPolynomial);
+                const sp = new SphericalPolynomial();
+                Vector3.FromArrayToRef(irradianceInfo.x, 0, sp.x);
+                Vector3.FromArrayToRef(irradianceInfo.y, 0, sp.y);
+                Vector3.FromArrayToRef(irradianceInfo.z, 0, sp.z);
+                Vector3.FromArrayToRef(irradianceInfo.xx, 0, sp.xx);
+                Vector3.FromArrayToRef(irradianceInfo.yy, 0, sp.yy);
+                Vector3.FromArrayToRef(irradianceInfo.zz, 0, sp.zz);
+                Vector3.FromArrayToRef(irradianceInfo.yz, 0, sp.yz);
+                Vector3.FromArrayToRef(irradianceInfo.zx, 0, sp.zx);
+                Vector3.FromArrayToRef(irradianceInfo.xy, 0, sp.xy);
+                texture._sphericalPolynomial = sp;
             }
             else {
-                // convert From SH to SP.
-                EnvironmentTextureTools._ConvertSHIrradianceToLambertianRadiance(irradianceInfo);
-                EnvironmentTextureTools._ConvertSHToSP(irradianceInfo, texture._sphericalPolynomial);
+                const sh = new SphericalHarmonics();
+                Vector3.FromArrayToRef(irradianceInfo.l00, 0, sh.L00);
+                Vector3.FromArrayToRef(irradianceInfo.l1_1, 0, sh.L1_1);
+                Vector3.FromArrayToRef(irradianceInfo.l10, 0, sh.L10);
+                Vector3.FromArrayToRef(irradianceInfo.l11, 0, sh.L11);
+                Vector3.FromArrayToRef(irradianceInfo.l2_2, 0, sh.L2_2);
+                Vector3.FromArrayToRef(irradianceInfo.l2_1, 0, sh.L2_1);
+                Vector3.FromArrayToRef(irradianceInfo.l20, 0, sh.L20);
+                Vector3.FromArrayToRef(irradianceInfo.l21, 0, sh.L21);
+                Vector3.FromArrayToRef(irradianceInfo.l22, 0, sh.L22);
+                texture._sphericalPolynomial = SphericalPolynomial.FromHarmonics(sh);
             }
         }
-
-        /**
-         * 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;
-        }
     }
 }