Tanoda
AtlasBuilder.cs
Go to the documentation of this file.
1/******************************************************************************
2 * Copyright (C) Ultraleap, Inc. 2011-2020. *
3 * *
4 * Use subject to the terms of the Apache License 2.0 available at *
5 * http://www.apache.org/licenses/LICENSE-2.0, or another agreement *
6 * between Ultraleap and you, your company or other organization. *
7 ******************************************************************************/
8
9using System;
10using System.Collections.Generic;
11using UnityEngine;
12using UnityEngine.Rendering;
13using Leap.Unity.Query;
15
17
22 [Serializable]
23 public class AtlasUvs {
24
25 [Serializable]
26 public class TextureToRect : SerializableDictionary<UnityEngine.Object, Rect> { }
27
28 [SerializeField]
29 private TextureToRect _channel0 = new TextureToRect();
30 [SerializeField]
31 private TextureToRect _channel1 = new TextureToRect();
32 [SerializeField]
33 private TextureToRect _channel2 = new TextureToRect();
34 [SerializeField]
35 private TextureToRect _channel3 = new TextureToRect();
36
37 [SerializeField]
38 private Rect[] _nullRects = new Rect[4];
39
47 public Rect GetRect(int channel, UnityEngine.Object key) {
48 if (key == null) {
49 return _nullRects[channel];
50 } else {
51 Rect r;
52 getChannel(channel).TryGetValue(key, out r);
53 return r;
54 }
55 }
56
62 public void SetRect(int channel, UnityEngine.Object key, Rect rect) {
63 if (key == null) {
64 _nullRects[channel] = rect;
65 } else {
66 getChannel(channel)[key] = rect;
67 }
68 }
69
70 private TextureToRect getChannel(int channel) {
71 switch (channel) {
72 case 0:
73 return _channel0;
74 case 1:
75 return _channel1;
76 case 2:
77 return _channel2;
78 case 3:
79 return _channel3;
80 default:
81 throw new Exception();
82 }
83 }
84 }
85
86 [Serializable]
87 public class AtlasBuilder {
88
89 [Tooltip("When non-zero, extends each texture by a certain number of pixels, filling in the space with texture data based on the texture wrap mode.")]
90 [MinValue(0)]
91 [EditTimeOnly, SerializeField]
92 private int _border = 0;
93
94 [Tooltip("When non-zero, adds an amount of empty space between each texture.")]
95 [MinValue(0)]
96 [EditTimeOnly, SerializeField]
97 private int _padding = 0;
98
99 [Tooltip("Should the atlas have mip maps?")]
100 [EditTimeOnly, SerializeField]
101 private bool _mipMap = true;
102
103 [Tooltip("The filter mode that should be used for the atlas.")]
104 [EditTimeOnly, SerializeField]
105 private FilterMode _filterMode = FilterMode.Bilinear;
106
107 [Tooltip("The texture format that should be used for the atlas.")]
108 [EditTimeOnly, SerializeField]
109 private TextureFormat _format = TextureFormat.ARGB32;
110
111 [Tooltip("The maximum atlas size in pixels.")]
112 [MinValue(16)]
113 [MaxValue(8192)]
114 [EditTimeOnly, SerializeField]
115 private int _maxAtlasSize = 4096;
116
117 [Tooltip("Add textures to this array to ensure that they are always present in the atlas.")]
118 [SerializeField]
119 private TextureReference[] _extraTextures;
120
124 public bool isDirty {
125 get {
126 return _currHash != _atlasHash;
127 }
128 }
129
130 private static Material _cachedBlitMaterial = null;
131 private static void enableBlitPass(Texture tex) {
132 if (_cachedBlitMaterial == null) {
133 _cachedBlitMaterial = new Material(Shader.Find("Hidden/Leap Motion/Graphic Renderer/InternalPack"));
134 _cachedBlitMaterial.hideFlags = HideFlags.HideAndDontSave;
135 }
136 _cachedBlitMaterial.mainTexture = tex;
137 _cachedBlitMaterial.SetPass(0);
138 }
139
140 private List<LeapTextureFeature> _features = new List<LeapTextureFeature>();
141 private Hash _atlasHash = 1;
142 private Hash _currHash = 0;
143
149 public void UpdateTextureList(List<LeapTextureFeature> textureFeatures) {
150 _features.Clear();
151 _features.AddRange(textureFeatures);
152
153 _currHash = new Hash() {
154 _border,
155 _padding,
156 _mipMap,
157 _filterMode,
158 _format,
159 _maxAtlasSize
160 };
161
162 if (_extraTextures == null) {
163 _extraTextures = new TextureReference[0];
164 }
165
166 for (int i = 0; i < _extraTextures.Length; i++) {
167 switch (_extraTextures[i].channel) {
168 case UVChannelFlags.UV0:
169 case UVChannelFlags.UV1:
170 case UVChannelFlags.UV2:
171 case UVChannelFlags.UV3:
172 break;
173 default:
174 _extraTextures[i].channel = UVChannelFlags.UV0;
175 break;
176 }
177 }
178
179 foreach (var extra in _extraTextures) {
180 _currHash.Add(extra.texture);
181 _currHash.Add(extra.channel);
182 }
183
184 foreach (var feature in _features) {
185 _currHash.Add(feature.channel);
186 foreach (var dataObj in feature.featureData) {
187 _currHash.Add(dataObj.texture);
188 }
189 }
190 }
191
198 public void RebuildAtlas(ProgressBar progress, out Texture2D[] packedTextures, out AtlasUvs channelMapping) {
199 if (!Utils.IsCompressible(_format)) {
200 Debug.LogWarning("Format " + _format + " is not compressible! Using ARGB32 instead.");
201 _format = TextureFormat.ARGB32;
202 }
203
204 _atlasHash = _currHash;
205
206 packedTextures = new Texture2D[_features.Count];
207 channelMapping = new AtlasUvs();
208
209 mainProgressLoop(progress, packedTextures, channelMapping);
210
211 //Clear cache
212 foreach (var texture in _cachedProcessedTextures.Values) {
213 UnityEngine.Object.DestroyImmediate(texture);
214 }
215 _cachedProcessedTextures.Clear();
216
217 foreach (var texture in _cachedDefaultTextures.Values) {
218 UnityEngine.Object.DestroyImmediate(texture);
219 }
220 _cachedDefaultTextures.Clear();
221 }
222
223 private void mainProgressLoop(ProgressBar progress, Texture2D[] packedTextures, AtlasUvs channelMapping) {
224 progress.Begin(5, "", "", () => {
225 foreach (var channel in MeshUtil.allUvChannels) {
226 progress.Begin(1, "", channel + ": ", () => {
227 doPerChannelPack(progress, channel, packedTextures, channelMapping);
228 });
229 }
230
231 finalizeTextures(progress, packedTextures);
232 });
233 }
234
235 private void doPerChannelPack(ProgressBar progress, UVChannelFlags channel, Texture2D[] packedTextures, AtlasUvs channelMapping) {
236 var mainTextureFeature = _features.Query().FirstOrDefault(f => f.channel == channel);
237 if (mainTextureFeature == null) return;
238
239 Texture2D defaultTexture, packedTexture;
240 Texture2D[] rawTextureArray, processedTextureArray;
241
242 progress.Step("Prepare " + channel);
243 prepareForPacking(mainTextureFeature, out defaultTexture,
244 out packedTexture,
245 out rawTextureArray,
246 out processedTextureArray);
247
248 progress.Step("Pack " + channel);
249 var packedRects = packedTexture.PackTextures(processedTextureArray,
250 _padding,
251 _maxAtlasSize,
252 makeNoLongerReadable: false);
253
254 packedTexture.Apply(updateMipmaps: true, makeNoLongerReadable: true);
255 packedTextures[_features.IndexOf(mainTextureFeature)] = packedTexture;
256
257 packSecondaryTextures(progress, channel, mainTextureFeature, packedTexture, packedRects, packedTextures);
258
259 //Correct uvs to account for the added border
260 for (int i = 0; i < packedRects.Length; i++) {
261 float dx = 1.0f / packedTexture.width;
262 float dy = 1.0f / packedTexture.height;
263 Rect r = packedRects[i];
264
265 if (processedTextureArray[i] != defaultTexture) {
266 dx *= _border;
267 dy *= _border;
268 }
269
270 r.x += dx;
271 r.y += dy;
272 r.width -= dx * 2;
273 r.height -= dy * 2;
274 packedRects[i] = r;
275 }
276
277 for (int i = 0; i < rawTextureArray.Length; i++) {
278 channelMapping.SetRect(channel.Index(), rawTextureArray[i], packedRects[i]);
279 }
280 }
281
282 private void packSecondaryTextures(ProgressBar progress, UVChannelFlags channel, LeapTextureFeature mainFeature, Texture2D packedTexture, Rect[] packedRects, Texture2D[] packedTextures) {
283 //All texture features that are NOT the main texture do not get their own atlas step
284 //They are simply copied into a new texture
285 var nonMainFeatures = _features.Query().Where(f => f.channel == channel).Skip(1).ToList();
286
287 progress.Begin(nonMainFeatures.Count, "", "Copying secondary textures: ", () => {
288 foreach (var secondaryFeature in nonMainFeatures) {
289
290 RenderTexture secondaryRT = new RenderTexture(packedTexture.width, packedTexture.height, 0, RenderTextureFormat.ARGB32, RenderTextureReadWrite.Linear);
291
292 RenderTexture.active = secondaryRT;
293 GL.Clear(clearDepth: false, clearColor: true, backgroundColor: Color.black);
294 GL.LoadPixelMatrix(0, 1, 0, 1);
295
296 progress.Begin(secondaryFeature.featureData.Count, "", secondaryFeature.propertyName, () => {
297 for (int i = 0; i < secondaryFeature.featureData.Count; i++) {
298 var mainTexture = mainFeature.featureData[i].texture;
299 if (mainTexture == null) {
300 progress.Step();
301 continue;
302 }
303
304 var secondaryTexture = secondaryFeature.featureData[i].texture;
305 if (secondaryTexture == null) {
306 progress.Step();
307 continue;
308 }
309
310 progress.Step(secondaryTexture.name);
311
312 Rect rect = packedRects[i];
313
314 //Use mainTexture instead of secondaryTexture here to calculate correct border to line up with main texture
315 float borderDX = _border / (float)mainTexture.width;
316 float borderDY = _border / (float)mainTexture.height;
317
318 drawTexture(secondaryTexture, secondaryRT, rect, borderDX, borderDY);
319 }
320 });
321
322 packedTextures[_features.IndexOf(secondaryFeature)] = convertToTexture2D(secondaryRT, mipmap: false);
323 }
324 });
325 }
326
327 private void finalizeTextures(ProgressBar progress, Texture2D[] packedTextures) {
328 progress.Begin(packedTextures.Length, "", "Finalizing ", () => {
329 for (int i = 0; i < packedTextures.Length; i++) {
330 progress.Begin(2, "", _features[i].propertyName + ": ", () => {
331 Texture2D tex = packedTextures[i];
332 RenderTexture rt = new RenderTexture(tex.width, tex.height, 0, RenderTextureFormat.ARGB32, RenderTextureReadWrite.Linear);
333
334 GL.LoadPixelMatrix(0, 1, 0, 1);
335 drawTexture(tex, rt, new Rect(0, 0, 1, 1), 0, 0);
336
337 progress.Step("Copying Texture");
338 tex = convertToTexture2D(rt, _mipMap);
339
340 progress.Step("Compressing Texture");
341
342#if UNITY_EDITOR
343#if UNITY_2018_3_OR_NEWER
344 UnityEditor.EditorUtility.CompressTexture(tex, _format, UnityEditor.TextureCompressionQuality.Best);
345#else
346 UnityEditor.EditorUtility.CompressTexture(tex, _format, TextureCompressionQuality.Best);
347#endif
348#endif
349 tex.filterMode = _filterMode;
350
351 progress.Step("Updating Texture");
352 //keep the texture as readable because the user might want to do things with the texture!
353 tex.Apply(updateMipmaps: true, makeNoLongerReadable: false);
354
355 packedTextures[i] = tex;
356 });
357 }
358 });
359 }
360
361 private void prepareForPacking(LeapTextureFeature feature,
362 out Texture2D defaultTexture,
363 out Texture2D packedTexture,
364 out Texture2D[] rawTextureArray,
365 out Texture2D[] processedTextureArray) {
366 if (_extraTextures == null) {
367 _extraTextures = new TextureReference[0];
368 }
369
370 rawTextureArray = feature.featureData.Query().
371 Select(dataObj => dataObj.texture).
372 Concat(_extraTextures.Query().
373 Where(p => p.channel == feature.channel).
374 Select(p => p.texture)).
375 ToArray();
376
377 processedTextureArray = rawTextureArray.Query().
378 Select(t => processTexture(t)).
379 ToArray();
380
381 defaultTexture = getDefaultTexture(Color.white); //TODO, pull color from feature data
382 for (int i = 0; i < processedTextureArray.Length; i++) {
383 if (processedTextureArray[i] == null) {
384 processedTextureArray[i] = defaultTexture;
385 }
386 }
387
388 #if UNITY_2018_2_OR_NEWER
389 packedTexture = new Texture2D(1, 1, TextureFormat.ARGB32, mipChain: false,
390 linear: true);
391 #else
392 packedTexture = new Texture2D(1, 1, TextureFormat.ARGB32, mipmap: false,
393 linear: true);
394 #endif
395 packedTexture.filterMode = _filterMode;
396 }
397
398 private Dictionary<Texture2D, Texture2D> _cachedProcessedTextures = new Dictionary<Texture2D, Texture2D>();
399 private Texture2D processTexture(Texture2D source) {
400 using (new ProfilerSample("Process Texture")) {
401 if (source == null) {
402 return null;
403 }
404
405 Texture2D processed;
406 if (_cachedProcessedTextures.TryGetValue(source, out processed)) {
407 return processed;
408 }
409
410 RenderTexture destRT = new RenderTexture(source.width + _border * 2, source.height + _border * 2, 0, RenderTextureFormat.ARGB32, RenderTextureReadWrite.Linear);
411
412 GL.LoadPixelMatrix(0, 1, 0, 1);
413 drawTexture(source, destRT, new Rect(0, 0, 1, 1), _border / (float)source.width, _border / (float)source.height);
414
415 processed = convertToTexture2D(destRT, mipmap: false);
416 _cachedProcessedTextures[source] = processed;
417 return processed;
418 }
419 }
420
421 private void drawTexture(Texture2D source, RenderTexture dst, Rect rect, float borderDX, float borderDY) {
422 enableBlitPass(source);
423 RenderTexture.active = dst;
424
425 GL.Begin(GL.QUADS);
426 GL.TexCoord(new Vector2(0 - borderDX, 0 - borderDY));
427 GL.Vertex(rect.Corner00());
428 GL.TexCoord(new Vector2(1 + borderDX, 0 - borderDY));
429 GL.Vertex(rect.Corner10());
430 GL.TexCoord(new Vector2(1 + borderDX, 1 + borderDY));
431 GL.Vertex(rect.Corner11());
432 GL.TexCoord(new Vector2(0 - borderDX, 1 + borderDY));
433 GL.Vertex(rect.Corner01());
434 GL.End();
435 }
436
437 private Texture2D convertToTexture2D(RenderTexture source, bool mipmap) {
438 Texture2D tex = new Texture2D(source.width, source.height, TextureFormat.ARGB32, mipmap, linear: true);
439
440 RenderTexture.active = source;
441 tex.ReadPixels(new Rect(0, 0, tex.width, tex.height), 0, 0);
442 tex.Apply(updateMipmaps: false, makeNoLongerReadable: false);
443 RenderTexture.active = null;
444
445 source.Release();
446 UnityEngine.Object.DestroyImmediate(source);
447
448 return tex;
449 }
450
451 private Dictionary<Color, Texture2D> _cachedDefaultTextures = new Dictionary<Color, Texture2D>();
452 private Texture2D getDefaultTexture(Color color) {
453 Texture2D texture;
454 if (!_cachedDefaultTextures.TryGetValue(color, out texture)) {
455
456 #if UNITY_2018_2_OR_NEWER
457 texture = new Texture2D(3, 3, TextureFormat.ARGB32, mipChain: false);
458 #else
459 texture = new Texture2D(3, 3, TextureFormat.ARGB32, mipmap: false);
460 #endif
461
462 texture.SetPixels(new Color[3 * 3].Fill(color));
463 _cachedDefaultTextures[color] = texture;
464 }
465 return texture;
466 }
467
468 [Serializable]
469 public class TextureReference {
470 public Texture2D texture;
471 public UVChannelFlags channel = UVChannelFlags.UV0;
472 }
473 }
474}
UnityEngine.Debug Debug
Definition: TanodaServer.cs:19
UnityEngine.Color Color
Definition: TestScript.cs:32
void RebuildAtlas(ProgressBar progress, out Texture2D[] packedTextures, out AtlasUvs channelMapping)
Actually perform the build for the atlas. This method outputs the atlas textures, and the atlas uvs t...
bool isDirty
Returns whether or not the results built by this atlas have become invalid.
void UpdateTextureList(List< LeapTextureFeature > textureFeatures)
Updates the internal list of textures given some texture features to build an atlas for....
A class that contains mapping information that specifies how a texture is packed into an atlas.
Definition: AtlasBuilder.cs:23
Rect GetRect(int channel, UnityEngine.Object key)
Given a texture object and a uv channel, return the rect that this texture occupies within the atlas....
Definition: AtlasBuilder.cs:47
void SetRect(int channel, UnityEngine.Object key, Rect rect)
Given a texture object and a uv channel, store into this data structure the rect that the texture tak...
Definition: AtlasBuilder.cs:62
This class allows you to easily give feedback of an action as it completes.
Definition: ProgressBar.cs:66
void Begin(int sections, string title, string info, Action action)
Begins a new chunk. If this call is made from within a chunk it will generate a sub-chunk that repres...
Definition: ProgressBar.cs:109
In order to have this class be serialized, you will always need to create your own non-generic versio...
bool TryGetValue(TKey key, out TValue value)