Browse Source

Update babylon standard material conversion to PBR material

noalak 8 years ago
parent
commit
564d7a4e0c

+ 98 - 0
Exporters/3ds Max/BabylonExport.Entities/BabylonColor3.cs

@@ -0,0 +1,98 @@
+using System;
+using System.Drawing;
+using System.Runtime.Serialization;
+
+namespace BabylonExport.Entities
+{
+    public class BabylonColor3
+    {
+        public float r;
+        public float g;
+        public float b;
+
+        public BabylonColor3(float r = 0, float g = 0, float b = 0)
+        {
+            this.r = r;
+            this.g = g;
+            this.b = b;
+        }
+
+        public BabylonColor3(float[] array)
+        {
+            if (array.Length >= 3)
+            {
+                this.r = array[0];
+                this.g = array[1];
+                this.b = array[2];
+            }
+        }
+
+        public BabylonColor3(Color color)
+        {
+            this.r = color.R / 255.0f;
+            this.g = color.G / 255.0f;
+            this.b = color.B / 255.0f;
+        }
+
+        public override string ToString()
+        {
+            return "{ r=" + r + ", g=" + g + ", b=" + b + "}";
+        }
+
+        public BabylonColor3 clamp(float min = 0, float max = 1)
+        {
+            this.r = ClampScalar(this.r, min, max);
+            this.g = ClampScalar(this.g, min, max);
+            this.b = ClampScalar(this.b, min, max);
+            return this;
+        }
+
+        public float getPerceivedBrightness()
+        {
+            return (float)Math.Sqrt(0.299 * this.r * this.r + 0.587 * this.g * this.g + 0.114 * this.b * this.b);
+        }
+
+        public float getMaxComponent()
+        {
+            return Math.Max(this.r, Math.Max(this.g, this.b));
+        }
+
+        /**
+         * Multiplies in place each rgb value by scale.  
+         * Returns the updated Color3.  
+         */
+        public BabylonColor3 scale(float scale)
+        {
+            return new BabylonColor3(this.r * scale, this.g * scale, this.b * scale);
+        }
+
+        /**
+         * Returns a new Color3 set with the subtracted values of the passed one from the current Color3.    
+         */
+        public BabylonColor3 subtract(BabylonColor3 right)
+        {
+            return new BabylonColor3(this.r - right.r, this.g - right.g, this.b - right.b);
+        }
+
+        /**
+         * Creates a new Color3 with values linearly interpolated of "amount" between the start Color3 and the end Color3.  
+         */
+        public static BabylonColor3 Lerp(BabylonColor3 start, BabylonColor3 end, float amount)
+        {
+            var r = start.r + ((end.r - start.r) * amount);
+            var g = start.g + ((end.g - start.g) * amount);
+            var b = start.b + ((end.b - start.b) * amount);
+            return new BabylonColor3(r, g, b);
+        }
+
+        /**
+         * Returns the value itself if it's between min and max.  
+         * Returns min if the value is lower than min.
+         * Returns max if the value is greater than max.  
+         */
+        private static float ClampScalar(float value, float min = 0, float max = 1)
+        {
+            return Math.Min(max, Math.Max(min, value));
+        }
+    }
+}

+ 1 - 0
Exporters/3ds Max/BabylonExport.Entities/BabylonExport.Entities.csproj

@@ -70,6 +70,7 @@
     <Compile Include="BabylonNode.cs" />
     <Compile Include="BabylonPBRMaterial.cs" />
     <Compile Include="BabylonShaderMaterial.cs" />
+    <Compile Include="BabylonColor3.cs" />
     <Compile Include="BabylonStandardMaterial.cs" />
     <Compile Include="BabylonAbstractMesh.cs" />
     <Compile Include="BabylonMesh.cs" />

+ 4 - 0
Exporters/3ds Max/BabylonExport.Entities/BabylonStandardMaterial.cs

@@ -77,6 +77,9 @@ namespace BabylonExport.Entities
         [DataMember]
         public int maxSimultaneousLights { get; set; }
 
