Sfoglia il codice sorgente

Merge pull request #3701 from kcoley/kcoley/gltfserializertexturefix

Refactored material creation to gltFMaterials.ts for glTF serializer.…
David Catuhe 7 anni fa
parent
commit
42800a8e09

+ 22 - 218
serializers/src/glTF/2.0/babylon.glTFExporter.ts

@@ -556,97 +556,6 @@ module BABYLON.GLTF2 {
         }
 
         /**
-         * 
-         * @param babylonTexture - Babylon texture to extract.
-         * @param mimeType - Mime Type of the babylonTexture.
-         * @return - glTF texture, or null if the texture format is not supported.
-         */
-        private exportTexture(babylonTexture: BaseTexture, mimeType: ImageMimeType = ImageMimeType.JPEG): Nullable<ITextureInfo> {
-            let textureInfo: Nullable<ITextureInfo> = null;
-
-            let glTFTexture: Nullable<ITexture>;
-
-            glTFTexture = {
-                source: this.images.length
-            };
-
-            let textureName = babylonTexture.getInternalTexture()!.url;
-            if (textureName.search('/') !== -1) {
-                const splitFilename = textureName.split('/');
-                textureName = splitFilename[splitFilename.length - 1];
-                const basefile = textureName.split('.')[0];
-                let extension = textureName.split('.')[1];
-                if (mimeType === ImageMimeType.JPEG) {
-                    extension = ".jpg";
-                }
-                else if (mimeType === ImageMimeType.PNG) {
-                    extension = ".png";
-                }
-                else {
-                    throw new Error("Unsupported mime type " + mimeType);
-                }
-                textureName = basefile + extension;
-            }
-
-            const pixels = babylonTexture!.readPixels() as Uint8Array;
-
-            const imageCanvas = document.createElement('canvas');
-            imageCanvas.id = "ImageCanvas";
-
-            const ctx = <CanvasRenderingContext2D>imageCanvas.getContext('2d');
-            const size = babylonTexture.getSize();
-            imageCanvas.width = size.width;
-            imageCanvas.height = size.height;
-
-            const imgData = ctx.createImageData(size.width, size.height);
-
-
-            imgData.data.set(pixels!);
-            ctx.putImageData(imgData, 0, 0);
-            const base64Data = imageCanvas.toDataURL(mimeType);
-            const binStr = atob(base64Data.split(',')[1]);
-            const arr = new Uint8Array(binStr.length);
-            for (let i = 0; i < binStr.length; ++i) {
-                arr[i] = binStr.charCodeAt(i);
-            }
-            const imageValues = { data: arr, mimeType: mimeType };
-
-            this.imageData[textureName] = imageValues;
-            if (mimeType === ImageMimeType.JPEG) {
-                const glTFImage: IImage = {
-                    uri: textureName
-                }
-                let foundIndex = -1;
-                for (let i = 0; i < this.images.length; ++i) {
-                    if (this.images[i].uri === textureName) {
-                        foundIndex = i;
-                        break;
-                    }
-                }
-                if (foundIndex === -1) {
-                    this.images.push(glTFImage);
-                    glTFTexture.source = this.images.length - 1;
-                    this.textures.push({
-                        source: this.images.length - 1
-                    });
-
-                    textureInfo = {
-                        index: this.images.length - 1
-                    }
-                }
-                else {
-                    glTFTexture.source = foundIndex;
-
-                    textureInfo = {
-                        index: foundIndex
-                    }
-                }
-            }
-
-            return textureInfo;
-        }
-
-        /**
          * Creates a bufferview based on the vertices type for the Babylon mesh
          * @param kind - Indicates the type of vertices data.
          * @param babylonMesh - The Babylon mesh to get the vertices data from.
@@ -721,7 +630,7 @@ module BABYLON.GLTF2 {
                                 break;
                             }
                             default: {
-                                console.warn("Unsupported VertexBuffer kind: " + kind);
+                                Tools.Warn("Unsupported VertexBuffer kind: " + kind);
                             }
                         }
 
@@ -817,7 +726,6 @@ module BABYLON.GLTF2 {
                 const submesh = babylonMesh.subMeshes[j];
                 const meshPrimitive: IMeshPrimitive = { attributes: {} };
 
-
                 if (bufferMesh !== null) {
                     // Create a bufferview storing all the positions
                     if (!dataBuffer) {
@@ -829,8 +737,8 @@ module BABYLON.GLTF2 {
                             const positionStrideSize = positionVertexBuffer!.getStrideSize();
 
                             // Create accessor
-                            const result = this.calculateMinMax(positions!, 0, positions!.length/positionStrideSize, positionStrideSize!, useRightHandedSystem);
-                            const accessor = this.createAccessor(positionBufferViewIndex, "Position", AccessorType.VEC3, AccessorComponentType.FLOAT, positions!.length/positionStrideSize, 0, result.min, result.max);
+                            const result = this.calculateMinMax(positions!, 0, positions!.length / positionStrideSize, positionStrideSize!, useRightHandedSystem);
+                            const accessor = this.createAccessor(positionBufferViewIndex, "Position", AccessorType.VEC3, AccessorComponentType.FLOAT, positions!.length / positionStrideSize, 0, result.min, result.max);
                             this.accessors.push(accessor);
 
                             meshPrimitive.attributes.POSITION = this.accessors.length - 1;
@@ -842,7 +750,7 @@ module BABYLON.GLTF2 {
                             const normalStrideSize = normalVertexBuffer!.getStrideSize();
 
                             // Create accessor
-                            const accessor = this.createAccessor(normalBufferViewIndex, "Normal", AccessorType.VEC3, AccessorComponentType.FLOAT, normals!.length/normalStrideSize);
+                            const accessor = this.createAccessor(normalBufferViewIndex, "Normal", AccessorType.VEC3, AccessorComponentType.FLOAT, normals!.length / normalStrideSize);
                             this.accessors.push(accessor);
 
                             meshPrimitive.attributes.NORMAL = this.accessors.length - 1;
@@ -854,7 +762,7 @@ module BABYLON.GLTF2 {
                             const tangentStrideSize = tangentVertexBuffer!.getStrideSize();
 
                             // Create accessor
-                            const accessor = this.createAccessor(tangentBufferViewIndex, "Tangent", AccessorType.VEC4, AccessorComponentType.FLOAT, tangents!.length/tangentStrideSize);
+                            const accessor = this.createAccessor(tangentBufferViewIndex, "Tangent", AccessorType.VEC4, AccessorComponentType.FLOAT, tangents!.length / tangentStrideSize);
                             this.accessors.push(accessor);
 
                             meshPrimitive.attributes.TANGENT = this.accessors.length - 1;
@@ -866,7 +774,7 @@ module BABYLON.GLTF2 {
                             const colorStrideSize = colorVertexBuffer!.getStrideSize();
 
                             // Create accessor
-                            const accessor = this.createAccessor(colorBufferViewIndex, "Color", AccessorType.VEC4, AccessorComponentType.FLOAT, colors!.length/colorStrideSize);
+                            const accessor = this.createAccessor(colorBufferViewIndex, "Color", AccessorType.VEC4, AccessorComponentType.FLOAT, colors!.length / colorStrideSize);
                             this.accessors.push(accessor);
 
                             meshPrimitive.attributes.COLOR_0 = this.accessors.length - 1;
@@ -877,7 +785,7 @@ module BABYLON.GLTF2 {
                             const texCoord0VertexBuffer = bufferMesh.getVertexBuffer(VertexBuffer.UVKind);
                             const texCoord0s = texCoord0VertexBuffer!.getData();
                             const texCoord0StrideSize = texCoord0VertexBuffer!.getStrideSize();
-                            const accessor = this.createAccessor(texCoord0BufferViewIndex, "Texture Coords 0", AccessorType.VEC2, AccessorComponentType.FLOAT, texCoord0s!.length/texCoord0StrideSize);
+                            const accessor = this.createAccessor(texCoord0BufferViewIndex, "Texture Coords 0", AccessorType.VEC2, AccessorComponentType.FLOAT, texCoord0s!.length / texCoord0StrideSize);
                             this.accessors.push(accessor);
 
                             meshPrimitive.attributes.TEXCOORD_0 = this.accessors.length - 1;
@@ -888,7 +796,7 @@ module BABYLON.GLTF2 {
                             const texCoord1VertexBuffer = bufferMesh.getVertexBuffer(VertexBuffer.UV2Kind);
                             const texCoord1s = texCoord1VertexBuffer!.getData();
                             const texCoord1StrideSize = texCoord1VertexBuffer!.getStrideSize();
-                            const accessor = this.createAccessor(texCoord1BufferViewIndex, "Texture Coords 1", AccessorType.VEC2, AccessorComponentType.FLOAT, texCoord1s!.length/texCoord1StrideSize);
+                            const accessor = this.createAccessor(texCoord1BufferViewIndex, "Texture Coords 1", AccessorType.VEC2, AccessorComponentType.FLOAT, texCoord1s!.length / texCoord1StrideSize);
                             this.accessors.push(accessor);
 
                             meshPrimitive.attributes.TEXCOORD_1 = this.accessors.length - 1;
@@ -904,129 +812,22 @@ module BABYLON.GLTF2 {
                         }
                     }
                     if (bufferMesh.material) {
-                        if (bufferMesh.material instanceof StandardMaterial) {
-                            console.warn("Standard Material is currently not fully supported/implemented in glTF serializer");
-                            const babylonStandardMaterial = bufferMesh.material as StandardMaterial;
-                            const glTFPbrMetallicRoughness = _GLTFMaterial.ConvertToGLTFPBRMetallicRoughness(babylonStandardMaterial);
-
-                            const glTFMaterial: IMaterial = { name: babylonStandardMaterial.name };
-                            if (!babylonStandardMaterial.backFaceCulling) {
-                                glTFMaterial.doubleSided = true;
-                            }
-                            if (babylonStandardMaterial.diffuseTexture && bufferMesh.isVerticesDataPresent(VertexBuffer.UVKind)) {
-                                const glTFTexture = this.exportTexture(babylonStandardMaterial.diffuseTexture);
-                                if (glTFTexture !== null) {
-                                    glTFPbrMetallicRoughness.baseColorTexture = glTFTexture;
-                                }
-                            }
-                            if (babylonStandardMaterial.bumpTexture && bufferMesh.isVerticesDataPresent(VertexBuffer.UVKind)) {
-                                const glTFTexture = this.exportTexture(babylonStandardMaterial.bumpTexture);
-                                if (glTFTexture) {
-                                    glTFMaterial.normalTexture = glTFTexture;
-                                }
-                            }
-                            if (babylonStandardMaterial.emissiveTexture && bufferMesh.isVerticesDataPresent(VertexBuffer.UVKind)) {
-                                const glTFEmissiveTexture = this.exportTexture(babylonStandardMaterial.emissiveTexture);
-                                if (glTFEmissiveTexture) {
-                                    glTFMaterial.emissiveTexture = glTFEmissiveTexture;
-                                }
-                                glTFMaterial.emissiveFactor = [1.0, 1.0, 1.0];
-                            }
-                            if (babylonStandardMaterial.ambientTexture && bufferMesh.isVerticesDataPresent(VertexBuffer.UVKind)) {
-                                const glTFOcclusionTexture = this.exportTexture(babylonStandardMaterial.ambientTexture);
-                                if (glTFOcclusionTexture) {
-                                    glTFMaterial.occlusionTexture = glTFOcclusionTexture;
-                                }
-                            }
-                            if (babylonStandardMaterial.alpha < 1.0 || babylonStandardMaterial.opacityTexture) {
-
-                                if (babylonStandardMaterial.alphaMode === Engine.ALPHA_COMBINE) {
-                                    glTFMaterial.alphaMode = GLTF2.MaterialAlphaMode.BLEND;
-                                }
-                                else {
-                                    console.warn("glTF 2.0 does not support alpha mode: " + babylonStandardMaterial.alphaMode.toString());
-                                }
-                            }
-
-                            glTFMaterial.pbrMetallicRoughness = glTFPbrMetallicRoughness;
-
-                            this.materials.push(glTFMaterial);
-                            meshPrimitive.material = this.materials.length - 1;
+                        if (bufferMesh.material instanceof StandardMaterial || bufferMesh.material instanceof PBRMetallicRoughnessMaterial) {
+                            const materialIndex = babylonMesh.getScene().materials.indexOf(bufferMesh.material);
+                            meshPrimitive.material = materialIndex;
                         }
-                        else if (bufferMesh.material instanceof PBRMetallicRoughnessMaterial) {
-                            const babylonPBRMaterial = bufferMesh.material as PBRMetallicRoughnessMaterial;
-                            const glTFPbrMetallicRoughness: IMaterialPbrMetallicRoughness = {};
-
-                            if (babylonPBRMaterial.baseColor) {
-                                glTFPbrMetallicRoughness.baseColorFactor = [
-                                    babylonPBRMaterial.baseColor.r,
-                                    babylonPBRMaterial.baseColor.g,
-                                    babylonPBRMaterial.baseColor.b,
-                                    babylonPBRMaterial.alpha
-                                ];
-                            }
-                            if (babylonPBRMaterial.baseTexture !== undefined) {
-                                const glTFTexture = this.exportTexture(babylonPBRMaterial.baseTexture);
-                                if (glTFTexture !== null) {
-                                    glTFPbrMetallicRoughness.baseColorTexture = glTFTexture;
-                                }
-                                glTFPbrMetallicRoughness.baseColorTexture
-                            }
-                            if (babylonPBRMaterial.metallic !== undefined) {
-                                glTFPbrMetallicRoughness.metallicFactor = babylonPBRMaterial.metallic;
-                            }
-                            if (babylonPBRMaterial.roughness !== undefined) {
-                                glTFPbrMetallicRoughness.roughnessFactor = babylonPBRMaterial.roughness;
-                            }
+                        else if (bufferMesh.material instanceof MultiMaterial) {
+                            const babylonMultiMaterial = bufferMesh.material as MultiMaterial;
 
-                            const glTFMaterial: IMaterial = {
-                                name: babylonPBRMaterial.name
-                            };
-                            if (babylonPBRMaterial.doubleSided) {
-                                glTFMaterial.doubleSided = babylonPBRMaterial.doubleSided;
-                            }
-                            if (babylonPBRMaterial.normalTexture) {
-                                const glTFTexture = this.exportTexture(babylonPBRMaterial.normalTexture);
-                                if (glTFTexture) {
-                                    glTFMaterial.normalTexture = glTFTexture;
-                                }
-                            }
-                            if (babylonPBRMaterial.occlusionTexture) {
-                                const glTFTexture = this.exportTexture(babylonPBRMaterial.occlusionTexture);
-                                if (glTFTexture) {
-                                    glTFMaterial.occlusionTexture = glTFTexture;
-                                    if (babylonPBRMaterial.occlusionStrength !== undefined) {
-                                        glTFMaterial.occlusionTexture.strength = babylonPBRMaterial.occlusionStrength;
-                                    }
-                                }
-                            }
-                            if (babylonPBRMaterial.emissiveTexture) {
-                                const glTFTexture = this.exportTexture(babylonPBRMaterial.emissiveTexture);
-                                if (glTFTexture !== null) {
-                                    glTFMaterial.emissiveTexture = glTFTexture;
-                                }
-                            }
-                            if (!babylonPBRMaterial.emissiveColor.equals(new Color3(0.0, 0.0, 0.0))) {
-                                glTFMaterial.emissiveFactor = babylonPBRMaterial.emissiveColor.asArray();
-                            }
-                            if (babylonPBRMaterial.transparencyMode) {
-                                const alphaMode = _GLTFMaterial.GetAlphaMode(babylonPBRMaterial);
-
-                                if (alphaMode !== MaterialAlphaMode.OPAQUE) { //glTF defaults to opaque
-                                    glTFMaterial.alphaMode = alphaMode;
-                                    if (alphaMode === MaterialAlphaMode.BLEND) {
-                                        glTFMaterial.alphaCutoff = babylonPBRMaterial.alphaCutOff;
-                                    }
-                                }
-                            }
+                            const material = babylonMultiMaterial.subMaterials[submesh.materialIndex];
 
-                            glTFMaterial.pbrMetallicRoughness = glTFPbrMetallicRoughness;
-
-                            this.materials.push(glTFMaterial);
-                            meshPrimitive.material = this.materials.length - 1;
+                            if (material !== null) {
+                                const materialIndex = babylonMesh.getScene().materials.indexOf(material);
+                                meshPrimitive.material = materialIndex;
+                            }
                         }
                         else {
-                            console.warn("Material type is not yet implemented in glTF serializer: " + bufferMesh.material.name);
+                            Tools.Warn("Material type " + bufferMesh.material.getClassName() + " for material " + bufferMesh.material.name + " is not yet implemented in glTF serializer.");
                         }
                     }
                     mesh.primitives.push(meshPrimitive);
@@ -1047,6 +848,9 @@ module BABYLON.GLTF2 {
             if (babylonScene.meshes.length > 0) {
                 const babylonMeshes = babylonScene.meshes;
                 const scene = { nodes: new Array<number>() };
+                if (dataBuffer == null) {
+                    _GLTFMaterial.ConvertMaterialsToGLTF(babylonScene.materials, ImageMimeType.JPEG, this.images, this.textures, this.materials, this.imageData, true);
+                }
 
                 for (let i = 0; i < babylonMeshes.length; ++i) {
                     if (this.options &&

+ 328 - 113
serializers/src/glTF/2.0/babylon.glTFMaterial.ts

@@ -2,62 +2,40 @@
 
 module BABYLON.GLTF2 {
     /**
-     * Represents the components used for representing a physically-based specular glossiness material
+     * Utility methods for working with glTF material conversion properties.  This class should only be used internally.
      */
