ResourceManager.cs 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Linq;
  4. using Microsoft.Xna.Framework;
  5. using Microsoft.Xna.Framework.Graphics;
  6. namespace SharpGLTF.Runtime
  7. {
  8. class GraphicsResourceTracker
  9. {
  10. #region data
  11. private readonly List<GraphicsResource> _Disposables = new List<GraphicsResource>();
  12. #endregion
  13. #region properties
  14. public IReadOnlyList<GraphicsResource> Disposables => _Disposables;
  15. #endregion
  16. #region API
  17. public void AddDisposable(GraphicsResource resource)
  18. {
  19. if (resource == null) throw new ArgumentNullException();
  20. if (_Disposables.Contains(resource)) throw new ArgumentException();
  21. _Disposables.Add(resource);
  22. }
  23. #endregion
  24. }
  25. class TextureFactory
  26. {
  27. #region lifecycle
  28. public TextureFactory(GraphicsDevice device, GraphicsResourceTracker disposables)
  29. {
  30. _Device = device;
  31. _Disposables = disposables;
  32. }
  33. #endregion
  34. #region data
  35. private readonly GraphicsDevice _Device;
  36. private readonly GraphicsResourceTracker _Disposables;
  37. private readonly Dictionary<Memory.MemoryImage, Texture2D> _Textures = new Dictionary<Memory.MemoryImage, Texture2D>();
  38. #endregion
  39. #region API
  40. public Texture2D UseTexture(Memory.MemoryImage image, string name = null)
  41. {
  42. if (_Device == null) throw new InvalidOperationException();
  43. if (!image.IsValid) return null;
  44. if (_Textures.TryGetValue(image, out Texture2D tex)) return tex;
  45. using (var m = image.Open())
  46. {
  47. tex = Texture2D.FromStream(_Device, m);
  48. _Disposables.AddDisposable(tex);
  49. tex.Name = name;
  50. _Textures[image] = tex;
  51. return tex;
  52. }
  53. }
  54. public Texture2D UseWhiteImage()
  55. {
  56. const string solidWhitePNg = "iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAIAAACQkWg2AAAACXBIWXMAAA7DAAAOwwHHb6hkAAAAFHpUWHRUaXRsZQAACJkrz8gsSQUABoACIippo0oAAAAoelRYdEF1dGhvcgAACJkLy0xOzStJVQhIzUtMSS1WcCzKTc1Lzy8BAG89CQyAoFAQAAAAGklEQVQoz2P8//8/AymAiYFEMKphVMPQ0QAAVW0DHZ8uFaIAAAAASUVORK5CYII=";
  57. var toBytes = Convert.FromBase64String(solidWhitePNg);
  58. return UseTexture(new ArraySegment<byte>(toBytes), "_InternalSolidWhite");
  59. }
  60. #endregion
  61. }
  62. class MaterialFactory
  63. {
  64. #region lifecycle
  65. public MaterialFactory(GraphicsDevice device, GraphicsResourceTracker disposables)
  66. {
  67. _Device = device;
  68. _TexFactory = new TextureFactory(device, disposables);
  69. _Disposables = disposables;
  70. }
  71. #endregion
  72. #region data
  73. private readonly GraphicsDevice _Device;
  74. private readonly TextureFactory _TexFactory;
  75. private readonly GraphicsResourceTracker _Disposables;
  76. private readonly Dictionary<Object, Effect> _RigidEffects = new Dictionary<Object, Effect>();
  77. private readonly Dictionary<Object, SkinnedEffect> _SkinnedEffects = new Dictionary<Object, SkinnedEffect>();
  78. private BasicEffect _DefaultRigid;
  79. private SkinnedEffect _DefaultSkinned;
  80. #endregion
  81. #region API - Schema
  82. // Monogame's BasicEffect uses Phong's shading, while glTF uses PBR shading, so
  83. // given monogame's limitations, we try to guess the most appropiate values
  84. // to have a reasonably good looking renders.
  85. public Effect UseRigidEffect(Schema2.Material srcMaterial)
  86. {
  87. if (_Device == null) throw new InvalidOperationException();
  88. if (srcMaterial == null)
  89. {
  90. if (_DefaultRigid == null)
  91. {
  92. _DefaultRigid = new BasicEffect(_Device);
  93. _Disposables.AddDisposable(_DefaultRigid);
  94. }
  95. return _DefaultRigid;
  96. }
  97. if (_RigidEffects.TryGetValue(srcMaterial, out Effect dstMaterial)) return dstMaterial;
  98. dstMaterial = srcMaterial.Alpha == Schema2.AlphaMode.MASK ? CreateAlphaTestEffect(srcMaterial) : CreateBasicEffect(srcMaterial);
  99. _RigidEffects[srcMaterial] = dstMaterial;
  100. return dstMaterial;
  101. }
  102. private Effect CreateBasicEffect(Schema2.Material srcMaterial)
  103. {
  104. var dstMaterial = new BasicEffect(_Device);
  105. _Disposables.AddDisposable(dstMaterial);
  106. dstMaterial.Name = srcMaterial.Name;
  107. dstMaterial.Alpha = GetAlphaLevel(srcMaterial);
  108. dstMaterial.DiffuseColor = GetDiffuseColor(srcMaterial);
  109. dstMaterial.SpecularColor = GetSpecularColor(srcMaterial);
  110. dstMaterial.SpecularPower = GetSpecularPower(srcMaterial);
  111. dstMaterial.EmissiveColor = GeEmissiveColor(srcMaterial);
  112. dstMaterial.Texture = GetDiffuseTexture(srcMaterial);
  113. if (srcMaterial.Unlit)
  114. {
  115. dstMaterial.EmissiveColor = dstMaterial.DiffuseColor;
  116. dstMaterial.SpecularColor = Vector3.Zero;
  117. dstMaterial.SpecularPower = 16;
  118. }
  119. dstMaterial.PreferPerPixelLighting = true;
  120. dstMaterial.TextureEnabled = dstMaterial.Texture != null;
  121. return dstMaterial;
  122. }
  123. private Effect CreateAlphaTestEffect(Schema2.Material srcMaterial)
  124. {
  125. var dstMaterial = new AlphaTestEffect(_Device);
  126. _Disposables.AddDisposable(dstMaterial);
  127. dstMaterial.Name = srcMaterial.Name;
  128. dstMaterial.Alpha = GetAlphaLevel(srcMaterial);
  129. //dstMaterial.AlphaFunction = CompareFunction.GreaterEqual;
  130. dstMaterial.ReferenceAlpha = (int)(srcMaterial.AlphaCutoff * 255);
  131. dstMaterial.DiffuseColor = GetDiffuseColor(srcMaterial);
  132. dstMaterial.Texture = GetDiffuseTexture(srcMaterial);
  133. return dstMaterial;
  134. }
  135. public Effect UseSkinnedEffect(Schema2.Material srcMaterial)
  136. {
  137. if (_Device == null) throw new InvalidOperationException();
  138. if (srcMaterial == null)
  139. {
  140. if (_DefaultSkinned == null)
  141. {
  142. _DefaultSkinned = new SkinnedEffect(_Device);
  143. _Disposables.AddDisposable(_DefaultRigid);
  144. }
  145. return _DefaultSkinned;
  146. }
  147. if (_SkinnedEffects.TryGetValue(srcMaterial, out SkinnedEffect dstMaterial)) return dstMaterial;
  148. dstMaterial = new SkinnedEffect(_Device);
  149. _SkinnedEffects[srcMaterial] = dstMaterial;
  150. _Disposables.AddDisposable(dstMaterial);
  151. dstMaterial.Name = srcMaterial.Name;
  152. dstMaterial.Alpha = GetAlphaLevel(srcMaterial);
  153. dstMaterial.DiffuseColor = GetDiffuseColor(srcMaterial);
  154. dstMaterial.SpecularColor = GetSpecularColor(srcMaterial);
  155. dstMaterial.SpecularPower = GetSpecularPower(srcMaterial);
  156. dstMaterial.EmissiveColor = GeEmissiveColor(srcMaterial);
  157. dstMaterial.Texture = GetDiffuseTexture(srcMaterial);
  158. dstMaterial.WeightsPerVertex = 4;
  159. dstMaterial.PreferPerPixelLighting = true;
  160. // apparently, SkinnedEffect does not support disabling textures, so we set a white texture here.
  161. if (dstMaterial.Texture == null) dstMaterial.Texture = _TexFactory.UseWhiteImage();
  162. return dstMaterial;
  163. }
  164. private static float GetAlphaLevel(Schema2.Material srcMaterial)
  165. {
  166. if (srcMaterial.Alpha == Schema2.AlphaMode.OPAQUE) return 1;
  167. var baseColor = srcMaterial.FindChannel("BaseColor");
  168. if (baseColor == null) return 1;
  169. return baseColor.Value.Parameter.W;
  170. }
  171. private static Vector3 GetDiffuseColor(Schema2.Material srcMaterial)
  172. {
  173. var diffuse = srcMaterial.FindChannel("Diffuse");
  174. if (diffuse == null) diffuse = srcMaterial.FindChannel("BaseColor");
  175. if (diffuse == null) return Vector3.One;
  176. return new Vector3(diffuse.Value.Parameter.X, diffuse.Value.Parameter.Y, diffuse.Value.Parameter.Z);
  177. }
  178. private static Vector3 GetSpecularColor(Schema2.Material srcMaterial)
  179. {
  180. var mr = srcMaterial.FindChannel("MetallicRoughness");
  181. if (mr == null) return Vector3.One; // default value 16
  182. var diffuse = GetDiffuseColor(srcMaterial);
  183. var metallic = mr.Value.Parameter.X;
  184. var roughness = mr.Value.Parameter.Y;
  185. var k = Vector3.Zero;
  186. k += Vector3.Lerp(diffuse, Vector3.Zero, roughness);
  187. k += Vector3.Lerp(diffuse, Vector3.One, metallic);
  188. k *= 0.5f;
  189. return k;
  190. }
  191. private static float GetSpecularPower(Schema2.Material srcMaterial)
  192. {
  193. var mr = srcMaterial.FindChannel("MetallicRoughness");
  194. if (mr == null) return 16; // default value = 16
  195. var metallic = mr.Value.Parameter.X;
  196. var roughness = mr.Value.Parameter.Y;
  197. return 4 + 16 * metallic;
  198. }
  199. private static Vector3 GeEmissiveColor(Schema2.Material srcMaterial)
  200. {
  201. var emissive = srcMaterial.FindChannel("Emissive");
  202. if (emissive == null) return Vector3.Zero;
  203. return new Vector3(emissive.Value.Parameter.X, emissive.Value.Parameter.Y, emissive.Value.Parameter.Z);
  204. }
  205. private Texture2D GetDiffuseTexture(Schema2.Material srcMaterial)
  206. {
  207. var diffuse = srcMaterial.FindChannel("Diffuse");
  208. if (diffuse == null) diffuse = srcMaterial.FindChannel("BaseColor");
  209. if (diffuse == null) return null;
  210. var name = srcMaterial.Name;
  211. if (name == null) name = "null";
  212. name += "-Diffuse";
  213. return _TexFactory.UseTexture(diffuse.Value.Texture?.PrimaryImage?.Content ?? default, name);
  214. }
  215. #endregion
  216. }
  217. }