ProceduralTexture2DEditor.cs 39 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891
  1. using System.Collections.Generic;
  2. using UnityEngine;
  3. using UnityEditor;
  4. [CustomEditor(typeof(ProceduralTexture2D)), CanEditMultipleObjects]
  5. public class ProceduralTexture2DEditor : Editor
  6. {
  7. ProceduralTexture2D[] targetAssets;
  8. public void OnEnable()
  9. {
  10. Object[] monoObjects = targets;
  11. targetAssets = new ProceduralTexture2D[monoObjects.Length];
  12. for (int i = 0; i < monoObjects.Length; i++)
  13. {
  14. targetAssets[i] = monoObjects[i] as ProceduralTexture2D;
  15. }
  16. }
  17. public override void OnInspectorGUI()
  18. {
  19. serializedObject.Update();
  20. // Input Texture
  21. EditorGUI.BeginChangeCheck();
  22. EditorGUILayout.PropertyField(serializedObject.FindProperty("input"), new GUIContent("Texture"));
  23. if (EditorGUI.EndChangeCheck())
  24. {
  25. serializedObject.ApplyModifiedProperties();
  26. CopyInputTextureImportType(targetAssets[0]);
  27. }
  28. GUILayout.Space(10);
  29. // Texture Type
  30. EditorGUILayout.PropertyField(serializedObject.FindProperty("type"), new GUIContent("Texture Type"));
  31. // Include alpha for color textures
  32. if ((ProceduralTexture2D.TextureType)serializedObject.FindProperty("type").enumValueIndex == ProceduralTexture2D.TextureType.Color)
  33. EditorGUILayout.PropertyField(serializedObject.FindProperty("includeAlpha"), new GUIContent("Include Alpha"));
  34. GUILayout.Space(10);
  35. // Filtering
  36. EditorGUILayout.PropertyField(serializedObject.FindProperty("generateMipMaps"), new GUIContent("Generate Mip Maps"));
  37. EditorGUILayout.PropertyField(serializedObject.FindProperty("filterMode"), new GUIContent("Filter mode"));
  38. Rect sliderRect = GUILayoutUtility.GetLastRect();
  39. if (serializedObject.FindProperty("generateMipMaps").boolValue == true)
  40. {
  41. sliderRect = new Rect(sliderRect.x, sliderRect.y + sliderRect.height, sliderRect.width, sliderRect.height);
  42. PropertyIntSlider(sliderRect, serializedObject.FindProperty("anisoLevel"), 0, 16, new GUIContent("Aniso Level"));
  43. GUILayoutUtility.GetRect(new GUIContent("Aniso Level"), EditorStyles.label);
  44. }
  45. GUILayout.Space(10);
  46. // Compression
  47. EditorGUILayout.PropertyField(serializedObject.FindProperty("compressionQuality"), new GUIContent("Compression"));
  48. // Memory size display
  49. string size = targetAssets.Length == 1 && targetAssets[0].memoryUsageBytes > 0 ?
  50. SizeSuffix(targetAssets[0].memoryUsageBytes) : "--";
  51. EditorGUILayout.LabelField("Size in memory: " + size, EditorStyles.centeredGreyMiniLabel);
  52. GUILayout.Space(10);
  53. // Apply changes button
  54. GUILayout.BeginHorizontal();
  55. GUILayout.FlexibleSpace();
  56. if (GUILayout.Button("Apply"))
  57. for (int i = 0; i < targetAssets.Length; i++)
  58. PreprocessData(targetAssets[i]);
  59. GUILayout.EndHorizontal();
  60. // Normal compression warning
  61. if (targetAssets[0].type == ProceduralTexture2D.TextureType.Normal && targetAssets[0].compressionQuality != ProceduralTexture2D.CompressionLevel.HighQuality)
  62. EditorGUILayout.HelpBox("High quality compression recommended for normal maps", MessageType.Info);
  63. // Unapplied changes warning
  64. if (UnappliedSettingChanges(targetAssets[0]) == true)
  65. EditorGUILayout.HelpBox("Unapplied settings", MessageType.Info);
  66. serializedObject.ApplyModifiedProperties();
  67. }
  68. // A slider function that takes a SerializedProperty
  69. void PropertyIntSlider(Rect position, SerializedProperty property, int leftValue, int rightValue, GUIContent label)
  70. {
  71. label = EditorGUI.BeginProperty(position, label, property);
  72. EditorGUI.BeginChangeCheck();
  73. var newValue = EditorGUI.IntSlider(position, label, property.intValue, leftValue, rightValue);
  74. if (EditorGUI.EndChangeCheck())
  75. property.intValue = newValue;
  76. EditorGUI.EndProperty();
  77. }
  78. private readonly string[] SizeSuffixes =
  79. { "bytes", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB" };
  80. private string SizeSuffix(long value, int decimalPlaces = 1)
  81. {
  82. if (value < 0) { return "-" + SizeSuffix(-value); }
  83. if (value == 0) { return string.Format("{0:n" + decimalPlaces + "} bytes", 0); }
  84. // mag is 0 for bytes, 1 for KB, 2, for MB, etc.
  85. int mag = (int)Mathf.Log(value, 1024);
  86. // 1L << (mag * 10) == 2 ^ (10 * mag)
  87. // [i.e. the number of bytes in the unit corresponding to mag]
  88. decimal adjustedSize = (decimal)value / (1L << (mag * 10));
  89. // make adjustment when the value is large enough that
  90. // it would round up to 1000 or more
  91. if (System.Math.Round(adjustedSize, decimalPlaces) >= 1000)
  92. {
  93. mag += 1;
  94. adjustedSize /= 1024;
  95. }
  96. return string.Format("{0:n" + decimalPlaces + "} {1}",
  97. adjustedSize,
  98. SizeSuffixes[mag]);
  99. }
  100. private void CopyInputTextureImportType(ProceduralTexture2D target)
  101. {
  102. string path = AssetDatabase.GetAssetPath(target.input);
  103. TextureImporter inputImporter = (TextureImporter)TextureImporter.GetAtPath(path);
  104. switch (inputImporter.textureType)
  105. {
  106. case TextureImporterType.NormalMap:
  107. target.type = ProceduralTexture2D.TextureType.Normal;
  108. break;
  109. default:
  110. target.type = ProceduralTexture2D.TextureType.Color;
  111. break;
  112. }
  113. }
  114. private bool UnappliedSettingChanges(ProceduralTexture2D target)
  115. {
  116. if(target.currentInput != target.input
  117. || target.currentIncludeAlpha != target.includeAlpha
  118. || target.currentGenerateMipMaps != target.generateMipMaps
  119. || target.currentFilterMode != target.filterMode
  120. || target.currentAnisoLevel != target.anisoLevel
  121. || target.currentCompressionQuality != target.compressionQuality)
  122. {
  123. return true;
  124. }
  125. else
  126. {
  127. return false;
  128. }
  129. }
  130. /*********************************************************************/
  131. /*********************************************************************/
  132. /*************Procedural Stochastic Texturing Pre-process*************/
  133. /*********************************************************************/
  134. /*********************************************************************/
  135. const float GAUSSIAN_AVERAGE = 0.5f; // Expectation of the Gaussian distribution
  136. const float GAUSSIAN_STD = 0.1666f; // Std of the Gaussian distribution
  137. const int LUT_WIDTH = 128; // Size of the look-up table
  138. private static int stepCounter = 0;
  139. private static int totalSteps = 0;
  140. struct TextureData
  141. {
  142. public Color[] data;
  143. public int width;
  144. public int height;
  145. public TextureData(int w, int h)
  146. {
  147. width = w;
  148. height = h;
  149. data = new Color[w * h];
  150. }
  151. public TextureData(TextureData td)
  152. {
  153. width = td.width;
  154. height = td.height;
  155. data = new Color[width * height];
  156. for (int y = 0; y < height; y++)
  157. for (int x = 0; x < width; x++)
  158. data[y * width + x] = td.data[y * width + x];
  159. }
  160. public Color GetColor(int w, int h)
  161. {
  162. return data[h * width + w];
  163. }
  164. public ref Color GetColorRef(int w, int h)
  165. {
  166. return ref data[h * width + w];
  167. }
  168. public void SetColorAt(int w, int h, Color value)
  169. {
  170. data[h * width + w] = value;
  171. }
  172. };
  173. private static void PreprocessData(ProceduralTexture2D target)
  174. {
  175. if (target.input == null)
  176. return;
  177. // Init progress bar
  178. stepCounter = 0;
  179. totalSteps = (target.type != ProceduralTexture2D.TextureType.Other ? 4 : 0) + (target.type != ProceduralTexture2D.TextureType.Other ? 9 : 12) + 1;
  180. EditorUtility.DisplayProgressBar("Pre-processing Procedural Texture Data", target.name, (float)stepCounter / (totalSteps - 1));
  181. // Section 1.4 Improvement: using a decorrelated color space for Color RGB and Normal XYZ textures
  182. TextureFormat inputFormat = TextureFormat.RGB24;
  183. TextureData albedoData = TextureToTextureData(target.input, ref inputFormat);
  184. TextureData decorrelated = new TextureData(albedoData);
  185. if (target.type != ProceduralTexture2D.TextureType.Other)
  186. DecorrelateColorSpace(ref albedoData, ref decorrelated, ref target.colorSpaceVector1, ref target.colorSpaceVector2, ref target.colorSpaceVector3, ref target.colorSpaceOrigin, target.name);
  187. ComputeCompressionScalers(target);
  188. // Perform precomputations
  189. TextureData Tinput = new TextureData(decorrelated.width, decorrelated.height);
  190. TextureData invT = new TextureData(LUT_WIDTH, (int)(Mathf.Log((float)Tinput.width) / Mathf.Log(2.0f))); // Height = Number of prefiltered LUT levels
  191. List<int> channelsToProcess = new List<int> { 0, 1, 2 };
  192. if ((target.type == ProceduralTexture2D.TextureType.Color && target.includeAlpha == true) || target.type == ProceduralTexture2D.TextureType.Other)
  193. channelsToProcess.Add(3);
  194. Precomputations(ref decorrelated, channelsToProcess, ref Tinput, ref invT, target.name);
  195. RescaleForCompression(target, ref Tinput);
  196. EditorUtility.DisplayProgressBar("Pre-processing Procedural Texture Data", target.name, (float)stepCounter++ / (totalSteps - 1));
  197. // Serialize precomputed data and setup material
  198. FinalizePrecomputedTextures(ref inputFormat, target, ref Tinput, ref invT);
  199. target.memoryUsageBytes = target.Tinput.GetRawTextureData().Length + target.invT.GetRawTextureData().Length;
  200. EditorUtility.ClearProgressBar();
  201. // Update current applied settings
  202. target.currentInput = target.input;
  203. target.currentIncludeAlpha = target.includeAlpha;
  204. target.currentGenerateMipMaps = target.generateMipMaps;
  205. target.currentFilterMode = target.filterMode;
  206. target.currentAnisoLevel = target.anisoLevel;
  207. target.currentCompressionQuality = target.compressionQuality;
  208. }
  209. static TextureData TextureToTextureData(Texture2D input, ref TextureFormat inputFormat)
  210. {
  211. // Modify input texture import settings temporarily
  212. string texpath = AssetDatabase.GetAssetPath(input);
  213. TextureImporter importer = (TextureImporter)TextureImporter.GetAtPath(texpath);
  214. TextureImporterCompression prev = importer.textureCompression;
  215. TextureImporterType prevType = importer.textureType;
  216. bool linearInput = importer.sRGBTexture == false || importer.textureType == TextureImporterType.NormalMap;
  217. bool prevReadable = importer.isReadable;
  218. if (importer != null)
  219. {
  220. importer.textureType = TextureImporterType.Default;
  221. importer.isReadable = true;
  222. importer.textureCompression = TextureImporterCompression.Uncompressed;
  223. AssetDatabase.ImportAsset(texpath, ImportAssetOptions.ForceUpdate);
  224. inputFormat = input.format;
  225. }
  226. // Copy input texture pixel data
  227. Color[] colors = input.GetPixels();
  228. TextureData res = new TextureData(input.width, input.height);
  229. for (int x = 0; x < res.width; x++)
  230. {
  231. for (int y = 0; y < res.height; y++)
  232. {
  233. res.SetColorAt(x, y, linearInput || PlayerSettings.colorSpace == ColorSpace.Gamma ?
  234. colors[y * res.width + x] : colors[y * res.width + x].linear);
  235. }
  236. }
  237. // Revert input texture settings
  238. if (importer != null)
  239. {
  240. importer.textureType = prevType;
  241. importer.isReadable = prevReadable;
  242. importer.textureCompression = prev;
  243. AssetDatabase.ImportAsset(texpath, ImportAssetOptions.ForceUpdate);
  244. }
  245. return res;
  246. }
  247. static void FinalizePrecomputedTextures(ref TextureFormat inputFormat, ProceduralTexture2D target, ref TextureData Tinput, ref TextureData invT)
  248. {
  249. // Serialize precomputed data as new subasset texture. Reuse existing texture if possible to avoid breaking texture references in shadergraph.
  250. if(target.Tinput == null)
  251. {
  252. target.Tinput = new Texture2D(Tinput.width, Tinput.height, inputFormat, target.generateMipMaps, true);
  253. AssetDatabase.AddObjectToAsset(target.Tinput, target);
  254. }
  255. target.Tinput.Resize(Tinput.width, Tinput.height, inputFormat, target.generateMipMaps);
  256. target.Tinput.name = target.input.name + "_T";
  257. target.Tinput.SetPixels(Tinput.data);
  258. target.Tinput.wrapMode = TextureWrapMode.Repeat;
  259. target.Tinput.filterMode = target.filterMode;
  260. target.Tinput.anisoLevel = target.anisoLevel;
  261. target.Tinput.Apply();
  262. if (target.compressionQuality != ProceduralTexture2D.CompressionLevel.None)
  263. {
  264. if(target.compressionQuality == ProceduralTexture2D.CompressionLevel.HighQuality)
  265. EditorUtility.CompressTexture(target.Tinput, TextureFormat.BC7, (int)target.compressionQuality);
  266. else if (inputFormat == TextureFormat.RGBA32)
  267. EditorUtility.CompressTexture(target.Tinput, TextureFormat.DXT5, (int)target.compressionQuality);
  268. else
  269. EditorUtility.CompressTexture(target.Tinput, TextureFormat.DXT1, (int)target.compressionQuality);
  270. }
  271. target.Tinput.Apply();
  272. if (target.invT == null)
  273. {
  274. target.invT = new Texture2D(invT.width, invT.height, inputFormat, false, true);
  275. AssetDatabase.AddObjectToAsset(target.invT, target);
  276. }
  277. target.invT.Resize(invT.width, invT.height, inputFormat, false);
  278. target.invT.name = target.input.name + "_invT";
  279. target.invT.wrapMode = TextureWrapMode.Clamp;
  280. target.invT.filterMode = FilterMode.Bilinear;
  281. target.invT.SetPixels(invT.data);
  282. target.invT.Apply();
  283. // Update asset database
  284. AssetDatabase.SaveAssets();
  285. AssetDatabase.Refresh();
  286. }
  287. private static void Precomputations(
  288. ref TextureData input, // input: example image
  289. List<int> channels, // input: channels to process
  290. ref TextureData Tinput, // output: T(input) image
  291. ref TextureData invT, // output: T^{-1} look-up table
  292. string assetName)
  293. {
  294. // Section 1.3.2 Applying the histogram transformation T on the input
  295. foreach (int channel in channels)
  296. {
  297. ComputeTinput(ref input, ref Tinput, channel);
  298. EditorUtility.DisplayProgressBar("Pre-processing Procedural Texture Data", assetName, (float)stepCounter++ / (totalSteps - 1));
  299. }
  300. // Section 1.3.3 Precomputing the inverse histogram transformation T^{-1}
  301. foreach (int channel in channels)
  302. {
  303. ComputeinvT(ref input, ref invT, channel);
  304. EditorUtility.DisplayProgressBar("Pre-processing Procedural Texture Data", assetName, (float)stepCounter++ / (totalSteps - 1));
  305. }
  306. // Section 1.5 Improvement: prefiltering the look-up table
  307. foreach (int channel in channels)
  308. {
  309. PrefilterLUT(ref Tinput, ref invT, channel);
  310. EditorUtility.DisplayProgressBar("Pre-processing Procedural Texture Data", assetName, (float)stepCounter++ / (totalSteps - 1));
  311. }
  312. }
  313. private static void ComputeCompressionScalers(ProceduralTexture2D target)
  314. {
  315. target.compressionScalers = Vector4.one;
  316. if (target.compressionQuality != ProceduralTexture2D.CompressionLevel.None && target.type != ProceduralTexture2D.TextureType.Other)
  317. {
  318. target.compressionScalers.x = 1.0f / target.colorSpaceVector1.magnitude;
  319. target.compressionScalers.y = 1.0f / target.colorSpaceVector2.magnitude;
  320. target.compressionScalers.z = 1.0f / target.colorSpaceVector3.magnitude;
  321. }
  322. }
  323. private static void RescaleForCompression(ProceduralTexture2D target, ref TextureData Tinput)
  324. {
  325. int channelCount = (target.type == ProceduralTexture2D.TextureType.Color && target.includeAlpha == true) || target.type == ProceduralTexture2D.TextureType.Other ?
  326. 4 : 3;
  327. // If we use DXT compression
  328. // we need to rescale the Gaussian channels (see Section 1.6)
  329. if (target.compressionQuality != ProceduralTexture2D.CompressionLevel.None && target.type != ProceduralTexture2D.TextureType.Other)
  330. {
  331. for (int y = 0; y < Tinput.height; y++)
  332. for (int x = 0; x < Tinput.width; x++)
  333. for (int i = 0; i < channelCount; i++)
  334. {
  335. float v = Tinput.GetColor(x, y)[i];
  336. v = (v - 0.5f) / target.compressionScalers[i] + 0.5f;
  337. Tinput.GetColorRef(x, y)[i] = v;
  338. }
  339. }
  340. }
  341. /*****************************************************************************/
  342. /**************** Section 1.3.1 Target Gaussian distribution *****************/
  343. /*****************************************************************************/
  344. private static float Erf(float x)
  345. {
  346. // Save the sign of x
  347. int sign = 1;
  348. if (x < 0)
  349. sign = -1;
  350. x = Mathf.Abs(x);
  351. // A&S formula 7.1.26
  352. float t = 1.0f / (1.0f + 0.3275911f * x);
  353. float y = 1.0f - (((((1.061405429f * t + -1.453152027f) * t) + 1.421413741f)
  354. * t + -0.284496736f) * t + 0.254829592f) * t * Mathf.Exp(-x * x);
  355. return sign * y;
  356. }
  357. private static float ErfInv(float x)
  358. {
  359. float w, p;
  360. w = -Mathf.Log((1.0f - x) * (1.0f + x));
  361. if (w < 5.000000f)
  362. {
  363. w = w - 2.500000f;
  364. p = 2.81022636e-08f;
  365. p = 3.43273939e-07f + p * w;
  366. p = -3.5233877e-06f + p * w;
  367. p = -4.39150654e-06f + p * w;
  368. p = 0.00021858087f + p * w;
  369. p = -0.00125372503f + p * w;
  370. p = -0.00417768164f + p * w;
  371. p = 0.246640727f + p * w;
  372. p = 1.50140941f + p * w;
  373. }
  374. else
  375. {
  376. w = Mathf.Sqrt(w) - 3.000000f;
  377. p = -0.000200214257f;
  378. p = 0.000100950558f + p * w;
  379. p = 0.00134934322f + p * w;
  380. p = -0.00367342844f + p * w;
  381. p = 0.00573950773f + p * w;
  382. p = -0.0076224613f + p * w;
  383. p = 0.00943887047f + p * w;
  384. p = 1.00167406f + p * w;
  385. p = 2.83297682f + p * w;
  386. }
  387. return p * x;
  388. }
  389. private static float CDF(float x, float mu, float sigma)
  390. {
  391. float U = 0.5f * (1 + Erf((x - mu) / (sigma * Mathf.Sqrt(2.0f))));
  392. return U;
  393. }
  394. private static float invCDF(float U, float mu, float sigma)
  395. {
  396. float x = sigma * Mathf.Sqrt(2.0f) * ErfInv(2.0f * U - 1.0f) + mu;
  397. return x;
  398. }
  399. /*****************************************************************************/
  400. /**** Section 1.3.2 Applying the histogram transformation T on the input *****/
  401. /*****************************************************************************/
  402. private struct PixelSortStruct
  403. {
  404. public int x;
  405. public int y;
  406. public float value;
  407. };
  408. private static void ComputeTinput(ref TextureData input, ref TextureData T_input, int channel)
  409. {
  410. // Sort pixels of example image
  411. PixelSortStruct[] sortedInputValues = new PixelSortStruct[input.width * input.height];
  412. for (int y = 0; y < input.height; y++)
  413. for (int x = 0; x < input.width; x++)
  414. {
  415. sortedInputValues[y * input.width + x].x = x;
  416. sortedInputValues[y * input.width + x].y = y;
  417. sortedInputValues[y * input.width + x].value = input.GetColor(x, y)[channel];
  418. }
  419. System.Array.Sort(sortedInputValues, (x, y) => x.value.CompareTo(y.value));
  420. // Assign Gaussian value to each pixel
  421. for (uint i = 0; i < sortedInputValues.Length; i++)
  422. {
  423. // Pixel coordinates
  424. int x = sortedInputValues[i].x;
  425. int y = sortedInputValues[i].y;
  426. // Input quantile (given by its order in the sorting)
  427. float U = (i + 0.5f) / (sortedInputValues.Length);
  428. // Gaussian quantile
  429. float G = invCDF(U, GAUSSIAN_AVERAGE, GAUSSIAN_STD);
  430. // Store
  431. T_input.GetColorRef(x, y)[channel] = G;
  432. }
  433. }
  434. /*****************************************************************************/
  435. /* Section 1.3.3 Precomputing the inverse histogram transformation T^{-1} */
  436. /*****************************************************************************/
  437. private static void ComputeinvT(ref TextureData input, ref TextureData Tinv, int channel)
  438. {
  439. // Sort pixels of example image
  440. float[] sortedInputValues = new float[input.width * input.height];
  441. for (int y = 0; y < input.height; y++)
  442. for (int x = 0; x < input.width; x++)
  443. {
  444. sortedInputValues[y * input.width + x] = input.GetColor(x, y)[channel];
  445. }
  446. System.Array.Sort(sortedInputValues);
  447. // Generate Tinv look-up table
  448. for (int i = 0; i < Tinv.width; i++)
  449. {
  450. // Gaussian value in [0, 1]
  451. float G = (i + 0.5f) / (Tinv.width);
  452. // Quantile value
  453. float U = CDF(G, GAUSSIAN_AVERAGE, GAUSSIAN_STD);
  454. // Find quantile in sorted pixel values
  455. int index = (int)Mathf.Floor(U * sortedInputValues.Length);
  456. // Get input value
  457. float I = sortedInputValues[index];
  458. // Store in LUT
  459. Tinv.GetColorRef(i, 0)[channel] = I;
  460. }
  461. }
  462. /*****************************************************************************/
  463. /******** Section 1.4 Improvement: using a decorrelated color space **********/
  464. /*****************************************************************************/
  465. // Compute the eigen vectors of the histogram of the input
  466. private static void ComputeEigenVectors(ref TextureData input, Vector3[] eigenVectors)
  467. {
  468. // First and second order moments
  469. float R = 0, G = 0, B = 0, RR = 0, GG = 0, BB = 0, RG = 0, RB = 0, GB = 0;
  470. for (int y = 0; y < input.height; y++)
  471. {
  472. for (int x = 0; x < input.width; x++)
  473. {
  474. Color col = input.GetColor(x, y);
  475. R += col.r;
  476. G += col.g;
  477. B += col.b;
  478. RR += col.r * col.r;
  479. GG += col.g * col.g;
  480. BB += col.b * col.b;
  481. RG += col.r * col.g;
  482. RB += col.r * col.b;
  483. GB += col.g * col.b;
  484. }
  485. }
  486. R /= (float)(input.width * input.height);
  487. G /= (float)(input.width * input.height);
  488. B /= (float)(input.width * input.height);
  489. RR /= (float)(input.width * input.height);
  490. GG /= (float)(input.width * input.height);
  491. BB /= (float)(input.width * input.height);
  492. RG /= (float)(input.width * input.height);
  493. RB /= (float)(input.width * input.height);
  494. GB /= (float)(input.width * input.height);
  495. // Covariance matrix
  496. double[][] covarMat = new double[3][];
  497. for (int i = 0; i < 3; i++)
  498. covarMat[i] = new double[3];
  499. covarMat[0][0] = RR - R * R;
  500. covarMat[0][1] = RG - R * G;
  501. covarMat[0][2] = RB - R * B;
  502. covarMat[1][0] = RG - R * G;
  503. covarMat[1][1] = GG - G * G;
  504. covarMat[1][2] = GB - G * B;
  505. covarMat[2][0] = RB - R * B;
  506. covarMat[2][1] = GB - G * B;
  507. covarMat[2][2] = BB - B * B;
  508. // Find eigen values and vectors using Jacobi algorithm
  509. double[][] eigenVectorsTemp = new double[3][];
  510. for (int i = 0; i < 3; i++)
  511. eigenVectorsTemp[i] = new double[3];
  512. double[] eigenValuesTemp = new double[3];
  513. ComputeEigenValuesAndVectors(covarMat, eigenVectorsTemp, eigenValuesTemp);
  514. // Set return values
  515. eigenVectors[0] = new Vector3((float)eigenVectorsTemp[0][0], (float)eigenVectorsTemp[1][0], (float)eigenVectorsTemp[2][0]);
  516. eigenVectors[1] = new Vector3((float)eigenVectorsTemp[0][1], (float)eigenVectorsTemp[1][1], (float)eigenVectorsTemp[2][1]);
  517. eigenVectors[2] = new Vector3((float)eigenVectorsTemp[0][2], (float)eigenVectorsTemp[1][2], (float)eigenVectorsTemp[2][2]);
  518. }
  519. // ----------------------------------------------------------------------------
  520. // Numerical diagonalization of 3x3 matrcies
  521. // Copyright (C) 2006 Joachim Kopp
  522. // ----------------------------------------------------------------------------
  523. // This library is free software; you can redistribute it and/or
  524. // modify it under the terms of the GNU Lesser General Public
  525. // License as published by the Free Software Foundation; either
  526. // version 2.1 of the License, or (at your option) any later version.
  527. //
  528. // This library is distributed in the hope that it will be useful,
  529. // but WITHOUT ANY WARRANTY; without even the implied warranty of
  530. // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
  531. // Lesser General Public License for more details.
  532. //
  533. // You should have received a copy of the GNU Lesser General Public
  534. // License along with this library; if not, write to the Free Software
  535. // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
  536. // ----------------------------------------------------------------------------
  537. // Calculates the eigenvalues and normalized eigenvectors of a symmetric 3x3
  538. // matrix A using the Jacobi algorithm.
  539. // The upper triangular part of A is destroyed during the calculation,
  540. // the diagonal elements are read but not destroyed, and the lower
  541. // triangular elements are not referenced at all.
  542. // ----------------------------------------------------------------------------
  543. // Parameters:
  544. // A: The symmetric input matrix
  545. // Q: Storage buffer for eigenvectors
  546. // w: Storage buffer for eigenvalues
  547. // ----------------------------------------------------------------------------
  548. // Return value:
  549. // 0: Success
  550. // -1: Error (no convergence)
  551. private static int ComputeEigenValuesAndVectors(double[][] A, double[][] Q, double[] w)
  552. {
  553. const int n = 3;
  554. double sd, so; // Sums of diagonal resp. off-diagonal elements
  555. double s, c, t; // sin(phi), cos(phi), tan(phi) and temporary storage
  556. double g, h, z, theta; // More temporary storage
  557. double thresh;
  558. // Initialize Q to the identitity matrix
  559. for (int i = 0; i < n; i++)
  560. {
  561. Q[i][i] = 1.0;
  562. for (int j = 0; j < i; j++)
  563. Q[i][j] = Q[j][i] = 0.0;
  564. }
  565. // Initialize w to diag(A)
  566. for (int i = 0; i < n; i++)
  567. w[i] = A[i][i];
  568. // Calculate SQR(tr(A))
  569. sd = 0.0;
  570. for (int i = 0; i < n; i++)
  571. sd += System.Math.Abs(w[i]);
  572. sd = sd * sd;
  573. // Main iteration loop
  574. for (int nIter = 0; nIter < 50; nIter++)
  575. {
  576. // Test for convergence
  577. so = 0.0;
  578. for (int p = 0; p < n; p++)
  579. for (int q = p + 1; q < n; q++)
  580. so += System.Math.Abs(A[p][q]);
  581. if (so == 0.0)
  582. return 0;
  583. if (nIter < 4)
  584. thresh = 0.2 * so / (n * n);
  585. else
  586. thresh = 0.0;
  587. // Do sweep
  588. for (int p = 0; p < n; p++)
  589. {
  590. for (int q = p + 1; q < n; q++)
  591. {
  592. g = 100.0 * System.Math.Abs(A[p][q]);
  593. if (nIter > 4 && System.Math.Abs(w[p]) + g == System.Math.Abs(w[p])
  594. && System.Math.Abs(w[q]) + g == System.Math.Abs(w[q]))
  595. {
  596. A[p][q] = 0.0;
  597. }
  598. else if (System.Math.Abs(A[p][q]) > thresh)
  599. {
  600. // Calculate Jacobi transformation
  601. h = w[q] - w[p];
  602. if (System.Math.Abs(h) + g == System.Math.Abs(h))
  603. {
  604. t = A[p][q] / h;
  605. }
  606. else
  607. {
  608. theta = 0.5 * h / A[p][q];
  609. if (theta < 0.0)
  610. t = -1.0 / (System.Math.Sqrt(1.0 + (theta * theta)) - theta);
  611. else
  612. t = 1.0 / (System.Math.Sqrt(1.0 + (theta * theta)) + theta);
  613. }
  614. c = 1.0 / System.Math.Sqrt(1.0 + (t * t));
  615. s = t * c;
  616. z = t * A[p][q];
  617. // Apply Jacobi transformation
  618. A[p][q] = 0.0;
  619. w[p] -= z;
  620. w[q] += z;
  621. for (int r = 0; r < p; r++)
  622. {
  623. t = A[r][p];
  624. A[r][p] = c * t - s * A[r][q];
  625. A[r][q] = s * t + c * A[r][q];
  626. }
  627. for (int r = p + 1; r < q; r++)
  628. {
  629. t = A[p][r];
  630. A[p][r] = c * t - s * A[r][q];
  631. A[r][q] = s * t + c * A[r][q];
  632. }
  633. for (int r = q + 1; r < n; r++)
  634. {
  635. t = A[p][r];
  636. A[p][r] = c * t - s * A[q][r];
  637. A[q][r] = s * t + c * A[q][r];
  638. }
  639. // Update eigenvectors
  640. for (int r = 0; r < n; r++)
  641. {
  642. t = Q[r][p];
  643. Q[r][p] = c * t - s * Q[r][q];
  644. Q[r][q] = s * t + c * Q[r][q];
  645. }
  646. }
  647. }
  648. }
  649. }
  650. return -1;
  651. }
  652. // Main function of Section 1.4
  653. private static void DecorrelateColorSpace(
  654. ref TextureData input, // input: example image
  655. ref TextureData input_decorrelated, // output: decorrelated input
  656. ref Vector3 colorSpaceVector1, // output: color space vector1
  657. ref Vector3 colorSpaceVector2, // output: color space vector2
  658. ref Vector3 colorSpaceVector3, // output: color space vector3
  659. ref Vector3 colorSpaceOrigin, // output: color space origin
  660. string assetName)
  661. {
  662. // Compute the eigenvectors of the histogram
  663. Vector3[] eigenvectors = new Vector3[3];
  664. ComputeEigenVectors(ref input, eigenvectors);
  665. EditorUtility.DisplayProgressBar("Pre-processing Procedural Texture Data", assetName, (float)stepCounter++ / (totalSteps - 1));
  666. // Rotate to eigenvector space
  667. for (int y = 0; y < input.height; y++)
  668. for (int x = 0; x < input.width; x++)
  669. for (int channel = 0; channel < 3; ++channel)
  670. {
  671. // Get current color
  672. Color color = input.GetColor(x, y);
  673. Vector3 vec = new Vector3(color.r, color.g, color.b);
  674. // Project on eigenvector
  675. float new_channel_value = Vector3.Dot(vec, eigenvectors[channel]);
  676. // Store
  677. input_decorrelated.GetColorRef(x, y)[channel] = new_channel_value;
  678. }
  679. EditorUtility.DisplayProgressBar("Pre-processing Procedural Texture Data", assetName, (float)stepCounter++ / (totalSteps - 1));
  680. // Compute ranges of the new color space
  681. Vector2[] colorSpaceRanges = new Vector2[3]{
  682. new Vector2(float.MaxValue, float.MinValue),
  683. new Vector2(float.MaxValue, float.MinValue),
  684. new Vector2(float.MaxValue, float.MinValue) };
  685. for (int y = 0; y < input.height; y++)
  686. for (int x = 0; x < input.width; x++)
  687. for (int channel = 0; channel < 3; ++channel)
  688. {
  689. colorSpaceRanges[channel].x = Mathf.Min(colorSpaceRanges[channel].x, input_decorrelated.GetColor(x, y)[channel]);
  690. colorSpaceRanges[channel].y = Mathf.Max(colorSpaceRanges[channel].y, input_decorrelated.GetColor(x, y)[channel]);
  691. }
  692. EditorUtility.DisplayProgressBar("Pre-processing Procedural Texture Data", assetName, (float)stepCounter++ / (totalSteps - 1));
  693. // Remap range to [0, 1]
  694. for (int y = 0; y < input.height; y++)
  695. for (int x = 0; x < input.width; x++)
  696. for (int channel = 0; channel < 3; ++channel)
  697. {
  698. // Get current value
  699. float value = input_decorrelated.GetColor(x, y)[channel];
  700. // Remap in [0, 1]
  701. float remapped_value = (value - colorSpaceRanges[channel].x) / (colorSpaceRanges[channel].y - colorSpaceRanges[channel].x);
  702. // Store
  703. input_decorrelated.GetColorRef(x, y)[channel] = remapped_value;
  704. }
  705. EditorUtility.DisplayProgressBar("Pre-processing Procedural Texture Data", assetName, (float)stepCounter++ / (totalSteps - 1));
  706. // Compute color space origin and vectors scaled for the normalized range
  707. colorSpaceOrigin.x = colorSpaceRanges[0].x * eigenvectors[0].x + colorSpaceRanges[1].x * eigenvectors[1].x + colorSpaceRanges[2].x * eigenvectors[2].x;
  708. colorSpaceOrigin.y = colorSpaceRanges[0].x * eigenvectors[0].y + colorSpaceRanges[1].x * eigenvectors[1].y + colorSpaceRanges[2].x * eigenvectors[2].y;
  709. colorSpaceOrigin.z = colorSpaceRanges[0].x * eigenvectors[0].z + colorSpaceRanges[1].x * eigenvectors[1].z + colorSpaceRanges[2].x * eigenvectors[2].z;
  710. colorSpaceVector1.x = eigenvectors[0].x * (colorSpaceRanges[0].y - colorSpaceRanges[0].x);
  711. colorSpaceVector1.y = eigenvectors[0].y * (colorSpaceRanges[0].y - colorSpaceRanges[0].x);
  712. colorSpaceVector1.z = eigenvectors[0].z * (colorSpaceRanges[0].y - colorSpaceRanges[0].x);
  713. colorSpaceVector2.x = eigenvectors[1].x * (colorSpaceRanges[1].y - colorSpaceRanges[1].x);
  714. colorSpaceVector2.y = eigenvectors[1].y * (colorSpaceRanges[1].y - colorSpaceRanges[1].x);
  715. colorSpaceVector2.z = eigenvectors[1].z * (colorSpaceRanges[1].y - colorSpaceRanges[1].x);
  716. colorSpaceVector3.x = eigenvectors[2].x * (colorSpaceRanges[2].y - colorSpaceRanges[2].x);
  717. colorSpaceVector3.y = eigenvectors[2].y * (colorSpaceRanges[2].y - colorSpaceRanges[2].x);
  718. colorSpaceVector3.z = eigenvectors[2].z * (colorSpaceRanges[2].y - colorSpaceRanges[2].x);
  719. }
  720. /*****************************************************************************/
  721. /* ===== Section 1.5 Improvement: prefiltering the look-up table =========== */
  722. /*****************************************************************************/
  723. // Compute average subpixel variance at a given LOD
  724. private static float ComputeLODAverageSubpixelVariance(ref TextureData image, int LOD, int channel)
  725. {
  726. // Window width associated with
  727. int windowWidth = 1 << LOD;
  728. // Compute average variance in all the windows
  729. float average_window_variance = 0.0f;
  730. // Loop over al the windows
  731. for (int window_y = 0; window_y < image.height; window_y += windowWidth)
  732. for (int window_x = 0; window_x < image.width; window_x += windowWidth)
  733. {
  734. // Estimate variance of current window
  735. float v = 0.0f;
  736. float v2 = 0.0f;
  737. for (int y = 0; y < windowWidth; y++)
  738. for (int x = 0; x < windowWidth; x++)
  739. {
  740. float value = image.GetColor(window_x + x, window_y + y)[channel];
  741. v += value;
  742. v2 += value * value;
  743. }
  744. v /= (float)(windowWidth * windowWidth);
  745. v2 /= (float)(windowWidth * windowWidth);
  746. float window_variance = Mathf.Max(0.0f, v2 - v * v);
  747. // Update average
  748. average_window_variance += window_variance / (image.width * image.height / windowWidth / windowWidth);
  749. }
  750. return average_window_variance;
  751. }
  752. // Filter LUT by sampling a Gaussian N(mu, std²)
  753. private static float FilterLUTValueAtx(ref TextureData LUT, float x, float std, int channel)
  754. {
  755. // Number of samples for filtering (heuristic: twice the LUT resolution)
  756. const int numberOfSamples = 2 * LUT_WIDTH;
  757. // Filter
  758. float filtered_value = 0.0f;
  759. for (int sample = 0; sample < numberOfSamples; sample++)
  760. {
  761. // Quantile used to sample the Gaussian
  762. float U = (sample + 0.5f) / numberOfSamples;
  763. // Sample the Gaussian
  764. float sample_x = invCDF(U, x, std);
  765. // Find sample texel in LUT (the LUT covers the domain [0, 1])
  766. int sample_texel = Mathf.Max(0, Mathf.Min(LUT_WIDTH - 1, (int)Mathf.Floor(sample_x * LUT_WIDTH)));
  767. // Fetch LUT at level 0
  768. float sample_value = LUT.GetColor(sample_texel, 0)[channel];
  769. // Accumulate
  770. filtered_value += sample_value;
  771. }
  772. // Normalize and return
  773. filtered_value /= (float)numberOfSamples;
  774. return filtered_value;
  775. }
  776. // Main function of section 1.5
  777. private static void PrefilterLUT(ref TextureData image_T_Input, ref TextureData LUT_Tinv, int channel)
  778. {
  779. // Prefilter
  780. for (int LOD = 1; LOD < LUT_Tinv.height; LOD++)
  781. {
  782. // Compute subpixel variance at LOD
  783. float window_variance = ComputeLODAverageSubpixelVariance(ref image_T_Input, LOD, channel);
  784. float window_std = Mathf.Sqrt(window_variance);
  785. // Prefilter LUT with Gaussian kernel of this variance
  786. for (int i = 0; i < LUT_Tinv.width; i++)
  787. {
  788. // Texel position in [0, 1]
  789. float x_texel = (i + 0.5f) / LUT_Tinv.width;
  790. // Filter look-up table around this position with Gaussian kernel
  791. float filteredValue = FilterLUTValueAtx(ref LUT_Tinv, x_texel, window_std, channel);
  792. // Store filtered value
  793. LUT_Tinv.GetColorRef(i, LOD)[channel] = filteredValue;
  794. }
  795. }
  796. }
  797. }