+        [DataMember]
+        public bool useGlossinessFromSpecularMapAlpha { get; set; }
+
         public BabylonStandardMaterial() : base()
         {
             SetCustomType("BABYLON.StandardMaterial");
@@ -89,6 +92,7 @@ namespace BabylonExport.Entities
             useSpecularOverAlpha = true;
             useEmissiveAsIllumination = false;
             linkEmissiveWithDiffuse = false;
+            useGlossinessFromSpecularMapAlpha = false;
         }
 
         public void SetCustomType(string type)

+ 201 - 13
Exporters/3ds Max/Max2Babylon/2017/Exporter/BabylonExporter.GLTFExporter.Material.cs

@@ -1,5 +1,8 @@
 using BabylonExport.Entities;
 using GLTFExport.Entities;
+using System;
+using System.Drawing;
+using System.IO;
 
 namespace Max2Babylon
 {
@@ -23,6 +26,7 @@ namespace Max2Babylon
                 RaiseMessage("GLTFExporter.Material | babylonMaterial.alpha=" + babylonMaterial.alpha, 3);
                 RaiseMessage("GLTFExporter.Material | babylonMaterial.backFaceCulling=" + babylonMaterial.backFaceCulling, 3);
                 RaiseMessage("GLTFExporter.Material | babylonMaterial.wireframe=" + babylonMaterial.wireframe, 3);
+                RaiseMessage("GLTFExporter.Material | babylonStandardMaterial.specularPower=" + babylonStandardMaterial.specularPower, 3);
 
                 // Ambient
                 for (int i = 0; i < babylonStandardMaterial.ambient.Length; i++)
@@ -113,28 +117,126 @@ namespace Max2Babylon
                 var gltfPbrMetallicRoughness = new GLTFPBRMetallicRoughness();
                 gltfMaterial.pbrMetallicRoughness = gltfPbrMetallicRoughness;
 
-                // TODO - Retreive diffuse or albedo?
+                // --- Global ---
+
+                SpecularGlossiness _specularGlossiness = new SpecularGlossiness
+                {
+                    diffuse = new BabylonColor3(babylonStandardMaterial.diffuse),
+                    opacity = babylonMaterial.alpha,
+                    specular = new BabylonColor3(babylonStandardMaterial.specular),
+                    glossiness = babylonStandardMaterial.specularPower / 256
+                };
+
+                MetallicRoughness _metallicRoughness = ConvertToMetallicRoughness(_specularGlossiness, true);
+
                 // Base color
-                var babylonDiffuseColor = babylonStandardMaterial.diffuse;
                 gltfPbrMetallicRoughness.baseColorFactor = new float[4]
                 {
-                    babylonDiffuseColor[0],
-                    babylonDiffuseColor[1],
-                    babylonDiffuseColor[2],
-                    babylonMaterial.alpha
+                    _metallicRoughness.baseColor.r,
+                    _metallicRoughness.baseColor.g,
+                    _metallicRoughness.baseColor.b,
+                    _metallicRoughness.opacity
                 };
-                gltfPbrMetallicRoughness.baseColorTexture = ExportTexture(babylonStandardMaterial.diffuseTexture, gltf);
-                 
-                // TODO - Metallic roughness
-                gltfPbrMetallicRoughness.metallicFactor = 0; // Non metal
-                // TODO - roughnessFactor
-                // TODO - metallicRoughnessTexture
+
+                // Metallic roughness
+                gltfPbrMetallicRoughness.metallicFactor = _metallicRoughness.metallic;
+                gltfPbrMetallicRoughness.roughnessFactor = _metallicRoughness.roughness;
+
+
+                // --- Textures ---
+
+                if (babylonStandardMaterial.diffuseTexture != null)
+                {
+                    Func<string, Bitmap> loadTexture = delegate (string textureName)
+                    {
+                        var pathDiffuse = Path.Combine(gltf.OutputPath, textureName);
+                        if (File.Exists(pathDiffuse))
+                        {
+                            return new Bitmap(pathDiffuse);
+                        }
+                        else
+                        {
+                            RaiseWarning(string.Format("GLTFExporter.Material | Texture {0} not found.", textureName), 2);
+                            return null;
+                        }
+                    };
+
+                    Bitmap diffuseBitmap = loadTexture(babylonStandardMaterial.diffuseTexture.name);
+
+                    if (diffuseBitmap != null)
+                    {
+                        Bitmap specularBitmap = null;
+                        if (babylonStandardMaterial.specularTexture != null)
+                        {
+                            specularBitmap = loadTexture(babylonStandardMaterial.specularTexture.name);
+                        }
+
+                        Bitmap opacityBitmap = null;
+                        if (babylonStandardMaterial.diffuseTexture.hasAlpha == false && babylonStandardMaterial.opacityTexture != null)
+                        {
+                            opacityBitmap = loadTexture(babylonStandardMaterial.opacityTexture.name);
+                        }
+
+                        // Retreive dimension from diffuse map
+                        var width = diffuseBitmap.Width;
+                        var height = diffuseBitmap.Height;
+
+                        // Create base color and metallic+roughness maps
+                        Bitmap baseColorBitmap = new Bitmap(width, height);
+                        Bitmap metallicRoughnessBitmap = new Bitmap(width, height);
+                        for (int x = 0; x < width; x++)
+                        {
+                            for (int y = 0; y < height; y++)
+                            {
+                                var diffuse = diffuseBitmap.GetPixel(x, y);
+                                SpecularGlossiness specularGlossinessTexture = new SpecularGlossiness
+                                {
+                                    diffuse = new BabylonColor3(diffuse),
+                                    opacity = babylonStandardMaterial.diffuseTexture.hasAlpha? diffuse.A / 255.0f :
+                                              opacityBitmap != null && babylonStandardMaterial.opacityTexture.getAlphaFromRGB ? opacityBitmap.GetPixel(x, y).R / 255.0f :
+                                              opacityBitmap != null && babylonStandardMaterial.opacityTexture.getAlphaFromRGB == false ? opacityBitmap.GetPixel(x, y).A / 255.0f :
+                                              1,
+                                    specular = specularBitmap != null ? new BabylonColor3(specularBitmap.GetPixel(x, y)) :
+                                               new BabylonColor3(),
+                                    glossiness = babylonStandardMaterial.useGlossinessFromSpecularMapAlpha && specularBitmap != null ? specularBitmap.GetPixel(x, y).A / 255.0f :
+                                                 babylonStandardMaterial.specularPower / 256.0f
+                                };
+
+                                var displayPrints = x == width / 2 && y == height / 2;
+                                MetallicRoughness metallicRoughnessTexture = ConvertToMetallicRoughness(specularGlossinessTexture, displayPrints);
+                                
+                                Color colorBase = Color.FromArgb(
+                                    (int)(metallicRoughnessTexture.opacity * 255),
+                                    (int)(metallicRoughnessTexture.baseColor.r * 255),
+                                    (int)(metallicRoughnessTexture.baseColor.g * 255),
+                                    (int)(metallicRoughnessTexture.baseColor.b * 255)
+                                );
+                                baseColorBitmap.SetPixel(x, y, colorBase);
+
+                                // The metalness values are sampled from the B channel.
+                                // The roughness values are sampled from the G channel.
+                                // These values are linear. If other channels are present (R or A), they are ignored for metallic-roughness calculations.
+                                Color colorMetallicRoughness = Color.FromArgb(
+                                    0,
+                                    (int)(metallicRoughnessTexture.roughness * 255),
+                                    (int)(metallicRoughnessTexture.metallic * 255)
+                                );
+                                metallicRoughnessBitmap.SetPixel(x, y, colorMetallicRoughness);
+                            }
+                        }
+
+                        // Export maps and textures
+                        gltfPbrMetallicRoughness.baseColorTexture = ExportBitmapTexture(babylonStandardMaterial.diffuseTexture, baseColorBitmap, babylonMaterial.name + "_baseColor" + ".png", gltf);
+                        gltfPbrMetallicRoughness.metallicRoughnessTexture = ExportBitmapTexture(babylonStandardMaterial.diffuseTexture, metallicRoughnessBitmap, babylonMaterial.name + "_metallicRoughness" + ".jpg", gltf);
+                    }
+                }
             }
         }
 
         private void getAlphaMode(BabylonStandardMaterial babylonMaterial, out string alphaMode, out float? alphaCutoff)
         {
-            if (babylonMaterial.diffuseTexture != null && babylonMaterial.diffuseTexture.hasAlpha)
+            if ((babylonMaterial.diffuseTexture != null && babylonMaterial.diffuseTexture.hasAlpha) ||
+                 babylonMaterial.opacityTexture != null)
             {
                 // TODO - Babylon standard material is assumed to useAlphaFromDiffuseTexture. If not, the alpha mode is a mask.
                 alphaMode = GLTFMaterial.AlphaMode.BLEND.ToString();
@@ -146,5 +248,91 @@ namespace Max2Babylon
             }
             alphaCutoff = null;
         }
