NormalTangentFactories.cs 8.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Linq;
  4. using System.Numerics;
  5. using System.Text;
  6. using System.Threading.Tasks;
  7. using System.Xml.Schema;
  8. namespace SharpGLTF.Runtime
  9. {
  10. using VERTEXKEY = System.ValueTuple<Vector3, Vector3, Vector2>;
  11. static class VertexNormalsFactory
  12. {
  13. #pragma warning disable CA1034 // Nested types should not be visible
  14. public interface IMeshPrimitive
  15. #pragma warning restore CA1034 // Nested types should not be visible
  16. {
  17. int VertexCount { get; }
  18. Vector3 GetVertexPosition(int idx);
  19. void SetVertexNormal(int idx, Vector3 normal);
  20. IEnumerable<(int A, int B, int C)> GetTriangleIndices();
  21. }
  22. private static bool _IsFinite(this float value) { return !float.IsNaN(value) && !float.IsInfinity(value); }
  23. private static bool _IsFinite(this Vector3 value) { return value.X._IsFinite() && value.Y._IsFinite() && value.Z._IsFinite(); }
  24. public static void CalculateSmoothNormals<T>(IReadOnlyList<T> primitives)
  25. where T : IMeshPrimitive
  26. {
  27. // Guard.NotNull(primitives, nameof(primitives));
  28. var normalMap = new Dictionary<Vector3, Vector3>();
  29. // calculate
  30. foreach (var primitive in primitives)
  31. {
  32. foreach (var (ta, tb, tc) in primitive.GetTriangleIndices())
  33. {
  34. var p1 = primitive.GetVertexPosition(ta);
  35. var p2 = primitive.GetVertexPosition(tb);
  36. var p3 = primitive.GetVertexPosition(tc);
  37. var d = Vector3.Cross(p2 - p1, p3 - p1);
  38. _AddDirection(normalMap, p1, d);
  39. _AddDirection(normalMap, p2, d);
  40. _AddDirection(normalMap, p3, d);
  41. }
  42. }
  43. // normalize
  44. foreach (var pos in normalMap.Keys.ToList())
  45. {
  46. var nrm = Vector3.Normalize(normalMap[pos]);
  47. normalMap[pos] = nrm._IsFinite() && nrm.LengthSquared() > 0.5f ? nrm : Vector3.UnitZ;
  48. }
  49. // apply
  50. foreach (var primitive in primitives)
  51. {
  52. for (int i = 0; i < primitive.VertexCount; ++i)
  53. {
  54. var pos = primitive.GetVertexPosition(i);
  55. if (normalMap.TryGetValue(pos, out Vector3 nrm))
  56. {
  57. primitive.SetVertexNormal(i, nrm);
  58. }
  59. else
  60. {
  61. primitive.SetVertexNormal(i, Vector3.UnitZ);
  62. }
  63. }
  64. }
  65. }
  66. private static void _AddDirection(Dictionary<Vector3, Vector3> dict, Vector3 pos, Vector3 dir)
  67. {
  68. if (!dir._IsFinite()) return;
  69. if (!dict.TryGetValue(pos, out Vector3 n)) n = Vector3.Zero;
  70. dict[pos] = n + dir;
  71. }
  72. }
  73. static class VertexTangentsFactory
  74. {
  75. // https://gamedev.stackexchange.com/questions/128023/how-does-mikktspace-work-for-calculating-the-tangent-space-during-normal-mapping
  76. // https://stackoverflow.com/questions/25349350/calculating-per-vertex-tangents-for-glsl
  77. // 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
  78. // https://gamedev.stackexchange.com/questions/68612/how-to-compute-tangent-and-bitangent-vectors
  79. // https://www.marti.works/calculating-tangents-for-your-mesh/
  80. // https://www.html5gamedevs.com/topic/34364-gltf-support-and-mikkt-space/
  81. /// <summary>
  82. /// this interface must be defined by the input primitive to which we want to add tangents
  83. /// </summary>
  84. public interface IMeshPrimitive
  85. {
  86. int VertexCount { get; }
  87. Vector3 GetVertexPosition(int idx);
  88. Vector3 GetVertexNormal(int idx);
  89. Vector2 GetVertexTexCoord(int idx);
  90. void SetVertexTangent(int idx, Vector4 tangent);
  91. IEnumerable<(int A, int B, int C)> GetTriangleIndices();
  92. }
  93. private static bool _IsFinite(this float value) { return !float.IsNaN(value) && !float.IsInfinity(value); }
  94. private static bool _IsFinite(this Vector3 value) { return value.X._IsFinite() && value.Y._IsFinite() && value.Z._IsFinite(); }
  95. public static void CalculateTangents<T>(IReadOnlyList<T> primitives)
  96. where T : IMeshPrimitive
  97. {
  98. // Guard.NotNull(primitives, nameof(primitives));
  99. var tangentsMap = new Dictionary<VERTEXKEY, (Vector3 u, Vector3 v)>();
  100. // calculate
  101. foreach (var primitive in primitives)
  102. {
  103. foreach (var (i1, i2, i3) in primitive.GetTriangleIndices())
  104. {
  105. var p1 = primitive.GetVertexPosition(i1);
  106. var p2 = primitive.GetVertexPosition(i2);
  107. var p3 = primitive.GetVertexPosition(i3);
  108. // check for degenerated triangle
  109. if (p1 == p2 || p1 == p3 || p2 == p3) continue;
  110. var uv1 = primitive.GetVertexTexCoord(i1);
  111. var uv2 = primitive.GetVertexTexCoord(i2);
  112. var uv3 = primitive.GetVertexTexCoord(i3);
  113. // check for degenerated triangle
  114. if (uv1 == uv2 || uv1 == uv3 || uv2 == uv3) continue;
  115. var n1 = primitive.GetVertexNormal(i1);
  116. var n2 = primitive.GetVertexNormal(i2);
  117. var n3 = primitive.GetVertexNormal(i3);
  118. // calculate tangents
  119. var svec = p2 - p1;
  120. var tvec = p3 - p1;
  121. var stex = uv2 - uv1;
  122. var ttex = uv3 - uv1;
  123. float sx = stex.X;
  124. float tx = ttex.X;
  125. float sy = stex.Y;
  126. float ty = ttex.Y;
  127. var r = 1.0F / ((sx * ty) - (tx * sy));
  128. if (!r._IsFinite()) continue;
  129. var sdir = new Vector3((ty * svec.X) - (sy * tvec.X), (ty * svec.Y) - (sy * tvec.Y), (ty * svec.Z) - (sy * tvec.Z)) * r;
  130. var tdir = new Vector3((sx * tvec.X) - (tx * svec.X), (sx * tvec.Y) - (tx * svec.Y), (sx * tvec.Z) - (tx * svec.Z)) * r;
  131. if (!sdir._IsFinite()) continue;
  132. if (!tdir._IsFinite()) continue;
  133. // accumulate tangents
  134. _AddTangent(tangentsMap, (p1, n1, uv1), (sdir, tdir));
  135. _AddTangent(tangentsMap, (p2, n2, uv2), (sdir, tdir));
  136. _AddTangent(tangentsMap, (p3, n3, uv3), (sdir, tdir));
  137. }
  138. }
  139. // normalize
  140. foreach (var key in tangentsMap.Keys.ToList())
  141. {
  142. var val = tangentsMap[key];
  143. // Gram-Schmidt orthogonalize
  144. val.u = Vector3.Normalize(val.u - (key.Item2 * Vector3.Dot(key.Item2, val.u)));
  145. val.v = Vector3.Normalize(val.v - (key.Item2 * Vector3.Dot(key.Item2, val.v)));
  146. tangentsMap[key] = val;
  147. }
  148. // apply
  149. foreach (var primitive in primitives)
  150. {
  151. for (int i = 0; i < primitive.VertexCount; ++i)
  152. {
  153. var p = primitive.GetVertexPosition(i);
  154. var n = primitive.GetVertexNormal(i);
  155. var t = primitive.GetVertexTexCoord(i);
  156. if (tangentsMap.TryGetValue((p, n, t), out (Vector3 u, Vector3 v) tangents))
  157. {
  158. var handedness = Vector3.Dot(Vector3.Cross(tangents.u, n), tangents.v) < 0 ? -1.0f : 1.0f;
  159. primitive.SetVertexTangent(i, new Vector4(tangents.u, handedness));
  160. }
  161. else
  162. {
  163. primitive.SetVertexTangent(i, new Vector4(1, 0, 0, 1));
  164. }
  165. }
  166. }
  167. }
  168. private static void _AddTangent(Dictionary<VERTEXKEY, (Vector3, Vector3)> dict, VERTEXKEY key, (Vector3 tu, Vector3 tv) alpha)
  169. {
  170. dict.TryGetValue(key, out (Vector3 tu, Vector3 tv) beta);
  171. dict[key] = (alpha.tu + beta.tu, alpha.tv + beta.tv);
  172. }
  173. }
  174. }