TerrainLitShaderGUI.cs 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323
  1. using System;
  2. using UnityEngine;
  3. using UnityEngine.Rendering;
  4. using UnityEditor;
  5. using UnityEngine.Experimental.Rendering;
  6. namespace UnityEditor.Rendering.Universal
  7. {
  8. internal class TerrainLitShaderGUI : UnityEditor.ShaderGUI, ITerrainLayerCustomUI
  9. {
  10. private class StylesLayer
  11. {
  12. public readonly GUIContent warningHeightBasedBlending = new GUIContent("Height-based blending is disabled if you have more than four TerrainLayer materials!");
  13. public readonly GUIContent enableHeightBlend = new GUIContent("Enable Height-based Blend", "Blend terrain layers based on height values.");
  14. public readonly GUIContent heightTransition = new GUIContent("Height Transition", "Size in world units of the smooth transition between layers.");
  15. public readonly GUIContent enableInstancedPerPixelNormal = new GUIContent("Enable Per-pixel Normal", "Enable per-pixel normal when the terrain uses instanced rendering.");
  16. public readonly GUIContent diffuseTexture = new GUIContent("Diffuse");
  17. public readonly GUIContent colorTint = new GUIContent("Color Tint");
  18. public readonly GUIContent opacityAsDensity = new GUIContent("Opacity as Density", "Enable Density Blend (if unchecked, opacity is used as Smoothness)");
  19. public readonly GUIContent normalMapTexture = new GUIContent("Normal Map");
  20. public readonly GUIContent normalScale = new GUIContent("Normal Scale");
  21. public readonly GUIContent maskMapTexture = new GUIContent("Mask", "R: Metallic\nG: AO\nB: Height\nA: Smoothness");
  22. public readonly GUIContent maskMapTextureWithoutHeight = new GUIContent("Mask Map", "R: Metallic\nG: AO\nA: Smoothness");
  23. public readonly GUIContent channelRemapping = new GUIContent("Channel Remapping");
  24. public readonly GUIContent defaultValues = new GUIContent("Channel Default Values");
  25. public readonly GUIContent metallic = new GUIContent("R: Metallic");
  26. public readonly GUIContent ao = new GUIContent("G: AO");
  27. public readonly GUIContent height = new GUIContent("B: Height");
  28. public readonly GUIContent heightParametrization = new GUIContent("Parametrization");
  29. public readonly GUIContent heightAmplitude = new GUIContent("Amplitude (cm)");
  30. public readonly GUIContent heightBase = new GUIContent("Base (cm)");
  31. public readonly GUIContent heightMin = new GUIContent("Min (cm)");
  32. public readonly GUIContent heightMax = new GUIContent("Max (cm)");
  33. public readonly GUIContent heightCm = new GUIContent("B: Height (cm)");
  34. public readonly GUIContent smoothness = new GUIContent("A: Smoothness");
  35. }
  36. static StylesLayer s_Styles = null;
  37. private static StylesLayer styles { get { if (s_Styles == null) s_Styles = new StylesLayer(); return s_Styles; } }
  38. public TerrainLitShaderGUI()
  39. {
  40. }
  41. // Height blend params
  42. MaterialProperty enableHeightBlend = null;
  43. const string kEnableHeightBlend = "_EnableHeightBlend";
  44. MaterialProperty heightTransition = null;
  45. const string kHeightTransition = "_HeightTransition";
  46. // Per-pixel Normal (while instancing)
  47. MaterialProperty enableInstancedPerPixelNormal = null;
  48. const string kEnableInstancedPerPixelNormal = "_EnableInstancedPerPixelNormal";
  49. private bool m_ShowChannelRemapping = false;
  50. enum HeightParametrization
  51. {
  52. Amplitude,
  53. MinMax
  54. };
  55. private HeightParametrization m_HeightParametrization = HeightParametrization.Amplitude;
  56. private static bool DoesTerrainUseMaskMaps(TerrainLayer[] terrainLayers)
  57. {
  58. for (int i = 0; i < terrainLayers.Length; ++i)
  59. {
  60. if (terrainLayers[i].maskMapTexture != null)
  61. return true;
  62. }
  63. return false;
  64. }
  65. protected void FindMaterialProperties(MaterialProperty[] props)
  66. {
  67. enableHeightBlend = FindProperty(kEnableHeightBlend, props, false);
  68. heightTransition = FindProperty(kHeightTransition, props, false);
  69. enableInstancedPerPixelNormal = FindProperty(kEnableInstancedPerPixelNormal, props, false);
  70. }
  71. static public void SetupMaterialKeywords(Material material)
  72. {
  73. bool enableHeightBlend = (material.HasProperty(kEnableHeightBlend) && material.GetFloat(kEnableHeightBlend) > 0);
  74. CoreUtils.SetKeyword(material, "_TERRAIN_BLEND_HEIGHT", enableHeightBlend);
  75. bool enableInstancedPerPixelNormal = material.GetFloat(kEnableInstancedPerPixelNormal) > 0.0f;
  76. CoreUtils.SetKeyword(material, "_TERRAIN_INSTANCED_PERPIXEL_NORMAL", enableInstancedPerPixelNormal);
  77. }
  78. static public bool TextureHasAlpha(Texture2D inTex)
  79. {
  80. if (inTex != null)
  81. {
  82. return GraphicsFormatUtility.HasAlphaChannel(GraphicsFormatUtility.GetGraphicsFormat(inTex.format, true));
  83. }
  84. return false;
  85. }
  86. public override void OnGUI(MaterialEditor materialEditorIn, MaterialProperty[] properties)
  87. {
  88. if (materialEditorIn == null)
  89. throw new ArgumentNullException("materialEditorIn");
  90. FindMaterialProperties(properties);
  91. bool optionsChanged = false;
  92. EditorGUI.BeginChangeCheck();
  93. {
  94. if (enableHeightBlend != null)
  95. {
  96. EditorGUI.indentLevel++;
  97. materialEditorIn.ShaderProperty(enableHeightBlend, styles.enableHeightBlend);
  98. if (enableHeightBlend.floatValue > 0)
  99. {
  100. EditorGUI.indentLevel++;
  101. EditorGUILayout.HelpBox(styles.warningHeightBasedBlending.text, MessageType.Info);
  102. materialEditorIn.ShaderProperty(heightTransition, styles.heightTransition);
  103. EditorGUI.indentLevel--;
  104. }
  105. EditorGUI.indentLevel--;
  106. }
  107. EditorGUILayout.Space();
  108. }
  109. if (EditorGUI.EndChangeCheck())
  110. {
  111. optionsChanged = true;
  112. }
  113. bool enablePerPixelNormalChanged = false;
  114. // Since Instanced Per-pixel normal is actually dependent on instancing enabled or not, it is not
  115. // important to check it in the GUI. The shader will make sure it is enabled/disabled properly.s
  116. if (enableInstancedPerPixelNormal != null)
  117. {
  118. EditorGUI.indentLevel++;
  119. EditorGUI.BeginChangeCheck();
  120. materialEditorIn.ShaderProperty(enableInstancedPerPixelNormal, styles.enableInstancedPerPixelNormal);
  121. enablePerPixelNormalChanged = EditorGUI.EndChangeCheck();
  122. EditorGUI.indentLevel--;
  123. }
  124. if (optionsChanged || enablePerPixelNormalChanged)
  125. {
  126. foreach (var obj in materialEditorIn.targets)
  127. {
  128. SetupMaterialKeywords((Material)obj);
  129. }
  130. }
  131. // We should always do this call at the end
  132. materialEditorIn.serializedObject.ApplyModifiedProperties();
  133. }
  134. bool ITerrainLayerCustomUI.OnTerrainLayerGUI(TerrainLayer terrainLayer, Terrain terrain)
  135. {
  136. var terrainLayers = terrain.terrainData.terrainLayers;
  137. // Don't use the member field enableHeightBlend as ShaderGUI.OnGUI might not be called if the material UI is folded.
  138. // heightblend shouldn't be available if we are in multi-pass mode, because it is guaranteed to be broken.
  139. bool heightBlendAvailable = (terrainLayers.Length <= 4);
  140. bool heightBlend = heightBlendAvailable && terrain.materialTemplate.HasProperty(kEnableHeightBlend) && (terrain.materialTemplate.GetFloat(kEnableHeightBlend) > 0);
  141. terrainLayer.diffuseTexture = EditorGUILayout.ObjectField(styles.diffuseTexture, terrainLayer.diffuseTexture, typeof(Texture2D), false) as Texture2D;
  142. TerrainLayerUtility.ValidateDiffuseTextureUI(terrainLayer.diffuseTexture);
  143. var diffuseRemapMin = terrainLayer.diffuseRemapMin;
  144. var diffuseRemapMax = terrainLayer.diffuseRemapMax;
  145. EditorGUI.BeginChangeCheck();
  146. bool enableDensity = false;
  147. if (terrainLayer.diffuseTexture != null)
  148. {
  149. var rect = GUILayoutUtility.GetLastRect();
  150. rect.y += 16 + 4;
  151. rect.width = EditorGUIUtility.labelWidth + 64;
  152. rect.height = 16;
  153. ++EditorGUI.indentLevel;
  154. var diffuseTint = new Color(diffuseRemapMax.x, diffuseRemapMax.y, diffuseRemapMax.z);
  155. diffuseTint = EditorGUI.ColorField(rect, styles.colorTint, diffuseTint, true, false, false);
  156. diffuseRemapMax.x = diffuseTint.r;
  157. diffuseRemapMax.y = diffuseTint.g;
  158. diffuseRemapMax.z = diffuseTint.b;
  159. diffuseRemapMin.x = diffuseRemapMin.y = diffuseRemapMin.z = 0;
  160. if (!heightBlend)
  161. {
  162. rect.y = rect.yMax + 2;
  163. enableDensity = EditorGUI.Toggle(rect, styles.opacityAsDensity, diffuseRemapMin.w > 0);
  164. }
  165. --EditorGUI.indentLevel;
  166. }
  167. diffuseRemapMax.w = 1;
  168. diffuseRemapMin.w = enableDensity ? 1 : 0;
  169. if (EditorGUI.EndChangeCheck())
  170. {
  171. terrainLayer.diffuseRemapMin = diffuseRemapMin;
  172. terrainLayer.diffuseRemapMax = diffuseRemapMax;
  173. }
  174. // Display normal map UI
  175. terrainLayer.normalMapTexture = EditorGUILayout.ObjectField(styles.normalMapTexture, terrainLayer.normalMapTexture, typeof(Texture2D), false) as Texture2D;
  176. TerrainLayerUtility.ValidateNormalMapTextureUI(terrainLayer.normalMapTexture, TerrainLayerUtility.CheckNormalMapTextureType(terrainLayer.normalMapTexture));
  177. if (terrainLayer.normalMapTexture != null)
  178. {
  179. var rect = GUILayoutUtility.GetLastRect();
  180. rect.y += 16 + 4;
  181. rect.width = EditorGUIUtility.labelWidth + 64;
  182. rect.height = 16;
  183. ++EditorGUI.indentLevel;
  184. terrainLayer.normalScale = EditorGUI.FloatField(rect, styles.normalScale, terrainLayer.normalScale);
  185. --EditorGUI.indentLevel;
  186. }
  187. // Display the mask map UI and the remap controls
  188. terrainLayer.maskMapTexture = EditorGUILayout.ObjectField(heightBlend ? styles.maskMapTexture : styles.maskMapTextureWithoutHeight, terrainLayer.maskMapTexture, typeof(Texture2D), false) as Texture2D;
  189. TerrainLayerUtility.ValidateMaskMapTextureUI(terrainLayer.maskMapTexture);
  190. var maskMapRemapMin = terrainLayer.maskMapRemapMin;
  191. var maskMapRemapMax = terrainLayer.maskMapRemapMax;
  192. var smoothness = terrainLayer.smoothness;
  193. var metallic = terrainLayer.metallic;
  194. ++EditorGUI.indentLevel;
  195. EditorGUI.BeginChangeCheck();
  196. m_ShowChannelRemapping = EditorGUILayout.Foldout(m_ShowChannelRemapping, terrainLayer.maskMapTexture != null ? s_Styles.channelRemapping : s_Styles.defaultValues);
  197. if (m_ShowChannelRemapping)
  198. {
  199. if (terrainLayer.maskMapTexture != null)
  200. {
  201. float min, max;
  202. min = maskMapRemapMin.x; max = maskMapRemapMax.x;
  203. EditorGUILayout.MinMaxSlider(s_Styles.metallic, ref min, ref max, 0, 1);
  204. maskMapRemapMin.x = min; maskMapRemapMax.x = max;
  205. min = maskMapRemapMin.y; max = maskMapRemapMax.y;
  206. EditorGUILayout.MinMaxSlider(s_Styles.ao, ref min, ref max, 0, 1);
  207. maskMapRemapMin.y = min; maskMapRemapMax.y = max;
  208. if (heightBlend)
  209. {
  210. EditorGUILayout.LabelField(styles.height);
  211. ++EditorGUI.indentLevel;
  212. m_HeightParametrization = (HeightParametrization)EditorGUILayout.EnumPopup(styles.heightParametrization, m_HeightParametrization);
  213. if (m_HeightParametrization == HeightParametrization.Amplitude)
  214. {
  215. // (height - heightBase) * amplitude
  216. float amplitude = Mathf.Max(maskMapRemapMax.z - maskMapRemapMin.z, Mathf.Epsilon); // to avoid divide by zero
  217. float heightBase = maskMapRemapMin.z / amplitude;
  218. amplitude = EditorGUILayout.FloatField(styles.heightAmplitude, amplitude * 100) / 100;
  219. heightBase = EditorGUILayout.FloatField(styles.heightBase, heightBase * 100) / 100;
  220. maskMapRemapMin.z = heightBase * amplitude;
  221. maskMapRemapMax.z = (1.0f - heightBase) * amplitude;
  222. }
  223. else
  224. {
  225. maskMapRemapMin.z = EditorGUILayout.FloatField(styles.heightMin, maskMapRemapMin.z * 100) / 100;
  226. maskMapRemapMax.z = EditorGUILayout.FloatField(styles.heightMax, maskMapRemapMax.z * 100) / 100;
  227. }
  228. --EditorGUI.indentLevel;
  229. }
  230. min = maskMapRemapMin.w; max = maskMapRemapMax.w;
  231. EditorGUILayout.MinMaxSlider(s_Styles.smoothness, ref min, ref max, 0, 1);
  232. maskMapRemapMin.w = min; maskMapRemapMax.w = max;
  233. }
  234. else
  235. {
  236. metallic = EditorGUILayout.Slider(s_Styles.metallic, metallic, 0, 1);
  237. // AO and Height are still exclusively controlled via the maskRemap controls
  238. // metallic and smoothness have their own values as fields within the LayerData.
  239. maskMapRemapMax.y = EditorGUILayout.Slider(s_Styles.ao, maskMapRemapMax.y, 0, 1);
  240. if (heightBlend)
  241. {
  242. maskMapRemapMax.z = EditorGUILayout.FloatField(s_Styles.heightCm, maskMapRemapMax.z * 100) / 100;
  243. }
  244. // There's a possibility that someone could slide max below the existing min value
  245. // so we'll just protect against that by locking the min value down a little bit.
  246. // In the case of height (Z), we are trying to set min to no lower than zero value unless
  247. // max goes negative. Zero is a good sensible value for the minimum. For AO (Y), we
  248. // don't need this extra protection step because the UI blocks us from going negative
  249. // anyway. In both cases, pushing the slider below the min value will lock them together,
  250. // but min will be "left behind" if you go back up.
  251. maskMapRemapMin.y = Mathf.Min(maskMapRemapMin.y, maskMapRemapMax.y);
  252. maskMapRemapMin.z = Mathf.Min(Mathf.Max(0, maskMapRemapMin.z), maskMapRemapMax.z);
  253. if (TextureHasAlpha(terrainLayer.diffuseTexture))
  254. {
  255. GUIStyle warnStyle = new GUIStyle(GUI.skin.label);
  256. warnStyle.wordWrap = true;
  257. GUILayout.Label("Smoothness is controlled by diffuse alpha channel", warnStyle);
  258. }
  259. else
  260. smoothness = EditorGUILayout.Slider(s_Styles.smoothness, smoothness, 0, 1);
  261. }
  262. }
  263. if (EditorGUI.EndChangeCheck())
  264. {
  265. terrainLayer.maskMapRemapMin = maskMapRemapMin;
  266. terrainLayer.maskMapRemapMax = maskMapRemapMax;
  267. terrainLayer.smoothness = smoothness;
  268. terrainLayer.metallic = metallic;
  269. }
  270. --EditorGUI.indentLevel;
  271. EditorGUILayout.Space();
  272. TerrainLayerUtility.TilingSettingsUI(terrainLayer);
  273. return true;
  274. }
  275. }
  276. }