experiments with fresnel and shadergraph
[WindSway-HDRP.git] / Library / PackageCache / com.unity.postprocessing@2.1.6 / PostProcessing / Runtime / PostProcessManager.cs
blob251818b662f27797db41d6fe9c2af4f44917bbdb
1 using System;
2 using System.Collections.Generic;
3 using System.Linq;
4 using UnityEngine.Assertions;
6 namespace UnityEngine.Rendering.PostProcessing
8 /// <summary>
9 /// This manager tracks all volumes in the scene and does all the interpolation work. It is
10 /// automatically created as soon as Post-processing is active in a scene.
11 /// </summary>
12 public sealed class PostProcessManager
14 static PostProcessManager s_Instance;
16 /// <summary>
17 /// The current singleton instance of <see cref="PostProcessManager"/>.
18 /// </summary>
19 public static PostProcessManager instance
21 get
23 if (s_Instance == null)
24 s_Instance = new PostProcessManager();
26 return s_Instance;
30 const int k_MaxLayerCount = 32; // Max amount of layers available in Unity
31 readonly Dictionary<int, List<PostProcessVolume>> m_SortedVolumes;
32 readonly List<PostProcessVolume> m_Volumes;
33 readonly Dictionary<int, bool> m_SortNeeded;
34 readonly List<PostProcessEffectSettings> m_BaseSettings;
35 readonly List<Collider> m_TempColliders;
37 /// <summary>
38 /// This dictionary maps all <see cref="PostProcessEffectSettings"/> available to their
39 /// corresponding <see cref="PostProcessAttribute"/>. It can be used to list all loaded
40 /// builtin and custom effects.
41 /// </summary>
42 public readonly Dictionary<Type, PostProcessAttribute> settingsTypes;
44 PostProcessManager()
46 m_SortedVolumes = new Dictionary<int, List<PostProcessVolume>>();
47 m_Volumes = new List<PostProcessVolume>();
48 m_SortNeeded = new Dictionary<int, bool>();
49 m_BaseSettings = new List<PostProcessEffectSettings>();
50 m_TempColliders = new List<Collider>(5);
52 settingsTypes = new Dictionary<Type, PostProcessAttribute>();
53 ReloadBaseTypes();
56 #if UNITY_EDITOR
57 // Called every time Unity recompile scripts in the editor. We need this to keep track of
58 // any new custom effect the user might add to the project
59 [UnityEditor.Callbacks.DidReloadScripts]
60 static void OnEditorReload()
62 instance.ReloadBaseTypes();
64 #endif
66 void CleanBaseTypes()
68 settingsTypes.Clear();
70 foreach (var settings in m_BaseSettings)
71 RuntimeUtilities.Destroy(settings);
73 m_BaseSettings.Clear();
76 // This will be called only once at runtime and everytime script reload kicks-in in the
77 // editor as we need to keep track of any compatible post-processing effects in the project
78 void ReloadBaseTypes()
80 CleanBaseTypes();
82 // Rebuild the base type map
83 var types = RuntimeUtilities.GetAllAssemblyTypes()
84 .Where(
85 t => t.IsSubclassOf(typeof(PostProcessEffectSettings))
86 && t.IsDefined(typeof(PostProcessAttribute), false)
87 && !t.IsAbstract
90 foreach (var type in types)
92 settingsTypes.Add(type, type.GetAttribute<PostProcessAttribute>());
94 // Create an instance for each effect type, these will be used for the lowest
95 // priority global volume as we need a default state when exiting volume ranges
96 var inst = (PostProcessEffectSettings)ScriptableObject.CreateInstance(type);
97 inst.SetAllOverridesTo(true, false);
98 m_BaseSettings.Add(inst);
102 /// <summary>
103 /// Gets a list of all volumes currently affecting the given layer. Results aren't sorted
104 /// and the list isn't cleared.
105 /// </summary>
106 /// <param name="layer">The layer to look for</param>
107 /// <param name="results">A list to store the volumes found</param>
108 /// <param name="skipDisabled">Should we skip disabled volumes?</param>
109 /// <param name="skipZeroWeight">Should we skip 0-weight volumes?</param>
110 public void GetActiveVolumes(PostProcessLayer layer, List<PostProcessVolume> results, bool skipDisabled = true, bool skipZeroWeight = true)
112 // If no trigger is set, only global volumes will have influence
113 int mask = layer.volumeLayer.value;
114 var volumeTrigger = layer.volumeTrigger;
115 bool onlyGlobal = volumeTrigger == null;
116 var triggerPos = onlyGlobal ? Vector3.zero : volumeTrigger.position;
118 // Sort the cached volume list(s) for the given layer mask if needed and return it
119 var volumes = GrabVolumes(mask);
121 // Traverse all volumes
122 foreach (var volume in volumes)
124 // Skip disabled volumes and volumes without any data or weight
125 if ((skipDisabled && !volume.enabled) || volume.profileRef == null || (skipZeroWeight && volume.weight <= 0f))
126 continue;
128 // Global volume always have influence
129 if (volume.isGlobal)
131 results.Add(volume);
132 continue;
135 if (onlyGlobal)
136 continue;
138 // If volume isn't global and has no collider, skip it as it's useless
139 var colliders = m_TempColliders;
140 volume.GetComponents(colliders);
141 if (colliders.Count == 0)
142 continue;
144 // Find closest distance to volume, 0 means it's inside it
145 float closestDistanceSqr = float.PositiveInfinity;
147 foreach (var collider in colliders)
149 if (!collider.enabled)
150 continue;
152 var closestPoint = collider.ClosestPoint(triggerPos); // 5.6-only API
153 var d = ((closestPoint - triggerPos) / 2f).sqrMagnitude;
155 if (d < closestDistanceSqr)
156 closestDistanceSqr = d;
159 colliders.Clear();
160 float blendDistSqr = volume.blendDistance * volume.blendDistance;
162 // Check for influence
163 if (closestDistanceSqr <= blendDistSqr)
164 results.Add(volume);
168 /// <summary>
169 /// Gets the highest priority volume affecting a given layer.
170 /// </summary>
171 /// <param name="layer">The layer to look for</param>
172 /// <returns>The highest priority volume affecting the layer</returns>
173 public PostProcessVolume GetHighestPriorityVolume(PostProcessLayer layer)
175 if (layer == null)
176 throw new ArgumentNullException("layer");
178 return GetHighestPriorityVolume(layer.volumeLayer);
181 /// <summary>
182 /// Gets the highest priority volume affecting <see cref="PostProcessLayer"/> in a given
183 /// <see cref="LayerMask"/>.
184 /// </summary>
185 /// <param name="mask">The layer mask to look for</param>
186 /// <returns>The highest priority volume affecting the layer mask</returns>
187 /// <seealso cref="PostProcessLayer.volumeLayer"/>
188 public PostProcessVolume GetHighestPriorityVolume(LayerMask mask)
190 float highestPriority = float.NegativeInfinity;
191 PostProcessVolume output = null;
193 List<PostProcessVolume> volumes;
194 if (m_SortedVolumes.TryGetValue(mask, out volumes))
196 foreach (var volume in volumes)
198 if (volume.priority > highestPriority)
200 highestPriority = volume.priority;
201 output = volume;
206 return output;
209 /// <summary>
210 /// Helper method to spawn a new volume in the scene.
211 /// </summary>
212 /// <param name="layer">The unity layer to put the volume in</param>
213 /// <param name="priority">The priority to set this volume to</param>
214 /// <param name="settings">A list of effects to put in this volume</param>
215 /// <returns></returns>
216 public PostProcessVolume QuickVolume(int layer, float priority, params PostProcessEffectSettings[] settings)
218 var gameObject = new GameObject()
220 name = "Quick Volume",
221 layer = layer,
222 hideFlags = HideFlags.HideAndDontSave
225 var volume = gameObject.AddComponent<PostProcessVolume>();
226 volume.priority = priority;
227 volume.isGlobal = true;
228 var profile = volume.profile;
230 foreach (var s in settings)
232 Assert.IsNotNull(s, "Trying to create a volume with null effects");
233 profile.AddSettings(s);
236 return volume;
239 internal void SetLayerDirty(int layer)
241 Assert.IsTrue(layer >= 0 && layer <= k_MaxLayerCount, "Invalid layer bit");
243 foreach (var kvp in m_SortedVolumes)
245 var mask = kvp.Key;
247 if ((mask & (1 << layer)) != 0)
248 m_SortNeeded[mask] = true;
252 internal void UpdateVolumeLayer(PostProcessVolume volume, int prevLayer, int newLayer)
254 Assert.IsTrue(prevLayer >= 0 && prevLayer <= k_MaxLayerCount, "Invalid layer bit");
255 Unregister(volume, prevLayer);
256 Register(volume, newLayer);
259 void Register(PostProcessVolume volume, int layer)
261 m_Volumes.Add(volume);
263 // Look for existing cached layer masks and add it there if needed
264 foreach (var kvp in m_SortedVolumes)
266 var mask = kvp.Key;
268 if ((mask & (1 << layer)) != 0)
269 kvp.Value.Add(volume);
272 SetLayerDirty(layer);
275 internal void Register(PostProcessVolume volume)
277 int layer = volume.gameObject.layer;
278 Register(volume, layer);
281 void Unregister(PostProcessVolume volume, int layer)
283 m_Volumes.Remove(volume);
285 foreach (var kvp in m_SortedVolumes)
287 var mask = kvp.Key;
289 // Skip layer masks this volume doesn't belong to
290 if ((mask & (1 << layer)) == 0)
291 continue;
293 kvp.Value.Remove(volume);
297 internal void Unregister(PostProcessVolume volume)
299 int layer = volume.gameObject.layer;
300 Unregister(volume, layer);
303 // Faster version of OverrideSettings to force replace values in the global state
304 void ReplaceData(PostProcessLayer postProcessLayer)
306 foreach (var settings in m_BaseSettings)
308 var target = postProcessLayer.GetBundle(settings.GetType()).settings;
309 int count = settings.parameters.Count;
311 for (int i = 0; i < count; i++)
312 target.parameters[i].SetValue(settings.parameters[i]);
316 internal void UpdateSettings(PostProcessLayer postProcessLayer, Camera camera)
318 // Reset to base state
319 ReplaceData(postProcessLayer);
321 // If no trigger is set, only global volumes will have influence
322 int mask = postProcessLayer.volumeLayer.value;
323 var volumeTrigger = postProcessLayer.volumeTrigger;
324 bool onlyGlobal = volumeTrigger == null;
325 var triggerPos = onlyGlobal ? Vector3.zero : volumeTrigger.position;
327 // Sort the cached volume list(s) for the given layer mask if needed and return it
328 var volumes = GrabVolumes(mask);
330 // Traverse all volumes
331 foreach (var volume in volumes)
333 #if UNITY_EDITOR
334 // Skip volumes that aren't in the scene currently displayed in the scene view
335 if (!IsVolumeRenderedByCamera(volume, camera))
336 continue;
337 #endif
339 // Skip disabled volumes and volumes without any data or weight
340 if (!volume.enabled || volume.profileRef == null || volume.weight <= 0f)
341 continue;
343 var settings = volume.profileRef.settings;
345 // Global volume always have influence
346 if (volume.isGlobal)
348 postProcessLayer.OverrideSettings(settings, Mathf.Clamp01(volume.weight));
349 continue;
352 if (onlyGlobal)
353 continue;
355 // If volume isn't global and has no collider, skip it as it's useless
356 var colliders = m_TempColliders;
357 volume.GetComponents(colliders);
358 if (colliders.Count == 0)
359 continue;
361 // Find closest distance to volume, 0 means it's inside it
362 float closestDistanceSqr = float.PositiveInfinity;
364 foreach (var collider in colliders)
366 if (!collider.enabled)
367 continue;
369 var closestPoint = collider.ClosestPoint(triggerPos); // 5.6-only API
370 var d = ((closestPoint - triggerPos) / 2f).sqrMagnitude;
372 if (d < closestDistanceSqr)
373 closestDistanceSqr = d;
376 colliders.Clear();
377 float blendDistSqr = volume.blendDistance * volume.blendDistance;
379 // Volume has no influence, ignore it
380 // Note: Volume doesn't do anything when `closestDistanceSqr = blendDistSqr` but
381 // we can't use a >= comparison as blendDistSqr could be set to 0 in which
382 // case volume would have total influence
383 if (closestDistanceSqr > blendDistSqr)
384 continue;
386 // Volume has influence
387 float interpFactor = 1f;
389 if (blendDistSqr > 0f)
390 interpFactor = 1f - (closestDistanceSqr / blendDistSqr);
392 // No need to clamp01 the interpolation factor as it'll always be in [0;1[ range
393 postProcessLayer.OverrideSettings(settings, interpFactor * Mathf.Clamp01(volume.weight));
397 List<PostProcessVolume> GrabVolumes(LayerMask mask)
399 List<PostProcessVolume> list;
401 if (!m_SortedVolumes.TryGetValue(mask, out list))
403 // New layer mask detected, create a new list and cache all the volumes that belong
404 // to this mask in it
405 list = new List<PostProcessVolume>();
407 foreach (var volume in m_Volumes)
409 if ((mask & (1 << volume.gameObject.layer)) == 0)
410 continue;
412 list.Add(volume);
413 m_SortNeeded[mask] = true;
416 m_SortedVolumes.Add(mask, list);
419 // Check sorting state
420 bool sortNeeded;
421 if (m_SortNeeded.TryGetValue(mask, out sortNeeded) && sortNeeded)
423 m_SortNeeded[mask] = false;
424 SortByPriority(list);
427 return list;
430 // Custom insertion sort. First sort will be slower but after that it'll be faster than
431 // using List<T>.Sort() which is also unstable by nature.
432 // Sort order is ascending.
433 static void SortByPriority(List<PostProcessVolume> volumes)
435 Assert.IsNotNull(volumes, "Trying to sort volumes of non-initialized layer");
437 for (int i = 1; i < volumes.Count; i++)
439 var temp = volumes[i];
440 int j = i - 1;
442 while (j >= 0 && volumes[j].priority > temp.priority)
444 volumes[j + 1] = volumes[j];
445 j--;
448 volumes[j + 1] = temp;
452 static bool IsVolumeRenderedByCamera(PostProcessVolume volume, Camera camera)
454 #if UNITY_2018_3_OR_NEWER && UNITY_EDITOR
455 return UnityEditor.SceneManagement.StageUtility.IsGameObjectRenderedByCamera(volume.gameObject, camera);
456 #else
457 return true;
458 #endif