PostProcessManager.cs 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Linq;
  4. using UnityEngine.Assertions;
  5. namespace UnityEngine.Rendering.PostProcessing
  6. {
  7. /// <summary>
  8. /// This manager tracks all volumes in the scene and does all the interpolation work. It is
  9. /// automatically created as soon as Post-processing is active in a scene.
  10. /// </summary>
  11. public sealed class PostProcessManager
  12. {
  13. static PostProcessManager s_Instance;
  14. /// <summary>
  15. /// The current singleton instance of <see cref="PostProcessManager"/>.
  16. /// </summary>
  17. public static PostProcessManager instance
  18. {
  19. get
  20. {
  21. if (s_Instance == null)
  22. s_Instance = new PostProcessManager();
  23. return s_Instance;
  24. }
  25. }
  26. const int k_MaxLayerCount = 32; // Max amount of layers available in Unity
  27. readonly Dictionary<int, List<PostProcessVolume>> m_SortedVolumes;
  28. readonly List<PostProcessVolume> m_Volumes;
  29. readonly Dictionary<int, bool> m_SortNeeded;
  30. readonly List<PostProcessEffectSettings> m_BaseSettings;
  31. readonly List<Collider> m_TempColliders;
  32. /// <summary>
  33. /// This dictionary maps all <see cref="PostProcessEffectSettings"/> available to their
  34. /// corresponding <see cref="PostProcessAttribute"/>. It can be used to list all loaded
  35. /// builtin and custom effects.
  36. /// </summary>
  37. public readonly Dictionary<Type, PostProcessAttribute> settingsTypes;
  38. PostProcessManager()
  39. {
  40. m_SortedVolumes = new Dictionary<int, List<PostProcessVolume>>();
  41. m_Volumes = new List<PostProcessVolume>();
  42. m_SortNeeded = new Dictionary<int, bool>();
  43. m_BaseSettings = new List<PostProcessEffectSettings>();
  44. m_TempColliders = new List<Collider>(5);
  45. settingsTypes = new Dictionary<Type, PostProcessAttribute>();
  46. ReloadBaseTypes();
  47. }
  48. #if UNITY_EDITOR
  49. // Called every time Unity recompile scripts in the editor. We need this to keep track of
  50. // any new custom effect the user might add to the project
  51. [UnityEditor.Callbacks.DidReloadScripts]
  52. static void OnEditorReload()
  53. {
  54. instance.ReloadBaseTypes();
  55. }
  56. #endif
  57. void CleanBaseTypes()
  58. {
  59. settingsTypes.Clear();
  60. foreach (var settings in m_BaseSettings)
  61. RuntimeUtilities.Destroy(settings);
  62. m_BaseSettings.Clear();
  63. }
  64. // This will be called only once at runtime and everytime script reload kicks-in in the
  65. // editor as we need to keep track of any compatible post-processing effects in the project
  66. void ReloadBaseTypes()
  67. {
  68. CleanBaseTypes();
  69. // Rebuild the base type map
  70. var types = RuntimeUtilities.GetAllAssemblyTypes()
  71. .Where(
  72. t => t.IsSubclassOf(typeof(PostProcessEffectSettings))
  73. && t.IsDefined(typeof(PostProcessAttribute), false)
  74. && !t.IsAbstract
  75. );
  76. foreach (var type in types)
  77. {
  78. settingsTypes.Add(type, type.GetAttribute<PostProcessAttribute>());
  79. // Create an instance for each effect type, these will be used for the lowest
  80. // priority global volume as we need a default state when exiting volume ranges
  81. var inst = (PostProcessEffectSettings)ScriptableObject.CreateInstance(type);
  82. inst.SetAllOverridesTo(true, false);
  83. m_BaseSettings.Add(inst);
  84. }
  85. }
  86. /// <summary>
  87. /// Gets a list of all volumes currently affecting the given layer. Results aren't sorted
  88. /// and the list isn't cleared.
  89. /// </summary>
  90. /// <param name="layer">The layer to look for</param>
  91. /// <param name="results">A list to store the volumes found</param>
  92. /// <param name="skipDisabled">Should we skip disabled volumes?</param>
  93. /// <param name="skipZeroWeight">Should we skip 0-weight volumes?</param>
  94. public void GetActiveVolumes(PostProcessLayer layer, List<PostProcessVolume> results, bool skipDisabled = true, bool skipZeroWeight = true)
  95. {
  96. // If no trigger is set, only global volumes will have influence
  97. int mask = layer.volumeLayer.value;
  98. var volumeTrigger = layer.volumeTrigger;
  99. bool onlyGlobal = volumeTrigger == null;
  100. var triggerPos = onlyGlobal ? Vector3.zero : volumeTrigger.position;
  101. // Sort the cached volume list(s) for the given layer mask if needed and return it
  102. var volumes = GrabVolumes(mask);
  103. // Traverse all volumes
  104. foreach (var volume in volumes)
  105. {
  106. // Skip disabled volumes and volumes without any data or weight
  107. if ((skipDisabled && !volume.enabled) || volume.profileRef == null || (skipZeroWeight && volume.weight <= 0f))
  108. continue;
  109. // Global volume always have influence
  110. if (volume.isGlobal)
  111. {
  112. results.Add(volume);
  113. continue;
  114. }
  115. if (onlyGlobal)
  116. continue;
  117. // If volume isn't global and has no collider, skip it as it's useless
  118. var colliders = m_TempColliders;
  119. volume.GetComponents(colliders);
  120. if (colliders.Count == 0)
  121. continue;
  122. // Find closest distance to volume, 0 means it's inside it
  123. float closestDistanceSqr = float.PositiveInfinity;
  124. foreach (var collider in colliders)
  125. {
  126. if (!collider.enabled)
  127. continue;
  128. var closestPoint = collider.ClosestPoint(triggerPos); // 5.6-only API
  129. var d = ((closestPoint - triggerPos) / 2f).sqrMagnitude;
  130. if (d < closestDistanceSqr)
  131. closestDistanceSqr = d;
  132. }
  133. colliders.Clear();
  134. float blendDistSqr = volume.blendDistance * volume.blendDistance;
  135. // Check for influence
  136. if (closestDistanceSqr <= blendDistSqr)
  137. results.Add(volume);
  138. }
  139. }
  140. /// <summary>
  141. /// Gets the highest priority volume affecting a given layer.
  142. /// </summary>
  143. /// <param name="layer">The layer to look for</param>
  144. /// <returns>The highest priority volume affecting the layer</returns>
  145. public PostProcessVolume GetHighestPriorityVolume(PostProcessLayer layer)
  146. {
  147. if (layer == null)
  148. throw new ArgumentNullException("layer");
  149. return GetHighestPriorityVolume(layer.volumeLayer);
  150. }
  151. /// <summary>
  152. /// Gets the highest priority volume affecting <see cref="PostProcessLayer"/> in a given
  153. /// <see cref="LayerMask"/>.
  154. /// </summary>
  155. /// <param name="mask">The layer mask to look for</param>
  156. /// <returns>The highest priority volume affecting the layer mask</returns>
  157. /// <seealso cref="PostProcessLayer.volumeLayer"/>
  158. public PostProcessVolume GetHighestPriorityVolume(LayerMask mask)
  159. {
  160. float highestPriority = float.NegativeInfinity;
  161. PostProcessVolume output = null;
  162. List<PostProcessVolume> volumes;
  163. if (m_SortedVolumes.TryGetValue(mask, out volumes))
  164. {
  165. foreach (var volume in volumes)
  166. {
  167. if (volume.priority > highestPriority)
  168. {
  169. highestPriority = volume.priority;
  170. output = volume;
  171. }
  172. }
  173. }
  174. return output;
  175. }
  176. /// <summary>
  177. /// Helper method to spawn a new volume in the scene.
  178. /// </summary>
  179. /// <param name="layer">The unity layer to put the volume in</param>
  180. /// <param name="priority">The priority to set this volume to</param>
  181. /// <param name="settings">A list of effects to put in this volume</param>
  182. /// <returns></returns>
  183. public PostProcessVolume QuickVolume(int layer, float priority, params PostProcessEffectSettings[] settings)
  184. {
  185. var gameObject = new GameObject()
  186. {
  187. name = "Quick Volume",
  188. layer = layer,
  189. hideFlags = HideFlags.HideAndDontSave
  190. };
  191. var volume = gameObject.AddComponent<PostProcessVolume>();
  192. volume.priority = priority;
  193. volume.isGlobal = true;
  194. var profile = volume.profile;
  195. foreach (var s in settings)
  196. {
  197. Assert.IsNotNull(s, "Trying to create a volume with null effects");
  198. profile.AddSettings(s);
  199. }
  200. return volume;
  201. }
  202. internal void SetLayerDirty(int layer)
  203. {
  204. Assert.IsTrue(layer >= 0 && layer <= k_MaxLayerCount, "Invalid layer bit");
  205. foreach (var kvp in m_SortedVolumes)
  206. {
  207. var mask = kvp.Key;
  208. if ((mask & (1 << layer)) != 0)
  209. m_SortNeeded[mask] = true;
  210. }
  211. }
  212. internal void UpdateVolumeLayer(PostProcessVolume volume, int prevLayer, int newLayer)
  213. {
  214. Assert.IsTrue(prevLayer >= 0 && prevLayer <= k_MaxLayerCount, "Invalid layer bit");
  215. Unregister(volume, prevLayer);
  216. Register(volume, newLayer);
  217. }
  218. void Register(PostProcessVolume volume, int layer)
  219. {
  220. m_Volumes.Add(volume);
  221. // Look for existing cached layer masks and add it there if needed
  222. foreach (var kvp in m_SortedVolumes)
  223. {
  224. var mask = kvp.Key;
  225. if ((mask & (1 << layer)) != 0)
  226. kvp.Value.Add(volume);
  227. }
  228. SetLayerDirty(layer);
  229. }
  230. internal void Register(PostProcessVolume volume)
  231. {
  232. int layer = volume.gameObject.layer;
  233. Register(volume, layer);
  234. }
  235. void Unregister(PostProcessVolume volume, int layer)
  236. {
  237. m_Volumes.Remove(volume);
  238. foreach (var kvp in m_SortedVolumes)
  239. {
  240. var mask = kvp.Key;
  241. // Skip layer masks this volume doesn't belong to
  242. if ((mask & (1 << layer)) == 0)
  243. continue;
  244. kvp.Value.Remove(volume);
  245. }
  246. }
  247. internal void Unregister(PostProcessVolume volume)
  248. {
  249. int layer = volume.gameObject.layer;
  250. Unregister(volume, layer);
  251. }
  252. // Faster version of OverrideSettings to force replace values in the global state
  253. void ReplaceData(PostProcessLayer postProcessLayer)
  254. {
  255. foreach (var settings in m_BaseSettings)
  256. {
  257. var target = postProcessLayer.GetBundle(settings.GetType()).settings;
  258. int count = settings.parameters.Count;
  259. for (int i = 0; i < count; i++)
  260. target.parameters[i].SetValue(settings.parameters[i]);
  261. }
  262. }
  263. internal void UpdateSettings(PostProcessLayer postProcessLayer, Camera camera)
  264. {
  265. // Reset to base state
  266. ReplaceData(postProcessLayer);
  267. // If no trigger is set, only global volumes will have influence
  268. int mask = postProcessLayer.volumeLayer.value;
  269. var volumeTrigger = postProcessLayer.volumeTrigger;
  270. bool onlyGlobal = volumeTrigger == null;
  271. var triggerPos = onlyGlobal ? Vector3.zero : volumeTrigger.position;
  272. // Sort the cached volume list(s) for the given layer mask if needed and return it
  273. var volumes = GrabVolumes(mask);
  274. // Traverse all volumes
  275. foreach (var volume in volumes)
  276. {
  277. #if UNITY_EDITOR
  278. // Skip volumes that aren't in the scene currently displayed in the scene view
  279. if (!IsVolumeRenderedByCamera(volume, camera))
  280. continue;
  281. #endif
  282. // Skip disabled volumes and volumes without any data or weight
  283. if (!volume.enabled || volume.profileRef == null || volume.weight <= 0f)
  284. continue;
  285. var settings = volume.profileRef.settings;
  286. // Global volume always have influence
  287. if (volume.isGlobal)
  288. {
  289. postProcessLayer.OverrideSettings(settings, Mathf.Clamp01(volume.weight));
  290. continue;
  291. }
  292. if (onlyGlobal)
  293. continue;
  294. // If volume isn't global and has no collider, skip it as it's useless
  295. var colliders = m_TempColliders;
  296. volume.GetComponents(colliders);
  297. if (colliders.Count == 0)
  298. continue;
  299. // Find closest distance to volume, 0 means it's inside it
  300. float closestDistanceSqr = float.PositiveInfinity;
  301. foreach (var collider in colliders)
  302. {
  303. if (!collider.enabled)
  304. continue;
  305. var closestPoint = collider.ClosestPoint(triggerPos); // 5.6-only API
  306. var d = ((closestPoint - triggerPos) / 2f).sqrMagnitude;
  307. if (d < closestDistanceSqr)
  308. closestDistanceSqr = d;
  309. }
  310. colliders.Clear();
  311. float blendDistSqr = volume.blendDistance * volume.blendDistance;
  312. // Volume has no influence, ignore it
  313. // Note: Volume doesn't do anything when `closestDistanceSqr = blendDistSqr` but
  314. // we can't use a >= comparison as blendDistSqr could be set to 0 in which
  315. // case volume would have total influence
  316. if (closestDistanceSqr > blendDistSqr)
  317. continue;
  318. // Volume has influence
  319. float interpFactor = 1f;
  320. if (blendDistSqr > 0f)
  321. interpFactor = 1f - (closestDistanceSqr / blendDistSqr);
  322. // No need to clamp01 the interpolation factor as it'll always be in [0;1[ range
  323. postProcessLayer.OverrideSettings(settings, interpFactor * Mathf.Clamp01(volume.weight));
  324. }
  325. }
  326. List<PostProcessVolume> GrabVolumes(LayerMask mask)
  327. {
  328. List<PostProcessVolume> list;
  329. if (!m_SortedVolumes.TryGetValue(mask, out list))
  330. {
  331. // New layer mask detected, create a new list and cache all the volumes that belong
  332. // to this mask in it
  333. list = new List<PostProcessVolume>();
  334. foreach (var volume in m_Volumes)
  335. {
  336. if ((mask & (1 << volume.gameObject.layer)) == 0)
  337. continue;
  338. list.Add(volume);
  339. m_SortNeeded[mask] = true;
  340. }
  341. m_SortedVolumes.Add(mask, list);
  342. }
  343. // Check sorting state
  344. bool sortNeeded;
  345. if (m_SortNeeded.TryGetValue(mask, out sortNeeded) && sortNeeded)
  346. {
  347. m_SortNeeded[mask] = false;
  348. SortByPriority(list);
  349. }
  350. return list;
  351. }
  352. // Custom insertion sort. First sort will be slower but after that it'll be faster than
  353. // using List<T>.Sort() which is also unstable by nature.
  354. // Sort order is ascending.
  355. static void SortByPriority(List<PostProcessVolume> volumes)
  356. {
  357. Assert.IsNotNull(volumes, "Trying to sort volumes of non-initialized layer");
  358. for (int i = 1; i < volumes.Count; i++)
  359. {
  360. var temp = volumes[i];
  361. int j = i - 1;
  362. while (j >= 0 && volumes[j].priority > temp.priority)
  363. {
  364. volumes[j + 1] = volumes[j];
  365. j--;
  366. }
  367. volumes[j + 1] = temp;
  368. }
  369. }
  370. static bool IsVolumeRenderedByCamera(PostProcessVolume volume, Camera camera)
  371. {
  372. #if UNITY_2018_3_OR_NEWER && UNITY_EDITOR
  373. return UnityEditor.SceneManagement.StageUtility.IsGameObjectRenderedByCamera(volume.gameObject, camera);
  374. #else
  375. return true;
  376. #endif
  377. }
  378. }
  379. }