| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461 |
- using System;
- using System.Collections.Generic;
- using System.Linq;
- using UnityEngine.Assertions;
- namespace UnityEngine.Rendering.PostProcessing
- {
- /// <summary>
- /// This manager tracks all volumes in the scene and does all the interpolation work. It is
- /// automatically created as soon as Post-processing is active in a scene.
- /// </summary>
- public sealed class PostProcessManager
- {
- static PostProcessManager s_Instance;
- /// <summary>
- /// The current singleton instance of <see cref="PostProcessManager"/>.
- /// </summary>
- public static PostProcessManager instance
- {
- get
- {
- if (s_Instance == null)
- s_Instance = new PostProcessManager();
- return s_Instance;
- }
- }
- const int k_MaxLayerCount = 32; // Max amount of layers available in Unity
- readonly Dictionary<int, List<PostProcessVolume>> m_SortedVolumes;
- readonly List<PostProcessVolume> m_Volumes;
- readonly Dictionary<int, bool> m_SortNeeded;
- readonly List<PostProcessEffectSettings> m_BaseSettings;
- readonly List<Collider> m_TempColliders;
- /// <summary>
- /// This dictionary maps all <see cref="PostProcessEffectSettings"/> available to their
- /// corresponding <see cref="PostProcessAttribute"/>. It can be used to list all loaded
- /// builtin and custom effects.
- /// </summary>
- public readonly Dictionary<Type, PostProcessAttribute> settingsTypes;
- PostProcessManager()
- {
- m_SortedVolumes = new Dictionary<int, List<PostProcessVolume>>();
- m_Volumes = new List<PostProcessVolume>();
- m_SortNeeded = new Dictionary<int, bool>();
- m_BaseSettings = new List<PostProcessEffectSettings>();
- m_TempColliders = new List<Collider>(5);
- settingsTypes = new Dictionary<Type, PostProcessAttribute>();
- ReloadBaseTypes();
- }
- #if UNITY_EDITOR
- // Called every time Unity recompile scripts in the editor. We need this to keep track of
- // any new custom effect the user might add to the project
- [UnityEditor.Callbacks.DidReloadScripts]
- static void OnEditorReload()
- {
- instance.ReloadBaseTypes();
- }
- #endif
- void CleanBaseTypes()
- {
- settingsTypes.Clear();
- foreach (var settings in m_BaseSettings)
- RuntimeUtilities.Destroy(settings);
- m_BaseSettings.Clear();
- }
- // This will be called only once at runtime and everytime script reload kicks-in in the
- // editor as we need to keep track of any compatible post-processing effects in the project
- void ReloadBaseTypes()
- {
- CleanBaseTypes();
- // Rebuild the base type map
- var types = RuntimeUtilities.GetAllAssemblyTypes()
- .Where(
- t => t.IsSubclassOf(typeof(PostProcessEffectSettings))
- && t.IsDefined(typeof(PostProcessAttribute), false)
- && !t.IsAbstract
- );
- foreach (var type in types)
- {
- settingsTypes.Add(type, type.GetAttribute<PostProcessAttribute>());
- // Create an instance for each effect type, these will be used for the lowest
- // priority global volume as we need a default state when exiting volume ranges
- var inst = (PostProcessEffectSettings)ScriptableObject.CreateInstance(type);
- inst.SetAllOverridesTo(true, false);
- m_BaseSettings.Add(inst);
- }
- }
- /// <summary>
- /// Gets a list of all volumes currently affecting the given layer. Results aren't sorted
- /// and the list isn't cleared.
- /// </summary>
- /// <param name="layer">The layer to look for</param>
- /// <param name="results">A list to store the volumes found</param>
- /// <param name="skipDisabled">Should we skip disabled volumes?</param>
- /// <param name="skipZeroWeight">Should we skip 0-weight volumes?</param>
- public void GetActiveVolumes(PostProcessLayer layer, List<PostProcessVolume> results, bool skipDisabled = true, bool skipZeroWeight = true)
- {
- // If no trigger is set, only global volumes will have influence
- int mask = layer.volumeLayer.value;
- var volumeTrigger = layer.volumeTrigger;
- bool onlyGlobal = volumeTrigger == null;
- var triggerPos = onlyGlobal ? Vector3.zero : volumeTrigger.position;
- // Sort the cached volume list(s) for the given layer mask if needed and return it
- var volumes = GrabVolumes(mask);
- // Traverse all volumes
- foreach (var volume in volumes)
- {
- // Skip disabled volumes and volumes without any data or weight
- if ((skipDisabled && !volume.enabled) || volume.profileRef == null || (skipZeroWeight && volume.weight <= 0f))
- continue;
- // Global volume always have influence
- if (volume.isGlobal)
- {
- results.Add(volume);
- continue;
- }
- if (onlyGlobal)
- continue;
- // If volume isn't global and has no collider, skip it as it's useless
- var colliders = m_TempColliders;
- volume.GetComponents(colliders);
- if (colliders.Count == 0)
- continue;
- // Find closest distance to volume, 0 means it's inside it
- float closestDistanceSqr = float.PositiveInfinity;
- foreach (var collider in colliders)
- {
- if (!collider.enabled)
- continue;
- var closestPoint = collider.ClosestPoint(triggerPos); // 5.6-only API
- var d = ((closestPoint - triggerPos) / 2f).sqrMagnitude;
- if (d < closestDistanceSqr)
- closestDistanceSqr = d;
- }
- colliders.Clear();
- float blendDistSqr = volume.blendDistance * volume.blendDistance;
- // Check for influence
- if (closestDistanceSqr <= blendDistSqr)
- results.Add(volume);
- }
- }
- /// <summary>
- /// Gets the highest priority volume affecting a given layer.
- /// </summary>
- /// <param name="layer">The layer to look for</param>
- /// <returns>The highest priority volume affecting the layer</returns>
- public PostProcessVolume GetHighestPriorityVolume(PostProcessLayer layer)
- {
- if (layer == null)
- throw new ArgumentNullException("layer");
- return GetHighestPriorityVolume(layer.volumeLayer);
- }
- /// <summary>
- /// Gets the highest priority volume affecting <see cref="PostProcessLayer"/> in a given
- /// <see cref="LayerMask"/>.
- /// </summary>
- /// <param name="mask">The layer mask to look for</param>
- /// <returns>The highest priority volume affecting the layer mask</returns>
- /// <seealso cref="PostProcessLayer.volumeLayer"/>
- public PostProcessVolume GetHighestPriorityVolume(LayerMask mask)
- {
- float highestPriority = float.NegativeInfinity;
- PostProcessVolume output = null;
- List<PostProcessVolume> volumes;
- if (m_SortedVolumes.TryGetValue(mask, out volumes))
- {
- foreach (var volume in volumes)
- {
- if (volume.priority > highestPriority)
- {
- highestPriority = volume.priority;
- output = volume;
- }
- }
- }
- return output;
- }
- /// <summary>
- /// Helper method to spawn a new volume in the scene.
- /// </summary>
- /// <param name="layer">The unity layer to put the volume in</param>
- /// <param name="priority">The priority to set this volume to</param>
- /// <param name="settings">A list of effects to put in this volume</param>
- /// <returns></returns>
- public PostProcessVolume QuickVolume(int layer, float priority, params PostProcessEffectSettings[] settings)
- {
- var gameObject = new GameObject()
- {
- name = "Quick Volume",
- layer = layer,
- hideFlags = HideFlags.HideAndDontSave
- };
- var volume = gameObject.AddComponent<PostProcessVolume>();
- volume.priority = priority;
- volume.isGlobal = true;
- var profile = volume.profile;
- foreach (var s in settings)
- {
- Assert.IsNotNull(s, "Trying to create a volume with null effects");
- profile.AddSettings(s);
- }
- return volume;
- }
- internal void SetLayerDirty(int layer)
- {
- Assert.IsTrue(layer >= 0 && layer <= k_MaxLayerCount, "Invalid layer bit");
- foreach (var kvp in m_SortedVolumes)
- {
- var mask = kvp.Key;
- if ((mask & (1 << layer)) != 0)
- m_SortNeeded[mask] = true;
- }
- }
- internal void UpdateVolumeLayer(PostProcessVolume volume, int prevLayer, int newLayer)
- {
- Assert.IsTrue(prevLayer >= 0 && prevLayer <= k_MaxLayerCount, "Invalid layer bit");
- Unregister(volume, prevLayer);
- Register(volume, newLayer);
- }
- void Register(PostProcessVolume volume, int layer)
- {
- m_Volumes.Add(volume);
- // Look for existing cached layer masks and add it there if needed
- foreach (var kvp in m_SortedVolumes)
- {
- var mask = kvp.Key;
- if ((mask & (1 << layer)) != 0)
- kvp.Value.Add(volume);
- }
- SetLayerDirty(layer);
- }
- internal void Register(PostProcessVolume volume)
- {
- int layer = volume.gameObject.layer;
- Register(volume, layer);
- }
- void Unregister(PostProcessVolume volume, int layer)
- {
- m_Volumes.Remove(volume);
- foreach (var kvp in m_SortedVolumes)
- {
- var mask = kvp.Key;
- // Skip layer masks this volume doesn't belong to
- if ((mask & (1 << layer)) == 0)
- continue;
- kvp.Value.Remove(volume);
- }
- }
- internal void Unregister(PostProcessVolume volume)
- {
- int layer = volume.gameObject.layer;
- Unregister(volume, layer);
- }
- // Faster version of OverrideSettings to force replace values in the global state
- void ReplaceData(PostProcessLayer postProcessLayer)
- {
- foreach (var settings in m_BaseSettings)
- {
- var target = postProcessLayer.GetBundle(settings.GetType()).settings;
- int count = settings.parameters.Count;
- for (int i = 0; i < count; i++)
- target.parameters[i].SetValue(settings.parameters[i]);
- }
- }
- internal void UpdateSettings(PostProcessLayer postProcessLayer, Camera camera)
- {
- // Reset to base state
- ReplaceData(postProcessLayer);
- // If no trigger is set, only global volumes will have influence
- int mask = postProcessLayer.volumeLayer.value;
- var volumeTrigger = postProcessLayer.volumeTrigger;
- bool onlyGlobal = volumeTrigger == null;
- var triggerPos = onlyGlobal ? Vector3.zero : volumeTrigger.position;
- // Sort the cached volume list(s) for the given layer mask if needed and return it
- var volumes = GrabVolumes(mask);
- // Traverse all volumes
- foreach (var volume in volumes)
- {
- #if UNITY_EDITOR
- // Skip volumes that aren't in the scene currently displayed in the scene view
- if (!IsVolumeRenderedByCamera(volume, camera))
- continue;
- #endif
- // Skip disabled volumes and volumes without any data or weight
- if (!volume.enabled || volume.profileRef == null || volume.weight <= 0f)
- continue;
- var settings = volume.profileRef.settings;
- // Global volume always have influence
- if (volume.isGlobal)
- {
- postProcessLayer.OverrideSettings(settings, Mathf.Clamp01(volume.weight));
- continue;
- }
- if (onlyGlobal)
- continue;
- // If volume isn't global and has no collider, skip it as it's useless
- var colliders = m_TempColliders;
- volume.GetComponents(colliders);
- if (colliders.Count == 0)
- continue;
- // Find closest distance to volume, 0 means it's inside it
- float closestDistanceSqr = float.PositiveInfinity;
- foreach (var collider in colliders)
- {
- if (!collider.enabled)
- continue;
- var closestPoint = collider.ClosestPoint(triggerPos); // 5.6-only API
- var d = ((closestPoint - triggerPos) / 2f).sqrMagnitude;
- if (d < closestDistanceSqr)
- closestDistanceSqr = d;
- }
- colliders.Clear();
- float blendDistSqr = volume.blendDistance * volume.blendDistance;
- // Volume has no influence, ignore it
- // Note: Volume doesn't do anything when `closestDistanceSqr = blendDistSqr` but
- // we can't use a >= comparison as blendDistSqr could be set to 0 in which
- // case volume would have total influence
- if (closestDistanceSqr > blendDistSqr)
- continue;
- // Volume has influence
- float interpFactor = 1f;
- if (blendDistSqr > 0f)
- interpFactor = 1f - (closestDistanceSqr / blendDistSqr);
- // No need to clamp01 the interpolation factor as it'll always be in [0;1[ range
- postProcessLayer.OverrideSettings(settings, interpFactor * Mathf.Clamp01(volume.weight));
- }
- }
- List<PostProcessVolume> GrabVolumes(LayerMask mask)
- {
- List<PostProcessVolume> list;
- if (!m_SortedVolumes.TryGetValue(mask, out list))
- {
- // New layer mask detected, create a new list and cache all the volumes that belong
- // to this mask in it
- list = new List<PostProcessVolume>();
- foreach (var volume in m_Volumes)
- {
- if ((mask & (1 << volume.gameObject.layer)) == 0)
- continue;
- list.Add(volume);
- m_SortNeeded[mask] = true;
- }
- m_SortedVolumes.Add(mask, list);
- }
- // Check sorting state
- bool sortNeeded;
- if (m_SortNeeded.TryGetValue(mask, out sortNeeded) && sortNeeded)
- {
- m_SortNeeded[mask] = false;
- SortByPriority(list);
- }
- return list;
- }
- // Custom insertion sort. First sort will be slower but after that it'll be faster than
- // using List<T>.Sort() which is also unstable by nature.
- // Sort order is ascending.
- static void SortByPriority(List<PostProcessVolume> volumes)
- {
- Assert.IsNotNull(volumes, "Trying to sort volumes of non-initialized layer");
- for (int i = 1; i < volumes.Count; i++)
- {
- var temp = volumes[i];
- int j = i - 1;
- while (j >= 0 && volumes[j].priority > temp.priority)
- {
- volumes[j + 1] = volumes[j];
- j--;
- }
- volumes[j + 1] = temp;
- }
- }
- static bool IsVolumeRenderedByCamera(PostProcessVolume volume, Camera camera)
- {
- #if UNITY_2018_3_OR_NEWER && UNITY_EDITOR
- return UnityEditor.SceneManagement.StageUtility.IsGameObjectRenderedByCamera(volume.gameObject, camera);
- #else
- return true;
- #endif
- }
- }
- }
|