Forráskód Böngészése

Added BRDF texture generator

Vicente Penades 5 éve
szülő
commit
308e3d6f7a

+ 134 - 0
src/MonoGame.Framework.Graphics.PBR/Resources.BRDF.cs

@@ -0,0 +1,134 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+
+using Microsoft.Xna.Framework.Graphics.PackedVector;
+
+namespace Microsoft.Xna.Framework.Graphics
+{
+    static class BRDFGenerator
+    {
+        // based on code from:
+        // https://github.com/HectorMF/BRDFGenerator
+        // https://github.com/HectorMF/BRDFGenerator/blob/master/BRDFGenerator/BRDFGenerator.cpp
+        // explained here: 
+        // https://blog.selfshadow.com/publications/s2013-shading-course/karis/s2013_pbs_epic_notes_v2.pdf
+        // https://blog.selfshadow.com/publications/s2013-shading-course/karis/s2013_pbs_epic_slides.pdf
+
+        private const float PI = (float)Math.PI;
+
+        private static float RadicalInverse_VdC(uint bits)
+        {
+            bits = (bits << 16) | (bits >> 16);
+            bits = ((bits & 0x55555555u) << 1) | ((bits & 0xAAAAAAAAu) >> 1);
+            bits = ((bits & 0x33333333u) << 2) | ((bits & 0xCCCCCCCCu) >> 2);
+            bits = ((bits & 0x0F0F0F0Fu) << 4) | ((bits & 0xF0F0F0F0u) >> 4);
+            bits = ((bits & 0x00FF00FFu) << 8) | ((bits & 0xFF00FF00u) >> 8);
+            return (float)(bits * 2.3283064365386963e-10);
+        }
+
+        private static Vector2 Hammersley(uint i, uint N)
+        {
+            return new Vector2((float)i / (float)N, RadicalInverse_VdC(i));
+        }
+
+        private static Vector3 ImportanceSampleGGX(Vector2 Xi, float roughness, Vector3 N)
+        {
+            float a = roughness * roughness;
+
+            float phi = 2.0f * PI * Xi.X;
+            float cosTheta = (float)Math.Sqrt((1.0 - Xi.Y) / (1.0 + (a * a - 1.0) * Xi.Y));
+            float sinTheta = (float)Math.Sqrt(1.0 - cosTheta * cosTheta);
+
+            // from spherical coordinates to cartesian coordinates
+            Vector3 H;
+            H.X = (float)Math.Cos(phi) * sinTheta;
+            H.Y = (float)Math.Sin(phi) * sinTheta;
+            H.Z = cosTheta;
+
+            // from tangent-space vector to world-space sample vector
+            var up = Math.Abs(N.Z) < 0.999f ? Vector3.UnitZ : Vector3.UnitX;
+            var tangent = Vector3.Normalize(Vector3.Cross(up, N));
+            var bitangent = Vector3.Cross(N, tangent);
+
+            var sampleVec = tangent * H.X + bitangent * H.Y + N * H.Z;
+            return Vector3.Normalize(sampleVec);
+        }
+
+        private static float GeometrySchlickGGX(float NdotV, float roughness)
+        {
+            float a = roughness;
+            float k = (a * a) / 2.0f;
+
+            float nom = NdotV;
+            float denom = NdotV * (1.0f - k) + k;
+
+            return nom / denom;
+        }
+
+        private static float GeometrySmith(float roughness, float NoV, float NoL)
+        {
+            float ggx2 = GeometrySchlickGGX(NoV, roughness);
+            float ggx1 = GeometrySchlickGGX(NoL, roughness);
+
+            return ggx1 * ggx2;
+        }
+
+        private static Vector2 IntegrateBRDF(float NdotV, float roughness, uint samples)
+        {
+            var V = new Vector3((float)Math.Sqrt(1.0 - NdotV * NdotV), 0, NdotV);
+
+            float A = 0;
+            float B = 0;
+
+            var N = Vector3.UnitZ;
+
+            for (uint i = 0; i < samples; ++i)
+            {
+                var Xi = Hammersley(i, samples);
+                var H = ImportanceSampleGGX(Xi, roughness, N);
+                var L = Vector3.Normalize(2.0f * Vector3.Dot(V, H) * H - V);
+
+                float NoL = Math.Max(L.Z, 0.0f);
+                float NoH = Math.Max(H.Z, 0.0f);
+                float VoH = Math.Max(Vector3.Dot(V, H), 0.0f);
+                float NoV = Math.Max(Vector3.Dot(N, V), 0.0f);
+
+                if (NoL > 0.0)
+                {
+                    float G = GeometrySmith(roughness, NoV, NoL);
+
+                    float G_Vis = (G * VoH) / (NoH * NoV);
+                    float Fc = (float)Math.Pow(1.0 - VoH, 5.0);
+
+                    A += (1.0f - Fc) * G_Vis;
+                    B += Fc * G_Vis;
+                }
+            }
+
+            return new Vector2(A / (float)samples, B / (float)samples);
+        }
+
+        public static TPixel[] Generate<TPixel>(int size, Func<Vector2,TPixel> pixelConverter)
+        {
+            uint samples = 1024;
+
+            var tex = new TPixel[size * size];
+
+            for (int y = 0; y < size; y++)
+            {
+                for (int x = 0; x < size; x++)
+                {
+                    float NoV = (y + 0.5f) * (1.0f / size);
+                    float roughness = (x + 0.5f) * (1.0f / size);
+
+                    var value = IntegrateBRDF(NoV, roughness, samples);
+
+                    tex[y * size + (size - x - 1)] = pixelConverter(value);
+                }
+            }
+
+            return tex;
+        }
+    }
+}

