Browse Source

WIP: tangent space generation support

Vicente Penades 6 years ago
parent
commit
37cf323cc7

+ 2 - 0
.github/FUNDING.yml

@@ -0,0 +1,2 @@
+# Support 'GitHub Sponsors' funding.
+github: vpenades

+ 165 - 0
src/SharpGLTF.Toolkit/Geometry/VertexBufferColumns.cs

@@ -343,5 +343,170 @@ namespace SharpGLTF.Geometry
         }
         }
 
 
         #endregion
         #endregion
+
+        #region utilites
+
+        public static void CalculateSmoothNormals(IEnumerable<(VertexBufferColumns Vertices, IEnumerable<(int A, int B, int C)> Indices)> primitives)
+        {
+            Guard.NotNull(primitives, nameof(primitives));
+
+            void addDirection(Dictionary<Vector3, Vector3> dict, Vector3 pos, Vector3 dir)
+            {
+                if (!dir._IsFinite()) return;
+                if (!dict.TryGetValue(pos, out Vector3 n)) n = Vector3.Zero;
+                dict[pos] = n + dir;
+            }
+
+            // calculate
+
+            var normalMap = new Dictionary<Vector3, Vector3>();
+
+            foreach (var (vertices, indices) in primitives)
+            {
+                foreach (var (ta, tb, tc) in indices)
+                {
+                    var p1 = vertices.Positions[ta];
+                    var p2 = vertices.Positions[tb];
+                    var p3 = vertices.Positions[tc];
+
+                    var d = Vector3.Cross(p2 - p1, p3 - p1);
+
+                    addDirection(normalMap, p1, d);
+                    addDirection(normalMap, p2, d);
+                    addDirection(normalMap, p3, d);
+                }
+            }
+
+            // normalize
+
+            foreach (var pos in normalMap.Keys.ToList())
+            {
+                var nrm = Vector3.Normalize(normalMap[pos]);
+
+                normalMap[pos] = nrm._IsFinite() && nrm.LengthSquared() > 0.5f ? nrm : Vector3.UnitZ;
+            }
+
+            // apply
+
+            foreach (var (vertices, indices) in primitives)
+            {
+                vertices.Normals = new Vector3[vertices.Positions.Count];
+
+                for (int i = 0; i < vertices.Positions.Count; ++i)
+                {
+                    if (normalMap.TryGetValue(vertices.Positions[i],out Vector3 nrm))
+                    {
+                        vertices.Normals[i] = nrm;
+                    }
+                    else
+                    {
+                        vertices.Normals[i] = Vector3.UnitZ;
+                    }
+                }
+            }
+        }
+
+        public static void CalculateTangents(IEnumerable<(VertexBufferColumns Vertices, IEnumerable<(int A, int B, int C)> Indices)> primitives)
+        {
+            // https://gamedev.stackexchange.com/questions/128023/how-does-mikktspace-work-for-calculating-the-tangent-space-during-normal-mapping
+            // https://stackoverflow.com/questions/25349350/calculating-per-vertex-tangents-for-glsl
+            // https://github.com/buildaworldnet/IrrlichtBAW/wiki/How-to-Normal-Detail-Bump-Derivative-Map,-why-Mikkelsen-is-slightly-wrong-and-why-you-should-give-up-on-calculating-per-vertex-tangents
+            // https://gamedev.stackexchange.com/questions/68612/how-to-compute-tangent-and-bitangent-vectors
+            // https://www.marti.works/calculating-tangents-for-your-mesh/
+            // https://www.html5gamedevs.com/topic/34364-gltf-support-and-mikkt-space/
+
+            Guard.NotNull(primitives, nameof(primitives));
+
+            void addTangent(Dictionary<(Vector3 pos, Vector3 nrm, Vector2 uv), (Vector3, Vector3)> dict, (Vector3 pos, Vector3 nrm, Vector2 uv) key, (Vector3 tu, Vector3 tv) alpha)
+            {
+                dict.TryGetValue(key, out (Vector3 tu, Vector3 tv) beta);
+
+                dict[key] = (alpha.tu + beta.tu, alpha.tv + beta.tv);
+            }
+
+            // calculate
+
+            var tangentsMap = new Dictionary<(Vector3 pos, Vector3 nrm, Vector2 uv), (Vector3 u, Vector3 v)>();
+
+            foreach (var (vertices, indices) in primitives)
+            {
+                vertices.Tangents = new Vector4[vertices.Positions.Count];
+
+                foreach (var (i1, i2, i3) in indices)
+                {
+                    var p1 = vertices.Positions[i1];
+                    var p2 = vertices.Positions[i2];
+                    var p3 = vertices.Positions[i3];
+
+                    var n1 = vertices.Normals[i1];
+                    var n2 = vertices.Normals[i2];
+                    var n3 = vertices.Normals[i3];
+
+                    var uv1 = vertices.TexCoords0[i1];
+                    var uv2 = vertices.TexCoords0[i2];
+                    var uv3 = vertices.TexCoords0[i3];
+
+                    var svec = p2 - p1;
+                    var tvec = p3 - p1;
+
+                    var stex = uv2 - uv1;
+                    var ttex = uv3 - uv1;
+
+                    float s1 = stex.X;
+                    float s2 = ttex.X;
+                    float t1 = stex.Y;
+                    float t2 = ttex.Y;
+
+                    var r = 1.0F / ((s1 * t2) - (s2 * t1));
+
+                    var sdir = new Vector3((t2 * svec.X) - (t1 * tvec.X), (t2 * svec.Y) - (t1 * tvec.Y), (t2 * svec.Z) - (t1 * tvec.Z) ) * r;
+                    var tdir = new Vector3((s1 * tvec.X) - (s2 * svec.X), (s1 * tvec.Y) - (s2 * svec.Y), (s1 * tvec.Z) - (s2 * svec.Z) ) * r;
+
+                    addTangent(tangentsMap, (p1, n1, uv1), (sdir, tdir));
+                    addTangent(tangentsMap, (p2, n2, uv2), (sdir, tdir));
+                    addTangent(tangentsMap, (p3, n3, uv3), (sdir, tdir));
+                }
+            }
+
+            // normalize
+
+            foreach (var key in tangentsMap.Keys.ToList())
+            {
+                var val = tangentsMap[key];
+
+                // Gram-Schmidt orthogonalize
+                val.u = Vector3.Normalize(val.u - (key.nrm * Vector3.Dot(key.nrm, val.u)));
+                val.v = Vector3.Normalize(val.v - (key.nrm * Vector3.Dot(key.nrm, val.v)));
+
+                tangentsMap[key] = val;
+            }
+
+            // apply
+
+            foreach (var (vertices, indices) in primitives)
+            {
+                vertices.Tangents = new Vector4[vertices.Tangents.Count];
+
+                for (int i = 0; i < vertices.Positions.Count; ++i)
+                {
+                    var p = vertices.Positions[i];
+                    var n = vertices.Normals[i];
+                    var t = vertices.TexCoords0[i];
+
+                    if (tangentsMap.TryGetValue((p, n, t), out (Vector3 u, Vector3 v) tangents))
+                    {
+                        var handedness = Vector3.Dot(Vector3.Cross(n, tangents.u), tangents.v) < 0 ? -1.0f : 1.0f;
+
+                        vertices.Tangents[i] = new Vector4(tangents.u, handedness);
+                    }
+                    else
+                    {
+                        vertices.Tangents[i] = new Vector4(1, 0, 0, 1);
+                    }
+                }
+            }
+        }
+
+        #endregion
     }
     }
 }
 }

