using System; using System.Runtime.CompilerServices; using UnityEngine.Experimental.Rendering; namespace UnityEngine.Rendering { // Due to limitations in the builtin Gradient we need this custom wrapper. /// /// A wrapper around Gradient to automatically bake it into a texture. /// [Serializable] public class TextureGradient : IDisposable { /// /// Texture Size computed. /// [field: SerializeField, HideInInspector] public int textureSize { get; private set; } /// /// Internal Gradient used to generate the Texture /// [SerializeField] Gradient m_Gradient; Texture2D m_Texture = null; int m_RequestedTextureSize = -1; bool m_IsTextureDirty; bool m_Precise; /// All color keys defined in the gradient. [HideInInspector] public GradientColorKey[] colorKeys => m_Gradient?.colorKeys; /// All alpha keys defined in the gradient. [HideInInspector] public GradientAlphaKey[] alphaKeys => m_Gradient?.alphaKeys; /// Controls how the gradient colors are interpolated. [SerializeField, HideInInspector] public GradientMode mode = GradientMode.PerceptualBlend; /// Indicates the color space that the gradient color keys are using. [SerializeField, HideInInspector] public ColorSpace colorSpace = ColorSpace.Uninitialized; /// /// Creates a new from an existing Gradient. /// /// The source Gradient. public TextureGradient(Gradient baseCurve) : this(baseCurve.colorKeys, baseCurve.alphaKeys) { mode = baseCurve.mode; colorSpace = baseCurve.colorSpace; m_Gradient.mode = baseCurve.mode; m_Gradient.colorSpace = baseCurve.colorSpace; } /// /// Creates a new from an arbitrary number of keyframes. /// /// An array of keyframes used to define the color of gradient. /// An array of keyframes used to define the alpha of gradient. /// Indicates the color space that the gradient color keys are using. /// Controls how the gradient colors are interpolated. /// Texture Size used internally, if '-1' using Nyquist-Shannon limits. /// if precise uses 4*Nyquist-Shannon limits, 2* otherwise. public TextureGradient(GradientColorKey[] colorKeys, GradientAlphaKey[] alphaKeys, GradientMode mode = GradientMode.PerceptualBlend, ColorSpace colorSpace = ColorSpace.Uninitialized, int requestedTextureSize = -1, bool precise = false) { Rebuild(colorKeys, alphaKeys, mode, colorSpace, requestedTextureSize, precise); } void Rebuild(GradientColorKey[] colorKeys, GradientAlphaKey[] alphaKeys, GradientMode mode, ColorSpace colorSpace, int requestedTextureSize, bool precise) { m_Gradient = new Gradient(); m_Gradient.mode = mode; m_Gradient.colorSpace = colorSpace; m_Gradient.SetKeys(colorKeys, alphaKeys); m_Precise = precise; m_RequestedTextureSize = requestedTextureSize; if (requestedTextureSize > 0) { textureSize = requestedTextureSize; } else { float smallestDelta = 1.0f; float[] times = new float[colorKeys.Length + alphaKeys.Length]; for (int i = 0; i < colorKeys.Length; ++i) { times[i] = colorKeys[i].time; } for (int i = 0; i < alphaKeys.Length; ++i) { times[colorKeys.Length + i] = alphaKeys[i].time; } Array.Sort(times); // Found the smallest increment between 2 keys for (int i = 1; i < times.Length; ++i) { int k0 = Math.Max(i - 1, 0); int k1 = Math.Min(i, times.Length - 1); float delta = Mathf.Abs(times[k0] - times[k1]); // Do not compare if time is duplicated if (delta > 0 && delta < smallestDelta) smallestDelta = delta; } // Nyquist-Shannon // smallestDelta: 1.00f => Sampling => 2 // smallestDelta: 0.50f => Sampling => 3 // smallestDelta: 0.33f => Sampling => 4 // smallestDelta: 0.25f => Sampling => 5 // 2x: Theoretical limits // 4x: Preserve original frequency // Round to the closest 4 * Nyquist-Shannon limits // 4x for Fixed to capture sharp discontinuity float scale; if (precise || mode == GradientMode.Fixed) scale = 4.0f; else scale = 2.0f; float sizef = scale * Mathf.Ceil(1.0f / smallestDelta + 1.0f); textureSize = Mathf.RoundToInt(sizef); // Arbitrary max (1024) textureSize = Math.Min(textureSize, 1024); } SetDirty(); } /// /// Cleans up the internal texture resource. /// public void Dispose() { //Release(); } /// /// Releases the internal texture resource. /// public void Release() { if (m_Texture != null) 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_IsTextureDirty = true; } static GraphicsFormat GetTextureFormat() { return GraphicsFormat.R8G8B8A8_UNorm; } /// /// Gets the texture representation of this Gradient. /// /// A texture. public Texture2D GetTexture() { float step = 1.0f / (float)(textureSize - 1); if (m_Texture != null && m_Texture.width != textureSize) { Object.DestroyImmediate(m_Texture); m_Texture = null; } if (m_Texture == null) { m_Texture = new Texture2D(textureSize, 1, GetTextureFormat(), TextureCreationFlags.None); m_Texture.name = "GradientTexture"; m_Texture.hideFlags = HideFlags.HideAndDontSave; m_Texture.filterMode = FilterMode.Bilinear; m_Texture.wrapMode = TextureWrapMode.Clamp; m_Texture.anisoLevel = 0; m_IsTextureDirty = true; } if (m_IsTextureDirty) { var pixels = new Color[textureSize]; for (int i = 0; i < textureSize; i++) pixels[i] = Evaluate(i * step); m_Texture.SetPixels(pixels); m_Texture.Apply(false, false); m_IsTextureDirty = false; m_Texture.IncrementUpdateCount(); } return m_Texture; } /// /// Evaluate a time value on the Gradient. /// /// The time within the Gradient you want to evaluate. /// The value of the Gradient, at the point in time specified. public Color Evaluate(float time) { if (textureSize <= 0) return Color.black; return m_Gradient.Evaluate(time); } /// /// Setup Gradient with an array of color keys and alpha keys. /// /// Color keys of the gradient (maximum 8 color keys). /// Alpha keys of the gradient (maximum 8 alpha keys). /// Indicates the color space that the gradient color keys are using. /// Controls how the gradient colors are interpolated. public void SetKeys(GradientColorKey[] colorKeys, GradientAlphaKey[] alphaKeys, GradientMode mode, ColorSpace colorSpace) { m_Gradient.SetKeys(colorKeys, alphaKeys); m_Gradient.mode = mode; m_Gradient.colorSpace = colorSpace; // Rebuild will make the TextureGradient Dirty. Rebuild(colorKeys, alphaKeys, mode, colorSpace, m_RequestedTextureSize, m_Precise); } } /// /// A that holds a value. /// [Serializable] public class TextureGradientParameter : VolumeParameter { /// /// Creates a new instance. /// /// The initial value to store in the parameter. /// The initial override state for the parameter. public TextureGradientParameter(TextureGradient value, bool overrideState = false) : base(value, overrideState) { } /// /// Release implementation. /// public override void Release() => m_Value.Release(); // TODO: TextureGradient interpolation } }