TexturePacker.cs 32 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668
  1. using UnityEngine;
  2. using System.Collections;
  3. using System.Collections.Generic;
  4. using System.IO;
  5. #if UNITY_EDITOR
  6. using UnityEditor;
  7. #endif
  8. namespace LunarCatsStudio.SuperCombiner {
  9. /// <summary>
  10. /// This class manages the creation of the Atlas texture
  11. /// </summary>
  12. public class TexturePacker {
  13. /// <summary>
  14. /// List of combined _material
  15. /// </summary>
  16. public Material _copyedMaterials;
  17. /// <summary>
  18. /// List of combined _material for saving
  19. /// </summary>
  20. public Material _copyedToSaveMaterials;
  21. // List of original textures for each texture property
  22. private Dictionary<string, List<Texture2D>> _texturesForAtlas = new Dictionary<string, List<Texture2D>> ();
  23. // Packed textures associated with each texture property
  24. public Dictionary<string, Texture2D> _packedTextures = new Dictionary<string, Texture2D> ();
  25. /// <summary>
  26. /// The combined _index, used to differenciate the combined materials when using multiple _material function
  27. /// </summary>
  28. private int _combinedIndex = 0;
  29. /// <summary>
  30. /// The _index of this instance in the list of TexturePacker
  31. /// </summary>
  32. public int CombinedIndex
  33. {
  34. get
  35. {
  36. return _combinedIndex;
  37. }
  38. set
  39. {
  40. _combinedIndex = value;
  41. }
  42. }
  43. // List of texture property _names in shaders
  44. public Dictionary<string, string> TexturePropertyNames = new Dictionary<string, string> {
  45. {"_MainTex", "Diffuse"},
  46. {"_BumpMap", "Normal"},
  47. {"_SpecGlossMap", "Specular"},
  48. {"_ParallaxMap", "Height"},
  49. {"_OcclusionMap", "Occlusion"},
  50. {"_EmissionMap", "Emission"},
  51. {"_DetailMask", "Detail Mask"},
  52. {"_DetailAlbedoMap", "Detail Diffuse"},
  53. {"_DetailNormalMap", "Detail Normal"},
  54. {"_MetallicGlossMap", "Metallic"},
  55. {"_LightMap", "Light Map"}
  56. };
  57. // True if this material has emission
  58. private bool hasEmission = false;
  59. // The emission color
  60. private Color emissionColor = Color.black;
  61. // The list of custom properties name
  62. private List<string> customProperties = new List<string>();
  63. // List of all textures used in all children game objects
  64. private Dictionary<int, TextureImportSettings> importedTextures = new Dictionary<int, TextureImportSettings>();
  65. // Flags indicating if warning have already been registered
  66. private List<int> uvOutOfBoundWarnMesh = new List<int>();
  67. private bool differentMaterialWarning = false;
  68. // Default size of color texture packed when no main texture found in a _material
  69. private const int NO_TEXTURE_COLOR_SIZE = 256;
  70. // The maximum texture size handled by Unity
  71. private const int MAX_TEXTURE_SIZE = 16384;
  72. // The _combinedResult reference
  73. private CombinedResult combinedResult;
  74. public CombinedResult CombinedResult
  75. {
  76. set
  77. {
  78. combinedResult = value;
  79. }
  80. }
  81. /// <summary>
  82. /// Constructor
  83. /// </summary>
  84. public TexturePacker() {
  85. foreach (KeyValuePair <string, string> keyValue in TexturePropertyNames) {
  86. _texturesForAtlas.Add (keyValue.Key, new List<Texture2D> ());
  87. }
  88. }
  89. public void SetCustomPropertyNames(List<string> list) {
  90. foreach(string property in list) {
  91. if(!TexturePropertyNames.ContainsKey(property)) {
  92. TexturePropertyNames.Add(property, property);
  93. customProperties.Add(property);
  94. _texturesForAtlas.Add(property, new List<Texture2D>());
  95. }
  96. }
  97. }
  98. public Material GetCombinedMaterialToSave() {
  99. return _copyedToSaveMaterials;
  100. }
  101. public void GenerateCopyedMaterialToSave() {
  102. Material mat = new Material(_copyedMaterials);
  103. _copyedToSaveMaterials = mat;
  104. }
  105. /// <summary>
  106. /// Set the Copied Material value
  107. /// </summary>
  108. /// <param name="mat"></param>
  109. public void SetCopiedMaterial(Material mat)
  110. {
  111. _copyedMaterials = mat;
  112. }
  113. /// <summary>
  114. /// Clear all the texture data
  115. /// </summary>
  116. public void ClearTextures () {
  117. _packedTextures.Clear ();
  118. _texturesForAtlas.Clear();
  119. foreach(string property in customProperties) {
  120. TexturePropertyNames.Remove(property);
  121. }
  122. customProperties.Clear();
  123. foreach (KeyValuePair <string, string> keyValue in TexturePropertyNames) {
  124. _texturesForAtlas.Add (keyValue.Key, new List<Texture2D> ());
  125. }
  126. importedTextures.Clear();
  127. uvOutOfBoundWarnMesh.Clear();
  128. differentMaterialWarning = false;
  129. hasEmission = false;
  130. emissionColor = Color.black;
  131. }
  132. /// <summary>
  133. /// Returns the size of the texture in the atlas. If the size exceeds the maximum texture size, it will be reduced
  134. /// </summary>
  135. /// <param name="inputTextureSize"></param>
  136. /// <param name="scaleX"></param>
  137. /// <param name="scaleY"></param>
  138. /// <param name="materialName"></param>
  139. /// <returns></returns>
  140. private Vector3 GetTextureSizeInAtlas(Vector2 inputTextureSize, MaterialToCombine materialToCombine) {
  141. Rect uvBounds = materialToCombine.GetScaledAndOffsetedUVBounds();
  142. float scaleX = Mathf.Abs(uvBounds.width);
  143. float scaleY = Mathf.Abs(uvBounds.height);
  144. Vector3 size = new Vector3 (inputTextureSize.x * scaleX, inputTextureSize.y * scaleY, 1);
  145. if (size.x >= MAX_TEXTURE_SIZE || size.y >= MAX_TEXTURE_SIZE) {
  146. // If the tilled texture exceed the maximum texture size handled by Unity, we need to reduce it's size
  147. int reducingFactor = (int) Mathf.Max (Mathf.Ceil (size.x / SystemInfo.maxTextureSize), Mathf.Ceil (size.y / SystemInfo.maxTextureSize));
  148. Logger.Instance.AddLog("SuperCombiner", "Textures in material '" + materialToCombine._material.name + "' are being tiled and the total tiled size exceeds the maximum texture size for the current plateform (" + SystemInfo.maxTextureSize + "). All textures in this _material will be shrunk by " + reducingFactor + " to fit in the atlas. This could leads to a quality loss. Whenever possible, avoid combining tiled texture.", Logger.LogLevel.LOG_WARNING);
  149. if (!uvOutOfBoundWarnMesh.Contains(materialToCombine._meshHavingBiggestUVBounds.GetInstanceID()))
  150. {
  151. Debug.Log(materialToCombine._meshHavingBiggestUVBounds.GetInstanceID());
  152. combinedResult.AddWarningMessage("The mesh '" + materialToCombine._meshHavingBiggestUVBounds.name + "' has UVs way out of [0, 1] bounds, actual bounds are [" + uvBounds.width + ", " + uvBounds.height + "]\n"
  153. + "Textures associated to this mesh are therefore being tiled by " + uvBounds.width + " in width and " + uvBounds.height + " in height. Given a texture of size " + inputTextureSize
  154. + ", it's tiled size will be (" + size.x + ", " + size.y + "), which is too big (the max. texture size is (" + MAX_TEXTURE_SIZE + ", " + MAX_TEXTURE_SIZE + "))\n"
  155. + "This leads to quality loss in the atlas texture. You should consider editing the UV of mesh '" + materialToCombine._meshHavingBiggestUVBounds.name + "' with a 3D modeler tool to avoid this problem."
  156. );
  157. uvOutOfBoundWarnMesh.Add(materialToCombine._meshHavingBiggestUVBounds.GetInstanceID());
  158. }
  159. size.Set(size.x / reducingFactor, size.y / reducingFactor, reducingFactor);
  160. }
  161. return size;
  162. }
  163. /// <summary>
  164. /// Returns the texture that will be put in the atlas.
  165. /// This texture may differs from original texture in case of tiling, or if texture needs to be resized
  166. /// </summary>
  167. /// <param name="texture"></param>
  168. /// <param name="materialUVBounds"></param>
  169. /// <param name="meshUVBounds"></param>
  170. /// <param name="mat"></param>
  171. /// <param name="textureInAtlasSize"></param>
  172. /// <param name="targetTextureSize"></param>
  173. /// <param name="isMainTexture"></param>
  174. /// <returns></returns>
  175. private Texture2D CopyTexture(Texture2D texture, Rect materialUVBounds, Rect meshUVBounds, Material mat, Vector3 textureInAtlasSize, Vector2 targetTextureSize, bool isMainTexture) {
  176. int xSens = (int) Mathf.Sign (materialUVBounds.width);
  177. int ySens = (int) Mathf.Sign (materialUVBounds.height);
  178. float repeatX = Mathf.Abs(materialUVBounds.width);
  179. float repeatY = Mathf.Abs(materialUVBounds.height);
  180. Color mainColor = Color.white;
  181. if (mat.HasProperty ("_Color")) {
  182. mainColor = mat.color;
  183. }
  184. bool hasUVoutOfBound = false;
  185. if (repeatX != 1 || repeatY != 1 || materialUVBounds.position != Vector2.zero) {
  186. hasUVoutOfBound = true;
  187. }
  188. // Check Texture import settings
  189. TextureImportSettings importSettings = CheckTextureImportSettings (texture);
  190. // If an error occured, return a simple white texture
  191. if (!importSettings.isReadable)
  192. {
  193. Logger.Instance.AddLog("SuperCombiner", "The format of texture '" + texture.name + "' is not handled by Unity. Try manually setting 'Read/Write Enabled' parameter to true or converting this texture into a known format.",Logger.LogLevel.LOG_ERROR);
  194. return CreateColoredTexture2D((int)textureInAtlasSize.x, (int)textureInAtlasSize.y, Color.white);
  195. }
  196. // Get a copy of the original texture so that it remains intact
  197. Texture2D uncompressedTexture = new Texture2D(texture.width, texture.height, TextureFormat.RGBA32, false);
  198. uncompressedTexture.name = texture.name;
  199. // Copy original pixels /!\ Only possible if the texture could be red correctly
  200. uncompressedTexture.SetPixels(texture.GetPixels());
  201. if (texture.width != (int) targetTextureSize.x || texture.height != (int) targetTextureSize.y) {
  202. // Scale the texture to it's max size so that all textures from this _material share the same texture size
  203. TextureScale.Bilinear(uncompressedTexture, (int) targetTextureSize.x, (int) targetTextureSize.y);
  204. Logger.Instance.AddLog("SuperCombiner", "Texture '" + texture.name + "' will be scaled from " + GetStringTextureSize(texture.width, texture.height) + " to " + GetStringTextureSize(targetTextureSize.x, targetTextureSize.y) + " to match the size of the other textures in _material '" + mat.name + "'",Logger.LogLevel.LOG_WARNING);
  205. }
  206. // Create a new Texture2D which will contain the adjusted texture tilled and scaled
  207. Texture2D copy = new Texture2D((int) textureInAtlasSize.x, (int) textureInAtlasSize.y, uncompressedTexture.format, false);
  208. copy.name = texture.name;
  209. // If the tiled texture exceeds the maximum texture size, we have to shrink the texture
  210. if (textureInAtlasSize.z != 1 && textureInAtlasSize.z > 0) {
  211. TextureScale.Bilinear (uncompressedTexture, uncompressedTexture.width / (int) textureInAtlasSize.z, uncompressedTexture.height / (int) textureInAtlasSize.z);
  212. }
  213. if (hasUVoutOfBound)
  214. {
  215. if (Mathf.Abs (textureInAtlasSize.x - uncompressedTexture.width) > 1 || Mathf.Abs (textureInAtlasSize.y - uncompressedTexture.height) > 1) {
  216. Logger.Instance.AddLog("SuperCombiner", "Texture '" + texture.name + "' is being tiled in the atlas because mesh using it has UVs out of [0, 1] bound. The tiled size is " + GetStringTextureSize (textureInAtlasSize.x, textureInAtlasSize.y) + ".");
  217. }
  218. // If UVs are out of [0, 1] bound we have to duplicate the texture
  219. int xOffset = (int)(meshUVBounds.xMin * uncompressedTexture.width * mat.mainTextureScale.x + mat.mainTextureOffset.x * uncompressedTexture.width);
  220. int yOffset = (int)(meshUVBounds.yMin * uncompressedTexture.height * mat.mainTextureScale.y + mat.mainTextureOffset.y * uncompressedTexture.height);
  221. int i = 0, j = 0;
  222. if (xSens < 0 || ySens < 0 || ((!mainColor.Equals (Color.white)) && isMainTexture)) {
  223. for (i = 0; i < copy.width; i++) {
  224. for (j = 0; j < copy.height; j++) {
  225. copy.SetPixel (i, j, uncompressedTexture.GetPixel (xSens * (i + xOffset) % uncompressedTexture.width, ySens * (j + yOffset) % uncompressedTexture.height) * mainColor);
  226. }
  227. }
  228. } else {
  229. int blockWidth = 0, blockHeight = 0;
  230. while (i < copy.width) {
  231. int posx = (xSens * (i + xOffset) % uncompressedTexture.width + uncompressedTexture.width) % uncompressedTexture.width;
  232. blockWidth = (i + uncompressedTexture.width <= copy.width) ? uncompressedTexture.width : copy.width - i;
  233. if (posx + blockWidth > uncompressedTexture.width) {
  234. blockWidth = uncompressedTexture.width - posx;
  235. }
  236. while (j < copy.height) {
  237. int posy = (ySens * (j + yOffset) % uncompressedTexture.height + uncompressedTexture.height) % uncompressedTexture.height;
  238. blockHeight = (j + uncompressedTexture.height <= copy.height) ? uncompressedTexture.height : copy.height - j;
  239. if (posy + blockHeight > uncompressedTexture.height) {
  240. blockHeight = uncompressedTexture.height - posy;
  241. }
  242. copy.SetPixels (i, j, blockWidth, blockHeight, uncompressedTexture.GetPixels (posx, posy, blockWidth, blockHeight));
  243. j += blockHeight;
  244. }
  245. j = 0;
  246. i += blockWidth;
  247. }
  248. }
  249. }
  250. else
  251. {
  252. if (mainColor.Equals (Color.white) || !isMainTexture)
  253. {
  254. // UVs are all inside [0, 1] bound, so we can fast copy this texture
  255. copy.LoadRawTextureData (uncompressedTexture.GetRawTextureData ());
  256. }
  257. else
  258. {
  259. for (int i = 0; i < copy.width; i++)
  260. {
  261. for (int j = 0; j < copy.height; j++)
  262. {
  263. copy.SetPixel (i, j, uncompressedTexture.GetPixel (i, j) * mainColor);
  264. }
  265. }
  266. }
  267. }
  268. return copy;
  269. }
  270. /// <summary>
  271. /// Checks if all textures in the given _material has the same size.
  272. /// The returned sized is the smallest/biggest.
  273. /// </summary>
  274. /// <returns>The textures size.</returns>
  275. /// <param name="mat">Mat.</param>
  276. /// <param name="alignToSmallest">If set to <c>true</c> align to biggest.</param>
  277. private Vector2 checkTexturesSize(Material mat, bool alignToSmallest) {
  278. Vector2 maxSize = Vector2.zero;
  279. foreach (string textureProperty in TexturePropertyNames.Keys) {
  280. if (mat.HasProperty (textureProperty)) {
  281. Texture texture = mat.GetTexture (textureProperty);
  282. if (texture != null) {
  283. if (texture.width != maxSize.x || texture.height != maxSize.y) {
  284. if (alignToSmallest) {
  285. // Align to smallest
  286. if (maxSize != Vector2.zero)
  287. {
  288. Logger.Instance.AddLog("SuperCombiner", "Material '" + mat.name + "' has various textures with different size! Textures in this _material will be scaled to match the smallest one.\nTo avoid this, ensure to have all textures in a _material of the same size. Try adjusting 'Max Size' in import settings.", Logger.LogLevel.LOG_WARNING);
  289. }
  290. if (maxSize == Vector2.zero || texture.width * texture.height < maxSize.x * maxSize.y) {
  291. maxSize = new Vector2 (texture.width, texture.height);
  292. }
  293. } else {
  294. // Align to biggest
  295. if (maxSize != Vector2.zero)
  296. {
  297. Logger.Instance.AddLog("SuperCombiner", "Material '" + mat.name + "' has various textures with different size! Textures in this _material will be scaled to match the biggest one.\nTo avoid this, ensure to have all textures in a _material of the same size. Try adjusting 'Max Size' in import settings.", Logger.LogLevel.LOG_WARNING);
  298. }
  299. if (maxSize == Vector2.zero || texture.width * texture.height > maxSize.x * maxSize.y) {
  300. maxSize = new Vector2 (texture.width, texture.height);
  301. }
  302. }
  303. }
  304. }
  305. }
  306. }
  307. if (maxSize == Vector2.zero) {
  308. // If no texture found, returned a default texture size
  309. maxSize = new Vector2 (NO_TEXTURE_COLOR_SIZE, NO_TEXTURE_COLOR_SIZE);
  310. }
  311. return maxSize;
  312. }
  313. /// <summary>
  314. /// Process a new _material, adding all textures found based on the _material property list in the lists for the atlas
  315. /// </summary>
  316. /// <param name="mat">Mat.</param>
  317. /// <param name="combineMaterials">If set to <c>true</c> combine materials.</param>
  318. /// <param name="materialUVBounds">Material UV bounds.</param>
  319. /// <param name="meshUVBounds">Mesh UV bounds.</param>
  320. /// <param name="tilingFactor">Tilling factor.</param>
  321. //public void SetTextures(Material mat, bool combineMaterials, Rect materialUVBounds, Rect meshUVBounds, float tilingFactor)
  322. public void SetTextures(Material mat, bool combineMaterials, MaterialToCombine materialToCombine, float tilingFactor)
  323. {
  324. // Here we have to check if all textures have the same size, otherwise, rescale the texture with the biggest one
  325. Vector2 maxTextureSize = checkTexturesSize (mat, false);
  326. Rect materialUVBounds = materialToCombine.GetScaledAndOffsetedUVBounds();
  327. Rect meshUVBounds = materialToCombine._uvBounds;
  328. // Manage tiling factor
  329. if (tilingFactor > 1)
  330. {
  331. combinedResult._combinedMaterials[_combinedIndex].scaleFactors.Add(tilingFactor);
  332. materialUVBounds.size = Vector2.Scale(materialUVBounds.size, Vector2.one * tilingFactor);
  333. meshUVBounds.position -= new Vector2(meshUVBounds.width * (tilingFactor - 1) / 2f, meshUVBounds.height * (tilingFactor - 1) / 2f);
  334. }
  335. else
  336. {
  337. combinedResult._combinedMaterials[_combinedIndex].scaleFactors.Add(1);
  338. }
  339. combinedResult._combinedMaterials[_combinedIndex].meshUVBounds.Add(meshUVBounds);
  340. // Calculate the tiled texture size
  341. Vector3 textureInAtlasSize = GetTextureSizeInAtlas(maxTextureSize, materialToCombine);
  342. foreach(KeyValuePair<string, List<Texture2D>> keyValue in _texturesForAtlas) {
  343. if (mat.HasProperty (keyValue.Key)) {
  344. Texture texture = mat.GetTexture (keyValue.Key);
  345. if (texture != null) {
  346. Texture2D textureInAtlas = CopyTexture ((Texture2D)texture, materialUVBounds, meshUVBounds, mat, textureInAtlasSize, maxTextureSize, keyValue.Key.Equals ("_MainTex"));
  347. textureInAtlasSize = new Vector2(textureInAtlas.width, textureInAtlas.height);
  348. keyValue.Value.Add (textureInAtlas );
  349. // If texture is normal, revert modification in import settings of original texture
  350. if (importedTextures.ContainsKey(texture.GetInstanceID()))
  351. {
  352. if (importedTextures[texture.GetInstanceID()].isNormal)
  353. {
  354. #if UNITY_EDITOR
  355. string path = AssetDatabase.GetAssetPath(texture);
  356. TextureImporter textureImporter = AssetImporter.GetAtPath(path) as TextureImporter;
  357. #if UNITY_2017_1_OR_NEWER
  358. textureImporter.textureType = TextureImporterType.NormalMap;
  359. #else
  360. textureImporter.textureType = TextureImporterType.Bump;
  361. #endif
  362. AssetDatabase.ImportAsset(path, ImportAssetOptions.ForceUpdate);
  363. #endif
  364. }
  365. }
  366. } else {
  367. if (keyValue.Key.Equals ("_MainTex"))
  368. {
  369. keyValue.Value.Add (CreateColoredTexture2D ((int)textureInAtlasSize.x, (int)textureInAtlasSize.y, mat.HasProperty ("_Color") ? mat.color : Color.white));
  370. Logger.Instance.AddLog("SuperCombiner", "Creating a colored texture " + (mat.HasProperty ("_Color") ? mat.color : Color.white) + " of size " + GetStringTextureSize(textureInAtlasSize.x, textureInAtlasSize.y) + " for " + TexturePropertyNames[keyValue.Key] + " in _material '" + mat.name + "' because texture is missing.");
  371. }
  372. else if (keyValue.Key.Equals("_EmissionMap") && mat.IsKeywordEnabled("_EMISSION") && mat.GetColor("_EmissionColor") != Color.black)
  373. {
  374. keyValue.Value.Add(CreateColoredTexture2D((int)textureInAtlasSize.x, (int)textureInAtlasSize.y, Color.white));
  375. hasEmission = true;
  376. emissionColor = mat.GetColor("_EmissionColor");
  377. Logger.Instance.AddLog("SuperCombiner", "Creating a white texture of size " + GetStringTextureSize(textureInAtlasSize.x, textureInAtlasSize.y) + " for " + TexturePropertyNames[keyValue.Key] + " in material '" + mat.name + "' because texture is missing.");
  378. }
  379. else if (keyValue.Value.Count > 0) {
  380. // Add color texture because this texture is missing in _material
  381. keyValue.Value.Add (CreateColoredTexture2D ((int) textureInAtlasSize.x, (int)textureInAtlasSize.y, DefaultColoredTexture.GetDefaultTextureColor(keyValue.Key)));
  382. Logger.Instance.AddLog("SuperCombiner", "Creating a colored texture " + DefaultColoredTexture.GetDefaultTextureColor (keyValue.Key) + " of size " + GetStringTextureSize(textureInAtlasSize.x, textureInAtlasSize.y) + " for " + TexturePropertyNames[keyValue.Key] + " in _material '" + mat.name + "' because texture is missing.");
  383. }
  384. }
  385. } else {
  386. // The _material doesn't have this texture property
  387. if (keyValue.Key.Equals ("_MainTex")) {
  388. keyValue.Value.Add (CreateColoredTexture2D ((int)maxTextureSize.x, (int)maxTextureSize.y, mat.HasProperty("_Color") ? mat.color : Color.white));
  389. Logger.Instance.AddLog("SuperCombiner", "Creating a colored texture " + DefaultColoredTexture.GetDefaultTextureColor (keyValue.Key) + " of size " + GetStringTextureSize(textureInAtlasSize.x, textureInAtlasSize.y) + " for " + TexturePropertyNames[keyValue.Key] + " in _material '" + mat.name + "' because texture is missing.");
  390. } else if (keyValue.Value.Count > 0) {
  391. // Error here because we found materials with textures properties that don't match!
  392. Logger.Instance.AddLog("SuperCombiner", "Found materials with properties that don't match. Material '" + mat.name + "' do not have the property " + keyValue.Key + " whereas others material don't, combining materials with differents properties together may lead to inconsistent results.", Logger.LogLevel.LOG_WARNING);
  393. if (!differentMaterialWarning)
  394. {
  395. combinedResult.AddWarningMessage("Several materials where found that don't share the same property, this can lead to inconsistent results. You should always combine materials of the same type or which share the same properties, consider splitting your GameObjects to combine by types of materials or use multi material feature to group them.");
  396. differentMaterialWarning = true;
  397. }
  398. }
  399. }
  400. }
  401. }
  402. private string GetStringTextureSize(float width, float height) {
  403. return (int) width + "x" + (int) height + " pixels";
  404. }
  405. /// <summary>
  406. /// Creates a colored texture2d.
  407. /// </summary>
  408. /// <returns>The colored texture2 d.</returns>
  409. /// <param name="width">Width.</param>
  410. /// <param name="height">Height.</param>
  411. /// <param name="color">Color.</param>
  412. private Texture2D CreateColoredTexture2D(int width, int height, Color color) {
  413. Texture2D tex = new Texture2D(width, height, TextureFormat.RGBA32, false);
  414. for(int i=0; i<width; i++) {
  415. for(int j=0; j<height; j++) {
  416. tex.SetPixel(i, j, color);
  417. }
  418. }
  419. tex.Apply();
  420. return tex;
  421. }
  422. TextureImportSettings CheckTextureImportSettings(Texture2D texture)
  423. {
  424. TextureImportSettings importSettings;
  425. #if UNITY_EDITOR
  426. string path = AssetDatabase.GetAssetPath(texture);
  427. // Check if textures settings has already been setted up
  428. if (importedTextures.ContainsKey(texture.GetInstanceID()))
  429. {
  430. importSettings = importedTextures[texture.GetInstanceID()];
  431. if (!importSettings.isNormal)
  432. {
  433. return importSettings;
  434. }
  435. }
  436. else
  437. {
  438. importSettings = new TextureImportSettings();
  439. }
  440. TextureImporter textureImporter = AssetImporter.GetAtPath(path) as TextureImporter;
  441. if (textureImporter != null)
  442. {
  443. if (!textureImporter.isReadable)
  444. {
  445. textureImporter.isReadable = true;
  446. AssetDatabase.ImportAsset(path, ImportAssetOptions.ForceUpdate);
  447. }
  448. #if UNITY_2017_1_OR_NEWER
  449. if (textureImporter.textureType == TextureImporterType.NormalMap)
  450. {
  451. textureImporter.textureType = TextureImporterType.Default;
  452. AssetDatabase.ImportAsset(path, ImportAssetOptions.ForceUpdate);
  453. importSettings.isNormal = true;
  454. }
  455. #endif
  456. }
  457. else
  458. {
  459. try
  460. {
  461. texture.GetPixel(0, 0);
  462. }
  463. catch
  464. {
  465. importSettings.isReadable = false;
  466. Logger.Instance.AddLog("SuperCombiner", "The texture '" + texture.name + "' has a format not handled by Unity, the result in atlas may be inconsistent.", Logger.LogLevel.LOG_WARNING);
  467. }
  468. }
  469. #else
  470. importSettings = new TextureImportSettings();
  471. #endif
  472. importSettings.isReadable = true;
  473. if (!importedTextures.ContainsKey(texture.GetInstanceID()))
  474. {
  475. importedTextures.Add(texture.GetInstanceID(), importSettings);
  476. }
  477. return importSettings;
  478. }
  479. private void CheckTexturesConformity() {
  480. // Calculate the maximum texture count found on a _material
  481. int maxTextureCount = 0;
  482. foreach (KeyValuePair<string, List<Texture2D>> keyValue in _texturesForAtlas) {
  483. if (keyValue.Value.Count > 0) {
  484. maxTextureCount = Mathf.Max(maxTextureCount, keyValue.Value.Count);
  485. }
  486. }
  487. if (maxTextureCount > 0) {
  488. foreach (KeyValuePair<string, List<Texture2D>> keyValue in _texturesForAtlas) {
  489. if (keyValue.Value.Count > 0 && keyValue.Value.Count < maxTextureCount) {
  490. // Add the missing textures with colored one
  491. int numberTexturesMissing = (maxTextureCount - keyValue.Value.Count);
  492. for(int i=0; i < numberTexturesMissing; i++) {
  493. int width = _texturesForAtlas ["_MainTex"] [i].width;
  494. int height = _texturesForAtlas ["_MainTex"] [i].height;
  495. keyValue.Value.Insert(0, CreateColoredTexture2D (width, height, DefaultColoredTexture.GetDefaultTextureColor(keyValue.Key)));
  496. Logger.Instance.AddLog("SuperCombiner", "Creating a colored texture " + DefaultColoredTexture.GetDefaultTextureColor(keyValue.Key) + " of size " + GetStringTextureSize(width, height) + " for " + TexturePropertyNames[keyValue.Key] + " because texture is missing.");
  497. }
  498. }
  499. }
  500. }
  501. }
  502. /// <summary>
  503. /// Create all the textures atlas.
  504. /// </summary>
  505. /// <param name="textureAtlasSize">Texture atlas size.</param>
  506. /// <param name="combineMaterials">If set to <c>true</c> combine materials.</param>
  507. /// <param name="name">Name.</param>
  508. public void PackTextures(int textureAtlasSize, int atlasPadding, bool combineMaterials, string name) {
  509. // Check if all materials have been contributing the same texture quantity. If not, complete with a generated default colored texture
  510. CheckTexturesConformity ();
  511. int progressBarProgression = 0;
  512. foreach (KeyValuePair<string, List<Texture2D>> keyValue in _texturesForAtlas) {
  513. if (keyValue.Value.Count > 0) {
  514. Texture2D packedTexture = new Texture2D (textureAtlasSize, textureAtlasSize, TextureFormat.RGBA32, false);
  515. packedTexture.Resize(textureAtlasSize, textureAtlasSize);
  516. Rect[] uvs = packedTexture.PackTextures(keyValue.Value.ToArray(), atlasPadding, textureAtlasSize);
  517. _packedTextures.Add (keyValue.Key, packedTexture);
  518. if(keyValue.Key.Equals("_MainTex")) {
  519. combinedResult._combinedMaterials[_combinedIndex].uvs = uvs;
  520. }
  521. }
  522. #if UNITY_EDITOR
  523. // UI Progress bar display in Editor
  524. EditorUtility.DisplayProgressBar("Super Combiner", "Packing textures..." + progressBarProgression + " / " + _texturesForAtlas.Count, progressBarProgression / (float) _texturesForAtlas.Count);
  525. #endif
  526. progressBarProgression++;
  527. }
  528. // Create the unique combined _material
  529. if (combineMaterials) {
  530. Material mat;
  531. if (combinedResult._originalMaterialList[_combinedIndex].Count > 0) {
  532. mat = new Material (combinedResult._originalMaterialList[_combinedIndex][combinedResult._originalReferenceMaterial[_combinedIndex]]._material.shader);
  533. mat.CopyPropertiesFromMaterial (combinedResult._originalMaterialList [_combinedIndex] [combinedResult._originalReferenceMaterial [_combinedIndex]]._material);//_combinedResult._originalMaterialList[_combinedIndex][0]._material);
  534. } else {
  535. Logger.Instance.AddLog("SuperCombiner", "No reference _material to create the combined _material. A default standard shader _material will be created", Logger.LogLevel.LOG_ERROR);
  536. mat = new Material (Shader.Find("Standard"));
  537. }
  538. mat.mainTextureOffset = Vector2.zero;
  539. mat.mainTextureScale = Vector2.one;
  540. mat.color = Color.white;
  541. mat.name = name + "_material";
  542. foreach (KeyValuePair<string, Texture2D> keyValue in _packedTextures) {
  543. mat.SetTexture (keyValue.Key, keyValue.Value);
  544. }
  545. if (hasEmission)
  546. {
  547. mat.SetColor("_EmissionColor", emissionColor);
  548. mat.EnableKeyword("_EMISSION");
  549. }
  550. _copyedMaterials = mat;
  551. // Assign the new _material to result
  552. combinedResult.SetCombinedMaterial(mat, _combinedIndex, false);
  553. }
  554. else
  555. {
  556. // Create all transformed materials
  557. /*for (int i=0; i<_combinedResult._originalMaterialList.Count; i++) {
  558. Material mat = new Material (_combinedResult._originalMaterialList [i].shader);
  559. _copyedMaterials.Add (mat);
  560. _copyedMaterials [i].CopyPropertiesFromMaterial (_combinedResult._originalMaterialList [i]);
  561. foreach (KeyValuePair<string, Texture2D> keyValue in _packedTextures) {
  562. _copyedMaterials[i].SetTexture (keyValue.Key, keyValue.Value);
  563. }
  564. _copyedMaterials [i].name = name + "_" + _combinedResult._originalMaterialList [i].name;
  565. materialsDictionnary.Add(_copyedMaterials [i].name, _copyedMaterials [i]);
  566. }*/
  567. }
  568. }
  569. /// <summary>
  570. /// Saves all atlas textures in disk
  571. /// </summary>
  572. /// <param name="folder"></param>
  573. /// <param name="name"></param>
  574. public void SaveTextures(string folder, string name) {
  575. #if UNITY_EDITOR
  576. foreach (KeyValuePair<string, Texture2D> keyValue in _packedTextures) {
  577. if (keyValue.Value != null) {
  578. byte[] bytes = keyValue.Value.EncodeToPNG();
  579. if (bytes != null) {
  580. string textureName = keyValue.Key;
  581. TexturePropertyNames.TryGetValue (keyValue.Key, out textureName);
  582. File.WriteAllBytes (GetTextureFilePathName(folder, name, textureName, combinedResult._combinedMaterials[_combinedIndex].displayedIndex), bytes);
  583. } else {
  584. Logger.Instance.AddLog("SuperCombiner", "Error, please change compression mode of texture in import settings to 'none' and try combining again", Logger.LogLevel.LOG_ERROR);
  585. }
  586. }
  587. }
  588. #endif
  589. }
  590. /// <summary>
  591. /// Returns the path + name of the atlas texture to be saved
  592. /// </summary>
  593. /// <param name="folder"></param>
  594. /// <param name="sessionName"></param>
  595. /// <param name="textureName"></param>
  596. /// <param name="displayedIndex"></param>
  597. /// <returns></returns>
  598. public string GetTextureFilePathName(string folder, string sessionName, string textureName, int displayedIndex)
  599. {
  600. return folder + "/Textures/" + sessionName + "_" + textureName + "_" + displayedIndex + ".png";
  601. }
  602. }
  603. }