PigmentMapGenerator.cs 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659
  1. // Fantasy Adventure Environment
  2. // Copyright Staggart Creations
  3. // staggart.xyz
  4. using UnityEngine;
  5. using System.Collections;
  6. using System.IO;
  7. using System;
  8. using Workflow = FAE.TerrainUVUtil.Workflow;
  9. namespace FAE
  10. {
  11. using System.Collections.Generic;
  12. #if UNITY_EDITOR
  13. using UnityEditor;
  14. using UnityEditor.SceneManagement;
  15. [ExecuteInEditMode]
  16. #endif
  17. public class PigmentMapGenerator : MonoBehaviour
  18. {
  19. //Dev
  20. public bool debug = false;
  21. public bool performCleanup = true;
  22. public bool manualInput = false;
  23. //Terrain objects
  24. public GameObject[] terrainObjects;
  25. //Terrain utils
  26. public TerrainUVUtil util;
  27. public Workflow workflow;
  28. public int resIdx = 4;
  29. private int resolution = 1024;
  30. public Vector3 targetSize;
  31. public Vector3 targetOriginPosition;
  32. public Vector3 targetCenterPosition;
  33. //Runtime
  34. [SerializeField]
  35. public Vector4 terrainScaleOffset;
  36. //Terrain terrain
  37. public Terrain[] terrains;
  38. //Mesh terrain
  39. private MeshRenderer[] meshes;
  40. private Material material;
  41. #region Rendering
  42. //Constants
  43. const int HEIGHTOFFSET = 1000;
  44. const int CLIP_PADDING = 100;
  45. //Render options
  46. public LayerMask layerMask = 1;
  47. public float renderLightBrightness = 0.25f;
  48. public bool useAlternativeRenderer = false;
  49. //Rendering
  50. private Camera renderCam;
  51. private Light renderLight;
  52. private Light[] lights;
  53. #endregion
  54. #region Inputs
  55. //Inputs
  56. public Texture2D inputHeightmap;
  57. public Texture2D customPigmentMap;
  58. public bool useCustomPigmentMap;
  59. //Texture options
  60. public bool flipVertically;
  61. public bool flipHortizontally;
  62. public enum TextureRotation
  63. {
  64. None,
  65. Quarter,
  66. Half,
  67. ThreeQuarters
  68. }
  69. public TextureRotation textureRotation;
  70. #endregion
  71. //Textures
  72. public Texture2D pigmentMap;
  73. //Meta
  74. public bool isMultiTerrain;
  75. public string savePath;
  76. private float originalTargetYPos;
  77. [NonSerialized]
  78. public bool showArea;
  79. //MegaSplat
  80. public bool hasTerrainData = true;
  81. public bool isMegaSplat = false;
  82. //Reset lighting settings
  83. UnityEngine.Rendering.AmbientMode ambientMode;
  84. Color ambientColor;
  85. bool enableFog;
  86. Material skyboxMat;
  87. public enum HeightmapChannel
  88. {
  89. None,
  90. Texture1,
  91. Texture2,
  92. Texture3,
  93. Texture4,
  94. Texture5,
  95. Texture6,
  96. Texture7,
  97. Texture8
  98. }
  99. public HeightmapChannel heightmapChannel = HeightmapChannel.None;
  100. public string HeightmapChannelName;
  101. public string[] terrainTextureNames;
  102. //Used at runtime
  103. private void OnEnable()
  104. {
  105. Init();
  106. }
  107. private void OnDisable()
  108. {
  109. //This is to avoid the pigment map remaining in the shader
  110. Shader.SetGlobalTexture("_PigmentMap", null);
  111. }
  112. private void OnDrawGizmosSelected()
  113. {
  114. if (showArea)
  115. {
  116. Color32 color = new Color(0f, 0.66f, 1f, 0.1f);
  117. Gizmos.color = color;
  118. Gizmos.DrawCube(targetCenterPosition, targetSize);
  119. color = new Color(0f, 0.66f, 1f, 0.66f);
  120. Gizmos.color = color;
  121. Gizmos.DrawWireCube(targetCenterPosition, targetSize);
  122. }
  123. }
  124. public void Init()
  125. {
  126. #if UNITY_EDITOR
  127. CheckMegaSplat();
  128. if (GetComponent<Terrain>() || GetComponent<MeshRenderer>())
  129. {
  130. isMultiTerrain = false;
  131. //Single terrain, use first element
  132. terrainObjects = new GameObject[1];
  133. terrainObjects[0] = this.gameObject;
  134. }
  135. else
  136. {
  137. isMultiTerrain = true;
  138. //Init array
  139. if (terrainObjects == null) terrainObjects = new GameObject[0];
  140. }
  141. //Create initial pigment map
  142. if (pigmentMap == null)
  143. {
  144. Generate();
  145. }
  146. #endif
  147. SetPigmentMap();
  148. }
  149. private void CheckMegaSplat()
  150. {
  151. #if __MEGASPLAT__
  152. if(workflow == TerrainUVUtil.Workflow.Terrain)
  153. {
  154. if (terrains[0].materialType == Terrain.MaterialType.Custom)
  155. {
  156. if (terrains[0].materialTemplate.shader.name.Contains("MegaSplat"))
  157. {
  158. isMegaSplat = true;
  159. useAlternativeRenderer = true;
  160. }
  161. else
  162. {
  163. isMegaSplat = false;
  164. }
  165. }
  166. }
  167. #else
  168. isMegaSplat = false;
  169. #endif
  170. }
  171. public void GetChildTerrainObjects(Transform parent)
  172. {
  173. //All childs, recursive
  174. Transform[] children = parent.GetComponentsInChildren<Transform>();
  175. int childCount = 0;
  176. //Count first level transforms
  177. for (int i = 0; i < children.Length; i++)
  178. {
  179. if (children[i].parent == parent) childCount++;
  180. }
  181. //Temp list
  182. List<GameObject> terrainList = new List<GameObject>();
  183. //Init array with childcount length
  184. this.terrainObjects = new GameObject[childCount];
  185. //Fill array with first level transforms
  186. for (int i = 0; i < children.Length; i++)
  187. {
  188. if (children[i].parent == parent)
  189. {
  190. terrainList.Add(children[i].gameObject);
  191. }
  192. }
  193. terrainObjects = terrainList.ToArray();
  194. }
  195. //Grab the terrain position and size and pass it to the shaders
  196. public void GetTargetInfo()
  197. {
  198. if (debug) Debug.Log("Getting target info for " + terrainObjects.Length + " object(s)");
  199. if (!util) util = ScriptableObject.CreateInstance<TerrainUVUtil>();
  200. util.GetObjectPlanarUV(terrainObjects);
  201. //Determine if the object is a terrain or mesh
  202. workflow = util.workflow;
  203. //If using Unity Terrains
  204. terrains = util.terrains;
  205. //Avoid unused variable warning
  206. material = null;
  207. //Summed size
  208. targetSize = util.size;
  209. //First terrain makes up the corner
  210. targetOriginPosition = util.originPosition;
  211. //Center of terrain(s)
  212. targetCenterPosition = util.centerPostion;
  213. //Terrain UV
  214. terrainScaleOffset = util.terrainScaleOffset;
  215. SetPigmentMap();
  216. }
  217. //Set the pigmentmap texture on all shaders that utilize it
  218. public void SetPigmentMap()
  219. {
  220. if (pigmentMap)
  221. {
  222. Shader.SetGlobalVector("_TerrainUV", new Vector4(targetSize.x, targetSize.z, Mathf.Abs(targetOriginPosition.x - 1), Mathf.Abs(targetOriginPosition.z - 1)));
  223. //Set this at runtime to account for different instances having different pigment maps
  224. Shader.SetGlobalTexture("_PigmentMap", pigmentMap);
  225. }
  226. }
  227. public static int IndexToResolution(int i)
  228. {
  229. int res = 0;
  230. switch (i)
  231. {
  232. case 0:
  233. res = 64; break;
  234. case 1:
  235. res = 128; break;
  236. case 2:
  237. res = 256; break;
  238. case 3:
  239. res = 512; break;
  240. case 4:
  241. res = 1024; break;
  242. case 5:
  243. res = 2048; break;
  244. case 6:
  245. res = 4096; break;
  246. }
  247. return res;
  248. }
  249. //Editor functions
  250. #if UNITY_EDITOR
  251. //Primary function
  252. public void Generate()
  253. {
  254. if (terrainObjects.Length == 0) return;
  255. if (!manualInput)
  256. {
  257. GetTargetInfo();
  258. }
  259. else
  260. {
  261. workflow = (terrainObjects[0].GetComponent<Terrain>()) ? Workflow.Terrain : Workflow.Mesh;
  262. }
  263. //If a custom map is assigned, don't generate one, only assign
  264. if (useCustomPigmentMap)
  265. {
  266. pigmentMap = customPigmentMap;
  267. SetPigmentMap();
  268. return;
  269. }
  270. LightSetup();
  271. CameraSetup();
  272. MoveTerrains();
  273. RenderToTexture();
  274. SetPigmentMap();
  275. if (performCleanup) Cleanup();
  276. ResetLights();
  277. }
  278. //Position a camera above the terrain(s) so that the world positions line up perfectly with the texture UV
  279. public void CameraSetup()
  280. {
  281. //Create camera
  282. if (!renderCam)
  283. {
  284. GameObject cameraObj = new GameObject();
  285. cameraObj.name = this.name + " renderCam";
  286. renderCam = cameraObj.AddComponent<Camera>();
  287. }
  288. //Set up a square camera rect
  289. float rectWidth = resolution;
  290. rectWidth /= Screen.width;
  291. renderCam.rect = new Rect(0, 0, 1, 1);
  292. //Camera set up
  293. renderCam.orthographic = true;
  294. renderCam.orthographicSize = (targetSize.x / 2);
  295. renderCam.clearFlags = CameraClearFlags.Skybox;
  296. renderCam.allowHDR = true;
  297. renderCam.farClipPlane = 5000f;
  298. renderCam.useOcclusionCulling = false;
  299. renderCam.cullingMask = layerMask;
  300. //Rendering in Forward mode is a tad darker, so a Directional Light is used to make up for the difference
  301. renderCam.renderingPath = (useAlternativeRenderer || workflow == TerrainUVUtil.Workflow.Mesh) ? RenderingPath.Forward : RenderingPath.VertexLit;
  302. if (workflow == Workflow.Terrain)
  303. {
  304. //Hide tree objects
  305. foreach (Terrain terrain in terrains)
  306. {
  307. terrain.drawTreesAndFoliage = false;
  308. }
  309. }
  310. //Position cam in given center of terrain(s)
  311. renderCam.transform.position = new Vector3(
  312. targetCenterPosition.x,
  313. targetOriginPosition.y + targetSize.y + HEIGHTOFFSET + CLIP_PADDING,
  314. targetCenterPosition.z
  315. );
  316. renderCam.transform.localEulerAngles = new Vector3(90, 0, 0);
  317. }
  318. private void MoveTerrains()
  319. {
  320. //Store terrain position value, to revert to
  321. //Safe to assume all terrains have the same Y-position, should be the case for multi-terrains
  322. originalTargetYPos = targetOriginPosition.y;
  323. //Move terrain objects way up so they are rendered on top of all other objects
  324. foreach (GameObject terrain in terrainObjects)
  325. {
  326. terrain.transform.position = new Vector3(terrain.transform.position.x, HEIGHTOFFSET, terrain.transform.position.z);
  327. }
  328. }
  329. private void RenderToTexture()
  330. {
  331. if (!renderCam) return;
  332. pigmentMap = null;
  333. //If this is a terrain with no textures, abort (except in the case of MegaSplat)
  334. if (workflow == Workflow.Terrain)
  335. {
  336. #if UNITY_2018_3_OR_NEWER
  337. if (terrains[0].terrainData.terrainLayers.Length == 0 && !isMegaSplat) return;
  338. #else
  339. if (terrains[0].terrainData.splatPrototypes.Length == 0 && !isMegaSplat) return;
  340. #endif
  341. }
  342. resolution = IndexToResolution(resIdx);
  343. //Set up render texture
  344. RenderTexture rt = new RenderTexture(resolution, resolution, 0);
  345. renderCam.targetTexture = rt;
  346. savePath = GetTargetFolder();
  347. EditorUtility.DisplayProgressBar("PigmentMapGenerator", "Rendering texture", 1);
  348. //Render camera into a texture
  349. Texture2D render = new Texture2D(rt.width, rt.height, TextureFormat.ARGB32, false);
  350. RenderTexture.active = rt;
  351. renderCam.Render();
  352. //Compose texture on GPU
  353. rt = CompositePigmentMap(rt, inputHeightmap);
  354. render.ReadPixels(new Rect(0, 0, resolution, resolution), 0, 0);
  355. //Cleanup
  356. renderCam.targetTexture = null;
  357. RenderTexture.active = null;
  358. DestroyImmediate(rt);
  359. //Encode
  360. byte[] bytes = render.EncodeToPNG();
  361. //Create file
  362. EditorUtility.DisplayProgressBar("PigmentMapGenerator", "Saving texture...", 1);
  363. File.WriteAllBytes(savePath, bytes);
  364. //Import file
  365. AssetDatabase.Refresh();
  366. //Load the file
  367. pigmentMap = new Texture2D(resolution, resolution, TextureFormat.ARGB32, true);
  368. pigmentMap = AssetDatabase.LoadAssetAtPath(savePath, typeof(Texture2D)) as Texture2D;
  369. EditorUtility.ClearProgressBar();
  370. }
  371. //Add the heightmap and do texture transformations
  372. private RenderTexture CompositePigmentMap(RenderTexture inputMap, Texture2D heightmap = null)
  373. {
  374. Material compositeMat = new Material(Shader.Find("Hidden/PigmentMapComposite"));
  375. compositeMat.hideFlags = HideFlags.DontSave;
  376. compositeMat.SetTexture("_MainTex", inputMap);
  377. //No given heightmap, get from terrain splatmap
  378. //If a channel is chosen, add heightmap to the pigment map's alpha channel
  379. if (heightmap == null && workflow == Workflow.Terrain && (int)heightmapChannel > 0)
  380. {
  381. //Sample one of the two splatmaps (supporting 8 textures as input)
  382. int spatmapIndex = ((int)heightmapChannel >= 5) ? 1 : 0;
  383. int channelIndex = (spatmapIndex > 0) ? (int)heightmapChannel - 4 : (int)heightmapChannel;
  384. Texture2D splatmap = terrains[0].terrainData.alphamapTextures[spatmapIndex];
  385. compositeMat.SetTexture("_SplatMap", splatmap);
  386. compositeMat.SetVector("_SplatMask", new Vector4(
  387. channelIndex == 1 ? 1 : 0,
  388. channelIndex == 2 ? 1 : 0,
  389. channelIndex == 3 ? 1 : 0,
  390. channelIndex == 4 ? 1 : 0)
  391. );
  392. }
  393. if (workflow == Workflow.Mesh)
  394. {
  395. //Transforms
  396. Vector4 transform = new Vector4(0, 0, 0, 0);
  397. if (flipHortizontally) transform.x = 1;
  398. if (flipVertically) transform.y = 1;
  399. transform.z = -(int)textureRotation * (Mathf.PI / 2f);
  400. compositeMat.SetVector("_Transform", transform);
  401. }
  402. if (heightmap != null && isMultiTerrain) //Custom heightmap only for multi-terrains
  403. {
  404. compositeMat.SetTexture("_SplatMap", heightmap);
  405. //Given heightmap is already a grayscale map, unmask all color channels
  406. compositeMat.SetVector("_SplatMask", new Vector4(1, 0, 0, 0));
  407. }
  408. //Render shader output
  409. RenderTexture rt = new RenderTexture(inputMap.width, inputMap.height, 0);
  410. RenderTexture.active = rt;
  411. Graphics.Blit(inputMap, rt, compositeMat);
  412. DestroyImmediate(compositeMat);
  413. //inputMap.ReadPixels(new Rect(0, 0, inputMap.width, inputMap.height), 0, 0);
  414. //inputMap.Apply();
  415. //RenderTexture.active = null;
  416. return rt;
  417. }
  418. //Store pigment map next to TerrainData asset, or mesh's material
  419. private string GetTargetFolder()
  420. {
  421. string m_targetPath = null;
  422. //Compose target file path
  423. //For single terrain
  424. if (terrainObjects.Length == 1)
  425. {
  426. if (workflow == TerrainUVUtil.Workflow.Terrain)
  427. {
  428. //If there is a TerraData asset, use its file location
  429. if (terrains[0].terrainData.name != string.Empty)
  430. {
  431. hasTerrainData = true;
  432. m_targetPath = AssetDatabase.GetAssetPath(terrains[0].terrainData) + string.Format("{0}_pigmentmap.png", terrains[0].terrainData.name);
  433. m_targetPath = m_targetPath.Replace(terrains[0].terrainData.name + ".asset", string.Empty);
  434. }
  435. //If there is no TerrainData, store it next to the scene. Some terrain systems don't use TerrainData
  436. else
  437. {
  438. hasTerrainData = false;
  439. string scenePath = EditorSceneManager.GetActiveScene().path.Replace(".unity", string.Empty);
  440. m_targetPath = scenePath + "_pigmentmap.png";
  441. }
  442. }
  443. //If the target is a mesh, use the location of its material
  444. else if (workflow == TerrainUVUtil.Workflow.Mesh)
  445. {
  446. material = terrainObjects[0].GetComponent<MeshRenderer>().sharedMaterial;
  447. m_targetPath = AssetDatabase.GetAssetPath(material) + string.Format("{0}_pigmentmap.png", string.Empty);
  448. m_targetPath = m_targetPath.Replace(".mat", string.Empty);
  449. }
  450. }
  451. //For multi-terrain, use scene folder or material
  452. else
  453. {
  454. if (workflow == TerrainUVUtil.Workflow.Mesh)
  455. {
  456. material = terrainObjects[0].GetComponent<MeshRenderer>().sharedMaterial;
  457. m_targetPath = AssetDatabase.GetAssetPath(material) + string.Format("{0}_pigmentmap.png", string.Empty);
  458. m_targetPath = m_targetPath.Replace(".mat", string.Empty);
  459. }
  460. else
  461. {
  462. string scenePath = EditorSceneManager.GetActiveScene().path.Replace(".unity", string.Empty);
  463. m_targetPath = scenePath + "_pigmentmap.png";
  464. }
  465. }
  466. return m_targetPath;
  467. }
  468. void Cleanup()
  469. {
  470. DestroyImmediate(renderCam.gameObject);
  471. if (renderLight) DestroyImmediate(renderLight.gameObject);
  472. //Reset terrains
  473. foreach (GameObject terrain in terrainObjects)
  474. {
  475. //Reset terrain position(s)
  476. terrain.transform.position = new Vector3(terrain.transform.position.x, originalTargetYPos, terrain.transform.position.z);
  477. }
  478. //Reset draw foliage
  479. if (workflow == TerrainUVUtil.Workflow.Terrain)
  480. {
  481. foreach (Terrain terrain in terrains)
  482. {
  483. terrain.drawTreesAndFoliage = true;
  484. }
  485. }
  486. renderCam = null;
  487. renderLight = null;
  488. }
  489. //Disable directional light and set ambient color to white for an albedo result
  490. void LightSetup()
  491. {
  492. //Set up lighting for a proper albedo color
  493. lights = FindObjectsOfType<Light>();
  494. foreach (Light light in lights)
  495. {
  496. if (light.type == LightType.Directional)
  497. light.gameObject.SetActive(false);
  498. }
  499. //Store current settings to revert to
  500. ambientMode = RenderSettings.ambientMode;
  501. ambientColor = RenderSettings.ambientLight;
  502. enableFog = RenderSettings.fog;
  503. skyboxMat = RenderSettings.skybox;
  504. //Flat lighting
  505. RenderSettings.ambientMode = UnityEngine.Rendering.AmbientMode.Flat;
  506. RenderSettings.ambientLight = Color.white;
  507. RenderSettings.fog = false;
  508. RenderSettings.skybox = null;
  509. //To account for Forward rendering being slightly darker, add a light
  510. if (useAlternativeRenderer)
  511. {
  512. if (!renderLight) renderLight = new GameObject().AddComponent<Light>();
  513. renderLight.name = "renderLight";
  514. renderLight.type = LightType.Directional;
  515. renderLight.transform.localEulerAngles = new Vector3(90, 0, 0);
  516. renderLight.intensity = renderLightBrightness;
  517. }
  518. }
  519. //Re-enable directional light and reset ambient mode
  520. void ResetLights()
  521. {
  522. foreach (Light light in lights)
  523. {
  524. if (light.type == LightType.Directional)
  525. light.gameObject.SetActive(true);
  526. }
  527. RenderSettings.ambientMode = ambientMode;
  528. RenderSettings.ambientLight = ambientColor;
  529. RenderSettings.fog = enableFog;
  530. RenderSettings.skybox = skyboxMat;
  531. }
  532. #endif
  533. }
  534. }