+ 18 - 9
src/MonoGame.Framework.Graphics.PBR/Resources.cs

@@ -4,6 +4,8 @@ using System.Linq;
 using System.Text;
 using System.Text;
 using System.Threading.Tasks;
 using System.Threading.Tasks;
 
 
+using Microsoft.Xna.Framework.Graphics.PackedVector;
+
 namespace Microsoft.Xna.Framework.Graphics
 namespace Microsoft.Xna.Framework.Graphics
 {
 {
     static class Resources
     static class Resources
@@ -46,23 +48,30 @@ namespace Microsoft.Xna.Framework.Graphics
         private static Texture2D blackTransparentDotTexture;
         private static Texture2D blackTransparentDotTexture;
         private static Texture2D aoRoughMetalDefaltDotTexture;
         private static Texture2D aoRoughMetalDefaltDotTexture;
 
 
+        private static Texture2D bdrf_ibl_ggx;
+
         public static Texture2D WhiteDotTexture { get { if (whiteDotTexture != null) return whiteDotTexture; else { throw new NullReferenceException("SharpGLTF.Runtime.Generated.WhiteDotTexture()  ... The Generated dot texture was requested but never created. Make sure you have called Initialize(,,) on gltf in monogames load function first."); } } }
         public static Texture2D WhiteDotTexture { get { if (whiteDotTexture != null) return whiteDotTexture; else { throw new NullReferenceException("SharpGLTF.Runtime.Generated.WhiteDotTexture()  ... The Generated dot texture was requested but never created. Make sure you have called Initialize(,,) on gltf in monogames load function first."); } } }
         public static Texture2D BlackTransparentDotTexture { get { if (blackTransparentDotTexture != null) return blackTransparentDotTexture; else { throw new NullReferenceException("SharpGLTF.Runtime.Generated.BlackTransparentDotTexture()  ... The Generated dot texture was requested but never created. Make sure you have called Initialize(,,) on gltf in monogames load function first."); } } }
         public static Texture2D BlackTransparentDotTexture { get { if (blackTransparentDotTexture != null) return blackTransparentDotTexture; else { throw new NullReferenceException("SharpGLTF.Runtime.Generated.BlackTransparentDotTexture()  ... The Generated dot texture was requested but never created. Make sure you have called Initialize(,,) on gltf in monogames load function first."); } } }
         public static Texture2D AoRoughMetalDefaltDotTexture { get { if (aoRoughMetalDefaltDotTexture != null) return aoRoughMetalDefaltDotTexture; else { throw new NullReferenceException("SharpGLTF.Runtime.Generated.BlackTransparentDotTexture()  ... The Generated dot texture was requested but never created. Make sure you have called Initialize(,,) on gltf in monogames load function first."); } } }
         public static Texture2D AoRoughMetalDefaltDotTexture { get { if (aoRoughMetalDefaltDotTexture != null) return aoRoughMetalDefaltDotTexture; else { throw new NullReferenceException("SharpGLTF.Runtime.Generated.BlackTransparentDotTexture()  ... The Generated dot texture was requested but never created. Make sure you have called Initialize(,,) on gltf in monogames load function first."); } } }
 
 
+        public static Texture2D IblGGX { get { if (bdrf_ibl_ggx != null) return bdrf_ibl_ggx; else { throw new NullReferenceException("SharpGLTF.Runtime.Generated.BlackTransparentDotTexture()  ... The Generated dot texture was requested but never created. Make sure you have called Initialize(,,) on gltf in monogames load function first."); } } }
+
         public static void GenerateDotTextures(GraphicsDevice device)
         public static void GenerateDotTextures(GraphicsDevice device)
         {
         {
-            if (whiteDotTexture == null)
-            {                
-                whiteDotTexture = new Texture2D(device, 1, 1);
-                whiteDotTexture.SetData<Color>(new Color[] { Color.White });
+            if (whiteDotTexture != null) return;
+            
+            whiteDotTexture = new Texture2D(device, 1, 1);
+            whiteDotTexture.SetData(new Color[] { Color.White });
                 
                 
-                blackTransparentDotTexture = new Texture2D(device, 1, 1);
-                blackTransparentDotTexture.SetData<Color>(new Color[] { Color.Transparent });
+            blackTransparentDotTexture = new Texture2D(device, 1, 1);
+            blackTransparentDotTexture.SetData(new Color[] { Color.Transparent });
                 
                 
-                aoRoughMetalDefaltDotTexture = new Texture2D(device, 1, 1);
-                aoRoughMetalDefaltDotTexture.SetData<Color>(new Color[] { new Color(1.0f, 0.97f, 0.03f, 0.0f) });
-            }
+            aoRoughMetalDefaltDotTexture = new Texture2D(device, 1, 1);
+            aoRoughMetalDefaltDotTexture.SetData(new Color[] { new Color(1.0f, 0.97f, 0.03f, 0.0f) });
+
+            var pixels = BRDFGenerator.Generate(128, val => new Rg32(val));
+            bdrf_ibl_ggx = new Texture2D(device, 128, 128, false, SurfaceFormat.Rg32);
+            bdrf_ibl_ggx.SetData(pixels);
         }
         }
     }
     }
 }
 }