RuntimeTextureArrayLoader.cs 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351
  1. using System.Collections.Generic;
  2. using UnityEngine;
  3. namespace MTE
  4. {
  5. /// <summary>
  6. /// Create <see cref="Texture2DArray"/> according to a <see cref="TextureArraySettings"/>
  7. /// and assign to renderer's material.
  8. ///
  9. /// The material must use a compatible TextureArray shader.
  10. /// </summary>
  11. public class RuntimeTextureArrayLoader : MonoBehaviour
  12. {
  13. public TextureArraySettings settings;
  14. private Texture2DArray array0, array1;
  15. [System.NonSerialized]
  16. private bool loaded;
  17. private void Start()
  18. {
  19. if (!settings)
  20. {
  21. throw new System.ArgumentNullException(nameof(settings));
  22. }
  23. #if UNITY_EDITOR
  24. Debug.Log($"Creating TextureArray according to TextureArraySettings {settings.name}...");
  25. #endif
  26. //create in-memory texture arrays
  27. if (!CreateTextureArrays(settings, out array0, out array1))
  28. {
  29. Debug.LogError($"Failed to create texture array {settings.TextureArrayName}.");
  30. return;
  31. }
  32. array0.name += "(temporary in-memory)";
  33. array1.name += "(temporary in-memory)";
  34. //assign to corresponding material properties
  35. var meshRenderer = GetComponent<MeshRenderer>();
  36. if (!meshRenderer)
  37. {
  38. throw new MissingComponentException(nameof(MeshRenderer));
  39. }
  40. switch (settings.textureMode)
  41. {
  42. case TextureArrayMode.Color:
  43. meshRenderer.sharedMaterial.SetTexture(
  44. TextureArrayShaderPropertyNames.AlbedoArrayPropertyName, array0);
  45. break;
  46. case TextureArrayMode.ColorAndNormal:
  47. meshRenderer.sharedMaterial.SetTexture(
  48. TextureArrayShaderPropertyNames.AlbedoArrayPropertyName, array0);
  49. meshRenderer.sharedMaterial.SetTexture(
  50. TextureArrayShaderPropertyNames.NormalArrayPropertyName, array1);
  51. break;
  52. case TextureArrayMode.PBR:
  53. meshRenderer.sharedMaterial.SetTexture(
  54. TextureArrayShaderPropertyNames.AlbedoArrayPropertyName, array0);
  55. meshRenderer.sharedMaterial.SetTexture(
  56. TextureArrayShaderPropertyNames.RoughnessNormalAOArrayPropertyName, array1);
  57. break;
  58. default:
  59. throw new System.ArgumentOutOfRangeException();
  60. }
  61. #if UNITY_EDITOR
  62. Debug.Log("Finished creating TextureArray.");
  63. #endif
  64. loaded = true;
  65. }
  66. private void OnDestroy()
  67. {
  68. #if UNITY_EDITOR
  69. if (loaded)
  70. {
  71. UnloadInEditor();
  72. }
  73. #endif
  74. }
  75. #if UNITY_EDITOR
  76. public void LoadInEditor()
  77. {
  78. if (loaded)
  79. {
  80. return;
  81. }
  82. Start();
  83. }
  84. public void UnloadInEditor()
  85. {
  86. var meshRenderer = GetComponent<MeshRenderer>();
  87. if (!meshRenderer)
  88. {
  89. throw new MissingComponentException(nameof(MeshRenderer));
  90. }
  91. switch (settings.textureMode)
  92. {
  93. case TextureArrayMode.Color:
  94. meshRenderer.sharedMaterial.SetTexture(
  95. TextureArrayShaderPropertyNames.AlbedoArrayPropertyName, null);
  96. break;
  97. case TextureArrayMode.ColorAndNormal:
  98. meshRenderer.sharedMaterial.SetTexture(
  99. TextureArrayShaderPropertyNames.AlbedoArrayPropertyName, null);
  100. meshRenderer.sharedMaterial.SetTexture(
  101. TextureArrayShaderPropertyNames.NormalArrayPropertyName, null);
  102. break;
  103. case TextureArrayMode.PBR:
  104. meshRenderer.sharedMaterial.SetTexture(
  105. TextureArrayShaderPropertyNames.AlbedoArrayPropertyName, null);
  106. meshRenderer.sharedMaterial.SetTexture(
  107. TextureArrayShaderPropertyNames.RoughnessNormalAOArrayPropertyName, null);
  108. break;
  109. default:
  110. throw new System.ArgumentOutOfRangeException();
  111. }
  112. DestroyImmediate(array0);
  113. DestroyImmediate(array1);
  114. array0 = array1 = null;
  115. loaded = false;
  116. }
  117. #endif
  118. #region Implementation Details
  119. private static bool CreateTextureArrays(TextureArraySettings settings,
  120. out Texture2DArray array0, out Texture2DArray array1)
  121. {
  122. array0 = array1 = null;
  123. if (settings.textureMode == TextureArrayMode.Color)
  124. {
  125. Texture2DArray colorArray;
  126. CreateColorTextureArray(settings, out colorArray);
  127. if (!colorArray)
  128. {
  129. Debug.LogError("Failed to create colorArray.");
  130. return false;
  131. }
  132. array0 = colorArray;
  133. }
  134. else if (settings.textureMode == TextureArrayMode.ColorAndNormal)
  135. {
  136. Texture2DArray colorArray, normalArray;
  137. CreateColorTextureArray(settings, out colorArray);
  138. CreateNormalTextureArray(settings, out normalArray);
  139. if (!normalArray)
  140. {
  141. Debug.LogError("Failed to create colorArray or normalArray");
  142. return false;
  143. }
  144. array0 = colorArray;
  145. array1 = normalArray;
  146. }
  147. else//TextureArrayMode.PBR
  148. {
  149. Texture2DArray albedoArray, roughNormalAOArray;
  150. CreatePBRTextureArray(settings, out albedoArray, out roughNormalAOArray);
  151. if (!roughNormalAOArray)
  152. {
  153. Debug.LogError("Failed to create albedoArray.");
  154. return false;
  155. }
  156. if (!roughNormalAOArray)
  157. {
  158. Debug.LogError("Failed to create roughNormalAOArray.");
  159. return false;
  160. }
  161. array0 = albedoArray;
  162. array1 = roughNormalAOArray;
  163. }
  164. return true;
  165. }
  166. private static Texture2DArray Generate(IList<Texture2D> textures, TextureFormat format)
  167. {
  168. //We use first texture size as the texture array slices' size.
  169. // format needs to be ARGB32, RGBA32, RGB24, R8, Alpha8 or one of float formats.
  170. Texture2DArray texture2DArray = new Texture2DArray(textures[0].width,
  171. textures[0].height,
  172. textures.Count,
  173. format,
  174. true);
  175. for (int i = 0; i < textures.Count; i++)
  176. {
  177. // NOTE:
  178. // It is able to make a Texture2DArray with "Graphics.CopyTexture()".
  179. // However, it has a problem which is able to make Texture2DArray in Editor
  180. // without enabling read-write settings of texture.
  181. // And then, it causes some wrong result in build app.
  182. // So we should make a Texture2DArray with "SetPixels()".
  183. //
  184. // Graphics.CopyTexture(textures[i], 0, 0, texture2DArray, i, 0);
  185. texture2DArray.SetPixels(textures[i].GetPixels(), i);
  186. }
  187. texture2DArray.Apply();
  188. return texture2DArray;
  189. }
  190. private static void CreateColorTextureArray(TextureArraySettings settings,
  191. out Texture2DArray array)
  192. {
  193. array = null;
  194. var layers = settings.Layers;
  195. if (layers.Count < 2)
  196. {
  197. throw new System.Exception("Less than 2 layers in array settings.");
  198. }
  199. if (!layers.TrueForAll(l => l.Albedo != null))
  200. {
  201. throw new System.Exception("Some albedo texture hasn't been assigned.");
  202. }
  203. var albedoTextureList = new List<Texture2D>(layers.Count);
  204. foreach (var layer in layers)
  205. {
  206. var albedoTexture = layer.Albedo;
  207. albedoTextureList.Add(albedoTexture);
  208. }
  209. var textureArray = Generate(albedoTextureList, TextureFormat.RGBA32);
  210. textureArray.name = settings.TextureArrayName + TextureArraySettings.ColorArrayPostfix;
  211. array = textureArray;
  212. }
  213. private static void CreateNormalTextureArray(TextureArraySettings settings,
  214. out Texture2DArray array)
  215. {
  216. array = null;
  217. var layers = settings.Layers;
  218. if (layers.Count < 2)
  219. {
  220. throw new System.Exception("Less than 2 layers in array settings.");
  221. }
  222. if (!layers.TrueForAll(l => l.Normal != null))
  223. {
  224. throw new System.Exception("Some normal texture hasn't been assigned.");
  225. }
  226. var textureList = new List<Texture2D>(layers.Count);
  227. foreach (var layer in layers)
  228. {
  229. var texture = layer.Normal;
  230. textureList.Add(texture);
  231. }
  232. var textureArray = Generate(textureList, TextureFormat.RGBA32);
  233. textureArray.name = settings.TextureArrayName + TextureArraySettings.NormalArrayPostfix;
  234. array = textureArray;
  235. }
  236. private static void CreatePBRTextureArray(TextureArraySettings settings,
  237. out Texture2DArray albedoArray, out Texture2DArray roughNormalAOArray)
  238. {
  239. albedoArray = roughNormalAOArray = null;
  240. var layers = settings.Layers;
  241. if (layers.Count < 2)
  242. {
  243. throw new System.Exception("Less than 2 layers in array settings.");
  244. }
  245. if (!layers.TrueForAll(l => l.IsReadyForPBRTextureArray()))
  246. {
  247. throw new System.Exception("Some layer hasn't been fully assigned.");
  248. }
  249. var albedoTextureList = new List<Texture2D>(layers.Count);
  250. for (var layerIndex = 0; layerIndex < layers.Count; layerIndex++)
  251. {
  252. var layer = layers[layerIndex];
  253. var albedoTexture = layer.Albedo;
  254. albedoTextureList.Add(albedoTexture);
  255. }
  256. //create Albedo texture array
  257. {
  258. var textureArray = Generate(albedoTextureList, TextureFormat.RGBA32);
  259. textureArray.name =
  260. settings.TextureArrayName + TextureArraySettings.AlbedoArrayPostfix;
  261. albedoArray = textureArray;
  262. }
  263. var roughNormalAOTextureList = new List<Texture2D>(layers.Count);
  264. for (var layerIndex = 0; layerIndex < layers.Count; layerIndex++)
  265. {
  266. var layer = layers[layerIndex];
  267. var roughnessTexture = layer.Roughness;
  268. var normalTexture = layer.Normal;
  269. var aoTexture = layer.AO;
  270. var packedTexture = MergeTextures(roughnessTexture, normalTexture, aoTexture, settings.TextureSize);
  271. roughNormalAOTextureList.Add(packedTexture);
  272. }
  273. //create RoughnessNormalAO texture array
  274. {
  275. var textureArray = Generate(roughNormalAOTextureList, TextureFormat.RGBA32);
  276. textureArray.name =
  277. settings.TextureArrayName + TextureArraySettings.RoughnessNormalAOArrayPostfix;
  278. roughNormalAOArray = textureArray;
  279. }
  280. }
  281. private static Texture2D MergeTextures(Texture2D roughnessTexture, Texture2D normalTexture,
  282. Texture2D aoTexture, int textureSize)
  283. {
  284. Texture2D result = new Texture2D(textureSize, textureSize, TextureFormat.RGBA32, false, true);
  285. var pixels = new Color[textureSize*textureSize];
  286. var roughnessPixels = roughnessTexture.GetPixels();
  287. var normalPixels = normalTexture.GetPixels();
  288. var aoPixels = aoTexture.GetPixels();
  289. for (var i = 0; i < pixels.Length; i++)
  290. {
  291. var roughness = roughnessPixels[i].r;
  292. var normalX = normalPixels[i].r * 2 - 1;
  293. var normalY = normalPixels[i].g * 2 - 1;
  294. var normalZ = normalPixels[i].b * 2 - 1;
  295. var normalVectorPossiblyNotNormalized = new Vector3(normalX, normalY, normalZ);
  296. var normal = Vector3.Normalize(normalVectorPossiblyNotNormalized);
  297. var normalAsRGB = normal * 0.5f + new Vector3(0.5f, 0.5f, 0.5f);
  298. var ao = aoPixels[i].r;
  299. pixels[i].r = roughness;
  300. pixels[i].g = Mathf.Clamp01(normalAsRGB.x);
  301. pixels[i].b = Mathf.Clamp01(normalAsRGB.y);
  302. pixels[i].a = ao;
  303. }
  304. result.SetPixels(pixels);
  305. result.Apply();
  306. return result;
  307. }
  308. #endregion
  309. }
  310. }