using System; using System.Runtime.CompilerServices; using UnityEngine.Experimental.Rendering; namespace UnityEngine.Rendering { // Due to limitations in the builtin AnimationCurve we need this custom wrapper. // Improvements: // - Dirty state handling so we know when a curve has changed or not // - Looping support (infinite curve) // - Zero-value curve // - Cheaper length property /// /// A wrapper around AnimationCurve to automatically bake it into a texture. /// [Serializable] public class TextureCurve : IDisposable { const int k_Precision = 128; // Edit LutBuilder3D if you change this value const float k_Step = 1f / k_Precision; /// /// The number of keys in the curve. /// [field: SerializeField] public int length { get; private set; } // Calling AnimationCurve.length is very slow, let's cache it [SerializeField] bool m_Loop; [SerializeField] float m_ZeroValue; [SerializeField] float m_Range; [SerializeField] AnimationCurve m_Curve; AnimationCurve m_LoopingCurve; Texture2D m_Texture; bool m_IsCurveDirty; bool m_IsTextureDirty; /// /// Retrieves the key at index. /// /// The index to look for. /// A key. public Keyframe this[int index] => m_Curve[index]; /// /// Creates a new from an existing AnimationCurve. /// /// The source AnimationCurve. /// The default value to use when the curve doesn't have any key. /// Should the curve automatically loop in the given ? /// The boundaries of the curve. public TextureCurve(AnimationCurve baseCurve, float zeroValue, bool loop, in Vector2 bounds) : this(baseCurve.keys, zeroValue, loop, bounds) { } /// /// Creates a new from an arbitrary number of keyframes. /// /// An array of Keyframes used to define the curve. /// The default value to use when the curve doesn't have any key. /// Should the curve automatically loop in the given ? /// The boundaries of the curve. public TextureCurve(Keyframe[] keys, float zeroValue, bool loop, in Vector2 bounds) { m_Curve = new AnimationCurve(keys); m_ZeroValue = zeroValue; m_Loop = loop; m_Range = bounds.magnitude; length = keys.Length; SetDirty(); } /// /// Finalizer. /// ~TextureCurve() { } /// /// Cleans up the internal texture resource. /// [Obsolete("Please use Release() instead.")] public void Dispose() { } /// /// Releases the internal texture resource. /// public void Release() { CoreUtils.Destroy(m_Texture); m_Texture = null; } /// /// Marks the curve as dirty to trigger a redraw of the texture the next time /// is called. /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public void SetDirty() { m_IsCurveDirty = true; m_IsTextureDirty = true; } static GraphicsFormat GetTextureFormat() { if (SystemInfo.IsFormatSupported(GraphicsFormat.R16_SFloat, FormatUsage.Sample | FormatUsage.SetPixels)) return GraphicsFormat.R16_SFloat; if (SystemInfo.IsFormatSupported(GraphicsFormat.R8_UNorm, FormatUsage.Sample | FormatUsage.SetPixels)) return GraphicsFormat.R8_UNorm; return GraphicsFormat.R8G8B8A8_UNorm; } /// /// Gets the texture representation of this curve. /// /// A 128x1 texture. public Texture2D GetTexture() { if (m_Texture == null) { m_Texture = new Texture2D(k_Precision, 1, GetTextureFormat(), TextureCreationFlags.None); m_Texture.name = "CurveTexture"; m_Texture.hideFlags = HideFlags.HideAndDontSave; m_Texture.filterMode = FilterMode.Bilinear; m_Texture.wrapMode = TextureWrapMode.Clamp; m_IsTextureDirty = true; } if (m_IsTextureDirty) { var pixels = new Color[k_Precision]; for (int i = 0; i < pixels.Length; i++) pixels[i].r = Evaluate(i * k_Step); m_Texture.SetPixels(pixels); m_Texture.Apply(false, false); m_IsTextureDirty = false; } return m_Texture; } /// /// Evaluate a time value on the curve. /// /// The time within the curve you want to evaluate. /// The value of the curve, at the point in time specified. public float Evaluate(float time) { if (m_IsCurveDirty) length = m_Curve.length; if (length == 0) return m_ZeroValue; if (!m_Loop || length == 1) return m_Curve.Evaluate(time); if (m_IsCurveDirty) { if (m_LoopingCurve == null) m_LoopingCurve = new AnimationCurve(); var prev = m_Curve[length - 1]; prev.time -= m_Range; var next = m_Curve[0]; next.time += m_Range; m_LoopingCurve.keys = m_Curve.keys; // GC pressure m_LoopingCurve.AddKey(prev); m_LoopingCurve.AddKey(next); m_IsCurveDirty = false; } return m_LoopingCurve.Evaluate(time); } /// /// Adds a new key to the curve. /// /// The time at which to add the key. /// The value for the key. /// The index of the added key, or -1 if the key could not be added. [MethodImpl(MethodImplOptions.AggressiveInlining)] public int AddKey(float time, float value) { int r = m_Curve.AddKey(time, value); if (r > -1) SetDirty(); return r; } /// /// Removes the keyframe at and inserts . /// /// /// /// The index of the keyframe after moving it. [MethodImpl(MethodImplOptions.AggressiveInlining)] public int MoveKey(int index, in Keyframe key) { int r = m_Curve.MoveKey(index, key); SetDirty(); return r; } /// /// Removes a key. /// /// The index of the key to remove. [MethodImpl(MethodImplOptions.AggressiveInlining)] public void RemoveKey(int index) { m_Curve.RemoveKey(index); SetDirty(); } /// /// Smoothes the in and out tangents of the keyframe at . A of 0 evens out tangents. /// /// The index of the keyframe to be smoothed. /// The smoothing weight to apply to the keyframe's tangents. [MethodImpl(MethodImplOptions.AggressiveInlining)] public void SmoothTangents(int index, float weight) { m_Curve.SmoothTangents(index, weight); SetDirty(); } } /// /// A that holds a value. /// [Serializable] public class TextureCurveParameter : VolumeParameter { /// /// Creates a new instance. /// /// The initial value to store in the parameter. /// The initial override state for the parameter. public TextureCurveParameter(TextureCurve value, bool overrideState = false) : base(value, overrideState) { } /// /// Release implementation. /// public override void Release() => m_Value.Release(); // TODO: TextureCurve interpolation } }