VolumeManager.cs 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Diagnostics;
  4. using System.Linq;
  5. using UnityEngine.Assertions;
  6. namespace UnityEngine.Rendering
  7. {
  8. using UnityObject = UnityEngine.Object;
  9. /// <summary>
  10. /// A global manager that tracks all the Volumes in the currently loaded Scenes and does all the
  11. /// interpolation work.
  12. /// </summary>
  13. public sealed class VolumeManager
  14. {
  15. internal static bool needIsolationFilteredByRenderer = false;
  16. static readonly Lazy<VolumeManager> s_Instance = new Lazy<VolumeManager>(() => new VolumeManager());
  17. /// <summary>
  18. /// The current singleton instance of <see cref="VolumeManager"/>.
  19. /// </summary>
  20. public static VolumeManager instance => s_Instance.Value;
  21. /// <summary>
  22. /// A reference to the main <see cref="VolumeStack"/>.
  23. /// </summary>
  24. /// <seealso cref="VolumeStack"/>
  25. public VolumeStack stack { get; private set; }
  26. /// <summary>
  27. /// The current list of all available types that derive from <see cref="VolumeComponent"/>.
  28. /// </summary>
  29. public IEnumerable<Type> baseComponentTypes { get; private set; }
  30. // Max amount of layers available in Unity
  31. const int k_MaxLayerCount = 32;
  32. // Cached lists of all volumes (sorted by priority) by layer mask
  33. readonly Dictionary<int, List<Volume>> m_SortedVolumes;
  34. // Holds all the registered volumes
  35. readonly List<Volume> m_Volumes;
  36. // Keep track of sorting states for layer masks
  37. readonly Dictionary<int, bool> m_SortNeeded;
  38. // Internal list of default state for each component type - this is used to reset component
  39. // states on update instead of having to implement a Reset method on all components (which
  40. // would be error-prone)
  41. readonly List<VolumeComponent> m_ComponentsDefaultState;
  42. // Recycled list used for volume traversal
  43. readonly List<Collider> m_TempColliders;
  44. VolumeManager()
  45. {
  46. m_SortedVolumes = new Dictionary<int, List<Volume>>();
  47. m_Volumes = new List<Volume>();
  48. m_SortNeeded = new Dictionary<int, bool>();
  49. m_TempColliders = new List<Collider>(8);
  50. m_ComponentsDefaultState = new List<VolumeComponent>();
  51. ReloadBaseTypes();
  52. stack = CreateStack();
  53. }
  54. /// <summary>
  55. /// Creates and returns a new <see cref="VolumeStack"/> to use when you need to store
  56. /// the result of the Volume blending pass in a separate stack.
  57. /// </summary>
  58. /// <returns></returns>
  59. /// <seealso cref="VolumeStack"/>
  60. /// <seealso cref="Update(VolumeStack,Transform,LayerMask)"/>
  61. public VolumeStack CreateStack()
  62. {
  63. var stack = new VolumeStack();
  64. stack.Reload(baseComponentTypes);
  65. return stack;
  66. }
  67. /// <summary>
  68. /// Destroy a Volume Stack
  69. /// </summary>
  70. /// <param name="stack">Volume Stack that needs to be destroyed.</param>
  71. public void DestroyStack(VolumeStack stack)
  72. {
  73. stack.Dispose();
  74. }
  75. // This will be called only once at runtime and everytime script reload kicks-in in the
  76. // editor as we need to keep track of any compatible component in the project
  77. void ReloadBaseTypes()
  78. {
  79. m_ComponentsDefaultState.Clear();
  80. // Grab all the component types we can find
  81. baseComponentTypes = CoreUtils.GetAllTypesDerivedFrom<VolumeComponent>()
  82. .Where(t => !t.IsAbstract);
  83. // Keep an instance of each type to be used in a virtual lowest priority global volume
  84. // so that we have a default state to fallback to when exiting volumes
  85. foreach (var type in baseComponentTypes)
  86. {
  87. var inst = (VolumeComponent)ScriptableObject.CreateInstance(type);
  88. m_ComponentsDefaultState.Add(inst);
  89. }
  90. }
  91. /// <summary>
  92. /// Registers a new Volume in the manager. Unity does this automatically when a new Volume is
  93. /// enabled, or its layer changes, but you can use this function to force-register a Volume
  94. /// that is currently disabled.
  95. /// </summary>
  96. /// <param name="volume">The volume to register.</param>
  97. /// <param name="layer">The LayerMask that this volume is in.</param>
  98. /// <seealso cref="Unregister"/>
  99. public void Register(Volume volume, int layer)
  100. {
  101. m_Volumes.Add(volume);
  102. // Look for existing cached layer masks and add it there if needed
  103. foreach (var kvp in m_SortedVolumes)
  104. {
  105. if ((kvp.Key & (1 << layer)) != 0)
  106. kvp.Value.Add(volume);
  107. }
  108. SetLayerDirty(layer);
  109. }
  110. /// <summary>
  111. /// Unregisters a Volume from the manager. Unity does this automatically when a Volume is
  112. /// disabled or goes out of scope, but you can use this function to force-unregister a Volume
  113. /// that you added manually while it was disabled.
  114. /// </summary>
  115. /// <param name="volume">The Volume to unregister.</param>
  116. /// <param name="layer">The LayerMask that this Volume is in.</param>
  117. /// <seealso cref="Register"/>
  118. public void Unregister(Volume volume, int layer)
  119. {
  120. m_Volumes.Remove(volume);
  121. foreach (var kvp in m_SortedVolumes)
  122. {
  123. // Skip layer masks this volume doesn't belong to
  124. if ((kvp.Key & (1 << layer)) == 0)
  125. continue;
  126. kvp.Value.Remove(volume);
  127. }
  128. }
  129. /// <summary>
  130. /// Checks if a <see cref="VolumeComponent"/> is active in a given LayerMask.
  131. /// </summary>
  132. /// <typeparam name="T">A type derived from <see cref="VolumeComponent"/></typeparam>
  133. /// <param name="layerMask">The LayerMask to check against</param>
  134. /// <returns><c>true</c> if the component is active in the LayerMask, <c>false</c>
  135. /// otherwise.</returns>
  136. public bool IsComponentActiveInMask<T>(LayerMask layerMask)
  137. where T : VolumeComponent
  138. {
  139. int mask = layerMask.value;
  140. foreach (var kvp in m_SortedVolumes)
  141. {
  142. if (kvp.Key != mask)
  143. continue;
  144. foreach (var volume in kvp.Value)
  145. {
  146. if (!volume.enabled || volume.profileRef == null)
  147. continue;
  148. if (volume.profileRef.TryGet(out T component) && component.active)
  149. return true;
  150. }
  151. }
  152. return false;
  153. }
  154. internal void SetLayerDirty(int layer)
  155. {
  156. Assert.IsTrue(layer >= 0 && layer <= k_MaxLayerCount, "Invalid layer bit");
  157. foreach (var kvp in m_SortedVolumes)
  158. {
  159. var mask = kvp.Key;
  160. if ((mask & (1 << layer)) != 0)
  161. m_SortNeeded[mask] = true;
  162. }
  163. }
  164. internal void UpdateVolumeLayer(Volume volume, int prevLayer, int newLayer)
  165. {
  166. Assert.IsTrue(prevLayer >= 0 && prevLayer <= k_MaxLayerCount, "Invalid layer bit");
  167. Unregister(volume, prevLayer);
  168. Register(volume, newLayer);
  169. }
  170. // Go through all listed components and lerp overridden values in the global state
  171. void OverrideData(VolumeStack stack, List<VolumeComponent> components, float interpFactor)
  172. {
  173. foreach (var component in components)
  174. {
  175. if (!component.active)
  176. continue;
  177. var state = stack.GetComponent(component.GetType());
  178. component.Override(state, interpFactor);
  179. }
  180. }
  181. // Faster version of OverrideData to force replace values in the global state
  182. void ReplaceData(VolumeStack stack, List<VolumeComponent> components)
  183. {
  184. foreach (var component in components)
  185. {
  186. var target = stack.GetComponent(component.GetType());
  187. int count = component.parameters.Count;
  188. for (int i = 0; i < count; i++)
  189. target.parameters[i].SetValue(component.parameters[i]);
  190. }
  191. }
  192. /// <summary>
  193. /// Checks the state of the base type library. This is only used in the editor to handle
  194. /// entering and exiting of play mode and domain reload.
  195. /// </summary>
  196. [Conditional("UNITY_EDITOR")]
  197. public void CheckBaseTypes()
  198. {
  199. // Editor specific hack to work around serialization doing funky things when exiting
  200. if (m_ComponentsDefaultState == null || (m_ComponentsDefaultState.Count > 0 && m_ComponentsDefaultState[0] == null))
  201. ReloadBaseTypes();
  202. }
  203. /// <summary>
  204. /// Checks the state of a given stack. This is only used in the editor to handle entering
  205. /// and exiting of play mode and domain reload.
  206. /// </summary>
  207. /// <param name="stack">The stack to check.</param>
  208. [Conditional("UNITY_EDITOR")]
  209. public void CheckStack(VolumeStack stack)
  210. {
  211. // The editor doesn't reload the domain when exiting play mode but still kills every
  212. // object created while in play mode, like stacks' component states
  213. var components = stack.components;
  214. if (components == null)
  215. {
  216. stack.Reload(baseComponentTypes);
  217. return;
  218. }
  219. foreach (var kvp in components)
  220. {
  221. if (kvp.Key == null || kvp.Value == null)
  222. {
  223. stack.Reload(baseComponentTypes);
  224. return;
  225. }
  226. }
  227. }
  228. /// <summary>
  229. /// Updates the global state of the Volume manager. Unity usually calls this once per Camera
  230. /// in the Update loop before rendering happens.
  231. /// </summary>
  232. /// <param name="trigger">A reference Transform to consider for positional Volume blending
  233. /// </param>
  234. /// <param name="layerMask">The LayerMask that the Volume manager uses to filter Volumes that it should consider
  235. /// for blending.</param>
  236. public void Update(Transform trigger, LayerMask layerMask)
  237. {
  238. Update(stack, trigger, layerMask);
  239. }
  240. /// <summary>
  241. /// Updates the Volume manager and stores the result in a custom <see cref="VolumeStack"/>.
  242. /// </summary>
  243. /// <param name="stack">The stack to store the blending result into.</param>
  244. /// <param name="trigger">A reference Transform to consider for positional Volume blending.
  245. /// </param>
  246. /// <param name="layerMask">The LayerMask that Unity uses to filter Volumes that it should consider
  247. /// for blending.</param>
  248. /// <seealso cref="VolumeStack"/>
  249. public void Update(VolumeStack stack, Transform trigger, LayerMask layerMask)
  250. {
  251. Assert.IsNotNull(stack);
  252. CheckBaseTypes();
  253. CheckStack(stack);
  254. // Start by resetting the global state to default values
  255. ReplaceData(stack, m_ComponentsDefaultState);
  256. bool onlyGlobal = trigger == null;
  257. var triggerPos = onlyGlobal ? Vector3.zero : trigger.position;
  258. // Sort the cached volume list(s) for the given layer mask if needed and return it
  259. var volumes = GrabVolumes(layerMask);
  260. Camera camera = null;
  261. // Behavior should be fine even if camera is null
  262. if (!onlyGlobal)
  263. trigger.TryGetComponent<Camera>(out camera);
  264. #if UNITY_EDITOR
  265. // requested or prefab isolation mode.
  266. bool needIsolation = needIsolationFilteredByRenderer || (UnityEditor.SceneManagement.StageUtility.GetCurrentStageHandle() != UnityEditor.SceneManagement.StageUtility.GetMainStageHandle());
  267. #endif
  268. // Traverse all volumes
  269. foreach (var volume in volumes)
  270. {
  271. #if UNITY_EDITOR
  272. // Skip volumes that aren't in the scene currently displayed in the scene view
  273. if (needIsolation
  274. && !IsVolumeRenderedByCamera(volume, camera))
  275. continue;
  276. #endif
  277. // Skip disabled volumes and volumes without any data or weight
  278. if (!volume.enabled || volume.profileRef == null || volume.weight <= 0f)
  279. continue;
  280. // Global volumes always have influence
  281. if (volume.isGlobal)
  282. {
  283. OverrideData(stack, volume.profileRef.components, Mathf.Clamp01(volume.weight));
  284. continue;
  285. }
  286. if (onlyGlobal)
  287. continue;
  288. // If volume isn't global and has no collider, skip it as it's useless
  289. var colliders = m_TempColliders;
  290. volume.GetComponents(colliders);
  291. if (colliders.Count == 0)
  292. continue;
  293. // Find closest distance to volume, 0 means it's inside it
  294. float closestDistanceSqr = float.PositiveInfinity;
  295. foreach (var collider in colliders)
  296. {
  297. if (!collider.enabled)
  298. continue;
  299. var closestPoint = collider.ClosestPoint(triggerPos);
  300. var d = (closestPoint - triggerPos).sqrMagnitude;
  301. if (d < closestDistanceSqr)
  302. closestDistanceSqr = d;
  303. }
  304. colliders.Clear();
  305. float blendDistSqr = volume.blendDistance * volume.blendDistance;
  306. // Volume has no influence, ignore it
  307. // Note: Volume doesn't do anything when `closestDistanceSqr = blendDistSqr` but we
  308. // can't use a >= comparison as blendDistSqr could be set to 0 in which case
  309. // volume would have total influence
  310. if (closestDistanceSqr > blendDistSqr)
  311. continue;
  312. // Volume has influence
  313. float interpFactor = 1f;
  314. if (blendDistSqr > 0f)
  315. interpFactor = 1f - (closestDistanceSqr / blendDistSqr);
  316. // No need to clamp01 the interpolation factor as it'll always be in [0;1[ range
  317. OverrideData(stack, volume.profileRef.components, interpFactor * Mathf.Clamp01(volume.weight));
  318. }
  319. }
  320. List<Volume> GrabVolumes(LayerMask mask)
  321. {
  322. List<Volume> list;
  323. if (!m_SortedVolumes.TryGetValue(mask, out list))
  324. {
  325. // New layer mask detected, create a new list and cache all the volumes that belong
  326. // to this mask in it
  327. list = new List<Volume>();
  328. foreach (var volume in m_Volumes)
  329. {
  330. if ((mask & (1 << volume.gameObject.layer)) == 0)
  331. continue;
  332. list.Add(volume);
  333. m_SortNeeded[mask] = true;
  334. }
  335. m_SortedVolumes.Add(mask, list);
  336. }
  337. // Check sorting state
  338. bool sortNeeded;
  339. if (m_SortNeeded.TryGetValue(mask, out sortNeeded) && sortNeeded)
  340. {
  341. m_SortNeeded[mask] = false;
  342. SortByPriority(list);
  343. }
  344. return list;
  345. }
  346. // Stable insertion sort. Faster than List<T>.Sort() for our needs.
  347. static void SortByPriority(List<Volume> volumes)
  348. {
  349. Assert.IsNotNull(volumes, "Trying to sort volumes of non-initialized layer");
  350. for (int i = 1; i < volumes.Count; i++)
  351. {
  352. var temp = volumes[i];
  353. int j = i - 1;
  354. // Sort order is ascending
  355. while (j >= 0 && volumes[j].priority > temp.priority)
  356. {
  357. volumes[j + 1] = volumes[j];
  358. j--;
  359. }
  360. volumes[j + 1] = temp;
  361. }
  362. }
  363. static bool IsVolumeRenderedByCamera(Volume volume, Camera camera)
  364. {
  365. #if UNITY_2018_3_OR_NEWER && UNITY_EDITOR
  366. // IsGameObjectRenderedByCamera does not behave correctly when camera is null so we have to catch it here.
  367. return camera == null ? true : UnityEditor.SceneManagement.StageUtility.IsGameObjectRenderedByCamera(volume.gameObject, camera);
  368. #else
  369. return true;
  370. #endif
  371. }
  372. }
  373. /// <summary>
  374. /// A scope in which a Camera filters a Volume.
  375. /// </summary>
  376. public struct VolumeIsolationScope : IDisposable
  377. {
  378. /// <summary>
  379. /// Constructs a scope in which a Camera filters a Volume.
  380. /// </summary>
  381. /// <param name="unused">Unused parameter.</param>
  382. public VolumeIsolationScope(bool unused)
  383. => VolumeManager.needIsolationFilteredByRenderer = true;
  384. /// <summary>
  385. /// Stops the Camera from filtering a Volume.
  386. /// </summary>
  387. void IDisposable.Dispose()
  388. => VolumeManager.needIsolationFilteredByRenderer = false;
  389. }
  390. }