TilemapEffect.cs 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481
  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 = typeof(TilemapEffect).Assembly.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 = typeof(Effect).Assembly.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. #endregion
  98. #region Public Properties
  99. public Matrix Projection
  100. {
  101. get { return projection; }
  102. set
  103. {
  104. projection = value;
  105. dirtyFlags |= EffectDirtyFlags.WorldViewProj;
  106. }
  107. }
  108. public Matrix View
  109. {
  110. get { return view; }
  111. set
  112. {
  113. view = value;
  114. dirtyFlags |= EffectDirtyFlags.WorldViewProj | EffectDirtyFlags.Fog;
  115. }
  116. }
  117. public Matrix World
  118. {
  119. get { return world; }
  120. set
  121. {
  122. world = value;
  123. dirtyFlags |= EffectDirtyFlags.WorldViewProj | EffectDirtyFlags.Fog;
  124. }
  125. }
  126. /// <summary>
  127. /// Gets or sets the Tilemap texture.
  128. /// </summary>
  129. public Texture2D Texture
  130. {
  131. get { return textureParam.GetValueTexture2D(); }
  132. set { textureParam.SetValue(value); }
  133. }
  134. /// <summary>
  135. /// Gets or sets the Atlas texture.
  136. /// </summary>
  137. public Texture2D TextureAtlas
  138. {
  139. get { return textureAtlasParam.GetValueTexture2D(); }
  140. set { textureAtlasParam.SetValue(value); }
  141. }
  142. /// <summary>
  143. /// Gets or sets the Map size.
  144. /// </summary>
  145. public Vector2 MapSize
  146. {
  147. get { return mapSizeParam.GetValueVector2(); }
  148. set { mapSizeParam.SetValue(value); }
  149. }
  150. /// <summary>
  151. /// Gets or sets the Atlas size.
  152. /// </summary>
  153. public Vector2 AtlasSize
  154. {
  155. get { return atlasSize; }
  156. set
  157. {
  158. atlasSize = value;
  159. value.X = 1f/value.X;
  160. value.Y = 1f/value.Y;
  161. invAtlasSizeParam.SetValue(value);
  162. }
  163. }
  164. /// <summary>
  165. /// Gets or sets the material diffuse color (range 0 to 1).
  166. /// </summary>
  167. public Vector3 DiffuseColor
  168. {
  169. get { return diffuseColor; }
  170. set
  171. {
  172. diffuseColor = value;
  173. dirtyFlags |= EffectDirtyFlags.MaterialColor;
  174. }
  175. }
  176. /// <summary>
  177. /// Gets or sets the material alpha.
  178. /// </summary>
  179. public float Alpha
  180. {
  181. get { return alpha; }
  182. set
  183. {
  184. alpha = value;
  185. dirtyFlags |= EffectDirtyFlags.MaterialColor;
  186. }
  187. }
  188. /// <summary>
  189. /// Gets or sets the fog enable flag.
  190. /// </summary>
  191. public bool FogEnabled
  192. {
  193. get { return fogEnabled; }
  194. set
  195. {
  196. if (fogEnabled != value)
  197. {
  198. fogEnabled = value;
  199. dirtyFlags |= EffectDirtyFlags.FogEnable;
  200. UpdateCurrentTechnique();
  201. }
  202. }
  203. }
  204. /// <summary>
  205. /// Gets or sets the fog start distance.
  206. /// </summary>
  207. public float FogStart
  208. {
  209. get { return fogStart; }
  210. set
  211. {
  212. fogStart = value;
  213. dirtyFlags |= EffectDirtyFlags.Fog;
  214. }
  215. }
  216. /// <summary>
  217. /// Gets or sets the fog end distance.
  218. /// </summary>
  219. public float FogEnd
  220. {
  221. get { return fogEnd; }
  222. set
  223. {
  224. fogEnd = value;
  225. dirtyFlags |= EffectDirtyFlags.Fog;
  226. }
  227. }
  228. /// <summary>
  229. /// Gets or sets the fog color.
  230. /// </summary>
  231. public Vector3 FogColor
  232. {
  233. get { return fogColorParam.GetValueVector3(); }
  234. set { fogColorParam.SetValue(value); }
  235. }
  236. /// <summary>
  237. /// Gets or sets whether vertex color is enabled.
  238. /// </summary>
  239. public bool VertexColorEnabled
  240. {
  241. get { return vertexColorEnabled; }
  242. set
  243. {
  244. if (vertexColorEnabled != value)
  245. {
  246. vertexColorEnabled = value;
  247. UpdateCurrentTechnique();
  248. }
  249. }
  250. }
  251. #endregion
  252. #region Methods
  253. public TilemapEffect(GraphicsDevice graphicsDevice)
  254. : base(graphicsDevice, LoadEffectResource(graphicsDevice, ResourceName))
  255. {
  256. CacheEffectParameters(null);
  257. }
  258. public TilemapEffect(GraphicsDevice graphicsDevice, byte[] Bytecode): base(graphicsDevice, Bytecode)
  259. {
  260. CacheEffectParameters(null);
  261. }
  262. protected TilemapEffect(TilemapEffect cloneSource)
  263. : base(cloneSource)
  264. {
  265. CacheEffectParameters(cloneSource);
  266. fogEnabled = cloneSource.fogEnabled;
  267. vertexColorEnabled = cloneSource.vertexColorEnabled;
  268. world = cloneSource.world;
  269. view = cloneSource.view;
  270. projection = cloneSource.projection;
  271. diffuseColor = cloneSource.diffuseColor;
  272. alpha = cloneSource.alpha;
  273. fogStart = cloneSource.fogStart;
  274. fogEnd = cloneSource.fogEnd;
  275. atlasSize = cloneSource.atlasSize;
  276. }
  277. public override Effect Clone()
  278. {
  279. return new TilemapEffect(this);
  280. }
  281. void CacheEffectParameters(TilemapEffect cloneSource)
  282. {
  283. textureParam = Parameters["Texture"];
  284. textureAtlasParam = Parameters["TextureAtlas"];
  285. mapSizeParam = Parameters["MapSize"];
  286. invAtlasSizeParam = Parameters["InvAtlasSize"];
  287. diffuseColorParam = Parameters["DiffuseColor"];
  288. fogColorParam = Parameters["FogColor"];
  289. fogVectorParam = Parameters["FogVector"];
  290. // IEffectMatrices
  291. worldViewProjParam = Parameters["WorldViewProj"];
  292. }
  293. /// <summary>
  294. /// Lazily computes derived parameter values immediately before applying the effect.
  295. /// </summary>
  296. protected override void OnApply()
  297. {
  298. // Recompute the world+view+projection matrix or fog vector?
  299. dirtyFlags = SetWorldViewProjAndFog(dirtyFlags, ref world, ref view, ref projection, ref worldView, fogEnabled, fogStart, fogEnd, worldViewProjParam, fogVectorParam);
  300. // Recompute the diffuse/alpha material color parameter?
  301. if ((dirtyFlags & EffectDirtyFlags.MaterialColor) != 0)
  302. {
  303. diffuseColorParam.SetValue(new Vector4(diffuseColor * alpha, alpha));
  304. dirtyFlags &= ~EffectDirtyFlags.MaterialColor;
  305. }
  306. }
  307. private void UpdateCurrentTechnique()
  308. {
  309. int shaderIndex = 0;
  310. if (!fogEnabled)
  311. shaderIndex += 1;
  312. if (vertexColorEnabled)
  313. shaderIndex += 2;
  314. CurrentTechnique = Techniques[shaderIndex];
  315. }
  316. /// <summary>
  317. /// Lazily recomputes the world+view+projection matrix and
  318. /// fog vector based on the current effect parameter settings.
  319. /// </summary>
  320. static EffectDirtyFlags SetWorldViewProjAndFog(EffectDirtyFlags dirtyFlags,
  321. ref Matrix world, ref Matrix view, ref Matrix projection, ref Matrix worldView,
  322. bool fogEnabled, float fogStart, float fogEnd,
  323. EffectParameter worldViewProjParam, EffectParameter fogVectorParam)
  324. {
  325. // Recompute the world+view+projection matrix?
  326. if ((dirtyFlags & EffectDirtyFlags.WorldViewProj) != 0)
  327. {
  328. Matrix worldViewProj;
  329. Matrix.Multiply(ref world, ref view, out worldView);
  330. Matrix.Multiply(ref worldView, ref projection, out worldViewProj);
  331. worldViewProjParam.SetValue(worldViewProj);
  332. dirtyFlags &= ~EffectDirtyFlags.WorldViewProj;
  333. }
  334. if (fogEnabled)
  335. {
  336. // Recompute the fog vector?
  337. if ((dirtyFlags & (EffectDirtyFlags.Fog | EffectDirtyFlags.FogEnable)) != 0)
  338. {
  339. SetFogVector(ref worldView, fogStart, fogEnd, fogVectorParam);
  340. dirtyFlags &= ~(EffectDirtyFlags.Fog | EffectDirtyFlags.FogEnable);
  341. }
  342. }
  343. else
  344. {
  345. // When fog is disabled, make sure the fog vector is reset to zero.
  346. if ((dirtyFlags & EffectDirtyFlags.FogEnable) != 0)
  347. {
  348. fogVectorParam.SetValue(Vector4.Zero);
  349. dirtyFlags &= ~EffectDirtyFlags.FogEnable;
  350. }
  351. }
  352. return dirtyFlags;
  353. }
  354. /// <summary>
  355. /// Sets a vector which can be dotted with the object space vertex position to compute fog amount.
  356. /// </summary>
  357. static void SetFogVector(ref Matrix worldView, float fogStart, float fogEnd, EffectParameter fogVectorParam)
  358. {
  359. if (fogStart == fogEnd)
  360. {
  361. // Degenerate case: force everything to 100% fogged if start and end are the same.
  362. fogVectorParam.SetValue(new Vector4(0, 0, 0, 1));
  363. }
  364. else
  365. {
  366. // We want to transform vertex positions into view space, take the resulting
  367. // Z value, then scale and offset according to the fog start/end distances.
  368. // Because we only care about the Z component, the shader can do all this
  369. // with a single dot product, using only the Z row of the world+view matrix.
  370. float scale = 1f / (fogStart - fogEnd);
  371. Vector4 fogVector = new Vector4();
  372. fogVector.X = worldView.M13 * scale;
  373. fogVector.Y = worldView.M23 * scale;
  374. fogVector.Z = worldView.M33 * scale;
  375. fogVector.W = (worldView.M43 + fogStart) * scale;
  376. fogVectorParam.SetValue(fogVector);
  377. }
  378. }
  379. #endregion
  380. enum EffectDirtyFlags
  381. {
  382. WorldViewProj = 1,
  383. //World = 2,
  384. //EyePosition = 4,
  385. MaterialColor = 8,
  386. Fog = 16,
  387. FogEnable = 32,
  388. //AlphaTest = 64,
  389. All = -1
  390. }
  391. }
  392. }