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
}
}