EffectsFactory.cs 8.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237
  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 Microsoft.Xna.Framework.Graphics;
  8. using SharpGLTF.Schema2;
  9. using GLTFMATERIAL = SharpGLTF.Schema2.Material;
  10. namespace SharpGLTF.Runtime.Pipeline
  11. {
  12. /// <summary>
  13. /// Converts glTF materials into MonoGame effects
  14. /// </summary>
  15. public abstract class EffectsFactory
  16. {
  17. #region lifecycle
  18. /// <summary>
  19. /// Register here your own <see cref="EffectsFactory"/> derived class to override effects creation
  20. /// </summary>
  21. public static Func<GraphicsDevice, GraphicsResourceTracker, TextureFactory, EffectsFactory> InstanceBuilder { get; set; }
  22. public static EffectsFactory Create(GraphicsDevice device, GraphicsResourceTracker disposables, TextureFactory textureFactory = null)
  23. {
  24. ArgumentNullException.ThrowIfNull(device);
  25. ArgumentNullException.ThrowIfNull(disposables);
  26. textureFactory ??= TextureFactory.Create(device, disposables);
  27. var ef = InstanceBuilder?.Invoke(device, disposables, textureFactory);
  28. ef ??= new DefaultEffectsFactory(device, disposables, textureFactory);
  29. return ef;
  30. }
  31. protected EffectsFactory(GraphicsDevice device, GraphicsResourceTracker disposables, TextureFactory textureFactory)
  32. {
  33. _Device = device;
  34. _TexFactory = textureFactory;
  35. _Disposables = disposables;
  36. }
  37. #endregion
  38. #region data
  39. private readonly GraphicsDevice _Device;
  40. private readonly TextureFactory _TexFactory;
  41. private readonly GraphicsResourceTracker _Disposables;
  42. // effects cache
  43. private readonly Dictionary<Object, Effect> _RigidEffects = new Dictionary<Object, Effect>();
  44. private readonly Dictionary<Object, SkinnedEffect> _SkinnedEffects = new Dictionary<Object, SkinnedEffect>();
  45. #endregion
  46. #region API - Schema
  47. protected GraphicsDevice Device => _Device;
  48. public Effect UseEffect(GLTFMATERIAL srcMaterial, bool isSkinned)
  49. {
  50. ArgumentNullException.ThrowIfNull(srcMaterial);
  51. var effect = _GetMaterialOrDefault(srcMaterial, isSkinned);
  52. if (effect != null) return effect;
  53. effect = CreateEffect(srcMaterial, isSkinned);
  54. if (isSkinned && effect is SkinnedEffect skEffect) { _SkinnedEffects[srcMaterial] = skEffect; }
  55. else { _RigidEffects[srcMaterial] = effect; }
  56. return effect;
  57. }
  58. private Effect _GetMaterialOrDefault(GLTFMATERIAL srcMaterial, bool isSkinned)
  59. {
  60. if (isSkinned)
  61. {
  62. if (_SkinnedEffects.TryGetValue(srcMaterial, out SkinnedEffect dstMaterial)) return dstMaterial;
  63. }
  64. else
  65. {
  66. if (_RigidEffects.TryGetValue(srcMaterial, out Effect dstMaterial)) return dstMaterial;
  67. }
  68. return null;
  69. }
  70. protected abstract Effect CreateEffect(GLTFMATERIAL srcMaterial, bool isSkinned);
  71. protected Texture2D UseTexture(MaterialChannel? channel, string name)
  72. {
  73. if (!channel.HasValue) return UseWhiteTexture();
  74. if (channel.HasValue && name == null)
  75. {
  76. name = channel.Value.LogicalParent.Name;
  77. name ??= "null";
  78. name += $"-{channel.Value.Key}";
  79. }
  80. if (channel.Value.Texture?.PrimaryImage?.Content.IsEmpty ?? true) return UseWhiteTexture();
  81. return _TexFactory.UseTexture(channel.Value.Texture?.PrimaryImage?.Content ?? default, name);
  82. }
  83. protected Texture2D UseWhiteTexture()
  84. {
  85. return _TexFactory.UseWhiteImage();
  86. }
  87. #endregion
  88. }
  89. /// <summary>
  90. /// Converts glTF materials into MonoGame effects
  91. /// </summary>
  92. /// <remarks>
  93. /// This will use default effects:<br/>
  94. /// <see cref="BasicEffect"/><br/>
  95. /// <see cref="AlphaTestEffect"/><br/>
  96. /// <see cref="SkinnedEffect"/><br/>
  97. /// Notice that these effects are very basic and limiting, so this factory does a best effort to try advanced glTF materials to fit in.
  98. /// </remarks>
  99. public class DefaultEffectsFactory : EffectsFactory
  100. {
  101. #region lifecycle
  102. public DefaultEffectsFactory(GraphicsDevice device, GraphicsResourceTracker disposables, TextureFactory textureFactory)
  103. : base(device, disposables, textureFactory) { }
  104. #endregion
  105. #region API
  106. // Monogame's BasicEffect uses Phong's shading, while glTF uses PBR shading,
  107. // so given monogame's limitations, we try to guess the most appropiate values
  108. // to have acceptable looking renders.
  109. protected override Effect CreateEffect(GLTFMATERIAL srcMaterial, bool isSkinned)
  110. {
  111. return isSkinned
  112. ? CreateSkinnedEffect(srcMaterial)
  113. : CreateRigidEffect(srcMaterial);
  114. }
  115. protected virtual Effect CreateRigidEffect(GLTFMATERIAL srcMaterial)
  116. {
  117. var dstMaterial = srcMaterial.Alpha == Schema2.AlphaMode.MASK
  118. ? CreateAlphaTestEffect(srcMaterial)
  119. : CreateBasicEffect(srcMaterial);
  120. return dstMaterial;
  121. }
  122. protected virtual Effect CreateBasicEffect(GLTFMATERIAL srcMaterial)
  123. {
  124. var dstMaterial = new BasicEffect(Device);
  125. dstMaterial.Name = srcMaterial.Name;
  126. dstMaterial.Alpha = _GltfMaterialBasicProperties.GetAlphaLevel(srcMaterial);
  127. dstMaterial.DiffuseColor = _GltfMaterialBasicProperties.GetDiffuseColor(srcMaterial);
  128. dstMaterial.SpecularColor = _GltfMaterialBasicProperties.GetSpecularColor(srcMaterial);
  129. dstMaterial.SpecularPower = _GltfMaterialBasicProperties.GetSpecularPower(srcMaterial);
  130. dstMaterial.EmissiveColor = _GltfMaterialBasicProperties.GeEmissiveColor(srcMaterial);
  131. dstMaterial.Texture = UseDiffuseTexture(srcMaterial);
  132. if (srcMaterial.Unlit)
  133. {
  134. dstMaterial.EmissiveColor = dstMaterial.DiffuseColor;
  135. dstMaterial.SpecularColor = Vector3.Zero;
  136. dstMaterial.SpecularPower = 16;
  137. }
  138. dstMaterial.PreferPerPixelLighting = true;
  139. dstMaterial.TextureEnabled = dstMaterial.Texture != null;
  140. return dstMaterial;
  141. }
  142. protected virtual Effect CreateAlphaTestEffect(GLTFMATERIAL srcMaterial)
  143. {
  144. var dstMaterial = new AlphaTestEffect(Device);
  145. dstMaterial.Name = srcMaterial.Name;
  146. dstMaterial.Alpha = _GltfMaterialBasicProperties.GetAlphaLevel(srcMaterial);
  147. //dstMaterial.AlphaFunction = CompareFunction.GreaterEqual;
  148. dstMaterial.ReferenceAlpha = (int)(srcMaterial.AlphaCutoff * 255);
  149. dstMaterial.DiffuseColor = _GltfMaterialBasicProperties.GetDiffuseColor(srcMaterial);
  150. dstMaterial.Texture = UseDiffuseTexture(srcMaterial);
  151. return dstMaterial;
  152. }
  153. protected virtual Effect CreateSkinnedEffect(GLTFMATERIAL srcMaterial)
  154. {
  155. var dstMaterial = new SkinnedEffect(Device);
  156. dstMaterial.Name = srcMaterial.Name;
  157. dstMaterial.Alpha = _GltfMaterialBasicProperties.GetAlphaLevel(srcMaterial);
  158. dstMaterial.DiffuseColor = _GltfMaterialBasicProperties.GetDiffuseColor(srcMaterial);
  159. dstMaterial.SpecularColor = _GltfMaterialBasicProperties.GetSpecularColor(srcMaterial);
  160. dstMaterial.SpecularPower = _GltfMaterialBasicProperties.GetSpecularPower(srcMaterial);
  161. dstMaterial.EmissiveColor = _GltfMaterialBasicProperties.GeEmissiveColor(srcMaterial);
  162. dstMaterial.Texture = UseDiffuseTexture(srcMaterial);
  163. dstMaterial.WeightsPerVertex = 4;
  164. dstMaterial.PreferPerPixelLighting = true;
  165. // apparently, SkinnedEffect does not support disabling textures, so we set a white texture here.
  166. dstMaterial.Texture ??= UseWhiteTexture(); // creates a dummy white texture.
  167. return dstMaterial;
  168. }
  169. protected virtual Texture2D UseDiffuseTexture(GLTFMATERIAL srcMaterial)
  170. {
  171. var diffuse = srcMaterial.FindChannel("Diffuse")
  172. ?? srcMaterial.FindChannel("BaseColor");
  173. return diffuse == null
  174. ? null
  175. : UseTexture(diffuse, null);
  176. }
  177. #endregion
  178. }
  179. }