+ 44 - 30
src/SharpGLTF.Toolkit/Schema2/MeshExtensions.cs

@@ -471,42 +471,50 @@ namespace SharpGLTF.Schema2
             where TvM : struct, IVertexMaterial
             where TvM : struct, IVertexMaterial
             where TvS : struct, IVertexSkinning
             where TvS : struct, IVertexSkinning
         {
         {
+            if (xform != null && !xform.Visible) mesh = null;
             if (mesh == null) return Enumerable.Empty<(VertexBuilder<TvG, TvM, TvS>, VertexBuilder<TvG, TvM, TvS>, VertexBuilder<TvG, TvM, TvS>, Material)>();
             if (mesh == null) return Enumerable.Empty<(VertexBuilder<TvG, TvM, TvS>, VertexBuilder<TvG, TvM, TvS>, VertexBuilder<TvG, TvM, TvS>, Material)>();
 
 
-            return mesh.Primitives.SelectMany(item => item.EvaluateTriangles<TvG, TvM, TvS>(xform));
+            var primitives = mesh.Primitives
+                .Where(prim => prim.GetTriangleIndices().Any())
+                .Select(prim => (prim.Material, prim.GetVertexColumns(xform), (IEnumerable<(int, int, int)>)prim.GetTriangleIndices().ToList()))
+                .ToList();
+
+            bool needsNormals = default(TvG).TryGetNormal(out Vector3 nrm);
+            bool needsTangents = default(TvG).TryGetTangent(out Vector4 tgt);
+
+            if (needsNormals)
+            {
+                var prims = primitives
+                    .Where(p => p.Item2.Normals == null)
+                    .Select(p => (p.Item2, p.Item3));
+
+                if (prims.Any()) VertexBufferColumns.CalculateSmoothNormals(prims);
+            }
+
+            if (needsTangents)
+            {
+                var prims = primitives
+                    .Where(p => p.Item2.Tangents == null && p.Item2.TexCoords0 != null)
+                    .Select(p => (p.Item2, p.Item3));
+
+                if (prims.Any()) VertexBufferColumns.CalculateTangents(prims);
+            }
+
+            return primitives.SelectMany(prim => _EvaluateTriangles<TvG, TvM, TvS>(prim.Material, prim.Item2, prim.Item3));
         }
         }
 
 
