using System; using System.Collections.Generic; using System.Collections.ObjectModel; using System.Linq; using System.Reflection; namespace UnityEngine.Rendering { /// /// This attribute allows you to add commands to the Add Override popup menu /// on Volumes. /// To filter VolumeComponentMenu based on current Render Pipeline, add SupportedOnRenderPipeline attribute to the class alongside with this attribute. /// [AttributeUsage(AttributeTargets.Class, AllowMultiple = false)] public class VolumeComponentMenu : Attribute { /// /// The name of the entry in the override list. You can use slashes to create sub-menus. /// public readonly string menu; // TODO: Add support for component icons /// /// Creates a new instance. /// /// The name of the entry in the override list. You can use slashes to /// create sub-menus. public VolumeComponentMenu(string menu) { this.menu = menu; } } /// /// This attribute allows you to add commands to the Add Override popup menu /// on Volumes and specify for which render pipelines will be supported /// [Obsolete(@"VolumeComponentMenuForRenderPipelineAttribute is deprecated. Use VolumeComponentMenu with SupportedOnCurrentPipeline instead. #from(2023.1)", false)] public class VolumeComponentMenuForRenderPipeline : VolumeComponentMenu { /// /// The list of pipeline types that the target class supports /// public Type[] pipelineTypes { get; } /// /// Creates a new instance. /// /// The name of the entry in the override list. You can use slashes to /// create sub-menus. /// The list of pipeline types that the target class supports public VolumeComponentMenuForRenderPipeline(string menu, params Type[] pipelineTypes) : base(menu) { if (pipelineTypes == null) throw new Exception("Specify a list of supported pipeline"); // Make sure that we only allow the class types that inherit from the render pipeline foreach (var t in pipelineTypes) { if (!typeof(RenderPipeline).IsAssignableFrom(t)) throw new Exception( $"You can only specify types that inherit from {typeof(RenderPipeline)}, please check {t}"); } this.pipelineTypes = pipelineTypes; } } /// /// An attribute to hide the volume component to be added through `Add Override` button on the volume component list /// [AttributeUsage(AttributeTargets.Class)] [Obsolete("VolumeComponentDeprecated has been deprecated (UnityUpgradable) -> [UnityEngine] UnityEngine.HideInInspector", false)] public sealed class VolumeComponentDeprecated : Attribute { } /// /// The base class for all the components that can be part of a . /// The Volume framework automatically handles and interpolates any members found in this class. /// /// /// /// using UnityEngine.Rendering; /// /// [Serializable, VolumeComponentMenuForRenderPipeline("Custom/Example Component")] /// public class ExampleComponent : VolumeComponent /// { /// public ClampedFloatParameter intensity = new ClampedFloatParameter(0f, 0f, 1f); /// } /// /// [Serializable] public partial class VolumeComponent : ScriptableObject { /// /// Local attribute for VolumeComponent fields only. /// It handles relative indentation of a property for inspector. /// public sealed class Indent : PropertyAttribute { /// Relative indent amount registered in this attribute public readonly int relativeAmount; /// Constructor /// Relative indent change to use public Indent(int relativeAmount = 1) => this.relativeAmount = relativeAmount; } /// /// The active state of the set of parameters defined in this class. You can use this to /// quickly turn on or off all the overrides at once. /// public bool active = true; /// /// The name displayed in the component header. If you do not set a name, Unity generates one from /// the class name automatically. /// public string displayName { get; protected set; } = ""; /// /// The backing storage of . Use this for performance-critical work. /// internal readonly List parameterList = new(); ReadOnlyCollection m_ParameterReadOnlyCollection; /// /// A read-only collection of all the s defined in this class. /// public ReadOnlyCollection parameters { get { if (m_ParameterReadOnlyCollection == null) m_ParameterReadOnlyCollection = parameterList.AsReadOnly(); return m_ParameterReadOnlyCollection; } } /// /// Extracts all the s defined in this class and nested classes. /// /// The object to find the parameters /// The list filled with the parameters. /// If you want to filter the parameters internal static void FindParameters(object o, List parameters, Func filter = null) { if (o == null) return; var fields = o.GetType() .GetFields(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance) .OrderBy(t => t.MetadataToken); // Guaranteed order foreach (var field in fields) { if (field.FieldType.IsSubclassOf(typeof(VolumeParameter))) { if (filter?.Invoke(field) ?? true) { VolumeParameter volumeParameter = (VolumeParameter)field.GetValue(o); parameters.Add(volumeParameter); } } else if (!field.FieldType.IsArray && field.FieldType.IsClass) FindParameters(field.GetValue(o), parameters, filter); } } /// /// Unity calls this method when it loads the class. /// /// /// If you want to override this method, you must call base.OnEnable(). /// protected virtual void OnEnable() { // Automatically grab all fields of type VolumeParameter for this instance parameterList.Clear(); FindParameters(this, parameterList); foreach (var parameter in parameterList) { if (parameter != null) parameter.OnEnable(); else Debug.LogWarning("Volume Component " + GetType().Name + " contains a null parameter; please make sure all parameters are initialized to a default value. Until this is fixed the null parameters will not be considered by the system."); } } /// /// Unity calls this method when the object goes out of scope. /// protected virtual void OnDisable() { foreach (var parameter in parameterList) { if (parameter != null) parameter.OnDisable(); } } /// /// Interpolates a with this component by an interpolation /// factor and puts the result back into the given . /// /// /// You can override this method to do your own blending. Either loop through the /// list or reference direct fields. You should only use /// to set parameter values and not assign /// directly to the state object. you should also manually check /// before you set any values. /// /// The internal component to interpolate from. You must store /// the result of the interpolation in this same component. /// The interpolation factor in range [0,1]. /// /// Below is the default implementation for blending: /// /// public virtual void Override(VolumeComponent state, float interpFactor) /// { /// int count = parameters.Count; /// /// for (int i = 0; i < count; i++) /// { /// var stateParam = state.parameters[i]; /// var toParam = parameters[i]; /// /// if (toParam.overrideState) /// { /// // Keep track of the override state to ensure that state will be reset on next frame (and for debugging purpose) /// stateParam.overrideState = toParam.overrideState; /// stateParam.Interp(stateParam, toParam, interpFactor); /// } /// } /// } /// /// public virtual void Override(VolumeComponent state, float interpFactor) { int count = parameterList.Count; for (int i = 0; i < count; i++) { var stateParam = state.parameterList[i]; var toParam = parameterList[i]; if (toParam.overrideState) { // Keep track of the override state to ensure that state will be reset on next frame (and for debugging purpose) stateParam.overrideState = toParam.overrideState; stateParam.Interp(stateParam, toParam, interpFactor); } } } /// /// Sets the state of all the overrides on this component to a given value. /// /// The value to set the state of the overrides to. public void SetAllOverridesTo(bool state) { SetOverridesTo(parameterList, state); } /// /// Sets the override state of the given parameters on this component to a given value. /// /// The value to set the state of the overrides to. internal void SetOverridesTo(IEnumerable enumerable, bool state) { foreach (var prop in enumerable) { prop.overrideState = state; var t = prop.GetType(); if (VolumeParameter.IsObjectParameter(t)) { // This method won't be called a lot but this is sub-optimal, fix me var innerParams = (ReadOnlyCollection) t.GetProperty("parameters", BindingFlags.NonPublic | BindingFlags.Instance) .GetValue(prop, null); if (innerParams != null) SetOverridesTo(innerParams, state); } } } /// /// A custom hashing function that Unity uses to compare the state of parameters. /// /// A computed hash code for the current instance. public override int GetHashCode() { unchecked { //return parameters.Aggregate(17, (i, p) => i * 23 + p.GetHash()); int hash = 17; for (int i = 0; i < parameterList.Count; i++) hash = hash * 23 + parameterList[i].GetHashCode(); return hash; } } /// /// Returns true if any of the volume properites has been overridden. /// /// True if any of the volume properites has been overridden. public bool AnyPropertiesIsOverridden() { for (int i = 0; i < parameterList.Count; ++i) { if (parameterList[i].overrideState) return true; } return false; } /// /// Unity calls this method before the object is destroyed. /// protected virtual void OnDestroy() => Release(); /// /// Releases all the allocated resources. /// public void Release() { if (parameterList == null) return; for (int i = 0; i < parameterList.Count; i++) { if (parameterList[i] != null) parameterList[i].Release(); } } } }