MegascansImporter.cs 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526
  1. #if UNITY_EDITOR
  2. using UnityEngine;
  3. using UnityEditor;
  4. using System.Collections.Generic;
  5. using System.IO;
  6. using System;
  7. using Newtonsoft.Json.Linq;
  8. namespace Quixel
  9. {
  10. /// <summary>
  11. /// Completely rewritten importer with lots of commenting... Should be quite a bit faster than the previous one too.
  12. /// For those looking to customize, please be aware that some of the methods in here do not use built in Unity API calls.
  13. /// Ajwad Imran - Technical Artist @ Quixel.
  14. /// Lee Devonald - (Former) Technical Artist @ Quixel.
  15. /// </summary>
  16. public class MegascansImporter : Editor
  17. {
  18. private bool plant = false;
  19. private string assetName;
  20. private string type;
  21. private string mapName;
  22. private string folderNamingConvention;
  23. private string path;
  24. private string activeLOD;
  25. private int dispType;
  26. private int texPack;
  27. private int lodFadeMode;
  28. private int shaderType;
  29. private bool setupCollision = false;
  30. private bool applyToSelection;
  31. private bool addAssetToScene;
  32. private bool importAllTextures;
  33. private string texPath;
  34. private string matPath;
  35. private Material finalMat;
  36. private Material billboardMat;
  37. private bool highPoly = false;
  38. private bool isAlembic = false;
  39. private bool hasBillboardLOD = false;
  40. private bool hasBillboardLODOnly = false;
  41. /// <summary>
  42. /// Takes an imported JSON object, and breaks it into relevant components and data.
  43. /// Then calls relevant functions for actual import of asset.
  44. /// </summary>
  45. /// <param name="objectList"></param>
  46. public string ImportMegascansAssets(JObject objectList)
  47. {
  48. var startTime = System.DateTime.Now;
  49. activeLOD = (string)objectList["activeLOD"];
  50. string minLOD = (string)objectList["minLOD"];
  51. assetName = (string)objectList["name"];
  52. type = (string)objectList["type"];
  53. isAlembic = false;
  54. plant = false;
  55. highPoly = false;
  56. hasBillboardLOD = false;
  57. hasBillboardLODOnly = false;
  58. mapName = "";
  59. folderNamingConvention = (string)objectList["folderNamingConvention"];
  60. //get mesh components from the current object.
  61. JArray meshComps = (JArray)objectList["meshList"];
  62. //run a check to see if we're using Unity 5 or below, and then if we're trying to import a high poly mesh. if so, let the user know we are aborting the import.
  63. if (meshComps.Count > 0)
  64. {
  65. isAlembic = Path.GetExtension((string)meshComps[0]["path"]) == ".abc";
  66. }
  67. hasBillboardLOD = MegascansMeshUtils.ContainsLowestLOD((JArray)objectList["lodList"], minLOD, activeLOD);
  68. if (type.ToLower().Contains("3dplant"))
  69. {
  70. plant = true;
  71. if (minLOD == activeLOD)
  72. {
  73. hasBillboardLODOnly = true;
  74. }
  75. }
  76. try
  77. {
  78. LoadPreferences();
  79. shaderType = MegascansUtilities.GetShaderType();
  80. MegascansUtilities.CalculateNumberOfOperations(objectList, dispType, texPack, shaderType, hasBillboardLODOnly);
  81. path = ConstructPath(objectList);
  82. if (path == null || path == "")
  83. {
  84. Debug.Log("Asset: " + (string)objectList["name"] + " already exist in the Project. Please delete/rename the existing folder and re-import this asset.");
  85. AssetDatabase.Refresh();
  86. return null;
  87. }
  88. }
  89. catch (Exception ex)
  90. {
  91. Debug.Log("Error setting import path.");
  92. Debug.Log(ex.ToString());
  93. MegascansUtilities.HideProgressBar();
  94. }
  95. try
  96. {
  97. //process textures
  98. ProcessTextures(objectList);
  99. if (finalMat == null && !(plant && hasBillboardLODOnly))
  100. {
  101. Debug.Log("Could not import the textures and create the material.");
  102. return null;
  103. }
  104. else
  105. {
  106. if (type.ToLower().Contains("surface") && applyToSelection)
  107. {
  108. foreach (MeshRenderer render in MegascansUtilities.GetSelectedMeshRenderers())
  109. {
  110. render.material = finalMat;
  111. }
  112. }
  113. }
  114. }
  115. catch (Exception ex)
  116. {
  117. Debug.Log("Error importing textures.");
  118. Debug.Log(ex.ToString());
  119. MegascansUtilities.HideProgressBar();
  120. }
  121. //process meshes
  122. if (meshComps == null && !type.Contains("surface"))
  123. {
  124. Debug.LogError("No meshes found. Please double check your export settings.");
  125. Debug.Log("Import failed.");
  126. return null;
  127. }
  128. if (meshComps.Count > 0)
  129. {
  130. if (activeLOD == "high")
  131. {
  132. //detect if we're trying to import a high poly mesh...
  133. string msg = "You are about to import a high poly mesh. \nThese meshes are usually millions of polygons and can cause instability to your project. \nWould you like to proceed?";
  134. highPoly = EditorUtility.DisplayDialog("WARNING!", msg, "Yes", "No");
  135. }
  136. try
  137. {
  138. //process meshes and prefabs
  139. PrefabData prefData = new PrefabData(path, assetName, folderNamingConvention, lodFadeMode, highPoly, addAssetToScene, setupCollision, hasBillboardLOD, isAlembic, false, false, finalMat, billboardMat, new List<string>(), new List<List<string>>());
  140. MegascansMeshUtils.ProcessMeshes(objectList, path, highPoly, plant, prefData);
  141. }
  142. catch (Exception ex)
  143. {
  144. Debug.Log("Error importing meshes.");
  145. Debug.Log(ex.ToString());
  146. MegascansUtilities.HideProgressBar();
  147. }
  148. }
  149. var endTime = System.DateTime.Now;
  150. var totalTime = endTime - startTime;
  151. Debug.Log("Asset Import Time: " + totalTime);
  152. AssetDatabase.Refresh();
  153. MegascansUtilities.HideProgressBar();
  154. Resources.UnloadUnusedAssets();
  155. GC.Collect();
  156. return path;
  157. }
  158. #region Texture Processing Methods
  159. void ProcessTextures(JObject objectList)
  160. {
  161. texPath = MegascansUtilities.ValidateFolderCreate(path, "Textures");
  162. matPath = Path.Combine(MegascansUtilities.ValidateFolderCreate(path, "Materials"), folderNamingConvention);
  163. if (!(plant && hasBillboardLODOnly))
  164. {
  165. MegascansUtilities.UpdateProgressBar(1.0f, "Processing Asset " + assetName, "Creating material...");
  166. finalMat = MegascansMaterialUtils.CreateMaterial(shaderType, matPath, isAlembic, dispType, texPack);
  167. ImportAllTextures(finalMat, (JArray)objectList["components"]);
  168. ImportAllTextures(finalMat, (JArray)objectList["packedTextures"]);
  169. }
  170. if (plant && hasBillboardLOD)
  171. {
  172. texPath = MegascansUtilities.ValidateFolderCreate(texPath, "Billboard");
  173. matPath += "_Billboard";
  174. MegascansUtilities.UpdateProgressBar(1.0f, "Processing Asset " + assetName, "Creating material...");
  175. billboardMat = MegascansMaterialUtils.CreateMaterial(shaderType, matPath, isAlembic, dispType, texPack);
  176. ImportAllTextures(billboardMat, (JArray)objectList["components-billboard"]);
  177. ImportAllTextures(billboardMat, (JArray)objectList["packed-billboard"]);
  178. }
  179. }
  180. void ImportAllTextures(Material mat, JArray texturesList)
  181. {
  182. try
  183. {
  184. List<string> typesOfTexturesAvailable = new List<string>();
  185. for (int i = 0; i < texturesList.Count; i++)
  186. {
  187. typesOfTexturesAvailable.Add((string)texturesList[i]["type"]);
  188. }
  189. string destTexPath;
  190. Texture2D tex;
  191. for (int i = 0; i < texturesList.Count; i++)
  192. {
  193. mapName = (string)texturesList[i]["type"];
  194. MegascansUtilities.UpdateProgressBar(1.0f, "Processing Asset " + assetName, "Importing texture: " + mapName);
  195. if ((string)texturesList[i]["type"] == "albedo" || ((string)texturesList[i]["type"] == "diffuse" && !typesOfTexturesAvailable.Contains("albedo")))
  196. {
  197. destTexPath = Path.Combine(texPath, (string)texturesList[i]["nameOverride"]);
  198. MegascansTextureProcessor texPrcsr = new MegascansTextureProcessor((string)texturesList[i]["path"], destTexPath);
  199. tex = texPrcsr.ImportTexture();
  200. mat.SetTexture("_MainTex", tex);
  201. mat.SetTexture("_BaseColorMap", tex);
  202. if (shaderType == 1)
  203. {
  204. mat.SetTexture("_BaseMap", tex);
  205. mat.SetColor("_BaseColor", Color.white);
  206. }
  207. if (MegascansUtilities.AlbedoHasOpacity((JObject)texturesList[i]["channelsData"]))
  208. {
  209. float alphaCutoff = 0.33f;
  210. texPrcsr.AdjustAlphaCutoff();
  211. if (shaderType > 0)
  212. {
  213. mat.SetFloat("_AlphaClip", 1);
  214. mat.SetFloat("_Cutoff", 0.1f);
  215. mat.SetFloat("_Mode", 1);
  216. mat.SetFloat("_Cull", 0);
  217. mat.EnableKeyword("_ALPHATEST_ON");
  218. }
  219. else
  220. {
  221. mat.SetInt("_AlphaCutoffEnable", 1);
  222. mat.SetFloat("_AlphaCutoff", alphaCutoff);
  223. mat.SetInt("_DoubleSidedEnable", 1);
  224. mat.SetOverrideTag("RenderType", "TransparentCutout");
  225. mat.SetInt("_ZTestGBuffer", (int)UnityEngine.Rendering.CompareFunction.Equal);
  226. mat.SetInt("_CullMode", (int)UnityEngine.Rendering.CullMode.Off);
  227. mat.SetInt("_CullModeForward", (int)UnityEngine.Rendering.CullMode.Back);
  228. mat.SetInt("_SrcBlend", (int)UnityEngine.Rendering.BlendMode.One);
  229. mat.SetInt("_DstBlend", (int)UnityEngine.Rendering.BlendMode.Zero);
  230. mat.SetInt("_ZWrite", 1);
  231. mat.renderQueue = 2450;
  232. mat.SetInt("_ZTestGBuffer", (int)UnityEngine.Rendering.CompareFunction.Equal);
  233. mat.EnableKeyword("_ALPHATEST_ON");
  234. mat.EnableKeyword("_DOUBLESIDED_ON");
  235. mat.DisableKeyword("_BLENDMODE_ALPHA");
  236. }
  237. }
  238. }
  239. else if ((string)texturesList[i]["type"] == "specular")
  240. {
  241. if (texPack > 0)
  242. {
  243. destTexPath = Path.Combine(texPath, (string)texturesList[i]["nameOverride"]);
  244. MegascansTextureProcessor texPrcsr = new MegascansTextureProcessor((string)texturesList[i]["path"], destTexPath);
  245. tex = texPrcsr.ImportTexture();
  246. mat.SetTexture("_SpecGlossMap", tex);
  247. mat.SetTexture("_SpecularColorMap", tex);
  248. mat.SetColor("_SpecColor", new UnityEngine.Color(1.0f, 1.0f, 1.0f));
  249. mat.SetColor("_SpecularColor", new UnityEngine.Color(1.0f, 1.0f, 1.0f));
  250. mat.SetFloat("_WorkflowMode", 0);
  251. mat.SetFloat("_MaterialID", 4);
  252. mat.EnableKeyword("_METALLICSPECGLOSSMAP");
  253. mat.EnableKeyword("_SPECGLOSSMAP");
  254. mat.EnableKeyword("_SPECULAR_SETUP");
  255. mat.EnableKeyword("_SPECULARCOLORMAP");
  256. mat.EnableKeyword("_MATERIAL_FEATURE_SPECULAR_COLOR");
  257. }
  258. }
  259. else if ((string)texturesList[i]["type"] == "masks")
  260. {
  261. if (texPack < 1 || shaderType < 1)
  262. {
  263. destTexPath = Path.Combine(texPath, (string)texturesList[i]["nameOverride"]);
  264. MegascansTextureProcessor texPrcsr = new MegascansTextureProcessor((string)texturesList[i]["path"], destTexPath, false, false);
  265. tex = texPrcsr.ImportTexture();
  266. mat.SetTexture("_MaskMap", tex);
  267. mat.SetTexture("_MetallicGlossMap", tex);
  268. mat.EnableKeyword("_MASKMAP");
  269. mat.SetFloat("_MaterialID", 1);
  270. mat.EnableKeyword("_METALLICSPECGLOSSMAP");
  271. mat.EnableKeyword("_METALLICGLOSSMAP");
  272. bool hasMetalness;
  273. bool hasAO;
  274. bool hasGloss;
  275. MegascansUtilities.MaskMapComponents((JObject)texturesList[i]["channelsData"], out hasMetalness, out hasAO, out hasGloss);
  276. if (!hasMetalness)
  277. {
  278. mat.SetFloat("_Metallic", 1.0f);
  279. }
  280. if (hasAO)
  281. {
  282. mat.SetTexture("_OcclusionMap", tex);
  283. mat.EnableKeyword("_OCCLUSIONMAP");
  284. }
  285. }
  286. }
  287. else if ((string)texturesList[i]["type"] == "normal")
  288. {
  289. string normalMapPath = (string)texturesList[i]["path"];
  290. if (activeLOD == "high" && !normalMapPath.Contains("NormalBump"))
  291. {
  292. for (int x = 0; x < 10; x++)
  293. {
  294. string n = normalMapPath.Replace("_LOD" + x.ToString(), "Bump");
  295. if (File.Exists(n))
  296. {
  297. normalMapPath = n;
  298. break;
  299. }
  300. }
  301. if (normalMapPath.Contains("NormalBump"))
  302. continue;
  303. }
  304. destTexPath = Path.Combine(texPath, (string)texturesList[i]["nameOverride"]);
  305. MegascansTextureProcessor texPrcsr = new MegascansTextureProcessor(normalMapPath, destTexPath, true, false);
  306. tex = texPrcsr.ImportTexture();
  307. mat.SetTexture("_BumpMap", tex);
  308. mat.SetTexture("_NormalMap", tex);
  309. mat.EnableKeyword("_NORMALMAP_TANGENT_SPACE");
  310. mat.EnableKeyword("_NORMALMAP");
  311. }
  312. else if ((string)texturesList[i]["type"] == "ao" && texPack > 0)
  313. {
  314. destTexPath = Path.Combine(texPath, (string)texturesList[i]["nameOverride"]);
  315. MegascansTextureProcessor texPrcsr = new MegascansTextureProcessor((string)texturesList[i]["path"], destTexPath, false, false);
  316. tex = texPrcsr.ImportTexture();
  317. mat.SetTexture("_OcclusionMap", tex);
  318. mat.EnableKeyword("_OCCLUSIONMAP");
  319. }
  320. else if ((string)texturesList[i]["type"] == "displacement")
  321. {
  322. if (dispType > 0)
  323. {
  324. destTexPath = Path.Combine(texPath, (string)texturesList[i]["nameOverride"]);
  325. MegascansTextureProcessor texPrcsr = new MegascansTextureProcessor((string)texturesList[i]["path"], destTexPath, false, false);
  326. tex = texPrcsr.ImportTexture();
  327. mat.SetTexture("_HeightMap", tex);
  328. mat.SetTexture("_ParallaxMap", tex);
  329. mat.EnableKeyword("_DISPLACEMENT_LOCK_TILING_SCALE");
  330. if (shaderType == 0)
  331. mat.EnableKeyword("_HEIGHTMAP");
  332. if (dispType == 1)
  333. {
  334. mat.EnableKeyword("_VERTEX_DISPLACEMENT");
  335. mat.EnableKeyword("_VERTEX_DISPLACEMENT_LOCK_OBJECT_SCALE");
  336. }
  337. else if (dispType == 2)
  338. {
  339. mat.EnableKeyword("_PARALLAXMAP");
  340. mat.EnableKeyword("_PIXEL_DISPLACEMENT");
  341. mat.EnableKeyword("_PIXEL_DISPLACEMENT_LOCK_OBJECT_SCALE");
  342. }
  343. }
  344. }
  345. else if ((string)texturesList[i]["type"] == "translucency")
  346. {
  347. destTexPath = Path.Combine(texPath, (string)texturesList[i]["nameOverride"]);
  348. MegascansTextureProcessor texPrcsr = new MegascansTextureProcessor((string)texturesList[i]["path"], destTexPath);
  349. tex = texPrcsr.ImportTexture();
  350. mat.SetTexture("_SubsurfaceMaskMap", tex);
  351. mat.EnableKeyword("_SUBSURFACE_MASK_MAP");
  352. mat.SetInt("_DiffusionProfile", 1);
  353. mat.SetFloat("_EnableSubsurfaceScattering", 1);
  354. if (!typesOfTexturesAvailable.Contains("transmission"))
  355. {
  356. mat.SetTexture("_ThicknessMap", tex);
  357. mat.EnableKeyword("_THICKNESSMAP");
  358. }
  359. if (plant)
  360. {
  361. mat.SetInt("_DiffusionProfile", 2);
  362. mat.SetFloat("_CoatMask", 0.0f);
  363. mat.SetInt("_EnableWind", 1);
  364. mat.EnableKeyword("_VERTEX_WIND");
  365. }
  366. MegascansMaterialUtils.AddSSSSettings(mat, shaderType);
  367. }
  368. else if ((string)texturesList[i]["type"] == "transmission")
  369. {
  370. destTexPath = Path.Combine(texPath, (string)texturesList[i]["nameOverride"]);
  371. MegascansTextureProcessor texPrcsr = new MegascansTextureProcessor((string)texturesList[i]["path"], destTexPath, false, false);
  372. tex = texPrcsr.ImportTexture();
  373. mat.SetTexture("_ThicknessMap", tex);
  374. mat.EnableKeyword("_THICKNESSMAP");
  375. mat.SetInt("_DiffusionProfile", 2);
  376. MegascansMaterialUtils.AddSSSSettings(mat, shaderType);
  377. }
  378. else if (importAllTextures)
  379. {
  380. mapName = (string)texturesList[i]["type"];
  381. string mapPath = (string)texturesList[i]["path"];
  382. string otherTexFolder = MegascansUtilities.ValidateFolderCreate(texPath, "Others");
  383. destTexPath = Path.Combine(otherTexFolder, (string)texturesList[i]["nameOverride"]);
  384. MegascansTextureProcessor texPrcsr = new MegascansTextureProcessor(mapPath, destTexPath);
  385. tex = texPrcsr.ImportTexture();
  386. }
  387. }
  388. }
  389. catch (Exception ex)
  390. {
  391. Debug.Log("Exception::MegascansImporter::ImportAllTextures:: " + ex.ToString());
  392. MegascansUtilities.HideProgressBar();
  393. }
  394. }
  395. #endregion
  396. #region Formatting Utilities
  397. void LoadPreferences()
  398. {
  399. path = MegascansUtilities.FixPath(EditorPrefs.GetString("QuixelDefaultPath", "Quixel/Megascans/"));
  400. dispType = EditorPrefs.GetInt("QuixelDefaultDisplacement");
  401. texPack = EditorPrefs.GetInt("QuixelDefaultTexPacking");
  402. shaderType = EditorPrefs.GetInt("QuixelDefaultShader");
  403. lodFadeMode = EditorPrefs.GetInt("QuixelDefaultLodFadeMode", 1);
  404. setupCollision = EditorPrefs.GetBool("QuixelDefaultSetupCollision", true);
  405. applyToSelection = EditorPrefs.GetBool("QuixelDefaultApplyToSelection", false);
  406. addAssetToScene = EditorPrefs.GetBool("QuixelDefaultAddAssetToScene", false);
  407. importAllTextures = EditorPrefs.GetBool("QuixelDefaultImportAllTextures", false);
  408. }
  409. /// <summary>
  410. /// Returns the final directory for our asset, creating subfolders where necessary in the 'Assets' directory.
  411. /// </summary>
  412. string ConstructPath(JObject objectList)
  413. {
  414. /// Make sure path is "Assets/...." not "D:/Unity Projects/My Project/Assets/...." otherwise the AssetDatabase cannot write files to it.
  415. /// Lastly I also match the path with the Application DataPath in order to make sure this is the right path selected from the Bridge.
  416. AssetDatabase.Refresh();
  417. string defPath = "";
  418. bool addNextPathPart = false;
  419. if ((string)objectList["exportPath"] != "")
  420. {
  421. path = (string)objectList["exportPath"];
  422. }
  423. else
  424. {
  425. defPath = "Assets";
  426. addNextPathPart = true;
  427. }
  428. string[] pathParts = MegascansUtilities.FixSlashes(path).Split('/');
  429. List<string> finalPathParts = new List<string>();
  430. foreach (string part in pathParts)
  431. {
  432. if (part == "Assets" && !addNextPathPart)
  433. {
  434. addNextPathPart = true;
  435. }
  436. if (addNextPathPart)
  437. {
  438. finalPathParts.Add(part);
  439. }
  440. }
  441. if (!addNextPathPart)
  442. {
  443. return null;
  444. }
  445. //First, create the user specified path from the importer settings.
  446. if (finalPathParts.Count > 0)
  447. {
  448. for (int i = 0; i < finalPathParts.Count; i++)
  449. {
  450. defPath = MegascansUtilities.ValidateFolderCreate(defPath, finalPathParts[i]); //FixSlashes(Path.Combine(defPath, finalPathParts[i]));//ValidateFolderCreate(defPath, finalPathParts[i]);
  451. }
  452. }
  453. if (!AssetDatabase.IsValidFolder(defPath))
  454. {
  455. return null;
  456. }
  457. //then create check to see if the asset type subfolder exists, create it if it doesn't.
  458. defPath = MegascansUtilities.ValidateFolderCreate(defPath, MegascansUtilities.GetAssetType((string)objectList["path"]));
  459. defPath = MegascansUtilities.ValidateFolderCreate(defPath, folderNamingConvention);
  460. return defPath;
  461. }
  462. #endregion
  463. }
  464. }
  465. #endif