TilemapEffect.cs 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490
  1. #region License
  2. // Copyright 2019-2021 Kastellanos Nikolaos
  3. //
  4. // Licensed under the Apache License, Version 2.0 (the "License");
  5. // you may not use this file except in compliance with the License.
  6. // You may obtain a copy of the License at
  7. //
  8. // http://www.apache.org/licenses/LICENSE-2.0
  9. //
  10. // Unless required by applicable law or agreed to in writing, software
  11. // distributed under the License is distributed on an "AS IS" BASIS,
  12. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13. // See the License for the specific language governing permissions and
  14. // limitations under the License.
  15. #endregion
  16. using System;
  17. using System.IO;
  18. using System.Reflection;
  19. using Microsoft.Xna.Framework;
  20. using Microsoft.Xna.Framework.Graphics;
  21. namespace nkast.Aether.Shaders
  22. {
  23. public class TilemapEffect : Effect, IEffectMatrices
  24. {
  25. #region Effect Parameters
  26. EffectParameter textureParam;
  27. EffectParameter textureAtlasParam;
  28. EffectParameter mapSizeParam;
  29. EffectParameter invAtlasSizeParam;
  30. EffectParameter diffuseColorParam;
  31. EffectParameter fogColorParam;
  32. EffectParameter fogVectorParam;
  33. // IEffectMatrices
  34. EffectParameter worldViewProjParam;
  35. #endregion
  36. #region Fields
  37. bool fogEnabled;
  38. bool vertexColorEnabled;
  39. Matrix world = Matrix.Identity;
  40. Matrix view = Matrix.Identity;
  41. Matrix projection = Matrix.Identity;
  42. Matrix worldView;
  43. Vector3 diffuseColor = Vector3.One;
  44. float alpha = 1;
  45. float fogStart = 0;
  46. float fogEnd = 1;
  47. Vector2 atlasSize;
  48. EffectDirtyFlags dirtyFlags = EffectDirtyFlags.All;
  49. static readonly string ResourceName = "nkast.Aether.Shaders.Resources.TilemapEffect";
  50. internal static byte[] LoadEffectResource(GraphicsDevice graphicsDevice, string name)
  51. {
  52. name = GetResourceName(graphicsDevice, name);
  53. using (Stream stream = GetAssembly(typeof(TilemapEffect)).GetManifestResourceStream(name))
  54. using (MemoryStream ms = new MemoryStream())
  55. {
  56. stream.CopyTo(ms);
  57. return ms.ToArray();
  58. }
  59. }
  60. private static string GetResourceName(GraphicsDevice graphicsDevice, string name)
  61. {
  62. string platformName = "";
  63. string version = "";
  64. #if XNA
  65. platformName = ".xna.WinReach";
  66. #else
  67. switch (graphicsDevice.Adapter.Backend)
  68. {
  69. case GraphicsBackend.DirectX11:
  70. platformName = ".dx11.fxo";
  71. break;
  72. case GraphicsBackend.OpenGL:
  73. case GraphicsBackend.GLES:
  74. case GraphicsBackend.WebGL:
  75. platformName = ".ogl.fxo";
  76. break;
  77. default:
  78. throw new NotSupportedException("platform");
  79. }
  80. // Detect version
  81. version = ".10";
  82. Version kniVersion = GetAssembly(typeof(Effect)).GetName().Version;
  83. if (kniVersion.Major == 3)
  84. {
  85. if (kniVersion.Minor == 9)
  86. {
  87. version = ".10";
  88. }
  89. if (kniVersion.Minor == 10)
  90. {
  91. version = ".10";
  92. }
  93. }
  94. #endif
  95. return name + platformName + version;
  96. }
  97. private static Assembly GetAssembly(Type type)
  98. {
  99. #if W10
  100. return type.GetTypeInfo().Assembly;
  101. #else
  102. return type.Assembly;
  103. #endif
  104. }
  105. #endregion
  106. #region Public Properties
  107. public Matrix Projection
  108. {
  109. get { return projection; }
  110. set
  111. {
  112. projection = value;
  113. dirtyFlags |= EffectDirtyFlags.WorldViewProj;
  114. }
  115. }
  116. public Matrix View
  117. {
  118. get { return view; }
  119. set
  120. {
  121. view = value;
  122. dirtyFlags |= EffectDirtyFlags.WorldViewProj | EffectDirtyFlags.Fog;
  123. }
  124. }
  125. public Matrix World
  126. {
  127. get { return world; }
  128. set
  129. {
  130. world = value;
  131. dirtyFlags |= EffectDirtyFlags.WorldViewProj | EffectDirtyFlags.Fog;
  132. }
  133. }
  134. /// <summary>
  135. /// Gets or sets the Tilemap texture.
  136. /// </summary>
  137. public Texture2D Texture
  138. {
  139. get { return textureParam.GetValueTexture2D(); }
  140. set { textureParam.SetValue(value); }
  141. }
  142. /// <summary>
  143. /// Gets or sets the Atlas texture.
  144. /// </summary>
  145. public Texture2D TextureAtlas
  146. {
  147. get { return textureAtlasParam.GetValueTexture2D(); }
  148. set { textureAtlasParam.SetValue(value); }
  149. }
  150. /// <summary>
  151. /// Gets or sets the Map size.
  152. /// </summary>
  153. public Vector2 MapSize
  154. {
  155. get { return mapSizeParam.GetValueVector2(); }
  156. set { mapSizeParam.SetValue(value); }
  157. }
  158. /// <summary>
  159. /// Gets or sets the Atlas size.
  160. /// </summary>
  161. public Vector2 AtlasSize
  162. {
  163. get { return atlasSize; }
  164. set
  165. {
  166. atlasSize = value;
  167. value.X = 1f/value.X;
  168. value.Y = 1f/value.Y;
  169. invAtlasSizeParam.SetValue(value);
  170. }
  171. }
  172. /// <summary>
  173. /// Gets or sets the material diffuse color (range 0 to 1).
  174. /// </summary>
  175. public Vector3 DiffuseColor
  176. {
  177. get { return diffuseColor; }
  178. set
  179. {
  180. diffuseColor = value;
  181. dirtyFlags |= EffectDirtyFlags.MaterialColor;
  182. }
  183. }
  184. /// <summary>
  185. /// Gets or sets the material alpha.
  186. /// </summary>
  187. public float Alpha
  188. {
  189. get { return alpha; }
  190. set
  191. {
  192. alpha = value;
  193. dirtyFlags |= EffectDirtyFlags.MaterialColor;
  194. }
  195. }
  196. /// <summary>
  197. /// Gets or sets the fog enable flag.
  198. /// </summary>
  199. public bool FogEnabled
  200. {
  201. get { return fogEnabled; }
  202. set
  203. {
  204. if (fogEnabled != value)
  205. {
  206. fogEnabled = value;
  207. dirtyFlags |= EffectDirtyFlags.FogEnable;
  208. UpdateCurrentTechnique();
  209. }
  210. }
  211. }
  212. /// <summary>
  213. /// Gets or sets the fog start distance.
  214. /// </summary>
  215. public float FogStart
  216. {
  217. get { return fogStart; }
  218. set
  219. {
  220. fogStart = value;
  221. dirtyFlags |= EffectDirtyFlags.Fog;
  222. }
  223. }
  224. /// <summary>
  225. /// Gets or sets the fog end distance.
  226. /// </summary>
  227. public float FogEnd
  228. {
  229. get { return fogEnd; }
  230. set
  231. {
  232. fogEnd = value;
  233. dirtyFlags |= EffectDirtyFlags.Fog;
  234. }
  235. }
  236. /// <summary>
  237. /// Gets or sets the fog color.
  238. /// </summary>
  239. public Vector3 FogColor
  240. {
  241. get { return fogColorParam.GetValueVector3(); }
  242. set { fogColorParam.SetValue(value); }
  243. }
  244. /// <summary>
  245. /// Gets or sets whether vertex color is enabled.
  246. /// </summary>
  247. public bool VertexColorEnabled
  248. {
  249. get { return vertexColorEnabled; }
  250. set
  251. {
  252. if (vertexColorEnabled != value)
  253. {
  254. vertexColorEnabled = value;
  255. UpdateCurrentTechnique();
  256. }
  257. }
  258. }
  259. #endregion
  260. #region Methods
  261. public TilemapEffect(GraphicsDevice graphicsDevice)
  262. : base(graphicsDevice, LoadEffectResource(graphicsDevice, ResourceName))
  263. {
  264. CacheEffectParameters(null);
  265. }
  266. public TilemapEffect(GraphicsDevice graphicsDevice, byte[] Bytecode): base(graphicsDevice, Bytecode)
  267. {
  268. CacheEffectParameters(null);
  269. }
  270. protected TilemapEffect(TilemapEffect cloneSource)
  271. : base(cloneSource)
  272. {
  273. CacheEffectParameters(cloneSource);
  274. fogEnabled = cloneSource.fogEnabled;
  275. vertexColorEnabled = cloneSource.vertexColorEnabled;
  276. world = cloneSource.world;
  277. view = cloneSource.view;
  278. projection = cloneSource.projection;
  279. diffuseColor = cloneSource.diffuseColor;
  280. alpha = cloneSource.alpha;
  281. fogStart = cloneSource.fogStart;
  282. fogEnd = cloneSource.fogEnd;
  283. atlasSize = cloneSource.atlasSize;
  284. }
  285. public override Effect Clone()
  286. {
  287. return new TilemapEffect(this);
  288. }
  289. void CacheEffectParameters(TilemapEffect cloneSource)
  290. {
  291. textureParam = Parameters["Texture"];
  292. textureAtlasParam = Parameters["TextureAtlas"];
  293. mapSizeParam = Parameters["MapSize"];
  294. invAtlasSizeParam = Parameters["InvAtlasSize"];
  295. diffuseColorParam = Parameters["DiffuseColor"];
  296. fogColorParam = Parameters["FogColor"];
  297. fogVectorParam = Parameters["FogVector"];
  298. // IEffectMatrices
  299. worldViewProjParam = Parameters["WorldViewProj"];
  300. }
  301. /// <summary>
  302. /// Lazily computes derived parameter values immediately before applying the effect.
  303. /// </summary>
  304. protected override void OnApply()
  305. {
  306. // Recompute the world+view+projection matrix or fog vector?
  307. dirtyFlags = SetWorldViewProjAndFog(dirtyFlags, ref world, ref view, ref projection, ref worldView, fogEnabled, fogStart, fogEnd, worldViewProjParam, fogVectorParam);
  308. // Recompute the diffuse/alpha material color parameter?
  309. if ((dirtyFlags & EffectDirtyFlags.MaterialColor) != 0)
  310. {
  311. diffuseColorParam.SetValue(new Vector4(diffuseColor * alpha, alpha));
  312. dirtyFlags &= ~EffectDirtyFlags.MaterialColor;
  313. }
  314. }
  315. private void UpdateCurrentTechnique()
  316. {
  317. int shaderIndex = 0;
  318. if (!fogEnabled)
  319. shaderIndex += 1;
  320. if (vertexColorEnabled)
  321. shaderIndex += 2;
  322. CurrentTechnique = Techniques[shaderIndex];
  323. }
  324. /// <summary>
  325. /// Lazily recomputes the world+view+projection matrix and
  326. /// fog vector based on the current effect parameter settings.
  327. /// </summary>
  328. static EffectDirtyFlags SetWorldViewProjAndFog(EffectDirtyFlags dirtyFlags,
  329. ref Matrix world, ref Matrix view, ref Matrix projection, ref Matrix worldView,
  330. bool fogEnabled, float fogStart, float fogEnd,
  331. EffectParameter worldViewProjParam, EffectParameter fogVectorParam)
  332. {
  333. // Recompute the world+view+projection matrix?
  334. if ((dirtyFlags & EffectDirtyFlags.WorldViewProj) != 0)
  335. {
  336. Matrix worldViewProj;
  337. Matrix.Multiply(ref world, ref view, out worldView);
  338. Matrix.Multiply(ref worldView, ref projection, out worldViewProj);
  339. worldViewProjParam.SetValue(worldViewProj);
  340. dirtyFlags &= ~EffectDirtyFlags.WorldViewProj;
  341. }
  342. if (fogEnabled)
  343. {
  344. // Recompute the fog vector?
  345. if ((dirtyFlags & (EffectDirtyFlags.Fog | EffectDirtyFlags.FogEnable)) != 0)
  346. {
  347. SetFogVector(ref worldView, fogStart, fogEnd, fogVectorParam);
  348. dirtyFlags &= ~(EffectDirtyFlags.Fog | EffectDirtyFlags.FogEnable);
  349. }
  350. }
  351. else
  352. {
  353. // When fog is disabled, make sure the fog vector is reset to zero.
  354. if ((dirtyFlags & EffectDirtyFlags.FogEnable) != 0)
  355. {
  356. fogVectorParam.SetValue(Vector4.Zero);
  357. dirtyFlags &= ~EffectDirtyFlags.FogEnable;
  358. }
  359. }
  360. return dirtyFlags;
  361. }
  362. /// <summary>
  363. /// Sets a vector which can be dotted with the object space vertex position to compute fog amount.
  364. /// </summary>
  365. static void SetFogVector(ref Matrix worldView, float fogStart, float fogEnd, EffectParameter fogVectorParam)
  366. {
  367. if (fogStart == fogEnd)
  368. {
  369. // Degenerate case: force everything to 100% fogged if start and end are the same.
  370. fogVectorParam.SetValue(new Vector4(0, 0, 0, 1));
  371. }
  372. else
  373. {
  374. // We want to transform vertex positions into view space, take the resulting
  375. // Z value, then scale and offset according to the fog start/end distances.
  376. // Because we only care about the Z component, the shader can do all this
  377. // with a single dot product, using only the Z row of the world+view matrix.
  378. float scale = 1f / (fogStart - fogEnd);
  379. Vector4 fogVector = new Vector4();
  380. fogVector.X = worldView.M13 * scale;
  381. fogVector.Y = worldView.M23 * scale;
  382. fogVector.Z = worldView.M33 * scale;
  383. fogVector.W = (worldView.M43 + fogStart) * scale;
  384. fogVectorParam.SetValue(fogVector);
  385. }
  386. }
  387. #endregion
  388. enum EffectDirtyFlags
  389. {
  390. WorldViewProj = 1,
  391. //World = 2,
  392. //EyePosition = 4,
  393. MaterialColor = 8,
  394. Fog = 16,
  395. FogEnable = 32,
  396. //AlphaTest = 64,
  397. All = -1
  398. }
  399. }
  400. }