-        public static IEnumerable<(VertexBuilder<TvG, TvM, TvS> A, VertexBuilder<TvG, TvM, TvS> B, VertexBuilder<TvG, TvM, TvS> C, Material Material)> EvaluateTriangles<TvG, TvM, TvS>(this MeshPrimitive prim, MESHXFORM xform = null)
+        private static IEnumerable<(VertexBuilder<TvG, TvM, TvS> A, VertexBuilder<TvG, TvM, TvS> B, VertexBuilder<TvG, TvM, TvS> C, Material Material)> _EvaluateTriangles<TvG, TvM, TvS>(Material material, VertexBufferColumns vertices, IEnumerable<(int A, int B, int C)> indices)
             where TvG : struct, IVertexGeometry
             where TvG : struct, IVertexGeometry
             where TvM : struct, IVertexMaterial
             where TvM : struct, IVertexMaterial
             where TvS : struct, IVertexSkinning
             where TvS : struct, IVertexSkinning
         {
         {
-            if (prim == null) yield break;
-            if (xform != null && !xform.Visible) yield break;
-
-            var triangles = prim.GetTriangleIndices();
-            if (!triangles.Any()) yield break;
-
-            var vertices = prim.GetVertexColumns(xform);
-
-            bool hasNormals = vertices.Normals != null;
-
-            foreach (var (ta, tb, tc) in triangles)
+            foreach (var (ta, tb, tc) in indices)
             {
             {
                 var va = vertices.GetVertex<TvG, TvM, TvS>(ta);
                 var va = vertices.GetVertex<TvG, TvM, TvS>(ta);
                 var vb = vertices.GetVertex<TvG, TvM, TvS>(tb);
                 var vb = vertices.GetVertex<TvG, TvM, TvS>(tb);
                 var vc = vertices.GetVertex<TvG, TvM, TvS>(tc);
                 var vc = vertices.GetVertex<TvG, TvM, TvS>(tc);
 
 
-                if (!hasNormals)
-                {
-                    var n = Vector3.Cross(vb.Position - va.Position, vc.Position - va.Position);
-                    n = Vector3.Normalize(n);
-                    va.Geometry.SetNormal(n);
-                    vb.Geometry.SetNormal(n);
-                    vc.Geometry.SetNormal(n);
-                }
-
-                yield return (va, vb, vc, prim.Material);
+                yield return (va, vb, vc, material);
             }
             }
         }
         }
 
 
@@ -562,17 +570,23 @@ namespace SharpGLTF.Schema2
 
 
             if (srcMesh == null) return;
             if (srcMesh == null) return;
 
 
-            foreach (var srcPrim in srcMesh.Primitives)
-            {
-                if (srcPrim != null) continue;
+            var materialMap = srcMesh.Primitives
+                .Select(prim => prim.Material)
+                .Distinct()
+                .ToDictionary(mat => mat, mat => materialFunc(mat));
 
 
-                var dstMat = materialFunc(srcPrim.Material);
-                var dstPrim = meshBuilder.UsePrimitive(dstMat);
+            Material currMat = null;
+            PrimitiveBuilder<TMaterial, TvG, TvM, TvS> currPrim = null;
 
 
-                foreach (var tri in srcPrim.EvaluateTriangles<TvG, TvM, TvS>(null))
+            foreach (var tri in srcMesh.EvaluateTriangles<TvG, TvM, TvS>())
+            {
+                if (currMat != tri.Material)
                 {
                 {
-                    dstPrim.AddTriangle(tri.A, tri.B, tri.C);
+                    currMat = tri.Material;
+                    currPrim = meshBuilder.UsePrimitive(materialMap[currMat]);
                 }
                 }
+
+                currPrim.AddTriangle(tri.A, tri.B, tri.C);
             }
             }
         }
         }