A game about forced loneliness, made by TACStudios
1using System;
2using System.Runtime.CompilerServices;
3using UnityEngine.Experimental.Rendering;
4
5namespace UnityEngine.Rendering
6{
7 // Due to limitations in the builtin Gradient we need this custom wrapper.
8
9 /// <summary>
10 /// A wrapper around <c>Gradient</c> to automatically bake it into a texture.
11 /// </summary>
12 [Serializable]
13 public class TextureGradient : IDisposable
14 {
15 /// <summary>
16 /// Texture Size computed.
17 /// </summary>
18 [field: SerializeField, HideInInspector]
19 public int textureSize { get; private set; }
20
21 /// <summary>
22 /// Internal Gradient used to generate the Texture
23 /// </summary>
24 [SerializeField]
25 Gradient m_Gradient;
26
27 Texture2D m_Texture = null;
28
29 int m_RequestedTextureSize = -1;
30
31 bool m_IsTextureDirty;
32 bool m_Precise;
33
34 /// <summary>All color keys defined in the gradient.</summary>
35 [HideInInspector]
36 public GradientColorKey[] colorKeys => m_Gradient?.colorKeys;
37
38 /// <summary>All alpha keys defined in the gradient.</summary>
39 [HideInInspector]
40 public GradientAlphaKey[] alphaKeys => m_Gradient?.alphaKeys;
41
42 /// <summary>Controls how the gradient colors are interpolated.</summary>
43 [SerializeField, HideInInspector]
44 public GradientMode mode = GradientMode.PerceptualBlend;
45
46 /// <summary>Indicates the color space that the gradient color keys are using.</summary>
47 [SerializeField, HideInInspector]
48 public ColorSpace colorSpace = ColorSpace.Uninitialized;
49
50 /// <summary>
51 /// Creates a new <see cref="TextureGradient"/> from an existing <c>Gradient</c>.
52 /// </summary>
53 /// <param name="baseCurve">The source <c>Gradient</c>.</param>
54 public TextureGradient(Gradient baseCurve)
55 : this(baseCurve.colorKeys, baseCurve.alphaKeys)
56 {
57 mode = baseCurve.mode;
58 colorSpace = baseCurve.colorSpace;
59 m_Gradient.mode = baseCurve.mode;
60 m_Gradient.colorSpace = baseCurve.colorSpace;
61 }
62
63 /// <summary>
64 /// Creates a new <see cref="TextureCurve"/> from an arbitrary number of keyframes.
65 /// </summary>
66 /// <param name="colorKeys">An array of keyframes used to define the color of gradient.</param>
67 /// <param name="alphaKeys">An array of keyframes used to define the alpha of gradient.</param>
68 /// <param name="mode">Indicates the color space that the gradient color keys are using.</param>
69 /// <param name="colorSpace">Controls how the gradient colors are interpolated.</param>
70 /// <param name="requestedTextureSize">Texture Size used internally, if '-1' using Nyquist-Shannon limits.</param>
71 /// <param name="precise">if precise uses 4*Nyquist-Shannon limits, 2* otherwise.</param>
72 public TextureGradient(GradientColorKey[] colorKeys, GradientAlphaKey[] alphaKeys, GradientMode mode = GradientMode.PerceptualBlend, ColorSpace colorSpace = ColorSpace.Uninitialized, int requestedTextureSize = -1, bool precise = false)
73 {
74 Rebuild(colorKeys, alphaKeys, mode, colorSpace, requestedTextureSize, precise);
75 }
76
77 void Rebuild(GradientColorKey[] colorKeys, GradientAlphaKey[] alphaKeys, GradientMode mode, ColorSpace colorSpace, int requestedTextureSize, bool precise)
78 {
79 m_Gradient = new Gradient();
80 m_Gradient.mode = mode;
81 m_Gradient.colorSpace = colorSpace;
82 m_Gradient.SetKeys(colorKeys, alphaKeys);
83 m_Precise = precise;
84 m_RequestedTextureSize = requestedTextureSize;
85 if (requestedTextureSize > 0)
86 {
87 textureSize = requestedTextureSize;
88 }
89 else
90 {
91 float smallestDelta = 1.0f;
92 float[] times = new float[colorKeys.Length + alphaKeys.Length];
93 for (int i = 0; i < colorKeys.Length; ++i)
94 {
95 times[i] = colorKeys[i].time;
96 }
97 for (int i = 0; i < alphaKeys.Length; ++i)
98 {
99 times[colorKeys.Length + i] = alphaKeys[i].time;
100 }
101 Array.Sort(times);
102 // Found the smallest increment between 2 keys
103 for (int i = 1; i < times.Length; ++i)
104 {
105 int k0 = Math.Max(i - 1, 0);
106 int k1 = Math.Min(i, times.Length - 1);
107 float delta = Mathf.Abs(times[k0] - times[k1]);
108 // Do not compare if time is duplicated
109 if (delta > 0 && delta < smallestDelta)
110 smallestDelta = delta;
111 }
112
113 // Nyquist-Shannon
114 // smallestDelta: 1.00f => Sampling => 2
115 // smallestDelta: 0.50f => Sampling => 3
116 // smallestDelta: 0.33f => Sampling => 4
117 // smallestDelta: 0.25f => Sampling => 5
118
119 // 2x: Theoretical limits
120 // 4x: Preserve original frequency
121
122 // Round to the closest 4 * Nyquist-Shannon limits
123 // 4x for Fixed to capture sharp discontinuity
124 float scale;
125 if (precise || mode == GradientMode.Fixed)
126 scale = 4.0f;
127 else
128 scale = 2.0f;
129 float sizef = scale * Mathf.Ceil(1.0f / smallestDelta + 1.0f);
130 textureSize = Mathf.RoundToInt(sizef);
131 // Arbitrary max (1024)
132 textureSize = Math.Min(textureSize, 1024);
133 }
134
135 SetDirty();
136 }
137
138 /// <summary>
139 /// Cleans up the internal texture resource.
140 /// </summary>
141 public void Dispose()
142 {
143 //Release();
144 }
145
146 /// <summary>
147 /// Releases the internal texture resource.
148 /// </summary>
149 public void Release()
150 {
151 if (m_Texture != null)
152 CoreUtils.Destroy(m_Texture);
153 m_Texture = null;
154 }
155
156 /// <summary>
157 /// Marks the curve as dirty to trigger a redraw of the texture the next time <see cref="GetTexture"/>
158 /// is called.
159 /// </summary>
160 [MethodImpl(MethodImplOptions.AggressiveInlining)]
161 public void SetDirty()
162 {
163 m_IsTextureDirty = true;
164 }
165
166 static GraphicsFormat GetTextureFormat()
167 {
168 return GraphicsFormat.R8G8B8A8_UNorm;
169 }
170
171 /// <summary>
172 /// Gets the texture representation of this Gradient.
173 /// </summary>
174 /// <returns>A texture.</returns>
175 public Texture2D GetTexture()
176 {
177 float step = 1.0f / (float)(textureSize - 1);
178
179 if (m_Texture != null && m_Texture.width != textureSize)
180 {
181 Object.DestroyImmediate(m_Texture);
182 m_Texture = null;
183 }
184
185 if (m_Texture == null)
186 {
187 m_Texture = new Texture2D(textureSize, 1, GetTextureFormat(), TextureCreationFlags.None);
188 m_Texture.name = "GradientTexture";
189 m_Texture.hideFlags = HideFlags.HideAndDontSave;
190 m_Texture.filterMode = FilterMode.Bilinear;
191 m_Texture.wrapMode = TextureWrapMode.Clamp;
192 m_Texture.anisoLevel = 0;
193 m_IsTextureDirty = true;
194 }
195
196 if (m_IsTextureDirty)
197 {
198 var pixels = new Color[textureSize];
199
200 for (int i = 0; i < textureSize; i++)
201 pixels[i] = Evaluate(i * step);
202
203 m_Texture.SetPixels(pixels);
204 m_Texture.Apply(false, false);
205 m_IsTextureDirty = false;
206 m_Texture.IncrementUpdateCount();
207 }
208
209 return m_Texture;
210 }
211
212 /// <summary>
213 /// Evaluate a time value on the Gradient.
214 /// </summary>
215 /// <param name="time">The time within the Gradient you want to evaluate.</param>
216 /// <returns>The value of the Gradient, at the point in time specified.</returns>
217 public Color Evaluate(float time)
218 {
219 if (textureSize <= 0)
220 return Color.black;
221
222 return m_Gradient.Evaluate(time);
223 }
224
225
226 /// <summary>
227 /// Setup Gradient with an array of color keys and alpha keys.
228 /// </summary>
229 /// <param name="colorKeys">Color keys of the gradient (maximum 8 color keys).</param>
230 /// <param name="alphaKeys">Alpha keys of the gradient (maximum 8 alpha keys).</param>
231 /// <param name="mode">Indicates the color space that the gradient color keys are using.</param>
232 /// <param name="colorSpace">Controls how the gradient colors are interpolated.</param>
233 public void SetKeys(GradientColorKey[] colorKeys, GradientAlphaKey[] alphaKeys, GradientMode mode, ColorSpace colorSpace)
234 {
235 m_Gradient.SetKeys(colorKeys, alphaKeys);
236 m_Gradient.mode = mode;
237 m_Gradient.colorSpace = colorSpace;
238 // Rebuild will make the TextureGradient Dirty.
239 Rebuild(colorKeys, alphaKeys, mode, colorSpace, m_RequestedTextureSize, m_Precise);
240 }
241 }
242
243 /// <summary>
244 /// A <see cref="VolumeParameter"/> that holds a <see cref="TextureGradient"/> value.
245 /// </summary>
246 [Serializable]
247 public class TextureGradientParameter : VolumeParameter<TextureGradient>
248 {
249 /// <summary>
250 /// Creates a new <see cref="TextureGradientParameter"/> instance.
251 /// </summary>
252 /// <param name="value">The initial value to store in the parameter.</param>
253 /// <param name="overrideState">The initial override state for the parameter.</param>
254 public TextureGradientParameter(TextureGradient value, bool overrideState = false)
255 : base(value, overrideState) { }
256
257 /// <summary>
258 /// Release implementation.
259 /// </summary>
260 public override void Release() => m_Value.Release();
261
262 // TODO: TextureGradient interpolation
263 }
264}