123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915 |
- using System;
- using System.Collections.Generic;
- using System.Collections.ObjectModel;
- using System.Diagnostics;
- using System.Linq;
- using System.Reflection;
- using Unity.Profiling;
- using UnityEngine.Assertions;
-
- #if UNITY_EDITOR
- using UnityEditor;
- using UnityEditor.Rendering;
- #endif
-
- namespace UnityEngine.Rendering
- {
- /// <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
- {
- static readonly ProfilerMarker k_ProfilerMarkerUpdate = new ("VolumeManager.Update");
- static readonly ProfilerMarker k_ProfilerMarkerReplaceData = new ("VolumeManager.ReplaceData");
- static readonly ProfilerMarker k_ProfilerMarkerEvaluateVolumeDefaultState = new ("VolumeManager.EvaluateVolumeDefaultState");
-
- 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; set; }
-
- /// <summary>
- /// The current list of all available types that derive from <see cref="VolumeComponent"/>.
- /// </summary>
- [Obsolete("Please use baseComponentTypeArray instead.")]
- public IEnumerable<Type> baseComponentTypes => baseComponentTypeArray;
-
- static readonly Dictionary<Type, List<(string, Type)>> s_SupportedVolumeComponentsForRenderPipeline = new();
-
- internal List<(string, Type)> GetVolumeComponentsForDisplay(Type currentPipelineAssetType)
- {
- if (currentPipelineAssetType == null)
- return new List<(string, Type)>();
-
- if (!currentPipelineAssetType.IsSubclassOf(typeof(RenderPipelineAsset)))
- throw new ArgumentException(nameof(currentPipelineAssetType));
-
- if (s_SupportedVolumeComponentsForRenderPipeline.TryGetValue(currentPipelineAssetType, out var supportedVolumeComponents))
- return supportedVolumeComponents;
-
- if (baseComponentTypeArray == null)
- LoadBaseTypes(currentPipelineAssetType);
-
- supportedVolumeComponents = BuildVolumeComponentDisplayList(baseComponentTypeArray);
- s_SupportedVolumeComponentsForRenderPipeline[currentPipelineAssetType] = supportedVolumeComponents;
-
- return supportedVolumeComponents;
- }
-
- List<(string, Type)> BuildVolumeComponentDisplayList(Type[] types)
- {
- if (types == null)
- throw new ArgumentNullException(nameof(types));
-
- var volumes = new List<(string, Type)>();
- foreach (var t in types)
- {
- string path = string.Empty;
- bool skipComponent = false;
-
- // Look for the attributes of this volume component and decide how is added and if it needs to be skipped
- var attrs = t.GetCustomAttributes(false);
- foreach (var attr in attrs)
- {
- switch (attr)
- {
- case VolumeComponentMenu attrMenu:
- {
- path = attrMenu.menu;
- break;
- }
- case HideInInspector:
- case ObsoleteAttribute:
- skipComponent = true;
- break;
- }
- }
-
- if (skipComponent)
- continue;
-
- // If no attribute or in case something went wrong when grabbing it, fallback to a
- // beautified class name
- if (string.IsNullOrEmpty(path))
- {
- #if UNITY_EDITOR
- path = ObjectNames.NicifyVariableName(t.Name);
- #else
- path = t.Name;
- #endif
- }
-
-
- volumes.Add((path, t));
- }
-
- return volumes
- .OrderBy(i => i.Item1)
- .ToList();
- }
-
- /// <summary>
- /// The current list of all available types that derive from <see cref="VolumeComponent"/>.
- /// </summary>
- public Type[] baseComponentTypeArray { get; internal set; } // internal only for tests
-
- /// <summary>
- /// Global default profile that provides default values for volume components. VolumeManager applies
- /// this profile to its internal component default state first, before <see cref="qualityDefaultProfile"/>
- /// and <see cref="customDefaultProfiles"/>.
- /// </summary>
- public VolumeProfile globalDefaultProfile { get; private set; }
-
- /// <summary>
- /// Quality level specific volume profile that is applied to the default state after
- /// <see cref="globalDefaultProfile"/> and before <see cref="customDefaultProfiles"/>.
- /// </summary>
- public VolumeProfile qualityDefaultProfile { get; private set; }
-
- /// <summary>
- /// Collection of additional default profiles that can be used to override default values for volume components
- /// in a way that doesn't cause any overhead at runtime. Unity applies these Volume Profiles to its internal
- /// component default state after <see cref="globalDefaultProfile"/> and <see cref="qualityDefaultProfile"/>.
- /// The custom profiles are applied in the order that they appear in the collection.
- /// </summary>
- public ReadOnlyCollection<VolumeProfile> customDefaultProfiles { 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 = new();
-
- // Holds all the registered volumes
- readonly List<Volume> m_Volumes = new();
-
- // Keep track of sorting states for layer masks
- readonly Dictionary<int, bool> m_SortNeeded = new();
-
- // 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)
- // The "Default State" is evaluated as follows:
- // Default-constructed VolumeComponents (VolumeParameter values coming from code)
- // + Values from globalDefaultProfile
- // + Values from qualityDefaultProfile
- // + Values from customDefaultProfiles
- // = Default State.
- VolumeComponent[] m_ComponentsDefaultState;
-
- // Flat list of every volume parameter in default state for faster per-frame stack reset.
- internal VolumeParameter[] m_ParametersDefaultState;
-
- /// <summary>
- /// Retrieve the default state for a given VolumeComponent type. Default state is defined as
- /// "default-constructed VolumeComponent + Default Profiles evaluated in order".
- /// </summary>
- /// <remarks>
- /// If you want just the VolumeComponent with default-constructed values without overrides from
- /// Default Profiles, use <see cref="ScriptableObject.CreateInstance(Type)"/>.
- /// </remarks>
- /// <param name="volumeComponentType">Type of VolumeComponent</param>
- /// <returns>VolumeComponent in default state, or null if the type is not found</returns>
- public VolumeComponent GetVolumeComponentDefaultState(Type volumeComponentType)
- {
- if (!typeof(VolumeComponent).IsAssignableFrom(volumeComponentType))
- return null;
-
- foreach (VolumeComponent component in m_ComponentsDefaultState)
- {
- if (component.GetType() == volumeComponentType)
- return component;
- }
-
- return null;
- }
-
- // Recycled list used for volume traversal
- readonly List<Collider> m_TempColliders = new(8);
-
- // The default stack the volume manager uses.
- // We cache this as users able to change the stack through code and
- // we want to be able to switch to the default one through the ResetMainStack() function.
- VolumeStack m_DefaultStack;
-
- // List of stacks created through VolumeManager.
- readonly List<VolumeStack> m_CreatedVolumeStacks = new();
-
- // Internal for tests
- internal VolumeManager()
- {
- }
-
- // Note: The "isInitialized" state and explicit Initialize/Deinitialize are only required because VolumeManger
- // is a singleton whose lifetime exceeds that of RenderPipelines. Thus it must be initialized & deinitialized
- // explicitly by the RP to handle pipeline switch gracefully. It would be better to get rid of singletons and
- // have the RP own the class instance instead.
- /// <summary>
- /// Returns whether <see cref="VolumeManager.Initialize(VolumeProfile,VolumeProfile)"/> has been called, and the
- /// class is in valid state. It is not valid to use VolumeManager before this returns true.
- /// </summary>
- public bool isInitialized { get; private set; }
-
- /// <summary>
- /// Initialize VolumeManager with specified global and quality default volume profiles that are used to evaluate
- /// the default state of all VolumeComponents. Should be called from <see cref="RenderPipeline"/> constructor.
- /// </summary>
- /// <param name="globalDefaultVolumeProfile">Global default volume profile.</param>
- /// <param name="qualityDefaultVolumeProfile">Quality default volume profile.</param>
- public void Initialize(VolumeProfile globalDefaultVolumeProfile = null, VolumeProfile qualityDefaultVolumeProfile = null)
- {
- Debug.Assert(!isInitialized);
- Debug.Assert(m_CreatedVolumeStacks.Count == 0);
-
- LoadBaseTypes(GraphicsSettings.currentRenderPipelineAssetType);
- InitializeVolumeComponents();
-
- globalDefaultProfile = globalDefaultVolumeProfile;
- qualityDefaultProfile = qualityDefaultVolumeProfile;
- EvaluateVolumeDefaultState();
-
- m_DefaultStack = CreateStack();
- stack = m_DefaultStack;
-
- isInitialized = true;
- }
-
- /// <summary>
- /// Deinitialize VolumeManager. Should be called from <see cref="RenderPipeline.Dispose()"/>.
- /// </summary>
- public void Deinitialize()
- {
- Debug.Assert(isInitialized);
- DestroyStack(m_DefaultStack);
- m_DefaultStack = null;
- foreach (var s in m_CreatedVolumeStacks)
- s.Dispose();
- m_CreatedVolumeStacks.Clear();
- baseComponentTypeArray = null;
- globalDefaultProfile = null;
- qualityDefaultProfile = null;
- customDefaultProfiles = null;
- isInitialized = false;
- }
-
- /// <summary>
- /// Assign the given VolumeProfile as the global default profile and update the default component state.
- /// </summary>
- /// <param name="profile">The VolumeProfile to use as the global default profile.</param>
- public void SetGlobalDefaultProfile(VolumeProfile profile)
- {
- globalDefaultProfile = profile;
- EvaluateVolumeDefaultState();
- }
-
- /// <summary>
- /// Assign the given VolumeProfile as the quality default profile and update the default component state.
- /// </summary>
- /// <param name="profile">The VolumeProfile to use as the quality level default profile.</param>
- public void SetQualityDefaultProfile(VolumeProfile profile)
- {
- qualityDefaultProfile = profile;
- EvaluateVolumeDefaultState();
- }
-
- /// <summary>
- /// Assign the given VolumeProfiles as custom default profiles and update the default component state.
- /// </summary>
- /// <param name="profiles">List of VolumeProfiles to set as default profiles, or null to clear them.</param>
- public void SetCustomDefaultProfiles(List<VolumeProfile> profiles)
- {
- var validProfiles = profiles ?? new List<VolumeProfile>();
- validProfiles.RemoveAll(x => x == null);
- customDefaultProfiles = new ReadOnlyCollection<VolumeProfile>(validProfiles);
- EvaluateVolumeDefaultState();
- }
-
- /// <summary>
- /// Call when a VolumeProfile is modified to trigger default state update if necessary.
- /// </summary>
- /// <param name="profile">VolumeProfile that has changed.</param>
- public void OnVolumeProfileChanged(VolumeProfile profile)
- {
- if (!isInitialized)
- return;
-
- if (globalDefaultProfile == profile ||
- qualityDefaultProfile == profile ||
- (customDefaultProfiles != null && customDefaultProfiles.Contains(profile)))
- EvaluateVolumeDefaultState();
- }
-
- /// <summary>
- /// Call when a VolumeComponent is modified to trigger default state update if necessary.
- /// </summary>
- /// <param name="component">VolumeComponent that has changed.</param>
- public void OnVolumeComponentChanged(VolumeComponent component)
- {
- var defaultProfiles = new List<VolumeProfile> { globalDefaultProfile, globalDefaultProfile };
- if (customDefaultProfiles != null)
- defaultProfiles.AddRange(customDefaultProfiles);
-
- foreach (var defaultProfile in defaultProfiles)
- {
- if (defaultProfile.components.Contains(component))
- {
- EvaluateVolumeDefaultState();
- return;
- }
- }
- }
-
- /// <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>A new <see cref="VolumeStack"/> instance with freshly loaded components.</returns>
- /// <seealso cref="VolumeStack"/>
- /// <seealso cref="Update(VolumeStack,Transform,LayerMask)"/>
- public VolumeStack CreateStack()
- {
- var stack = new VolumeStack();
- stack.Reload(baseComponentTypeArray);
- m_CreatedVolumeStacks.Add(stack);
- return stack;
- }
-
- /// <summary>
- /// Resets the main stack to be the default one.
- /// Call this function if you've assigned the main stack to something other than the default one.
- /// </summary>
- public void ResetMainStack()
- {
- stack = m_DefaultStack;
- }
-
- /// <summary>
- /// Destroy a Volume Stack
- /// </summary>
- /// <param name="stack">Volume Stack that needs to be destroyed.</param>
- public void DestroyStack(VolumeStack stack)
- {
- m_CreatedVolumeStacks.Remove(stack);
- stack.Dispose();
- }
-
- // For now, if a user is having a VolumeComponent with the old attribute for filtering support.
- // We are adding it to the supported volume components, but we are showing a warning.
- bool IsSupportedByObsoleteVolumeComponentMenuForRenderPipeline(Type t, Type pipelineAssetType)
- {
- var legacySupported = false;
-
- #pragma warning disable CS0618
- var legacyPipelineAttribute = t.GetCustomAttribute<VolumeComponentMenuForRenderPipeline>();
- if (legacyPipelineAttribute != null)
- {
- Debug.LogWarning($"{nameof(VolumeComponentMenuForRenderPipeline)} is deprecated, use {nameof(SupportedOnRenderPipelineAttribute)} and {nameof(VolumeComponentMenu)} with {t} instead. #from(2023.1)");
- #if UNITY_EDITOR
- var renderPipelineTypeFromAsset = RenderPipelineEditorUtility.GetPipelineTypeFromPipelineAssetType(pipelineAssetType);
-
- for (int i = 0; i < legacyPipelineAttribute.pipelineTypes.Length; ++i)
- {
- if (legacyPipelineAttribute.pipelineTypes[i] == renderPipelineTypeFromAsset)
- {
- legacySupported = true;
- break;
- }
- }
- #endif
- }
- #pragma warning restore CS0618
-
- return legacySupported;
- }
-
- // This will be called only once at runtime and on domain reload / pipeline switch in the editor
- // as we need to keep track of any compatible component in the project
- internal void LoadBaseTypes(Type pipelineAssetType)
- {
- // Grab all the component types we can find that are compatible with current pipeline
- using (ListPool<Type>.Get(out var list))
- {
- foreach (var t in CoreUtils.GetAllTypesDerivedFrom<VolumeComponent>())
- {
- if (t.IsAbstract)
- continue;
-
- var isSupported = SupportedOnRenderPipelineAttribute.IsTypeSupportedOnRenderPipeline(t, pipelineAssetType) ||
- IsSupportedByObsoleteVolumeComponentMenuForRenderPipeline(t, pipelineAssetType);
-
- if (isSupported)
- list.Add(t);
- }
-
- baseComponentTypeArray = list.ToArray();
- }
- }
-
- internal void InitializeVolumeComponents()
- {
- // Call custom static Init method if present
- var flags = BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic;
- foreach (var type in baseComponentTypeArray)
- {
- var initMethod = type.GetMethod("Init", flags);
- if (initMethod != null)
- {
- initMethod.Invoke(null, null);
- }
- }
- }
-
- // Evaluate static default values for VolumeComponents, which is the baseline to reset the values to at the start of Update.
- internal void EvaluateVolumeDefaultState()
- {
- if (baseComponentTypeArray == null || baseComponentTypeArray.Length == 0)
- return;
-
- using var profilerScope = k_ProfilerMarkerEvaluateVolumeDefaultState.Auto();
-
- // TODO consider if the "component default values" array should be kept in memory separately. Creating the
- // instances is likely the slowest operation here, so doing that would mean it can only be done once in
- // Initialize() and the default state can be updated a lot quicker.
-
- // First, default-construct all VolumeComponents
- List<VolumeComponent> componentsDefaultStateList = new();
- foreach (var type in baseComponentTypeArray)
- {
- componentsDefaultStateList.Add((VolumeComponent) ScriptableObject.CreateInstance(type));
- }
-
- void ApplyDefaultProfile(VolumeProfile profile)
- {
- if (profile == null)
- return;
-
- for (int i = 0; i < profile.components.Count; i++)
- {
- var profileComponent = profile.components[i];
- var defaultStateComponent = componentsDefaultStateList.FirstOrDefault(
- x => x.GetType() == profileComponent.GetType());
-
- if (defaultStateComponent != null && profileComponent.active)
- {
- // Ideally we would just call SetValue here. However, there are custom non-trivial
- // implementations of VolumeParameter.Interp() (such as DiffusionProfileList) that make it
- // necessary for us to call the it. This ensures the new DefaultProfile behavior works
- // consistently with the old HDRP implementation where the Default Profile was implemented as
- // a regular global volume inside the scene.
- profileComponent.Override(defaultStateComponent, 1.0f);
- }
- }
- }
-
- ApplyDefaultProfile(globalDefaultProfile); // Apply global default profile first
- ApplyDefaultProfile(qualityDefaultProfile); // Apply quality default profile second
- if (customDefaultProfiles != null) // Finally, apply custom default profiles in order
- foreach (var profile in customDefaultProfiles)
- ApplyDefaultProfile(profile);
-
- // Build the flat parametersDefaultState list for fast per-frame resets
- var parametersDefaultStateList = new List<VolumeParameter>();
- foreach (var component in componentsDefaultStateList)
- {
- parametersDefaultStateList.AddRange(component.parameters);
- }
-
- m_ComponentsDefaultState = componentsDefaultStateList.ToArray();
- m_ParametersDefaultState = parametersDefaultStateList.ToArray();
-
- // All properties in stacks must be reset because the default state has changed
- foreach (var s in m_CreatedVolumeStacks)
- {
- s.requiresReset = true;
- s.requiresResetForAllProperties = true;
- }
- }
-
- /// <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)
- {
- // We add the volume to sorted lists only if the layer match and if it doesn't contain the volume already.
- if ((kvp.Key & (1 << layer)) != 0 && !kvp.Value.Contains(volume))
- 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)
- {
- var numComponents = components.Count;
- for (int i = 0; i < numComponents; i++)
- {
- var component = components[i];
- if (!component.active)
- continue;
-
- var state = stack.GetComponent(component.GetType());
- if (state != null)
- {
- component.Override(state, interpFactor);
- }
- }
- }
-
- // Faster version of OverrideData to force replace values in the global state.
- // NOTE: As an optimization, only the VolumeParameters with overrideState=true are reset. All other parameters
- // are assumed to be in their correct default state so no reset is necessary.
- internal void ReplaceData(VolumeStack stack)
- {
- using var profilerScope = k_ProfilerMarkerReplaceData.Auto();
-
- var stackParams = stack.parameters;
- bool resetAllParameters = stack.requiresResetForAllProperties;
- int count = stackParams.Length;
- Debug.Assert(count == m_ParametersDefaultState.Length);
-
- for (int i = 0; i < count; i++)
- {
- var stackParam = stackParams[i];
- if (stackParam.overrideState || resetAllParameters) // Only reset the parameters that have been overriden by a scene volume
- {
- stackParam.overrideState = false;
- stackParam.SetValue(m_ParametersDefaultState[i]);
- }
- }
-
- stack.requiresResetForAllProperties = false;
- }
-
- /// <summary>
- /// Checks component default state. This is only used in the editor to handle entering and exiting play mode
- /// because the instances created during playmode are automatically destroyed.
- /// </summary>
- [Conditional("UNITY_EDITOR")]
- public void CheckDefaultVolumeState()
- {
- if (m_ComponentsDefaultState == null || (m_ComponentsDefaultState.Length > 0 && m_ComponentsDefaultState[0] == null))
- {
- EvaluateVolumeDefaultState();
- }
- }
-
- /// <summary>
- /// Checks the state of a given stack. This is only used in the editor to handle entering and exiting play mode
- /// because the instances created during playmode are automatically destroyed.
- /// </summary>
- /// <param name="stack">The stack to check.</param>
- [Conditional("UNITY_EDITOR")]
- public void CheckStack(VolumeStack stack)
- {
- if (stack.components == null)
- {
- stack.Reload(baseComponentTypeArray);
- return;
- }
-
- foreach (var kvp in stack.components)
- {
- if (kvp.Key == null || kvp.Value == null)
- {
- stack.Reload(baseComponentTypeArray);
- return;
- }
- }
- }
-
- // Returns true if must execute Update() in full, and false if we can early exit.
- bool CheckUpdateRequired(VolumeStack stack)
- {
- if (m_Volumes.Count == 0)
- {
- if (stack.requiresReset)
- {
- // Update the stack one more time in case there was a volume that just ceased to exist. This ensures
- // the stack will return to default values correctly.
- stack.requiresReset = false;
- return true;
- }
-
- // There were no volumes last frame either, and stack has been returned to defaults, so no update is
- // needed and we can early exit from Update().
- return false;
- }
- stack.requiresReset = true; // Stack must be reset every frame whenever there are volumes present
- return true;
- }
-
- /// <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)
- {
- using var profilerScope = k_ProfilerMarkerUpdate.Auto();
-
- if (!isInitialized)
- return;
-
- Assert.IsNotNull(stack);
-
- CheckDefaultVolumeState();
- CheckStack(stack);
-
- if (!CheckUpdateRequired(stack))
- return;
-
- // Start by resetting the global state to default values.
- ReplaceData(stack);
-
- 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);
-
- // Traverse all volumes
- int numVolumes = volumes.Count;
- for (int i = 0; i < numVolumes; i++)
- {
- Volume volume = volumes[i];
- if (volume == null)
- continue;
-
- #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;
-
- // 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;
-
- int numColliders = colliders.Count;
- for (int c = 0; c < numColliders; c++)
- {
- var collider = colliders[c];
- 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));
- }
- }
-
- /// <summary>
- /// Get all volumes on a given layer mask sorted by influence.
- /// </summary>
- /// <param name="layerMask">The LayerMask that Unity uses to filter Volumes that it should consider.</param>
- /// <returns>An array of volume.</returns>
- public Volume[] GetVolumes(LayerMask layerMask)
- {
- var volumes = GrabVolumes(layerMask);
- volumes.RemoveAll(v => v == null);
- return volumes.ToArray();
- }
-
- 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>();
-
- var numVolumes = m_Volumes.Count;
- for (int i = 0; i < numVolumes; i++)
- {
- var volume = m_Volumes[i];
- 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
- // GameObject for default global volume may not belong to any scene, following check prevents it from being culled
- if (!volume.gameObject.scene.IsValid())
- return true;
- // 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>
- [Obsolete("VolumeIsolationScope is deprecated, it does not have any effect anymore.")]
- 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) { }
-
- /// <summary>
- /// Stops the Camera from filtering a Volume.
- /// </summary>
- void IDisposable.Dispose() { }
- }
- }
|