+
+        BabylonColor3 dielectricSpecular = new BabylonColor3(0.04f, 0.04f, 0.04f);
+        const float epsilon = 1e-6f;
+
+        private MetallicRoughness ConvertToMetallicRoughness(SpecularGlossiness specularGlossiness, bool displayPrints = false)
+        {
+            var diffuse = specularGlossiness.diffuse;
+            var opacity = specularGlossiness.opacity;
+            var specular = specularGlossiness.specular;
+            var glossiness = specularGlossiness.glossiness;
+
+            var oneMinusSpecularStrength = 1 - specular.getMaxComponent();
+            var metallic = solveMetallic(diffuse.getPerceivedBrightness(), specular.getPerceivedBrightness(), oneMinusSpecularStrength);
+
+            var diffuseScaleFactor = oneMinusSpecularStrength / (1 - dielectricSpecular.r) / Math.Max(1 - metallic, epsilon);
+            var baseColorFromDiffuse = diffuse.scale(diffuseScaleFactor);
+            var baseColorFromSpecular = specular.subtract(dielectricSpecular.scale(1 - metallic)).scale(1 / Math.Max(metallic, epsilon));
+            var baseColor = BabylonColor3.Lerp(baseColorFromDiffuse, baseColorFromSpecular, metallic * metallic).clamp();
+            //var baseColor = baseColorFromDiffuse.clamp();
+
+            if (displayPrints)
+            {
+                RaiseMessage("-----------------------", 3);
+                RaiseMessage("diffuse=" + diffuse, 3);
+                RaiseMessage("opacity=" + opacity, 3);
+                RaiseMessage("specular=" + specular, 3);
+                RaiseMessage("glossiness=" + glossiness, 3);
+
+                RaiseMessage("oneMinusSpecularStrength=" + oneMinusSpecularStrength, 3);
+                RaiseMessage("metallic=" + metallic, 3);
+                RaiseMessage("diffuseScaleFactor=" + diffuseScaleFactor, 3);
+                RaiseMessage("baseColorFromDiffuse=" + baseColorFromDiffuse, 3);
+                RaiseMessage("baseColorFromSpecular=" + baseColorFromSpecular, 3);
+                RaiseMessage("metallic * metallic=" + metallic * metallic, 3);
+                RaiseMessage("baseColor=" + baseColor, 3);
+                RaiseMessage("-----------------------", 3);
+            }
+
+            return new MetallicRoughness
+            {
+                baseColor = baseColor,
+                opacity = opacity,
+                metallic = metallic,
+                roughness = 1 - glossiness
+            };
+        }
+
+        private float solveMetallic(float diffuse, float specular, float oneMinusSpecularStrength)
+        {
+            if (specular < dielectricSpecular.r)
+            {
+                return 0;
+            }
+
+            var a = dielectricSpecular.r;
+            var b = diffuse * oneMinusSpecularStrength / (1 - dielectricSpecular.r) + specular - 2 * dielectricSpecular.r;
+            var c = dielectricSpecular.r - specular;
+            var D = b * b - 4 * a * c;
+            return ClampScalar((float)(-b + Math.Sqrt(D)) / (2 * a), 0, 1);
+        }
+
+        /**
+         * Returns the value itself if it's between min and max.  
+         * Returns min if the value is lower than min.
+         * Returns max if the value is greater than max.  
+         */
+        private static float ClampScalar(float value, float min = 0, float max = 1)
+        {
+            return Math.Min(max, Math.Max(min, value));
+        }
+
+        private class SpecularGlossiness
+        {
+            public BabylonColor3 diffuse;
+            public float opacity;
+            public BabylonColor3 specular;
+            public float glossiness;
+        }
+
+        private class MetallicRoughness
+        {
+            public BabylonColor3 baseColor;
+            public float opacity;
+            public float metallic;
+            public float roughness;
+        }
     }
 }

