VolumetricFogFoW.cs 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358
  1. //------------------------------------------------------------------------------------------------------------------
  2. // Volumetric Fog & Mist
  3. // Created by Ramiro Oliva (Kronnect)
  4. //------------------------------------------------------------------------------------------------------------------
  5. using UnityEngine;
  6. using System;
  7. using System.Collections;
  8. using System.Collections.Generic;
  9. namespace VolumetricFogAndMist {
  10. public partial class VolumetricFog : MonoBehaviour {
  11. const int MAX_SIMULTANEOUS_TRANSITIONS = 10000;
  12. #region Fog of War settings
  13. [SerializeField]
  14. bool _fogOfWarEnabled;
  15. public bool fogOfWarEnabled {
  16. get { return _fogOfWarEnabled; }
  17. set {
  18. if (value != _fogOfWarEnabled) {
  19. _fogOfWarEnabled = value;
  20. FogOfWarUpdateTexture ();
  21. UpdateMaterialProperties ();
  22. isDirty = true;
  23. }
  24. }
  25. }
  26. [SerializeField]
  27. Vector3 _fogOfWarCenter;
  28. public Vector3 fogOfWarCenter {
  29. get { return _fogOfWarCenter; }
  30. set {
  31. if (value != _fogOfWarCenter) {
  32. _fogOfWarCenter = value;
  33. UpdateMaterialProperties ();
  34. isDirty = true;
  35. }
  36. }
  37. }
  38. [SerializeField]
  39. Vector3 _fogOfWarSize = new Vector3 (1024, 0, 1024);
  40. public Vector3 fogOfWarSize {
  41. get { return _fogOfWarSize; }
  42. set {
  43. if (value != _fogOfWarSize) {
  44. if (value.x > 0 && value.z > 0) {
  45. _fogOfWarSize = value;
  46. UpdateMaterialProperties ();
  47. isDirty = true;
  48. }
  49. }
  50. }
  51. }
  52. [SerializeField]
  53. int _fogOfWarTextureSize = 256;
  54. public int fogOfWarTextureSize {
  55. get { return _fogOfWarTextureSize; }
  56. set {
  57. if (value != _fogOfWarTextureSize) {
  58. if (value > 16) {
  59. _fogOfWarTextureSize = value;
  60. FogOfWarUpdateTexture ();
  61. UpdateMaterialProperties ();
  62. isDirty = true;
  63. }
  64. }
  65. }
  66. }
  67. [SerializeField]
  68. float _fogOfWarRestoreDelay = 0;
  69. public float fogOfWarRestoreDelay {
  70. get { return _fogOfWarRestoreDelay; }
  71. set {
  72. if (value != _fogOfWarRestoreDelay) {
  73. _fogOfWarRestoreDelay = value;
  74. isDirty = true;
  75. }
  76. }
  77. }
  78. [SerializeField]
  79. float _fogOfWarRestoreDuration = 2f;
  80. public float fogOfWarRestoreDuration {
  81. get { return _fogOfWarRestoreDuration; }
  82. set {
  83. if (value != _fogOfWarRestoreDuration) {
  84. _fogOfWarRestoreDuration = value;
  85. isDirty = true;
  86. }
  87. }
  88. }
  89. #endregion
  90. Texture2D fogOfWarTexture;
  91. Color32[] fogOfWarColorBuffer;
  92. struct FogOfWarTransition {
  93. public bool enabled;
  94. public int x, y;
  95. public float startTime, startDelay;
  96. public float duration;
  97. public byte initialAlpha;
  98. public byte targetAlpha;
  99. }
  100. FogOfWarTransition[] fowTransitionList;
  101. int lastTransitionPos;
  102. Dictionary<int, int> fowTransitionIndices;
  103. bool requiresTextureUpload;
  104. #region Fog Of War
  105. void FogOfWarInit () {
  106. fowTransitionList = new FogOfWarTransition[MAX_SIMULTANEOUS_TRANSITIONS];
  107. fowTransitionIndices = new Dictionary<int, int> (MAX_SIMULTANEOUS_TRANSITIONS);
  108. lastTransitionPos = -1;
  109. }
  110. void FogOfWarUpdateTexture () {
  111. if (!_fogOfWarEnabled)
  112. return;
  113. int size = GetScaledSize (_fogOfWarTextureSize, 1.0f);
  114. fogOfWarTexture = new Texture2D (size, size, TextureFormat.Alpha8, false);
  115. fogOfWarTexture.hideFlags = HideFlags.DontSave;
  116. fogOfWarTexture.filterMode = FilterMode.Bilinear;
  117. fogOfWarTexture.wrapMode = TextureWrapMode.Clamp;
  118. ResetFogOfWar ();
  119. }
  120. void FogOfWarUpdate () {
  121. if (!_fogOfWarEnabled)
  122. return;
  123. int tw = fogOfWarTexture.width;
  124. for (int k = 0; k <= lastTransitionPos; k++) {
  125. FogOfWarTransition fw = fowTransitionList [k];
  126. if (!fw.enabled)
  127. continue;
  128. float elapsed = Time.time - fw.startTime - fw.startDelay;
  129. if (elapsed > 0) {
  130. float t = fw.duration <= 0 ? 1 : elapsed / fw.duration;
  131. t = Mathf.Clamp01 (t);
  132. byte alpha = (byte)Mathf.Lerp (fw.initialAlpha, fw.targetAlpha, t);
  133. int colorPos = fw.y * tw + fw.x;
  134. fogOfWarColorBuffer [colorPos].a = alpha;
  135. fogOfWarTexture.SetPixel (fw.x, fw.y, fogOfWarColorBuffer [colorPos]);
  136. requiresTextureUpload = true;
  137. if (t >= 1f) {
  138. fowTransitionList [k].enabled = false;
  139. // Add clearance slot if needed
  140. if (fw.targetAlpha < 255 && _fogOfWarRestoreDelay > 0) {
  141. AddFowOfWarTransitionSlot (fw.x, fw.y, fw.targetAlpha, 255, _fogOfWarRestoreDelay, _fogOfWarRestoreDuration);
  142. }
  143. }
  144. }
  145. }
  146. if (requiresTextureUpload) {
  147. requiresTextureUpload = false;
  148. fogOfWarTexture.Apply ();
  149. }
  150. }
  151. /// <summary>
  152. /// Instantly changes the alpha value of the fog of war at world position. It takes into account FogOfWarCenter and FogOfWarSize.
  153. /// Note that only x and z coordinates are used. Y (vertical) coordinate is ignored.
  154. /// </summary>
  155. /// <param name="worldPosition">in world space coordinates.</param>
  156. /// <param name="radius">radius of application in world units.</param>
  157. public void SetFogOfWarAlpha (Vector3 worldPosition, float radius, float fogNewAlpha) {
  158. SetFogOfWarAlpha (worldPosition, radius, fogNewAlpha, 1f);
  159. }
  160. /// <summary>
  161. /// Changes the alpha value of the fog of war at world position creating a transition from current alpha value to specified target alpha. It takes into account FogOfWarCenter and FogOfWarSize.
  162. /// Note that only x and z coordinates are used. Y (vertical) coordinate is ignored.
  163. /// </summary>
  164. /// <param name="worldPosition">in world space coordinates.</param>
  165. /// <param name="radius">radius of application in world units.</param>
  166. /// <param name="fogNewAlpha">target alpha value.</param>
  167. /// <param name="duration">duration of transition in seconds (0 = apply fogNewAlpha instantly).</param>
  168. public void SetFogOfWarAlpha (Vector3 worldPosition, float radius, float fogNewAlpha, float duration) {
  169. if (fogOfWarTexture == null)
  170. return;
  171. float tx = (worldPosition.x - _fogOfWarCenter.x) / _fogOfWarSize.x + 0.5f;
  172. if (tx < 0 || tx > 1f)
  173. return;
  174. float tz = (worldPosition.z - _fogOfWarCenter.z) / _fogOfWarSize.z + 0.5f;
  175. if (tz < 0 || tz > 1f)
  176. return;
  177. int tw = fogOfWarTexture.width;
  178. int th = fogOfWarTexture.height;
  179. int px = (int)(tx * tw);
  180. int pz = (int)(tz * th);
  181. int colorBufferPos = pz * tw + px;
  182. byte newAlpha8 = (byte)(fogNewAlpha * 255);
  183. Color32 existingColor = fogOfWarColorBuffer [colorBufferPos];
  184. if (newAlpha8 != existingColor.a) { // just to avoid over setting the texture in an Update() loop
  185. float tr = radius / _fogOfWarSize.z;
  186. int delta = (int) (th * tr);
  187. for (int r = pz - delta; r <= pz + delta; r++) {
  188. if (r > 0 && r < th - 1) {
  189. for (int c = px - delta; c <= px + delta; c++) {
  190. if (c > 0 && c < tw - 1) {
  191. int distance = (int) (Mathf.Sqrt ((pz - r) * (pz - r) + (px - c) * (px - c)));
  192. if (distance <= delta) {
  193. colorBufferPos = r * tw + c;
  194. Color32 colorBuffer = fogOfWarColorBuffer [colorBufferPos];
  195. byte targetAlpha = (byte)Mathf.Lerp (newAlpha8, colorBuffer.a, (float)distance / delta);
  196. if (duration > 0) {
  197. AddFowOfWarTransitionSlot (c, r, colorBuffer.a, targetAlpha, 0, duration);
  198. } else {
  199. colorBuffer.a = targetAlpha;
  200. fogOfWarColorBuffer [colorBufferPos] = colorBuffer;
  201. fogOfWarTexture.SetPixel (c, r, colorBuffer);
  202. if (_fogOfWarRestoreDuration > 0) {
  203. AddFowOfWarTransitionSlot (c, r, targetAlpha, 255, _fogOfWarRestoreDelay, _fogOfWarRestoreDuration);
  204. }
  205. }
  206. }
  207. }
  208. }
  209. }
  210. }
  211. requiresTextureUpload = true;
  212. }
  213. }
  214. public void ResetFogOfWarAlpha (Vector3 worldPosition, float radius) {
  215. if (fogOfWarTexture == null)
  216. return;
  217. float tx = (worldPosition.x - _fogOfWarCenter.x) / _fogOfWarSize.x + 0.5f;
  218. if (tx < 0 || tx > 1f)
  219. return;
  220. float tz = (worldPosition.z - _fogOfWarCenter.z) / _fogOfWarSize.z + 0.5f;
  221. if (tz < 0 || tz > 1f)
  222. return;
  223. int tw = fogOfWarTexture.width;
  224. int th = fogOfWarTexture.height;
  225. int px = (int)(tx * tw);
  226. int pz = (int)(tz * th);
  227. int colorBufferPos = pz * tw + px;
  228. float tr = radius / _fogOfWarSize.z;
  229. int delta = (int) (th * tr);
  230. int deltaSqr = delta * delta;
  231. for (int r = pz - delta; r <= pz + delta; r++) {
  232. if (r > 0 && r < th - 1) {
  233. for (int c = px - delta; c <= px + delta; c++) {
  234. if (c > 0 && c < tw - 1) {
  235. int distanceSqr = (pz - r) * (pz - r) + (px - c) * (px - c);
  236. if (distanceSqr <= deltaSqr) {
  237. colorBufferPos = r * tw + c;
  238. Color32 colorBuffer = fogOfWarColorBuffer [colorBufferPos];
  239. colorBuffer.a = 255;
  240. fogOfWarColorBuffer [colorBufferPos] = colorBuffer;
  241. fogOfWarTexture.SetPixel (c, r, colorBuffer);
  242. }
  243. }
  244. }
  245. }
  246. requiresTextureUpload = true;
  247. }
  248. }
  249. public void ResetFogOfWar () {
  250. if (fogOfWarTexture == null || !isPartOfScene)
  251. return;
  252. int h = fogOfWarTexture.height;
  253. int w = fogOfWarTexture.width;
  254. int newLength = h * w;
  255. if (fogOfWarColorBuffer == null || fogOfWarColorBuffer.Length != newLength) {
  256. fogOfWarColorBuffer = new Color32[newLength];
  257. }
  258. Color32 opaque = new Color32 (255, 255, 255, 255);
  259. for (int k = 0; k < newLength; k++)
  260. fogOfWarColorBuffer [k] = opaque;
  261. fogOfWarTexture.SetPixels32 (fogOfWarColorBuffer);
  262. fogOfWarTexture.Apply ();
  263. lastTransitionPos = -1;
  264. fowTransitionIndices.Clear ();
  265. isDirty = true;
  266. }
  267. /// <summary>
  268. /// Gets or set fog of war state as a Color32 buffer. The alpha channel stores the transparency of the fog at that position (0 = no fog, 1 = opaque).
  269. /// </summary>
  270. public Color32[] fogOfWarTextureData {
  271. get {
  272. return fogOfWarColorBuffer;
  273. }
  274. set {
  275. fogOfWarEnabled = true;
  276. fogOfWarColorBuffer = value;
  277. if (value == null || fogOfWarTexture == null)
  278. return;
  279. if (value.Length != fogOfWarTexture.width * fogOfWarTexture.height)
  280. return;
  281. fogOfWarTexture.SetPixels32 (fogOfWarColorBuffer);
  282. fogOfWarTexture.Apply ();
  283. }
  284. }
  285. void AddFowOfWarTransitionSlot (int x, int y, byte initialAlpha, byte targetAlpha, float delay, float duration) {
  286. // Check if this slot exists
  287. int index;
  288. int key = y * 64000 + x;
  289. if (!fowTransitionIndices.TryGetValue (key, out index)) {
  290. index = -1;
  291. for (int k = 0; k <= lastTransitionPos; k++) {
  292. if (!fowTransitionList [k].enabled) {
  293. index = k;
  294. fowTransitionIndices [key] = index;
  295. break;
  296. }
  297. }
  298. }
  299. if (index < 0) {
  300. index = ++lastTransitionPos;
  301. if (index>= MAX_SIMULTANEOUS_TRANSITIONS) return;
  302. fowTransitionIndices [key] = index;
  303. }
  304. fowTransitionList[index].x = x;
  305. fowTransitionList[index].y = y;
  306. fowTransitionList[index].duration = duration;
  307. fowTransitionList[index].startTime = Time.time;
  308. fowTransitionList[index].startDelay = delay;
  309. fowTransitionList[index].initialAlpha = initialAlpha;
  310. fowTransitionList[index].targetAlpha = targetAlpha;
  311. fowTransitionList[index].enabled = true;
  312. }
  313. #endregion
  314. }
  315. }