-    interface IBabylonPbrSpecularGlossiness {
-        /**
-         * The diffuse color of the model, whose color values should be 
-         * normalized from 0 to 1.  
-         */
-        diffuse: Color3;
-        /**
-         * Represents the transparency of the material, from a range of 0 to 1.
-         */
-        opacity: number;
-        /**
-         * Represents how specular the material is, from a range of 0 to 1.
-         */
-        specular: Color3;
+    export class _GLTFMaterial {
         /**
-         * Represents how glossy the material is, from a range of 0 to 1.
+         * Represents the dielectric specular values for R, G and B.
          */
-        glossiness: number;
-    }
+        private static readonly dielectricSpecular: Color3 = new Color3(0.04, 0.04, 0.04);
 
-    /**
-     * Represents the components used for representing a physically-based metallic roughness material.
-     */
-    interface _IBabylonPbrMetallicRoughness {
-        /**
-         * The albedo color of the material, whose color components should be normalized from 0 to 1.
-         */
-        baseColor: Color3;
-        /**
-         * Represents the transparency of the material, from a range of 0 (transparent) to 1 (opaque).
-         */
-        opacity: number;
-        /**
-         * Represents the "metalness" of a material, from a range of 0 (dielectric) to 1 (metal).
-         */
-        metallic: number;
         /**
-         * Represents the "roughness" of a material, from a range of 0 (completely smooth) to 1 (completely rough).
+         * Allows the maximum specular power to be defined for material calculations.
          */
-        roughness: number;
-    }
+        private static maxSpecularPower = 1024;
 
-    /**
-     * Utility methods for working with glTF material conversion properties.  This class should only be used internally.
-     */
-    export class _GLTFMaterial {
         /**
-         * Represents the dielectric specular values for R, G and B.
-         */
-        private static readonly dielectricSpecular = new Color3(0.04, 0.04, 0.04);
-        /**
-         * Epsilon value, used as a small tolerance value for a numeric value.
+         * Gets the materials from a Babylon scene and converts them to glTF materials.
+         * @param scene
+         * @param mimeType
+         * @param images
+         * @param textures
+         * @param materials
+         * @param imageData
+         * @param hasTextureCoords
          */
-        private static readonly epsilon = 1e-6;
+        public static ConvertMaterialsToGLTF(babylonMaterials: Material[], mimeType: ImageMimeType, images: IImage[], textures: ITexture[], materials: IMaterial[], imageData: { [fileName: string]: { data: Uint8Array, mimeType: ImageMimeType } }, hasTextureCoords: boolean) {
+            for (let i = 0; i < babylonMaterials.length; ++i) {
+                const babylonMaterial = babylonMaterials[i];
+                if (babylonMaterial instanceof StandardMaterial) {
+                    _GLTFMaterial.ConvertStandardMaterial(babylonMaterial, mimeType, images, textures, materials, imageData, hasTextureCoords);
+                }
+                else if (babylonMaterial instanceof PBRMetallicRoughnessMaterial) {
+                    _GLTFMaterial.ConvertPBRMetallicRoughnessMaterial(babylonMaterial, mimeType, images, textures, materials, imageData, hasTextureCoords);
+                }
+            }
+        }
 
         /**
          * Converts a Babylon StandardMaterial to a glTF Metallic Roughness Material.
@@ -65,72 +43,61 @@ module BABYLON.GLTF2 {
          * @returns - glTF Metallic Roughness Material representation
          */
         public static ConvertToGLTFPBRMetallicRoughness(babylonStandardMaterial: StandardMaterial): IMaterialPbrMetallicRoughness {
-            const babylonSpecularGlossiness: IBabylonPbrSpecularGlossiness = {
-                diffuse: babylonStandardMaterial.diffuseColor,
-                opacity: babylonStandardMaterial.alpha,
-                specular: babylonStandardMaterial.specularColor || Color3.Black(),
-                glossiness: babylonStandardMaterial.specularPower / 256
-            };
-            if (babylonStandardMaterial.specularTexture) {
+            const P0 = new BABYLON.Vector2(0, 1);
+            const P1 = new BABYLON.Vector2(0, 0.1);
+            const P2 = new BABYLON.Vector2(0, 0.1);
+            const P3 = new BABYLON.Vector2(1300, 0.1);
 
+            /**
+             * Given the control points, solve for x based on a given t for a cubic bezier curve.
+             * @param t - a value between 0 and 1.
+             * @param p0 - first control point.
+             * @param p1 - second control point.
+             * @param p2 - third control point.
+             * @param p3 - fourth control point.
+             * @returns - number result of cubic bezier curve at the specified t.
+             */
+            function cubicBezierCurve(t: number, p0: number, p1: number, p2: number, p3: number): number {
+                return (
+                    (1 - t) * (1 - t) * (1 - t) * p0 +
+                    3 * (1 - t) * (1 - t) * t * p1 +
+                    3 * (1 - t) * t * t * p2 +
+                    t * t * t * p3
+                );
             }
-            const babylonMetallicRoughness = _GLTFMaterial._ConvertToMetallicRoughness(babylonSpecularGlossiness);
+
+            /**
+             * Evaluates a specified specular power value to determine the appropriate roughness value, 
+             * based on a pre-defined cubic bezier curve with specular on the abscissa axis (x-axis) 
+             * and roughness on the ordinant axis (y-axis).
+             * @param specularPower - specular power of standard material.
+             * @returns - Number representing the roughness value.
+             */
+            function solveForRoughness(specularPower: number): number {
+                var t = Math.pow(specularPower / P3.x, 0.333333);
+                return cubicBezierCurve(t, P0.y, P1.y, P2.y, P3.y);
+            }
+
+            let diffuse = babylonStandardMaterial.diffuseColor.toLinearSpace().scale(0.5);
+            let opacity = babylonStandardMaterial.alpha;
+            let specularPower = Scalar.Clamp(babylonStandardMaterial.specularPower, 0, this.maxSpecularPower);
+
+            const roughness = solveForRoughness(specularPower);
 
             const glTFPbrMetallicRoughness: IMaterialPbrMetallicRoughness = {
                 baseColorFactor: [
-                    babylonMetallicRoughness.baseColor.r,
-                    babylonMetallicRoughness.baseColor.g,
-                    babylonMetallicRoughness.baseColor.b,
-                    babylonMetallicRoughness.opacity
+                    diffuse.r,
+                    diffuse.g,
+                    diffuse.b,
+                    opacity
                 ],
-                metallicFactor: babylonMetallicRoughness.metallic,
-                roughnessFactor: babylonMetallicRoughness.roughness
+                metallicFactor: 0,
+                roughnessFactor: roughness,
             };
 
             return glTFPbrMetallicRoughness;
         }
 
-        /**
-         * Converts Specular Glossiness to Metallic Roughness.  This is based on the algorithm used in the Babylon glTF 3ds Max Exporter.
-         * {@link https://github.com/BabylonJS/Exporters/blob/master/3ds%20Max/Max2Babylon/Exporter/BabylonExporter.GLTFExporter.Material.cs}
-         * @param  babylonSpecularGlossiness - Babylon specular glossiness parameters
-         * @returns - Babylon metallic roughness values
-         */
-        private static _ConvertToMetallicRoughness(babylonSpecularGlossiness: IBabylonPbrSpecularGlossiness): _IBabylonPbrMetallicRoughness {
-            const diffuse = babylonSpecularGlossiness.diffuse;
-            const opacity = babylonSpecularGlossiness.opacity;
-            const specular = babylonSpecularGlossiness.specular;
-            const glossiness = BABYLON.Scalar.Clamp(babylonSpecularGlossiness.glossiness);
-            
-            const oneMinusSpecularStrength = 1 - Math.max(specular.r, Math.max(specular.g, specular.b));
-            const diffusePerceivedBrightness = _GLTFMaterial.PerceivedBrightness(diffuse);
-            const specularPerceivedBrightness = _GLTFMaterial.PerceivedBrightness(specular);
-            const metallic = _GLTFMaterial.SolveMetallic(diffusePerceivedBrightness, specularPerceivedBrightness, oneMinusSpecularStrength);
-            const diffuseScaleFactor = oneMinusSpecularStrength/(1 - this.dielectricSpecular.r) / Math.max(1 - metallic, this.epsilon);
-            const baseColorFromDiffuse = diffuse.scale(diffuseScaleFactor);
-            const baseColorFromSpecular = specular.subtract(this.dielectricSpecular.scale(1 - metallic)).scale(1/ Math.max(metallic, this.epsilon));
-            const lerpColor = Color3.Lerp(baseColorFromDiffuse, baseColorFromSpecular, metallic * metallic);
-            let baseColor = new Color3();
-            lerpColor.clampToRef(0, 1, baseColor);
-
-            const babylonMetallicRoughness: _IBabylonPbrMetallicRoughness = {
-                baseColor: baseColor,
-                opacity: opacity,
-                metallic: metallic,
-                roughness: 1.0 - glossiness
-            };
-
-            return babylonMetallicRoughness;
-        }
-
-        /**
-         * Returns the perceived brightness value based on the provided color
-         * @param color - color used in calculating the perceived brightness
-         * @returns - perceived brightness value
-         */
-        private static PerceivedBrightness(color: Color3): number {
-            return Math.sqrt(0.299 * color.r * color.r + 0.587 * color.g * color.g + 0.114 * color.b * color.b);
-        }
 
         /**
          * Computes the metallic factor
@@ -140,17 +107,18 @@ module BABYLON.GLTF2 {
          * @returns - metallic value
          */
         public static SolveMetallic(diffuse: number, specular: number, oneMinusSpecularStrength: number): number {
-            if (specular < this.dielectricSpecular.r) {
+            if (specular < _GLTFMaterial.dielectricSpecular.r) {
+                _GLTFMaterial.dielectricSpecular
                 return 0;
             }
 
-            const a = this.dielectricSpecular.r;
-            const b = diffuse * oneMinusSpecularStrength /(1.0 - this.dielectricSpecular.r) + specular - 2.0 * this.dielectricSpecular.r;
-            const c = this.dielectricSpecular.r - specular;
+            const a = _GLTFMaterial.dielectricSpecular.r;
+            const b = diffuse * oneMinusSpecularStrength / (1.0 - _GLTFMaterial.dielectricSpecular.r) + specular - 2.0 * _GLTFMaterial.dielectricSpecular.r;
+            const c = _GLTFMaterial.dielectricSpecular.r - specular;
             const D = b * b - 4.0 * a * c;
-            return BABYLON.Scalar.Clamp((-b + Math.sqrt(D))/(2.0 * a));
+            return BABYLON.Scalar.Clamp((-b + Math.sqrt(D)) / (2.0 * a), 0, 1);
         }
-        
+
         /**
          * Gets the glTF alpha mode from the Babylon Material
          * @param babylonMaterial - Babylon Material
@@ -159,10 +127,10 @@ module BABYLON.GLTF2 {
         public static GetAlphaMode(babylonMaterial: Material): MaterialAlphaMode {
             if (babylonMaterial instanceof StandardMaterial) {
                 const babylonStandardMaterial = babylonMaterial as StandardMaterial;
-                if ((babylonStandardMaterial.alpha != 1.0) || 
+                if ((babylonStandardMaterial.alpha != 1.0) ||
                     (babylonStandardMaterial.diffuseTexture != null && babylonStandardMaterial.diffuseTexture.hasAlpha) ||
                     (babylonStandardMaterial.opacityTexture != null)) {
-                    return  MaterialAlphaMode.BLEND;
+                    return MaterialAlphaMode.BLEND;
                 }
                 else {
                     return MaterialAlphaMode.OPAQUE;
@@ -171,7 +139,7 @@ module BABYLON.GLTF2 {
             else if (babylonMaterial instanceof PBRMetallicRoughnessMaterial) {
                 const babylonPBRMetallicRoughness = babylonMaterial as PBRMetallicRoughnessMaterial;
 
-                switch(babylonPBRMetallicRoughness.transparencyMode) {
+                switch (babylonPBRMetallicRoughness.transparencyMode) {
                     case PBRMaterial.PBRMATERIAL_OPAQUE: {
                         return MaterialAlphaMode.OPAQUE;
                     }
@@ -182,7 +150,7 @@ module BABYLON.GLTF2 {
                         return MaterialAlphaMode.MASK;
                     }
                     case PBRMaterial.PBRMATERIAL_ALPHATESTANDBLEND: {
-                        console.warn("GLTF Exporter | Alpha test and blend mode not supported in glTF.  Alpha blend used instead.");
+                        Tools.Warn(babylonMaterial.name + ": GLTF Exporter | Alpha test and blend mode not supported in glTF.  Alpha blend used instead.");
                         return MaterialAlphaMode.BLEND;
                     }
                     default: {
@@ -192,7 +160,254 @@ module BABYLON.GLTF2 {
             }
             else {
                 throw new Error("Unsupported Babylon material type");
-            }   
+            }
+        }
+
+        /**
+         * Converts a Babylon Standard Material to a glTF Material.
+         * @param babylonStandardMaterial - BJS Standard Material.
+         * @param mimeType - mime type to use for the textures.
+         * @param images - array of glTF image interfaces.
+         * @param textures - array of glTF texture interfaces.
+         * @param materials - array of glTF material interfaces.
+         * @param imageData - map of image file name to data.
+         * @param hasTextureCoords - specifies if texture coordinates are present on the submesh to determine if textures should be applied.
+         */
+        public static ConvertStandardMaterial(babylonStandardMaterial: StandardMaterial, mimeType: ImageMimeType, images: IImage[], textures: ITexture[], materials: IMaterial[], imageData: { [fileName: string]: { data: Uint8Array, mimeType: ImageMimeType } }, hasTextureCoords: boolean) {
+            Tools.Warn(babylonStandardMaterial.name + ": Standard Material is currently not fully supported/implemented in glTF serializer");
+            const glTFPbrMetallicRoughness = _GLTFMaterial.ConvertToGLTFPBRMetallicRoughness(babylonStandardMaterial);
+
+            const glTFMaterial: IMaterial = { name: babylonStandardMaterial.name };
+            if (babylonStandardMaterial.backFaceCulling) {
+                if (!babylonStandardMaterial.twoSidedLighting) {
+                    Tools.Warn(babylonStandardMaterial.name + ": Back-face culling enabled and two-sided lighting disabled is not supported in glTF.");
+                }
+                glTFMaterial.doubleSided = true;
+            }
+            if (hasTextureCoords) {
+                if (babylonStandardMaterial.diffuseTexture) {
+                    const glTFTexture = _GLTFMaterial.ExportTexture(babylonStandardMaterial.diffuseTexture, mimeType, images, textures, imageData);
+                    if (glTFTexture != null) {
+                        glTFPbrMetallicRoughness.baseColorTexture = glTFTexture;
+                    }
+                }
+                if (babylonStandardMaterial.bumpTexture) {
+                    const glTFTexture = _GLTFMaterial.ExportTexture(babylonStandardMaterial.bumpTexture, mimeType, images, textures, imageData)
+                    if (glTFTexture) {
+                        glTFMaterial.normalTexture = glTFTexture;
+                    }
+                }
+                if (babylonStandardMaterial.emissiveTexture) {
+                    const glTFEmissiveTexture = _GLTFMaterial.ExportTexture(babylonStandardMaterial.emissiveTexture, mimeType, images, textures, imageData)
+                    if (glTFEmissiveTexture) {
+                        glTFMaterial.emissiveTexture = glTFEmissiveTexture;
+                    }
+                    glTFMaterial.emissiveFactor = [1.0, 1.0, 1.0];
+                }
+                if (babylonStandardMaterial.ambientTexture) {
+                    const glTFOcclusionTexture = _GLTFMaterial.ExportTexture(babylonStandardMaterial.ambientTexture, mimeType, images, textures, imageData)
+                    if (glTFOcclusionTexture) {
+                        glTFMaterial.occlusionTexture = glTFOcclusionTexture;
+                    }
+                }
+            }
+
+            if (babylonStandardMaterial.alpha < 1.0 || babylonStandardMaterial.opacityTexture) {
+
+                if (babylonStandardMaterial.alphaMode === Engine.ALPHA_COMBINE) {
+                    glTFMaterial.alphaMode = GLTF2.MaterialAlphaMode.BLEND;
+                }
+                else {
+                    Tools.Warn(babylonStandardMaterial.name + ": glTF 2.0 does not support alpha mode: " + babylonStandardMaterial.alphaMode.toString());
+                }
+            }
+
+            glTFMaterial.pbrMetallicRoughness = glTFPbrMetallicRoughness;
+
+            materials.push(glTFMaterial);
+        }
+
+        /**
+         * Converts a Babylon PBR Metallic Roughness Material to a glTF Material.
+         * @param babylonPBRMetalRoughMaterial - BJS PBR Metallic Roughness Material.
+         * @param mimeType - mime type to use for the textures.
+         * @param images - array of glTF image interfaces.
+         * @param textures - array of glTF texture interfaces.
+         * @param materials - array of glTF material interfaces.
+         * @param imageData - map of image file name to data.
+         * @param hasTextureCoords - specifies if texture coordinates are present on the submesh to determine if textures should be applied.
+         */
+        public static ConvertPBRMetallicRoughnessMaterial(babylonPBRMetalRoughMaterial: PBRMetallicRoughnessMaterial, mimeType: ImageMimeType, images: IImage[], textures: ITexture[], materials: IMaterial[], imageData: { [fileName: string]: { data: Uint8Array, mimeType: ImageMimeType } }, hasTextureCoords: boolean) {
+            const glTFPbrMetallicRoughness: IMaterialPbrMetallicRoughness = {};
+
+            if (babylonPBRMetalRoughMaterial.baseColor) {
+                glTFPbrMetallicRoughness.baseColorFactor = [
+                    babylonPBRMetalRoughMaterial.baseColor.r,
+                    babylonPBRMetalRoughMaterial.baseColor.g,
+                    babylonPBRMetalRoughMaterial.baseColor.b,
+                    babylonPBRMetalRoughMaterial.alpha
+                ];
+            }
+
+            if (babylonPBRMetalRoughMaterial.metallic != null) {
+                glTFPbrMetallicRoughness.metallicFactor = babylonPBRMetalRoughMaterial.metallic;
+            }
+            if (babylonPBRMetalRoughMaterial.roughness != null) {
+                glTFPbrMetallicRoughness.roughnessFactor = babylonPBRMetalRoughMaterial.roughness;
+            }
+
+            const glTFMaterial: IMaterial = {
+                name: babylonPBRMetalRoughMaterial.name
+            };
+            if (babylonPBRMetalRoughMaterial.doubleSided) {
+                glTFMaterial.doubleSided = babylonPBRMetalRoughMaterial.doubleSided;
+            }
+
+            if (hasTextureCoords) {
+                if (babylonPBRMetalRoughMaterial.baseTexture != null) {
+                    const glTFTexture = _GLTFMaterial.ExportTexture(babylonPBRMetalRoughMaterial.baseTexture, mimeType, images, textures, imageData);
+                    if (glTFTexture != null) {
+                        glTFPbrMetallicRoughness.baseColorTexture = glTFTexture;
+                    }
+                }
+                if (babylonPBRMetalRoughMaterial.normalTexture) {
+                    const glTFTexture = _GLTFMaterial.ExportTexture(babylonPBRMetalRoughMaterial.normalTexture, mimeType, images, textures, imageData);
+                    if (glTFTexture) {
+                        glTFMaterial.normalTexture = glTFTexture;
+                    }
+                }
+                if (babylonPBRMetalRoughMaterial.occlusionTexture) {
+                    const glTFTexture = _GLTFMaterial.ExportTexture(babylonPBRMetalRoughMaterial.occlusionTexture, mimeType, images, textures, imageData);
+                    if (glTFTexture) {
+                        glTFMaterial.occlusionTexture = glTFTexture;
+                        if (babylonPBRMetalRoughMaterial.occlusionStrength != null) {
+                            glTFMaterial.occlusionTexture.strength = babylonPBRMetalRoughMaterial.occlusionStrength;
+                        }
+                    }
+                }
+                if (babylonPBRMetalRoughMaterial.emissiveTexture) {
+                    const glTFTexture = _GLTFMaterial.ExportTexture(babylonPBRMetalRoughMaterial.emissiveTexture, mimeType, images, textures, imageData);
+                    if (glTFTexture != null) {
+                        glTFMaterial.emissiveTexture = glTFTexture;
+                    }
+                }
+            }
+
+            if (babylonPBRMetalRoughMaterial.emissiveColor.equalsFloats(0.0, 0.0, 0.0)) {
+                glTFMaterial.emissiveFactor = babylonPBRMetalRoughMaterial.emissiveColor.asArray();
+            }
+            if (babylonPBRMetalRoughMaterial.transparencyMode != null) {
+                const alphaMode = _GLTFMaterial.GetAlphaMode(babylonPBRMetalRoughMaterial);
+
+                if (alphaMode !== MaterialAlphaMode.OPAQUE) { //glTF defaults to opaque
+                    glTFMaterial.alphaMode = alphaMode;
+                    if (alphaMode === MaterialAlphaMode.BLEND) {
+                        glTFMaterial.alphaCutoff = babylonPBRMetalRoughMaterial.alphaCutOff;
+                    }
+                }
+            }
+
+            glTFMaterial.pbrMetallicRoughness = glTFPbrMetallicRoughness;
+
+            materials.push(glTFMaterial);
+        }
+
+        /**
+         * Extracts a texture from a Babylon texture into file data and glTF data.
+         * @param babylonTexture - Babylon texture to extract.
+         * @param mimeType - Mime Type of the babylonTexture.
+         * @param images - Array of glTF images.
+         * @param textures - Array of glTF textures.
+         * @param imageData - map of image file name and data.
+         * @return - glTF texture, or null if the texture format is not supported.
+         */
+        public static ExportTexture(babylonTexture: BaseTexture, mimeType: ImageMimeType, images: IImage[], textures: ITexture[], imageData: { [fileName: string]: { data: Uint8Array, mimeType: ImageMimeType } }): Nullable<ITextureInfo> {
+            let textureInfo: Nullable<ITextureInfo> = null;
+
+            let glTFTexture = {
+                source: images.length
+            };
+
+            let textureName = "texture_" + (textures.length - 1).toString();
+            let textureData = babylonTexture.getInternalTexture();
+
+            if (textureData != null) {
+                textureName = textureData.url;
+            }
+
+            textureName = Tools.GetFilename(textureName);
+            const baseFile = textureName.split('.')[0];
+            let extension = "";
+
+
+            if (mimeType === ImageMimeType.JPEG) {
+                extension = ".jpg";
+            }
+            else if (mimeType === ImageMimeType.PNG) {
+                extension = ".png";
+            }
+            else {
+                Tools.Error("Unsupported mime type " + mimeType);
+            }
+            textureName = baseFile + extension;
+
+
+            const pixels = babylonTexture.readPixels() as Uint8Array;
+
+            const imageCanvas = document.createElement('canvas');
+            imageCanvas.id = "ImageCanvas";
+
+            const ctx = <CanvasRenderingContext2D>imageCanvas.getContext('2d');
+            const size = babylonTexture.getSize();
+            imageCanvas.width = size.width;
+            imageCanvas.height = size.height;
+
+            const imgData = ctx.createImageData(size.width, size.height);
+
+
+            imgData.data.set(pixels);
+            ctx.putImageData(imgData, 0, 0);
+            const base64Data = imageCanvas.toDataURL(mimeType);
+            const binStr = atob(base64Data.split(',')[1]);
+            const arr = new Uint8Array(binStr.length);
+            for (let i = 0; i < binStr.length; ++i) {
+                arr[i] = binStr.charCodeAt(i);
+            }
+            const imageValues = { data: arr, mimeType: mimeType };
+
+            imageData[textureName] = imageValues;
+            if (mimeType === ImageMimeType.JPEG) {
+                const glTFImage: IImage = {
+                    uri: textureName
+                }
+                let foundIndex = -1;
+                for (let i = 0; i < images.length; ++i) {
+                    if (images[i].uri === textureName) {
+                        foundIndex = i;
+                        break;
+                    }
+                }
+                if (foundIndex === -1) {
+                    images.push(glTFImage);
+                    glTFTexture.source = images.length - 1;
+                    textures.push({
+                        source: images.length - 1
+                    });
+
+                    textureInfo = {
+                        index: images.length - 1
+                    }
+                }
+                else {
+                    glTFTexture.source = foundIndex;
+
+                    textureInfo = {
+                        index: foundIndex
+                    }
+                }
+            }
+
+            return textureInfo;
         }
     }
 }

+ 14 - 13
tests/unit/babylon/serializers/babylon.glTFSerializer.tests.ts

@@ -39,10 +39,10 @@ describe('Babylon glTF Serializer', () => {
             const scene = new BABYLON.Scene(subject);
             const babylonMaterial = new BABYLON.PBRMetallicRoughnessMaterial("metallicroughness", scene);
             babylonMaterial.transparencyMode = BABYLON.PBRMaterial.PBRMATERIAL_OPAQUE;
-            
+
             alphaMode = BABYLON.GLTF2._GLTFMaterial.GetAlphaMode(babylonMaterial);
             alphaMode.should.be.equal('OPAQUE');
-            
+
             babylonMaterial.transparencyMode = BABYLON.PBRMaterial.PBRMATERIAL_ALPHABLEND;
             alphaMode = BABYLON.GLTF2._GLTFMaterial.GetAlphaMode(babylonMaterial);
             alphaMode.should.be.equal('BLEND');
@@ -53,7 +53,7 @@ describe('Babylon glTF Serializer', () => {
 
             babylonMaterial.transparencyMode = BABYLON.PBRMaterial.PBRMATERIAL_ALPHATEST;
             alphaMode = BABYLON.GLTF2._GLTFMaterial.GetAlphaMode(babylonMaterial);
-            alphaMode.should.be.equal('MASK'); 
+            alphaMode.should.be.equal('MASK');
         });
 
         it('should convert Babylon standard material to metallic roughness', () => {
@@ -66,11 +66,11 @@ describe('Babylon glTF Serializer', () => {
 
             const metalRough = BABYLON.GLTF2._GLTFMaterial.ConvertToGLTFPBRMetallicRoughness(babylonStandardMaterial);
 
-            metalRough.baseColorFactor.should.deep.equal([1,1,1,1]);
+            metalRough.baseColorFactor.should.deep.equal([0.5, 0.5, 0.5, 1]);
 
             metalRough.metallicFactor.should.be.equal(0);
-            
-            metalRough.roughnessFactor.should.be.equal(0.75);
+
+            metalRough.roughnessFactor.should.be.approximately(0.328809, 1e-6);
         });
 
         it('should solve for metallic', () => {
@@ -107,8 +107,8 @@ describe('Babylon glTF Serializer', () => {
                 const jsonString = glTFData.glTFFiles['test.gltf'] as string;
                 const jsonData = JSON.parse(jsonString);
 
-                // accessors, asset, buffers, bufferViews, meshes, nodes, scene, scenes, 
-                Object.keys(jsonData).length.should.be.equal(8);
+                // accessors, asset, buffers, bufferViews, meshes, nodes, scene, scenes, materials
+                Object.keys(jsonData).length.should.be.equal(9);
 
                 // positions, normals, texture coords, indices
                 jsonData.accessors.length.should.be.equal(4);
@@ -132,7 +132,7 @@ describe('Babylon glTF Serializer', () => {
                 done();
             });
         });
-        
+
         it('should serialize alpha mode and cutoff', (done) => {
             mocha.timeout(10000);
             const scene = new BABYLON.Scene(subject);
@@ -151,14 +151,15 @@ describe('Babylon glTF Serializer', () => {
                 const jsonString = glTFData.glTFFiles['test.gltf'] as string;
                 const jsonData = JSON.parse(jsonString);
 
+                // accessors, asset, buffers, bufferViews, meshes, nodes, scene, scenes, materials
                 Object.keys(jsonData).length.should.be.equal(9);
 
-                jsonData.materials.length.should.be.equal(1);
-                
+                jsonData.materials.length.should.be.equal(2);
+
                 jsonData.materials[0].alphaMode.should.be.equal('BLEND');
-                
+
                 jsonData.materials[0].alphaCutoff.should.be.equal(alphaCutoff);
-                
+
                 done();
             });
         });