123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469 |
- using System;
- using System.Collections.Generic;
- using System.Diagnostics;
- using System.Linq;
- using UnityEngine.Assertions;
- namespace UnityEngine.Rendering
- {
- using UnityObject = UnityEngine.Object;
- /// <summary>
- /// A global manager that tracks all the Volumes in the currently loaded Scenes and does all the
- /// interpolation work.
- /// </summary>
- public sealed class VolumeManager
- {
- internal static bool needIsolationFilteredByRenderer = false;
- static readonly Lazy<VolumeManager> s_Instance = new Lazy<VolumeManager>(() => new VolumeManager());
- /// <summary>
- /// The current singleton instance of <see cref="VolumeManager"/>.
- /// </summary>
- public static VolumeManager instance => s_Instance.Value;
- /// <summary>
- /// A reference to the main <see cref="VolumeStack"/>.
- /// </summary>
- /// <seealso cref="VolumeStack"/>
- public VolumeStack stack { get; private set; }
- /// <summary>
- /// The current list of all available types that derive from <see cref="VolumeComponent"/>.
- /// </summary>
- public IEnumerable<Type> baseComponentTypes { get; private set; }
- // Max amount of layers available in Unity
- const int k_MaxLayerCount = 32;
- // Cached lists of all volumes (sorted by priority) by layer mask
- readonly Dictionary<int, List<Volume>> m_SortedVolumes;
- // Holds all the registered volumes
- readonly List<Volume> m_Volumes;
- // Keep track of sorting states for layer masks
- readonly Dictionary<int, bool> m_SortNeeded;
- // Internal list of default state for each component type - this is used to reset component
- // states on update instead of having to implement a Reset method on all components (which
- // would be error-prone)
- readonly List<VolumeComponent> m_ComponentsDefaultState;
- // Recycled list used for volume traversal
- readonly List<Collider> m_TempColliders;
- VolumeManager()
- {
- m_SortedVolumes = new Dictionary<int, List<Volume>>();
- m_Volumes = new List<Volume>();
- m_SortNeeded = new Dictionary<int, bool>();
- m_TempColliders = new List<Collider>(8);
- m_ComponentsDefaultState = new List<VolumeComponent>();
- ReloadBaseTypes();
- stack = CreateStack();
- }
- /// <summary>
- /// Creates and returns a new <see cref="VolumeStack"/> to use when you need to store
- /// the result of the Volume blending pass in a separate stack.
- /// </summary>
- /// <returns></returns>
- /// <seealso cref="VolumeStack"/>
- /// <seealso cref="Update(VolumeStack,Transform,LayerMask)"/>
- public VolumeStack CreateStack()
- {
- var stack = new VolumeStack();
- stack.Reload(baseComponentTypes);
- return stack;
- }
- /// <summary>
- /// Destroy a Volume Stack
- /// </summary>
- /// <param name="stack">Volume Stack that needs to be destroyed.</param>
- public void DestroyStack(VolumeStack stack)
- {
- stack.Dispose();
- }
- // 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 component in the project
- void ReloadBaseTypes()
- {
- m_ComponentsDefaultState.Clear();
- // Grab all the component types we can find
- baseComponentTypes = CoreUtils.GetAllTypesDerivedFrom<VolumeComponent>()
- .Where(t => !t.IsAbstract);
- // Keep an instance of each type to be used in a virtual lowest priority global volume
- // so that we have a default state to fallback to when exiting volumes
- foreach (var type in baseComponentTypes)
- {
- var inst = (VolumeComponent)ScriptableObject.CreateInstance(type);
- m_ComponentsDefaultState.Add(inst);
- }
- }
- /// <summary>
- /// Registers a new Volume in the manager. Unity does this automatically when a new Volume is
- /// enabled, or its layer changes, but you can use this function to force-register a Volume
- /// that is currently disabled.
- /// </summary>
- /// <param name="volume">The volume to register.</param>
- /// <param name="layer">The LayerMask that this volume is in.</param>
- /// <seealso cref="Unregister"/>
- public void Register(Volume 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)
- {
- if ((kvp.Key & (1 << layer)) != 0)
- kvp.Value.Add(volume);
- }
- SetLayerDirty(layer);
- }
- /// <summary>
- /// Unregisters a Volume from the manager. Unity does this automatically when a Volume is
- /// disabled or goes out of scope, but you can use this function to force-unregister a Volume
- /// that you added manually while it was disabled.
- /// </summary>
- /// <param name="volume">The Volume to unregister.</param>
- /// <param name="layer">The LayerMask that this Volume is in.</param>
- /// <seealso cref="Register"/>
- public void Unregister(Volume volume, int layer)
- {
- m_Volumes.Remove(volume);
- foreach (var kvp in m_SortedVolumes)
- {
- // Skip layer masks this volume doesn't belong to
- if ((kvp.Key & (1 << layer)) == 0)
- continue;
- kvp.Value.Remove(volume);
- }
- }
- /// <summary>
- /// Checks if a <see cref="VolumeComponent"/> is active in a given LayerMask.
- /// </summary>
- /// <typeparam name="T">A type derived from <see cref="VolumeComponent"/></typeparam>
- /// <param name="layerMask">The LayerMask to check against</param>
- /// <returns><c>true</c> if the component is active in the LayerMask, <c>false</c>
- /// otherwise.</returns>
- public bool IsComponentActiveInMask<T>(LayerMask layerMask)
- where T : VolumeComponent
- {
- int mask = layerMask.value;
- foreach (var kvp in m_SortedVolumes)
- {
- if (kvp.Key != mask)
- continue;
- foreach (var volume in kvp.Value)
- {
- if (!volume.enabled || volume.profileRef == null)
- continue;
- if (volume.profileRef.TryGet(out T component) && component.active)
- return true;
- }
- }
- return false;
- }
- 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(Volume volume, int prevLayer, int newLayer)
- {
- Assert.IsTrue(prevLayer >= 0 && prevLayer <= k_MaxLayerCount, "Invalid layer bit");
- Unregister(volume, prevLayer);
- Register(volume, newLayer);
- }
- // Go through all listed components and lerp overridden values in the global state
- void OverrideData(VolumeStack stack, List<VolumeComponent> components, float interpFactor)
- {
- foreach (var component in components)
- {
- if (!component.active)
- continue;
- var state = stack.GetComponent(component.GetType());
- component.Override(state, interpFactor);
- }
- }
- // Faster version of OverrideData to force replace values in the global state
- void ReplaceData(VolumeStack stack, List<VolumeComponent> components)
- {
- foreach (var component in components)
- {
- var target = stack.GetComponent(component.GetType());
- int count = component.parameters.Count;
- for (int i = 0; i < count; i++)
- target.parameters[i].SetValue(component.parameters[i]);
- }
- }
- /// <summary>
- /// Checks the state of the base type library. This is only used in the editor to handle
- /// entering and exiting of play mode and domain reload.
- /// </summary>
- [Conditional("UNITY_EDITOR")]
- public void CheckBaseTypes()
- {
- // Editor specific hack to work around serialization doing funky things when exiting
- if (m_ComponentsDefaultState == null || (m_ComponentsDefaultState.Count > 0 && m_ComponentsDefaultState[0] == null))
- ReloadBaseTypes();
- }
- /// <summary>
- /// Checks the state of a given stack. This is only used in the editor to handle entering
- /// and exiting of play mode and domain reload.
- /// </summary>
- /// <param name="stack">The stack to check.</param>
- [Conditional("UNITY_EDITOR")]
- public void CheckStack(VolumeStack stack)
- {
- // The editor doesn't reload the domain when exiting play mode but still kills every
- // object created while in play mode, like stacks' component states
- var components = stack.components;
- if (components == null)
- {
- stack.Reload(baseComponentTypes);
- return;
- }
- foreach (var kvp in components)
- {
- if (kvp.Key == null || kvp.Value == null)
- {
- stack.Reload(baseComponentTypes);
- return;
- }
- }
- }
- /// <summary>
- /// Updates the global state of the Volume manager. Unity usually calls this once per Camera
- /// in the Update loop before rendering happens.
- /// </summary>
- /// <param name="trigger">A reference Transform to consider for positional Volume blending
- /// </param>
- /// <param name="layerMask">The LayerMask that the Volume manager uses to filter Volumes that it should consider
- /// for blending.</param>
- public void Update(Transform trigger, LayerMask layerMask)
- {
- Update(stack, trigger, layerMask);
- }
- /// <summary>
- /// Updates the Volume manager and stores the result in a custom <see cref="VolumeStack"/>.
- /// </summary>
- /// <param name="stack">The stack to store the blending result into.</param>
- /// <param name="trigger">A reference Transform to consider for positional Volume blending.
- /// </param>
- /// <param name="layerMask">The LayerMask that Unity uses to filter Volumes that it should consider
- /// for blending.</param>
- /// <seealso cref="VolumeStack"/>
- public void Update(VolumeStack stack, Transform trigger, LayerMask layerMask)
- {
- Assert.IsNotNull(stack);
- CheckBaseTypes();
- CheckStack(stack);
- // Start by resetting the global state to default values
- ReplaceData(stack, m_ComponentsDefaultState);
- bool onlyGlobal = trigger == null;
- var triggerPos = onlyGlobal ? Vector3.zero : trigger.position;
- // Sort the cached volume list(s) for the given layer mask if needed and return it
- var volumes = GrabVolumes(layerMask);
- Camera camera = null;
- // Behavior should be fine even if camera is null
- if (!onlyGlobal)
- trigger.TryGetComponent<Camera>(out camera);
- #if UNITY_EDITOR
- // requested or prefab isolation mode.
- bool needIsolation = needIsolationFilteredByRenderer || (UnityEditor.SceneManagement.StageUtility.GetCurrentStageHandle() != UnityEditor.SceneManagement.StageUtility.GetMainStageHandle());
- #endif
- // 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 (needIsolation
- && !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;
- // Global volumes always have influence
- if (volume.isGlobal)
- {
- OverrideData(stack, volume.profileRef.components, 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);
- var d = (closestPoint - triggerPos).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
- OverrideData(stack, volume.profileRef.components, interpFactor * Mathf.Clamp01(volume.weight));
- }
- }
- List<Volume> GrabVolumes(LayerMask mask)
- {
- List<Volume> 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<Volume>();
- 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;
- }
- // Stable insertion sort. Faster than List<T>.Sort() for our needs.
- static void SortByPriority(List<Volume> 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;
- // Sort order is ascending
- while (j >= 0 && volumes[j].priority > temp.priority)
- {
- volumes[j + 1] = volumes[j];
- j--;
- }
- volumes[j + 1] = temp;
- }
- }
- static bool IsVolumeRenderedByCamera(Volume volume, Camera camera)
- {
- #if UNITY_2018_3_OR_NEWER && UNITY_EDITOR
- // IsGameObjectRenderedByCamera does not behave correctly when camera is null so we have to catch it here.
- return camera == null ? true : UnityEditor.SceneManagement.StageUtility.IsGameObjectRenderedByCamera(volume.gameObject, camera);
- #else
- return true;
- #endif
- }
- }
- /// <summary>
- /// A scope in which a Camera filters a Volume.
- /// </summary>
- public struct VolumeIsolationScope : IDisposable
- {
- /// <summary>
- /// Constructs a scope in which a Camera filters a Volume.
- /// </summary>
- /// <param name="unused">Unused parameter.</param>
- public VolumeIsolationScope(bool unused)
- => VolumeManager.needIsolationFilteredByRenderer = true;
- /// <summary>
- /// Stops the Camera from filtering a Volume.
- /// </summary>
- void IDisposable.Dispose()
- => VolumeManager.needIsolationFilteredByRenderer = false;
- }
- }
|