+ 33 - 27
Exporters/3ds Max/Max2Babylon/2017/Exporter/BabylonExporter.GLTFExporter.Texture.cs

@@ -1,18 +1,47 @@
 using BabylonExport.Entities;
 using GLTFExport.Entities;
+using System.Drawing;
+using System.IO;
 
 namespace Max2Babylon
 {
     partial class BabylonExporter
     {
-        private GLTFTextureInfo ExportTexture(BabylonTexture babylonTexture, GLTF gltf)
+        /// <summary>
+        /// Export the texture using the parameters of babylonTexture except its name.
+        /// Write the bitmap file
+        /// </summary>
+        /// <param name="babylonTexture"></param>
+        /// <param name="bitmap"></param>
+        /// <param name="name"></param>
+        /// <param name="gltf"></param>
+        /// <returns></returns>
+        private GLTFTextureInfo ExportBitmapTexture(BabylonTexture babylonTexture, Bitmap bitmap, string name, GLTF gltf)
+        {
+            // Copy image to output
+            if (CopyTexturesToOutput)
+            {
+                var absolutePath = Path.Combine(gltf.OutputPath, name);
+                RaiseMessage($"GLTFExporter.Texture | write image '{name}' to '{absolutePath}'", 1);
+                bitmap.Save(absolutePath);
+            }
+
+            return ExportTexture(babylonTexture, gltf, name);
+        }
+
+        private GLTFTextureInfo ExportTexture(BabylonTexture babylonTexture, GLTF gltf, string name = null)
         {
             if (babylonTexture == null)
             {
                 return null;
             }
 
-            RaiseMessage("GLTFExporter.Texture | Export texture named: " + babylonTexture.name, 1);
+            if (name == null)
+            {
+                name = babylonTexture.name;
+            }
+
+            RaiseMessage("GLTFExporter.Texture | Export texture named: " + name, 1);
 
             // --------------------------
             // -------- Sampler ---------
@@ -42,7 +71,7 @@ namespace Max2Babylon
             RaiseMessage("GLTFExporter.Texture | create image", 2);
             GLTFImage gltfImage = new GLTFImage
             {
-                uri = babylonTexture.name
+                uri = name
             };
 
             gltfImage.index = gltf.ImagesList.Count;
@@ -56,7 +85,7 @@ namespace Max2Babylon
             RaiseMessage("GLTFExporter.Texture | create texture", 2);
             var gltfTexture = new GLTFTexture
             {
-                name = babylonTexture.name,
+                name = name,
                 sampler = gltfSampler.index,
                 source = gltfImage.index
             };
@@ -75,29 +104,6 @@ namespace Max2Babylon
 
             // TODO - Animations
 
-            //// Copy image to output
-            //var absolutePath = texture.Map.FullFilePath;
-            //try
-            //{
-            //    if (File.Exists(absolutePath))
-            //    {
-            //        if (CopyTexturesToOutput)
-            //        {
-            //            RaiseMessage("GLTFExporter.Texture | copy image src path = "+ absolutePath + " and dest path = "+ Path.Combine(gltf.OutputPath, gltfTexture.name));
-            //            File.Copy(absolutePath, Path.Combine(gltf.OutputPath, gltfTexture.name), true);
-            //        }
-            //    }
-            //    else
-            //    {
-            //        RaiseWarning(string.Format("Texture {0} not found.", gltfTexture.name), 2);
-            //    }
-
-            //}
-            //catch
-            //{
-            //    // silently fails
-            //}
-
             return gltfTextureInfo;
         }