IESEngine.cs 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448
  1. using System.IO;
  2. using Unity.Collections;
  3. using UnityEditor;
  4. #if UNITY_2020_2_OR_NEWER
  5. using UnityEditor.AssetImporters;
  6. #else
  7. using UnityEditor.Experimental.AssetImporters;
  8. #endif
  9. using UnityEngine;
  10. namespace UnityEditor.Rendering
  11. {
  12. // Photometric type coordinate system references:
  13. // https://www.ies.org/product/approved-method-guide-to-goniometer-measurements-and-types-and-photometric-coordinate-systems/
  14. // https://support.agi32.com/support/solutions/articles/22000209748-type-a-type-b-and-type-c-photometry
  15. /// <summary>
  16. /// IES class which is common for the Importers
  17. /// </summary>
  18. [System.Serializable]
  19. public class IESEngine
  20. {
  21. const float k_HalfPi = 0.5f * Mathf.PI;
  22. const float k_TwoPi = 2.0f * Mathf.PI;
  23. internal IESReader m_iesReader = new IESReader();
  24. internal string FileFormatVersion { get => m_iesReader.FileFormatVersion; }
  25. internal TextureImporterType m_TextureGenerationType = TextureImporterType.Cookie;
  26. /// <summary>
  27. /// setter for the Texture generation Type
  28. /// </summary>
  29. public TextureImporterType TextureGenerationType
  30. {
  31. set { m_TextureGenerationType = value; }
  32. }
  33. /// <summary>
  34. /// Method to read the IES File
  35. /// </summary>
  36. /// <param name="iesFilePath">Path to the IES file in the Disk.</param>
  37. /// <returns>An error message or warning otherwise null if no error</returns>
  38. public string ReadFile(string iesFilePath)
  39. {
  40. if (!File.Exists(iesFilePath))
  41. {
  42. return "IES file does not exist.";
  43. }
  44. string errorMessage;
  45. try
  46. {
  47. errorMessage = m_iesReader.ReadFile(iesFilePath);
  48. }
  49. catch (IOException ioEx)
  50. {
  51. return ioEx.Message;
  52. }
  53. return errorMessage;
  54. }
  55. /// <summary>
  56. /// Check a keyword
  57. /// </summary>
  58. /// <param name="keyword">A keyword to check if exist.</param>
  59. /// <returns>A Keyword if exist inside the internal Dictionary</returns>
  60. public string GetKeywordValue(string keyword)
  61. {
  62. return m_iesReader.GetKeywordValue(keyword);
  63. }
  64. /// <summary>
  65. /// Getter (as a string) for the Photometric Type
  66. /// </summary>
  67. /// <returns>The current Photometric Type</returns>
  68. public string GetPhotometricType()
  69. {
  70. switch (m_iesReader.PhotometricType)
  71. {
  72. case 3: // type A
  73. return "Type A";
  74. case 2: // type B
  75. return "Type B";
  76. default: // type C
  77. return "Type C";
  78. }
  79. }
  80. /// <summary>
  81. /// Get the CUrrent Max intensity
  82. /// </summary>
  83. /// <returns>A pair of the intensity follow by the used unit (candelas or lumens)</returns>
  84. public (float, string) GetMaximumIntensity()
  85. {
  86. if (m_iesReader.TotalLumens == -1f) // absolute photometry
  87. {
  88. return (m_iesReader.MaxCandelas, "Candelas");
  89. }
  90. else
  91. {
  92. return (m_iesReader.TotalLumens, "Lumens");
  93. }
  94. }
  95. /// <summary>
  96. /// Generated a Cube texture based on the internal PhotometricType
  97. /// </summary>
  98. /// <param name="compression">Compression parameter requestted.</param>
  99. /// <param name="textureSize">The resquested size.</param>
  100. /// <returns>A Cubemap representing this IES</returns>
  101. public (string, Texture) GenerateCubeCookie(TextureImporterCompression compression, int textureSize)
  102. {
  103. int width = 2 * textureSize;
  104. int height = 2 * textureSize;
  105. NativeArray<Color32> colorBuffer;
  106. switch (m_iesReader.PhotometricType)
  107. {
  108. case 3: // type A
  109. colorBuffer = BuildTypeACylindricalTexture(width, height);
  110. break;
  111. case 2: // type B
  112. colorBuffer = BuildTypeBCylindricalTexture(width, height);
  113. break;
  114. default: // type C
  115. colorBuffer = BuildTypeCCylindricalTexture(width, height);
  116. break;
  117. }
  118. return GenerateTexture(m_TextureGenerationType, TextureImporterShape.TextureCube, compression, width, height, colorBuffer);
  119. }
  120. // Gnomonic projection reference:
  121. // http://speleotrove.com/pangazer/gnomonic_projection.html
  122. /// <summary>
  123. /// Generating a 2D Texture of this cookie, using a Gnomonic projection of the bottom of the IES
  124. /// </summary>
  125. /// <param name="compression">Compression parameter requestted.</param>
  126. /// <param name="coneAngle">Cone angle used to performe the Gnomonic projection.</param>
  127. /// <param name="textureSize">The resquested size.</param>
  128. /// <param name="applyLightAttenuation">Bool to enable or not the Light Attenuation based on the squared distance.</param>
  129. /// <returns>A Generated 2D texture doing the projection of the IES using the Gnomonic projection of the bottom half hemisphere with the given 'cone angle'</returns>
  130. public (string, Texture) Generate2DCookie(TextureImporterCompression compression, float coneAngle, int textureSize, bool applyLightAttenuation)
  131. {
  132. NativeArray<Color32> colorBuffer;
  133. switch (m_iesReader.PhotometricType)
  134. {
  135. case 3: // type A
  136. colorBuffer = BuildTypeAGnomonicTexture(coneAngle, textureSize, applyLightAttenuation);
  137. break;
  138. case 2: // type B
  139. colorBuffer = BuildTypeBGnomonicTexture(coneAngle, textureSize, applyLightAttenuation);
  140. break;
  141. default: // type C
  142. colorBuffer = BuildTypeCGnomonicTexture(coneAngle, textureSize, applyLightAttenuation);
  143. break;
  144. }
  145. return GenerateTexture(m_TextureGenerationType, TextureImporterShape.Texture2D, compression, textureSize, textureSize, colorBuffer);
  146. }
  147. private (string, Texture) GenerateCylindricalTexture(TextureImporterCompression compression, int textureSize)
  148. {
  149. int width = 2 * textureSize;
  150. int height = textureSize;
  151. NativeArray<Color32> colorBuffer;
  152. switch (m_iesReader.PhotometricType)
  153. {
  154. case 3: // type A
  155. colorBuffer = BuildTypeACylindricalTexture(width, height);
  156. break;
  157. case 2: // type B
  158. colorBuffer = BuildTypeBCylindricalTexture(width, height);
  159. break;
  160. default: // type C
  161. colorBuffer = BuildTypeCCylindricalTexture(width, height);
  162. break;
  163. }
  164. return GenerateTexture(TextureImporterType.Default, TextureImporterShape.Texture2D, compression, width, height, colorBuffer);
  165. }
  166. (string, Texture) GenerateTexture(TextureImporterType type, TextureImporterShape shape, TextureImporterCompression compression, int width, int height, NativeArray<Color32> colorBuffer)
  167. {
  168. // Default values set by the TextureGenerationSettings constructor can be found in this file on GitHub:
  169. // https://github.com/Unity-Technologies/UnityCsReference/blob/master/Editor/Mono/AssetPipeline/TextureGenerator.bindings.cs
  170. var settings = new TextureGenerationSettings(type);
  171. SourceTextureInformation textureInfo = settings.sourceTextureInformation;
  172. textureInfo.containsAlpha = true;
  173. textureInfo.height = height;
  174. textureInfo.width = width;
  175. TextureImporterSettings textureImporterSettings = settings.textureImporterSettings;
  176. textureImporterSettings.alphaSource = TextureImporterAlphaSource.FromInput;
  177. textureImporterSettings.aniso = 0;
  178. textureImporterSettings.borderMipmap = (textureImporterSettings.textureType == TextureImporterType.Cookie);
  179. textureImporterSettings.filterMode = FilterMode.Bilinear;
  180. textureImporterSettings.generateCubemap = TextureImporterGenerateCubemap.Cylindrical;
  181. textureImporterSettings.mipmapEnabled = false;
  182. textureImporterSettings.npotScale = TextureImporterNPOTScale.None;
  183. textureImporterSettings.readable = true;
  184. textureImporterSettings.sRGBTexture = false;
  185. textureImporterSettings.textureShape = shape;
  186. textureImporterSettings.wrapMode = textureImporterSettings.wrapModeU = textureImporterSettings.wrapModeV = textureImporterSettings.wrapModeW = TextureWrapMode.Clamp;
  187. TextureImporterPlatformSettings platformSettings = settings.platformSettings;
  188. platformSettings.maxTextureSize = 2048;
  189. platformSettings.resizeAlgorithm = TextureResizeAlgorithm.Bilinear;
  190. platformSettings.textureCompression = compression;
  191. TextureGenerationOutput output = TextureGenerator.GenerateTexture(settings, colorBuffer);
  192. if (output.importWarnings.Length > 0)
  193. {
  194. Debug.LogWarning("Cannot properly generate IES texture:\n" + string.Join("\n", output.importWarnings));
  195. }
  196. return (output.importInspectorWarnings, output.texture);
  197. }
  198. NativeArray<Color32> BuildTypeACylindricalTexture(int width, int height)
  199. {
  200. float stepU = 360f / (width - 1);
  201. float stepV = 180f / (height - 1);
  202. var textureBuffer = new NativeArray<Color32>(width * height, Allocator.Temp, NativeArrayOptions.UninitializedMemory);
  203. for (int y = 0; y < height; y++)
  204. {
  205. var slice = new NativeSlice<Color32>(textureBuffer, y * width, width);
  206. float latitude = y * stepV - 90f; // in range [-90..+90] degrees
  207. float verticalAnglePosition = m_iesReader.ComputeVerticalAnglePosition(latitude);
  208. for (int x = 0; x < width; x++)
  209. {
  210. float longitude = x * stepU - 180f; // in range [-180..+180] degrees
  211. float horizontalAnglePosition = m_iesReader.ComputeTypeAorBHorizontalAnglePosition(longitude);
  212. byte value = (byte)((m_iesReader.InterpolateBilinear(horizontalAnglePosition, verticalAnglePosition) / m_iesReader.MaxCandelas) * 255);
  213. slice[x] = new Color32(value, value, value, value);
  214. }
  215. }
  216. return textureBuffer;
  217. }
  218. NativeArray<Color32> BuildTypeBCylindricalTexture(int width, int height)
  219. {
  220. float stepU = k_TwoPi / (width - 1);
  221. float stepV = Mathf.PI / (height - 1);
  222. var textureBuffer = new NativeArray<Color32>(width * height, Allocator.Temp, NativeArrayOptions.UninitializedMemory);
  223. for (int y = 0; y < height; y++)
  224. {
  225. var slice = new NativeSlice<Color32>(textureBuffer, y * width, width);
  226. float v = y * stepV - k_HalfPi; // in range [-90..+90] degrees
  227. float sinV = Mathf.Sin(v);
  228. float cosV = Mathf.Cos(v);
  229. for (int x = 0; x < width; x++)
  230. {
  231. float u = Mathf.PI - x * stepU; // in range [+180..-180] degrees
  232. float sinU = Mathf.Sin(u);
  233. float cosU = Mathf.Cos(u);
  234. // Since a type B luminaire is turned on its side, rotate it to make its polar axis horizontal.
  235. float longitude = Mathf.Atan2(sinV, cosU * cosV) * Mathf.Rad2Deg; // in range [-180..+180] degrees
  236. float latitude = Mathf.Asin(-sinU * cosV) * Mathf.Rad2Deg; // in range [-90..+90] degrees
  237. float horizontalAnglePosition = m_iesReader.ComputeTypeAorBHorizontalAnglePosition(longitude);
  238. float verticalAnglePosition = m_iesReader.ComputeVerticalAnglePosition(latitude);
  239. byte value = (byte)((m_iesReader.InterpolateBilinear(horizontalAnglePosition, verticalAnglePosition) / m_iesReader.MaxCandelas) * 255);
  240. slice[x] = new Color32(value, value, value, value);
  241. }
  242. }
  243. return textureBuffer;
  244. }
  245. NativeArray<Color32> BuildTypeCCylindricalTexture(int width, int height)
  246. {
  247. float stepU = k_TwoPi / (width - 1);
  248. float stepV = Mathf.PI / (height - 1);
  249. var textureBuffer = new NativeArray<Color32>(width * height, Allocator.Temp, NativeArrayOptions.UninitializedMemory);
  250. for (int y = 0; y < height; y++)
  251. {
  252. var slice = new NativeSlice<Color32>(textureBuffer, y * width, width);
  253. float v = y * stepV - k_HalfPi; // in range [-90..+90] degrees
  254. float sinV = Mathf.Sin(v);
  255. float cosV = Mathf.Cos(v);
  256. for (int x = 0; x < width; x++)
  257. {
  258. float u = Mathf.PI - x * stepU; // in range [+180..-180] degrees
  259. float sinU = Mathf.Sin(u);
  260. float cosU = Mathf.Cos(u);
  261. // Since a type C luminaire is generally aimed at nadir, orient it toward +Z at the center of the cylindrical texture.
  262. float longitude = ((Mathf.Atan2(sinU * cosV, sinV) + k_TwoPi) % k_TwoPi) * Mathf.Rad2Deg; // in range [0..360] degrees
  263. float latitude = (Mathf.Asin(-cosU * cosV) + k_HalfPi) * Mathf.Rad2Deg; // in range [0..180] degrees
  264. float horizontalAnglePosition = m_iesReader.ComputeTypeCHorizontalAnglePosition(longitude);
  265. float verticalAnglePosition = m_iesReader.ComputeVerticalAnglePosition(latitude);
  266. byte value = (byte)((m_iesReader.InterpolateBilinear(horizontalAnglePosition, verticalAnglePosition) / m_iesReader.MaxCandelas) * 255);
  267. slice[x] = new Color32(value, value, value, value);
  268. }
  269. }
  270. return textureBuffer;
  271. }
  272. NativeArray<Color32> BuildTypeAGnomonicTexture(float coneAngle, int size, bool applyLightAttenuation)
  273. {
  274. float limitUV = Mathf.Tan(0.5f * coneAngle * Mathf.Deg2Rad);
  275. float stepUV = (2 * limitUV) / (size - 3);
  276. var textureBuffer = new NativeArray<Color32>(size * size, Allocator.Temp, NativeArrayOptions.ClearMemory);
  277. // Leave a one-pixel black border around the texture to avoid cookie spilling.
  278. for (int y = 1; y < size - 1; y++)
  279. {
  280. var slice = new NativeSlice<Color32>(textureBuffer, y * size, size);
  281. float v = (y - 1) * stepUV - limitUV;
  282. for (int x = 1; x < size - 1; x++)
  283. {
  284. float u = (x - 1) * stepUV - limitUV;
  285. float rayLengthSquared = u * u + v * v + 1;
  286. float longitude = Mathf.Atan(u) * Mathf.Rad2Deg; // in range [-90..+90] degrees
  287. float latitude = Mathf.Asin(v / Mathf.Sqrt(rayLengthSquared)) * Mathf.Rad2Deg; // in range [-90..+90] degrees
  288. float horizontalAnglePosition = m_iesReader.ComputeTypeCHorizontalAnglePosition(longitude);
  289. float verticalAnglePosition = m_iesReader.ComputeVerticalAnglePosition(latitude);
  290. // Factor in the light attenuation further from the texture center.
  291. float lightAttenuation = applyLightAttenuation ? rayLengthSquared : 1f;
  292. byte value = (byte)((m_iesReader.InterpolateBilinear(horizontalAnglePosition, verticalAnglePosition) / (m_iesReader.MaxCandelas * lightAttenuation)) * 255);
  293. slice[x] = new Color32(value, value, value, value);
  294. }
  295. }
  296. return textureBuffer;
  297. }
  298. NativeArray<Color32> BuildTypeBGnomonicTexture(float coneAngle, int size, bool applyLightAttenuation)
  299. {
  300. float limitUV = Mathf.Tan(0.5f * coneAngle * Mathf.Deg2Rad);
  301. float stepUV = (2 * limitUV) / (size - 3);
  302. var textureBuffer = new NativeArray<Color32>(size * size, Allocator.Temp, NativeArrayOptions.ClearMemory);
  303. // Leave a one-pixel black border around the texture to avoid cookie spilling.
  304. for (int y = 1; y < size - 1; y++)
  305. {
  306. var slice = new NativeSlice<Color32>(textureBuffer, y * size, size);
  307. float v = (y - 1) * stepUV - limitUV;
  308. for (int x = 1; x < size - 1; x++)
  309. {
  310. float u = (x - 1) * stepUV - limitUV;
  311. float rayLengthSquared = u * u + v * v + 1;
  312. // Since a type B luminaire is turned on its side, U and V are flipped.
  313. float longitude = Mathf.Atan(v) * Mathf.Rad2Deg; // in range [-90..+90] degrees
  314. float latitude = Mathf.Asin(u / Mathf.Sqrt(rayLengthSquared)) * Mathf.Rad2Deg; // in range [-90..+90] degrees
  315. float horizontalAnglePosition = m_iesReader.ComputeTypeCHorizontalAnglePosition(longitude);
  316. float verticalAnglePosition = m_iesReader.ComputeVerticalAnglePosition(latitude);
  317. // Factor in the light attenuation further from the texture center.
  318. float lightAttenuation = applyLightAttenuation ? rayLengthSquared : 1f;
  319. byte value = (byte)((m_iesReader.InterpolateBilinear(horizontalAnglePosition, verticalAnglePosition) / (m_iesReader.MaxCandelas * lightAttenuation)) * 255);
  320. slice[x] = new Color32(value, value, value, value);
  321. }
  322. }
  323. return textureBuffer;
  324. }
  325. NativeArray<Color32> BuildTypeCGnomonicTexture(float coneAngle, int size, bool applyLightAttenuation)
  326. {
  327. float limitUV = Mathf.Tan(0.5f * coneAngle * Mathf.Deg2Rad);
  328. float stepUV = (2 * limitUV) / (size - 3);
  329. var textureBuffer = new NativeArray<Color32>(size * size, Allocator.Temp, NativeArrayOptions.ClearMemory);
  330. // Leave a one-pixel black border around the texture to avoid cookie spilling.
  331. for (int y = 1; y < size - 1; y++)
  332. {
  333. var slice = new NativeSlice<Color32>(textureBuffer, y * size, size);
  334. float v = (y - 1) * stepUV - limitUV;
  335. for (int x = 1; x < size - 1; x++)
  336. {
  337. float u = (x - 1) * stepUV - limitUV;
  338. float uvLength = Mathf.Sqrt(u * u + v * v);
  339. float longitude = ((Mathf.Atan2(v, u) - k_HalfPi + k_TwoPi) % k_TwoPi) * Mathf.Rad2Deg; // in range [0..360] degrees
  340. float latitude = Mathf.Atan(uvLength) * Mathf.Rad2Deg; // in range [0..90] degrees
  341. float horizontalAnglePosition = m_iesReader.ComputeTypeCHorizontalAnglePosition(longitude);
  342. float verticalAnglePosition = m_iesReader.ComputeVerticalAnglePosition(latitude);
  343. // Factor in the light attenuation further from the texture center.
  344. float lightAttenuation = applyLightAttenuation ? (uvLength * uvLength + 1) : 1f;
  345. byte value = (byte)((m_iesReader.InterpolateBilinear(horizontalAnglePosition, verticalAnglePosition) / (m_iesReader.MaxCandelas * lightAttenuation)) * 255);
  346. slice[x] = new Color32(value, value, value, value);
  347. }
  348. }
  349. return textureBuffer;
  350. }
  351. }
  352. }