StylizedSurfaceEditor.cs 41 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957
  1. using System;
  2. using System.Collections.Generic;
  3. using System.IO;
  4. using System.Reflection;
  5. using System.Text;
  6. using System.Text.RegularExpressions;
  7. using FlatKit;
  8. using FlatKit.StylizedSurface;
  9. using JetBrains.Annotations;
  10. using UnityEditor;
  11. using UnityEngine;
  12. using UnityEngine.Rendering;
  13. using Object = UnityEngine.Object;
  14. // ReSharper disable Unity.PreferAddressByIdToGraphicsParams
  15. // ReSharper disable StringLiteralTypo
  16. [HelpURL("https://flatkit.dustyroom.com/")]
  17. public class StylizedSurfaceEditor : BaseShaderGUI {
  18. private Material _target;
  19. private MaterialProperty[] _properties;
  20. private int _celShadingNumSteps = 0;
  21. private AnimationCurve _gradient = new AnimationCurve(new Keyframe(0, 0), new Keyframe(1, 1));
  22. private bool? _smoothNormalsEnabled;
  23. private static GUIStyle _boxStyle;
  24. private static GUIStyle _richHelpBoxStyle;
  25. private static GUIStyle _foldoutStyle;
  26. private static readonly Dictionary<string, bool> FoldoutStates = new() { { RenderingOptionsName, false } };
  27. private static readonly Color HashColor = new Color(0.85023f, 0.85034f, 0.85045f, 0.85056f);
  28. private static readonly int ColorPropertyName = Shader.PropertyToID("_BaseColor");
  29. private static readonly int LightmapDirection = Shader.PropertyToID("_LightmapDirection");
  30. private static readonly int LightmapDirectionPitch = Shader.PropertyToID("_LightmapDirectionPitch");
  31. private static readonly int LightmapDirectionYaw = Shader.PropertyToID("_LightmapDirectionYaw");
  32. private const string OutlineSmoothNormalsKeyword = "DR_OUTLINE_SMOOTH_NORMALS";
  33. private const string RenderingOptionsName = "Rendering Options";
  34. private const string UnityVersion = "JLE8GP";
  35. private void DrawStandard(MaterialProperty property) {
  36. // Remove everything in square brackets.
  37. var cleanName = Regex.Replace(property.displayName, @" ?\[.*?\]", string.Empty);
  38. if (!Tooltips.Map.TryGetValue(property.displayName, out string tooltip)) {
  39. Tooltips.Map.TryGetValue(cleanName, out tooltip);
  40. }
  41. var guiContent = new GUIContent(cleanName, tooltip);
  42. if (property.type == MaterialProperty.PropType.Texture) {
  43. if (!property.name.Contains("_BaseMap")) {
  44. EditorGUILayout.Space(10);
  45. }
  46. materialEditor.TexturePropertySingleLine(guiContent, property);
  47. } else {
  48. materialEditor.ShaderProperty(property, guiContent);
  49. }
  50. }
  51. private MaterialProperty FindProperty(string name) {
  52. return FindProperty(name, _properties);
  53. }
  54. private bool HasProperty(string name) {
  55. return _target != null && _target.HasProperty(name);
  56. }
  57. #if UNITY_2021_2_OR_NEWER
  58. [Obsolete("MaterialChanged has been renamed ValidateMaterial", false)]
  59. #endif
  60. public override void MaterialChanged(Material material) { }
  61. public override void OnGUI(MaterialEditor editor, MaterialProperty[] properties) {
  62. materialEditor = editor;
  63. _properties = properties;
  64. _target = editor.target as Material;
  65. Debug.Assert(_target != null, "_target != null");
  66. if (_target.shader.name.Equals("FlatKit/Stylized Surface With Outline")) {
  67. EditorGUILayout.HelpBox(
  68. "'Stylized Surface with Outline' shader has been deprecated. Please use the outline section in the 'Stylized Surface' shader.",
  69. MessageType.Warning);
  70. }
  71. if (!Application.unityVersion.Contains(Rev(UnityVersion)) &&
  72. !Application.unityVersion.Contains('b') &&
  73. !Application.unityVersion.Contains('a') &&
  74. !Application.unityVersion.Replace('.', '^').Contains("23^2")) {
  75. EditorGUILayout.BeginVertical(EditorStyles.helpBox);
  76. EditorGUILayout.BeginHorizontal();
  77. // Icon.
  78. {
  79. EditorGUILayout.BeginVertical();
  80. GUILayout.FlexibleSpace();
  81. var icon = EditorGUIUtility.IconContent("console.erroricon@2x").image;
  82. var iconSize = Mathf.Min(Mathf.Min(60, icon.width), EditorGUIUtility.currentViewWidth - 100);
  83. GUILayout.Label(icon, new GUIStyle {
  84. alignment = TextAnchor.MiddleCenter,
  85. imagePosition = ImagePosition.ImageLeft,
  86. fixedWidth = iconSize,
  87. fixedHeight = iconSize,
  88. padding = new RectOffset(0, 0, 5, 5),
  89. margin = new RectOffset(0, 0, 0, 0),
  90. });
  91. GUILayout.FlexibleSpace();
  92. EditorGUILayout.EndVertical();
  93. }
  94. var unityMajorVersion = Application.unityVersion.Substring(0, Application.unityVersion.LastIndexOf('.'));
  95. var m = $"This version of <b>Flat Kit</b> is designed for <b>Unity {Rev(UnityVersion)}</b>. " +
  96. $"You are currently using <b>Unity {unityMajorVersion}</b>.\n" +
  97. "<i>The shader and the UI below may not work correctly.</i>\n" +
  98. "Please <b>re-download Flat Kit</b> to get the compatible version.";
  99. var style = new GUIStyle(EditorStyles.wordWrappedLabel) {
  100. alignment = TextAnchor.MiddleLeft,
  101. richText = true,
  102. fontSize = 12,
  103. padding = new RectOffset(0, 5, 5, 5),
  104. margin = new RectOffset(0, 0, 0, 0),
  105. };
  106. EditorGUILayout.LabelField(m, style);
  107. EditorGUILayout.EndHorizontal();
  108. // Unity version help buttons.
  109. {
  110. EditorGUILayout.BeginHorizontal();
  111. GUILayout.FlexibleSpace();
  112. const float buttonWidth = 120;
  113. if (GUILayout.Button("Package Manager", EditorStyles.miniButton, GUILayout.Width(buttonWidth))) {
  114. const string packageName = "Flat Kit: Toon Shading and Water";
  115. var type = typeof(UnityEditor.PackageManager.UI.Window);
  116. var method = type.GetMethod("OpenFilter", BindingFlags.NonPublic | BindingFlags.Static);
  117. if (method != null) {
  118. method.Invoke(null, new object[] { $"AssetStore/{packageName}" });
  119. } else {
  120. UnityEditor.PackageManager.UI.Window.Open(packageName);
  121. }
  122. }
  123. if (GUILayout.Button("Asset Store", EditorStyles.miniButton, GUILayout.Width(buttonWidth))) {
  124. const string assetStoreUrl = "https://u3d.as/1uVy";
  125. Application.OpenURL(assetStoreUrl);
  126. }
  127. if (GUILayout.Button("Support", EditorStyles.miniButton, GUILayout.Width(buttonWidth))) {
  128. const string contactUrl = "https://flatkit.dustyroom.com/contact-details/";
  129. Application.OpenURL(contactUrl);
  130. }
  131. EditorGUILayout.EndHorizontal();
  132. EditorGUILayout.Space(2);
  133. }
  134. EditorGUILayout.EndVertical();
  135. EditorGUILayout.Space();
  136. foreach (var property in properties) {
  137. // materialEditor.ShaderProperty(property, property.displayName);
  138. DrawStandard(property);
  139. }
  140. return;
  141. }
  142. if (_target.IsKeywordEnabled("DR_OUTLINE_ON") && _target.IsKeywordEnabled("_ALPHATEST_ON")) {
  143. EditorGUILayout.HelpBox(
  144. "The 'Outline' and 'Alpha Clip' features are usually incompatible. The outline shader pass will not be using alpha clipping.",
  145. MessageType.Warning);
  146. }
  147. int originalIntentLevel = EditorGUI.indentLevel;
  148. string foldoutName = string.Empty;
  149. int foldoutRemainingItems = 0;
  150. bool latestFoldoutState = false;
  151. _foldoutStyle ??= new GUIStyle(EditorStyles.foldout) {
  152. margin = {
  153. left = -5
  154. },
  155. padding = {
  156. left = 20
  157. },
  158. };
  159. _boxStyle ??= new GUIStyle(EditorStyles.helpBox);
  160. _richHelpBoxStyle ??= new GUIStyle(EditorStyles.helpBox) {
  161. richText = true
  162. };
  163. bool vGroupStarted = false;
  164. foreach (MaterialProperty property in properties) {
  165. string displayName = property.displayName;
  166. if (displayName.Contains("[") && !displayName.Contains("FOLDOUT")) {
  167. EditorGUI.indentLevel += 1;
  168. }
  169. bool skipProperty = false;
  170. skipProperty |= displayName.Contains("[_CELPRIMARYMODE_SINGLE]") &&
  171. !_target.IsKeywordEnabled("_CELPRIMARYMODE_SINGLE");
  172. skipProperty |= displayName.Contains("[_CELPRIMARYMODE_STEPS]") &&
  173. !_target.IsKeywordEnabled("_CELPRIMARYMODE_STEPS");
  174. skipProperty |= displayName.Contains("[_CELPRIMARYMODE_CURVE]") &&
  175. !_target.IsKeywordEnabled("_CELPRIMARYMODE_CURVE");
  176. skipProperty |= displayName.Contains("[DR_CEL_EXTRA_ON]") &&
  177. !property.name.Equals("_CelExtraEnabled") &&
  178. !_target.IsKeywordEnabled("DR_CEL_EXTRA_ON");
  179. skipProperty |= displayName.Contains("[DR_SPECULAR_ON]") &&
  180. !property.name.Equals("_SpecularEnabled") &&
  181. !_target.IsKeywordEnabled("DR_SPECULAR_ON");
  182. skipProperty |= displayName.Contains("[DR_RIM_ON]") &&
  183. !property.name.Equals("_RimEnabled") &&
  184. !_target.IsKeywordEnabled("DR_RIM_ON");
  185. skipProperty |= displayName.Contains("[DR_GRADIENT_ON]") &&
  186. !property.name.Equals("_GradientEnabled") &&
  187. !_target.IsKeywordEnabled("DR_GRADIENT_ON");
  188. skipProperty |= displayName.Contains("[_UNITYSHADOWMODE_MULTIPLY]") &&
  189. !_target.IsKeywordEnabled("_UNITYSHADOWMODE_MULTIPLY");
  190. skipProperty |= displayName.Contains("[_UNITYSHADOWMODE_COLOR]") &&
  191. !_target.IsKeywordEnabled("_UNITYSHADOWMODE_COLOR");
  192. skipProperty |= displayName.Contains("[DR_ENABLE_LIGHTMAP_DIR]") &&
  193. !_target.IsKeywordEnabled("DR_ENABLE_LIGHTMAP_DIR");
  194. skipProperty |= displayName.Contains("[DR_OUTLINE_ON]") &&
  195. !_target.IsKeywordEnabled("DR_OUTLINE_ON");
  196. skipProperty |= displayName.Contains("[_EMISSION]") &&
  197. !_target.IsKeywordEnabled("_EMISSION");
  198. skipProperty |= displayName.Contains("[_OUTLINESPACE_SCREEN]") &&
  199. !_target.IsKeywordEnabled("_OUTLINESPACE_SCREEN");
  200. bool isOverrideLightDirectionToggle = displayName.ToLower().Contains("override light direction");
  201. if (Lightmapping.lightingDataAsset != null)
  202. {
  203. // Lightmapping is enabled.
  204. _target.EnableKeyword("DR_ENABLE_LIGHTMAP_DIR");
  205. if (isOverrideLightDirectionToggle)
  206. {
  207. skipProperty = true;
  208. if (latestFoldoutState && foldoutName.Contains("Advanced Lighting"))
  209. {
  210. const string m = "<b>Lightmap Direction Override</b> is required because the scene is using baked lighting.";
  211. EditorGUILayout.LabelField(m, new GUIStyle(EditorStyles.label)
  212. {
  213. richText = true,
  214. wordWrap = true,
  215. padding = new RectOffset(16, 0, 8, 0)
  216. });
  217. }
  218. }
  219. }
  220. if (_target.IsKeywordEnabled("DR_ENABLE_LIGHTMAP_DIR") && isOverrideLightDirectionToggle) {
  221. var dirPitch = _target.GetFloat(LightmapDirectionPitch);
  222. var dirYaw = _target.GetFloat(LightmapDirectionYaw);
  223. var dirPitchRad = dirPitch * Mathf.Deg2Rad;
  224. var dirYawRad = dirYaw * Mathf.Deg2Rad;
  225. var direction = new Vector4(Mathf.Sin(dirPitchRad) * Mathf.Sin(dirYawRad), Mathf.Cos(dirPitchRad),
  226. Mathf.Sin(dirPitchRad) * Mathf.Cos(dirYawRad), 0.0f);
  227. _target.SetVector(LightmapDirection, direction);
  228. }
  229. if (displayName.Contains("FOLDOUT")) {
  230. foldoutName = displayName.Split('(', ')')[1];
  231. string foldoutItemCount = displayName.Split('{', '}')[1];
  232. foldoutRemainingItems = Convert.ToInt32(foldoutItemCount);
  233. FoldoutStates.TryAdd(property.name, false);
  234. EditorGUILayout.Space(10);
  235. FoldoutStates[property.name] =
  236. EditorGUILayout.Foldout(FoldoutStates[property.name], foldoutName, _foldoutStyle);
  237. latestFoldoutState = FoldoutStates[property.name];
  238. if (latestFoldoutState) {
  239. BeginBox();
  240. }
  241. }
  242. if (foldoutRemainingItems > 0) {
  243. skipProperty = skipProperty || !latestFoldoutState;
  244. EditorGUI.indentLevel += 1;
  245. --foldoutRemainingItems;
  246. } else if (!skipProperty) {
  247. if (EditorGUI.indentLevel > 0 && !vGroupStarted) {
  248. BeginBox();
  249. vGroupStarted = true;
  250. }
  251. if (EditorGUI.indentLevel <= 0 && vGroupStarted) {
  252. EndBox();
  253. vGroupStarted = false;
  254. }
  255. }
  256. if (_target.IsKeywordEnabled("_CELPRIMARYMODE_STEPS") && displayName.Contains("[LAST_PROP_STEPS]")) {
  257. EditorGUILayout.HelpBox(
  258. "This mode creates a step texture that control the light/shadow transition. To use:\n" +
  259. "1. Set the number of steps (e.g. 3 means three steps between lit and shaded regions), \n" +
  260. "2. Save the steps as a texture - 'Save Ramp Texture' button",
  261. MessageType.Info);
  262. int currentNumSteps = _target.GetInt("_CelNumSteps");
  263. if (currentNumSteps != _celShadingNumSteps) {
  264. if (GUILayout.Button("Save Ramp Texture")) {
  265. _celShadingNumSteps = currentNumSteps;
  266. PromptTextureSave(editor, GenerateStepTexture, "_CelStepTexture", FilterMode.Point);
  267. }
  268. }
  269. }
  270. if (_target.IsKeywordEnabled("_CELPRIMARYMODE_CURVE") && displayName.Contains("[LAST_PROP_CURVE]")) {
  271. EditorGUILayout.HelpBox(
  272. "This mode uses arbitrary curves to control the light/shadow transition. How to use:\n" +
  273. "1. Set shading curve (generally from 0.0 to 1.0)\n" +
  274. "2. [Optional] Save the curve preset\n" +
  275. "3. Save the curve as a texture.",
  276. MessageType.Info);
  277. _gradient = EditorGUILayout.CurveField("Shading curve", _gradient);
  278. if (GUILayout.Button("Save Ramp Texture")) {
  279. PromptTextureSave(editor, GenerateCurveTexture, "_CelCurveTexture",
  280. FilterMode.Trilinear);
  281. }
  282. }
  283. if (!skipProperty &&
  284. property.type == MaterialProperty.PropType.Color &&
  285. property.colorValue == HashColor) {
  286. property.colorValue = _target.GetColor(ColorPropertyName);
  287. }
  288. if (!skipProperty && property.name.Contains("_EmissionMap")) {
  289. EditorGUILayout.Space(10);
  290. bool emission = editor.EmissionEnabledProperty();
  291. EditorGUILayout.Space(-10);
  292. EditorGUI.indentLevel += 1;
  293. if (emission) {
  294. _target.EnableKeyword("_EMISSION");
  295. } else {
  296. _target.DisableKeyword("_EMISSION");
  297. }
  298. }
  299. if (!skipProperty && property.name.Contains("_EmissionColor")) {
  300. EditorGUI.indentLevel += 1;
  301. }
  302. bool hideInInspector = (property.flags & MaterialProperty.PropFlags.HideInInspector) != 0;
  303. if (!hideInInspector && !skipProperty) {
  304. EditorGUI.BeginChangeCheck();
  305. DrawStandard(property);
  306. // Toggle per-object outlines.
  307. if (property.name.Equals("_OutlineEnabled")) {
  308. var outlineEnabled = _target.IsKeywordEnabled("DR_OUTLINE_ON");
  309. if (_target.GetShaderPassEnabled("SRPDEFAULTUNLIT")) {
  310. // Legacy per-object outlines.
  311. const string m = "Using legacy per-object outlines. Please update to Unity 2022.3+ to use " +
  312. "the new Renderer Feature outlines.";
  313. EditorGUILayout.HelpBox(m, MessageType.Info);
  314. } else {
  315. // Per-object outlines are now handled by a Renderer Feature.
  316. var renderer = ObjectOutlineEditorUtils.GetRendererData();
  317. if (renderer != null) {
  318. GUILayout.Space(-EditorGUIUtility.standardVerticalSpacing -
  319. EditorGUIUtility.singleLineHeight);
  320. GUILayout.BeginHorizontal();
  321. GUILayout.FlexibleSpace();
  322. if (GUILayout.Button("Select Renderer Feature", EditorStyles.miniButtonRight)) {
  323. Selection.activeObject = renderer;
  324. }
  325. GUILayout.EndHorizontal();
  326. }
  327. }
  328. if (EditorGUI.EndChangeCheck()) {
  329. // Outline toggle changed state.
  330. #if UNITY_2022_3_OR_NEWER
  331. ObjectOutlineEditorUtils.SetActive(_target, outlineEnabled);
  332. #else
  333. _target.SetShaderPassEnabled("SRPDEFAULTUNLIT", outlineEnabled);
  334. #endif
  335. }
  336. #if UNITY_2022_3_OR_NEWER
  337. // Switch from legacy shader pass per-object outlines (pre-4.7.0) to new Renderer Feature outlines.
  338. const string legacyPassName = "SRPDEFAULTUNLIT";
  339. if (_target.GetShaderPassEnabled(legacyPassName) && outlineEnabled) {
  340. _target.SetShaderPassEnabled(legacyPassName, false);
  341. var m = $"<color=grey>[Flat Kit]</color> <b>Per-object outlines</b> are now handled by a " +
  342. $"<b>Renderer Feature</b>. The material <color=green><b>{_target.name}</b></color> " +
  343. $"and the <b>URP Renderer</b> currently in use will be updated now.";
  344. Debug.Log(m);
  345. ObjectOutlineEditorUtils.SetActive(_target, true);
  346. }
  347. #endif
  348. }
  349. }
  350. if (!skipProperty && displayName.Contains("Detail Impact")) {
  351. DrawTileOffset(editor, FindProperty("_DetailMap"));
  352. }
  353. // Horizontal line separators.
  354. {
  355. if (FoldoutStates.TryGetValue("_BaseMap", out bool baseMapFoldoutState) && baseMapFoldoutState) {
  356. if (displayName.Contains("Texture Impact") ||
  357. displayName.Contains("Detail Impact") ||
  358. displayName.Contains("Normal Map") ||
  359. displayName.Contains("Emission Color")) {
  360. var indent = EditorGUI.indentLevel;
  361. EditorGUI.indentLevel = originalIntentLevel + 1;
  362. EditorGUILayout.Space(5);
  363. EditorGUILayout.LabelField("", GUI.skin.horizontalSlider);
  364. EditorGUILayout.Space(-5);
  365. EditorGUI.indentLevel = indent;
  366. }
  367. }
  368. }
  369. if (!skipProperty && property.name.Contains("_EmissionColor")) {
  370. EditorGUILayout.Space(10);
  371. EditorGUI.indentLevel -= 1;
  372. EditorGUILayout.LabelField("Applied to All Maps", EditorStyles.boldLabel);
  373. DrawTileOffset(editor, FindProperty("_BaseMap"));
  374. }
  375. if (!skipProperty && property.displayName.Contains("Smooth Normals")) {
  376. SmoothNormalsUI();
  377. }
  378. EditorGUI.indentLevel = originalIntentLevel;
  379. if (foldoutRemainingItems == 0 && latestFoldoutState) {
  380. EndBox();
  381. latestFoldoutState = false;
  382. if (foldoutName.Contains("Advanced Lighting")) {
  383. const string m = "<b>Advanced Lighting</b> features have significantly different impact in real-time and baked lighting.";
  384. EditorGUILayout.LabelField(m, _richHelpBoxStyle);
  385. }
  386. }
  387. }
  388. EditorGUILayout.Space(10);
  389. var renderingOptionsName = RenderingOptionsName;
  390. FoldoutStates[renderingOptionsName] =
  391. EditorGUILayout.Foldout(FoldoutStates[renderingOptionsName], renderingOptionsName, _foldoutStyle);
  392. if (FoldoutStates[renderingOptionsName]) {
  393. EditorGUI.indentLevel += 1;
  394. BeginBox();
  395. HandleUrpSettings(_target, editor);
  396. EndBox();
  397. }
  398. if (_target.IsKeywordEnabled("_UNITYSHADOWMODE_NONE")) {
  399. _target.EnableKeyword("_RECEIVE_SHADOWS_OFF");
  400. } else {
  401. _target.DisableKeyword("_RECEIVE_SHADOWS_OFF");
  402. }
  403. // Toggle the outline pass. Disabling by name `Outline` doesn't work.
  404. // _target.SetShaderPassEnabled("SRPDEFAULTUNLIT", _target.IsKeywordEnabled("DR_OUTLINE_ON"));
  405. /*
  406. if (HasProperty("_MainTex")) {
  407. TransferToBaseMap();
  408. }
  409. */
  410. }
  411. private void BeginBox() {
  412. EditorGUILayout.BeginVertical(_boxStyle);
  413. EditorGUILayout.Space(3);
  414. }
  415. private void EndBox() {
  416. EditorGUILayout.Space(3);
  417. EditorGUILayout.EndVertical();
  418. }
  419. private void SmoothNormalsUI() {
  420. var keywordEnabled = _target.IsKeywordEnabled(OutlineSmoothNormalsKeyword);
  421. _smoothNormalsEnabled ??= keywordEnabled;
  422. if (keywordEnabled != _smoothNormalsEnabled) {
  423. var mesh = GetMeshFromSelection();
  424. if (mesh == null) {
  425. return;
  426. }
  427. if (keywordEnabled) {
  428. if (!MeshSmoother.HasSmoothNormals(mesh)) {
  429. SmoothNormals(mesh);
  430. }
  431. }
  432. } else if (keywordEnabled) {
  433. var mesh = GetMeshFromSelection();
  434. if (mesh == null) {
  435. return;
  436. }
  437. if (!MeshSmoother.HasSmoothNormals(mesh)) {
  438. EditorGUILayout.HelpBox(
  439. "Mesh does not have smooth normals. Please use the 'Smooth Normals' button to generate them.",
  440. MessageType.Warning);
  441. if (mesh.subMeshCount > 1) {
  442. EditorGUILayout.HelpBox(
  443. "Mesh smoothing is not supported for meshes with multiple sub-meshes. " +
  444. "Please combine sub-meshes into a single sub-mesh before smoothing.",
  445. MessageType.Warning);
  446. }
  447. GUILayout.BeginHorizontal();
  448. GUILayout.FlexibleSpace();
  449. if (GUILayout.Button("Smooth Normals", GUILayout.ExpandWidth(false))) {
  450. SmoothNormals(mesh);
  451. }
  452. GUILayout.EndHorizontal();
  453. }
  454. }
  455. _smoothNormalsEnabled = keywordEnabled;
  456. }
  457. private void SmoothNormals(Mesh mesh) {
  458. if (mesh.isReadable) {
  459. MeshSmoother.SmoothNormals(mesh);
  460. var path = AssetDatabase.GetAssetPath(mesh);
  461. var pathSmoothed =
  462. $"{Path.GetDirectoryName(path)}\\{Path.GetFileNameWithoutExtension(path)} Smooth Normals.asset";
  463. var fileExists = File.Exists(pathSmoothed);
  464. if (fileExists) {
  465. var action1 = EditorUtility.DisplayDialogComplex("Smoothing normals",
  466. "Asset already exists. Do you want to overwrite it?",
  467. "Overwrite", "Cancel", "Open asset");
  468. switch (action1) {
  469. case 0: {
  470. // Overwrite
  471. AssetDatabase.DeleteAsset(pathSmoothed);
  472. break;
  473. }
  474. case 1: {
  475. // Cancel
  476. _target.DisableKeyword(OutlineSmoothNormalsKeyword);
  477. return;
  478. }
  479. case 2: {
  480. // Open asset
  481. AssetDatabase.OpenAsset(AssetDatabase.LoadAssetAtPath<Object>(pathSmoothed));
  482. return;
  483. }
  484. }
  485. }
  486. var newMesh = Object.Instantiate(mesh);
  487. try {
  488. AssetDatabase.CreateAsset(newMesh, pathSmoothed);
  489. Debug.Log($"<b>[Flat Kit]</b> Created asset <i>{pathSmoothed}</i>.");
  490. }
  491. catch (Exception) {
  492. Debug.Log("<b>[Flat Kit]</b> Please ignore the error above. It is caused by a Unity bug.");
  493. var action2 = EditorUtility.DisplayDialogComplex("Error",
  494. $"Could not create asset at path '{pathSmoothed}'. Please check the Console for more information.",
  495. "OK", "Show Console", "Save to 'Assets' folder");
  496. switch (action2) {
  497. case 0: {
  498. // OK
  499. _target.DisableKeyword(OutlineSmoothNormalsKeyword);
  500. return;
  501. }
  502. case 1: {
  503. // Show Console
  504. EditorApplication.ExecuteMenuItem("Window/General/Console");
  505. return;
  506. }
  507. case 2: {
  508. // Save to Assets folder
  509. pathSmoothed = $"Assets/{Path.GetFileName(pathSmoothed)}";
  510. AssetDatabase.CreateAsset(newMesh, pathSmoothed);
  511. break;
  512. }
  513. }
  514. }
  515. AssetDatabase.SaveAssets();
  516. AssetDatabase.Refresh();
  517. var newAsset = AssetDatabase.LoadAssetAtPath<Mesh>(pathSmoothed);
  518. if (newAsset != null) {
  519. SetMeshToSelection(newAsset);
  520. }
  521. } else {
  522. var action = EditorUtility.DisplayDialogComplex("Smoothing normals",
  523. "Mesh is not readable. Please enable 'Read/Write Enabled' in the mesh import settings.",
  524. "Set readable", "Open import settings", "Cancel");
  525. switch (action) {
  526. case 0: {
  527. // Set readable
  528. var importer = AssetImporter.GetAtPath(AssetDatabase.GetAssetPath(mesh));
  529. if (importer != null) {
  530. var modelImporter = importer as ModelImporter;
  531. if (modelImporter != null) {
  532. modelImporter.isReadable = true;
  533. modelImporter.SaveAndReimport();
  534. }
  535. }
  536. break;
  537. }
  538. case 1: {
  539. // Open import settings
  540. var importer = AssetImporter.GetAtPath(AssetDatabase.GetAssetPath(mesh));
  541. if (importer != null) {
  542. AssetDatabase.OpenAsset(importer);
  543. }
  544. break;
  545. }
  546. case 2: {
  547. // Cancel
  548. _target.DisableKeyword(OutlineSmoothNormalsKeyword);
  549. break;
  550. }
  551. }
  552. }
  553. }
  554. private static Mesh GetMeshFromSelection() {
  555. var go = Selection.activeGameObject;
  556. if (go == null) {
  557. EditorGUILayout.HelpBox(
  558. "All meshes using smooth normals need processing. To process a mesh, select it in a scene and " +
  559. "re-enable 'Smooth Normals' in the inspector.",
  560. MessageType.Info);
  561. return null;
  562. }
  563. var meshFilter = go.GetComponent<MeshFilter>();
  564. if (meshFilter != null) {
  565. return meshFilter.sharedMesh;
  566. }
  567. var skinnedMeshRenderer = go.GetComponent<SkinnedMeshRenderer>();
  568. if (skinnedMeshRenderer != null) {
  569. return skinnedMeshRenderer.sharedMesh;
  570. }
  571. return null;
  572. }
  573. private static void SetMeshToSelection(Mesh mesh) {
  574. var go = Selection.activeGameObject;
  575. if (go == null) {
  576. return;
  577. }
  578. var meshFilter = go.GetComponent<MeshFilter>();
  579. if (meshFilter != null) {
  580. meshFilter.sharedMesh = mesh;
  581. return;
  582. }
  583. var skinnedMeshRenderer = go.GetComponent<SkinnedMeshRenderer>();
  584. if (skinnedMeshRenderer != null) {
  585. skinnedMeshRenderer.sharedMesh = mesh;
  586. }
  587. }
  588. // Adapted from BaseShaderGUI.cs.
  589. private void HandleUrpSettings(Material material, MaterialEditor editor) {
  590. queueOffsetProp = FindProperty("_QueueOffset");
  591. bool alphaClip = false;
  592. if (material.HasProperty("_AlphaClip")) {
  593. alphaClip = material.GetFloat("_AlphaClip") >= 0.5;
  594. }
  595. if (alphaClip) {
  596. material.EnableKeyword("_ALPHATEST_ON");
  597. } else {
  598. material.DisableKeyword("_ALPHATEST_ON");
  599. }
  600. if (HasProperty("_Surface")) {
  601. EditorGUI.BeginChangeCheck();
  602. var surfaceProp = FindProperty("_Surface");
  603. EditorGUI.showMixedValue = surfaceProp.hasMixedValue;
  604. var surfaceType = (SurfaceType)surfaceProp.floatValue;
  605. surfaceType = (SurfaceType)EditorGUILayout.EnumPopup("Surface Type", surfaceType);
  606. if (EditorGUI.EndChangeCheck()) {
  607. editor.RegisterPropertyChangeUndo("Surface Type");
  608. surfaceProp.floatValue = (float)surfaceType;
  609. }
  610. if (surfaceType == SurfaceType.Opaque) {
  611. if (alphaClip) {
  612. material.renderQueue = (int)RenderQueue.AlphaTest;
  613. material.SetOverrideTag("RenderType", "TransparentCutout");
  614. } else {
  615. material.renderQueue = (int)RenderQueue.Geometry;
  616. material.SetOverrideTag("RenderType", "Opaque");
  617. }
  618. material.renderQueue += queueOffsetProp != null ? (int)queueOffsetProp.floatValue : 0;
  619. material.SetInt("_SrcBlend", (int)UnityEngine.Rendering.BlendMode.One);
  620. material.SetInt("_DstBlend", (int)UnityEngine.Rendering.BlendMode.Zero);
  621. material.SetInt("_ZWrite", 1);
  622. material.DisableKeyword("_ALPHAPREMULTIPLY_ON");
  623. material.SetShaderPassEnabled("ShadowCaster", true);
  624. } else // Transparent
  625. {
  626. BlendMode blendMode = (BlendMode)material.GetFloat("_Blend");
  627. // Specific Transparent Mode Settings
  628. switch (blendMode) {
  629. case BlendMode.Alpha:
  630. material.SetInt("_SrcBlend", (int)UnityEngine.Rendering.BlendMode.SrcAlpha);
  631. material.SetInt("_DstBlend", (int)UnityEngine.Rendering.BlendMode.OneMinusSrcAlpha);
  632. material.DisableKeyword("_ALPHAPREMULTIPLY_ON");
  633. break;
  634. case BlendMode.Premultiply:
  635. material.SetInt("_SrcBlend", (int)UnityEngine.Rendering.BlendMode.One);
  636. material.SetInt("_DstBlend", (int)UnityEngine.Rendering.BlendMode.OneMinusSrcAlpha);
  637. material.EnableKeyword("_ALPHAPREMULTIPLY_ON");
  638. break;
  639. case BlendMode.Additive:
  640. material.SetInt("_SrcBlend", (int)UnityEngine.Rendering.BlendMode.SrcAlpha);
  641. material.SetInt("_DstBlend", (int)UnityEngine.Rendering.BlendMode.One);
  642. material.DisableKeyword("_ALPHAPREMULTIPLY_ON");
  643. break;
  644. case BlendMode.Multiply:
  645. material.SetInt("_SrcBlend", (int)UnityEngine.Rendering.BlendMode.DstColor);
  646. material.SetInt("_DstBlend", (int)UnityEngine.Rendering.BlendMode.Zero);
  647. material.DisableKeyword("_ALPHAPREMULTIPLY_ON");
  648. material.EnableKeyword("_ALPHAMODULATE_ON");
  649. break;
  650. }
  651. // General Transparent Material Settings
  652. material.SetOverrideTag("RenderType", "Transparent");
  653. material.SetInt("_ZWrite", 0);
  654. material.renderQueue = (int)RenderQueue.Transparent;
  655. material.renderQueue += queueOffsetProp != null ? (int)queueOffsetProp.floatValue : 0;
  656. material.SetShaderPassEnabled("ShadowCaster", false);
  657. }
  658. // DR: draw popup.
  659. if (surfaceType == SurfaceType.Transparent && HasProperty("_Blend")) {
  660. EditorGUI.BeginChangeCheck();
  661. var blendModeProperty = FindProperty("_Blend");
  662. EditorGUI.showMixedValue = blendModeProperty.hasMixedValue;
  663. var blendMode = (BlendMode)blendModeProperty.floatValue;
  664. blendMode = (BlendMode)EditorGUILayout.EnumPopup("Blend Mode", blendMode);
  665. if (EditorGUI.EndChangeCheck()) {
  666. editor.RegisterPropertyChangeUndo("Blend Mode");
  667. blendModeProperty.floatValue = (float)blendMode;
  668. }
  669. }
  670. }
  671. DrawQueueOffsetField();
  672. // DR: draw popup.
  673. if (HasProperty("_Cull")) {
  674. EditorGUI.BeginChangeCheck();
  675. var cullProp = FindProperty("_Cull");
  676. EditorGUI.showMixedValue = cullProp.hasMixedValue;
  677. var culling = (RenderFace)cullProp.floatValue;
  678. culling = (RenderFace)EditorGUILayout.EnumPopup("Render Faces", culling);
  679. if (EditorGUI.EndChangeCheck()) {
  680. editor.RegisterPropertyChangeUndo("Render Faces");
  681. cullProp.floatValue = (float)culling;
  682. material.doubleSidedGI = (RenderFace)cullProp.floatValue != RenderFace.Front;
  683. }
  684. }
  685. if (HasProperty("_AlphaClip")) {
  686. EditorGUI.BeginChangeCheck();
  687. var clipProp = FindProperty("_AlphaClip");
  688. EditorGUI.showMixedValue = clipProp.hasMixedValue;
  689. // ReSharper disable once CompareOfFloatsByEqualityOperator
  690. var alphaClipEnabled = EditorGUILayout.Toggle("Alpha Clipping", clipProp.floatValue == 1);
  691. if (EditorGUI.EndChangeCheck())
  692. clipProp.floatValue = alphaClipEnabled ? 1 : 0;
  693. EditorGUI.showMixedValue = false;
  694. // ReSharper disable once CompareOfFloatsByEqualityOperator
  695. if (clipProp.floatValue == 1 && HasProperty("_Cutoff")) {
  696. var cutoffProp = FindProperty("_Cutoff");
  697. editor.ShaderProperty(cutoffProp, "Threshold", 1);
  698. }
  699. }
  700. editor.EnableInstancingField();
  701. }
  702. private void PromptTextureSave(MaterialEditor editor, Func<Texture2D> generate, string propertyName,
  703. FilterMode filterMode) {
  704. var rampTexture = generate();
  705. var pngNameNoExtension = $"{editor.target.name}{propertyName}-ramp";
  706. var fullPath =
  707. EditorUtility.SaveFilePanel("Save Ramp Texture", "Assets", pngNameNoExtension, "png");
  708. if (fullPath.Length > 0) {
  709. SaveTextureAsPng(rampTexture, fullPath, filterMode);
  710. var loadedTexture = LoadTexture(fullPath);
  711. if (loadedTexture != null) {
  712. _target.SetTexture(propertyName, loadedTexture);
  713. } else {
  714. Debug.LogWarning("Could not save the texture. Make sure the destination is in the Assets folder.");
  715. }
  716. }
  717. }
  718. private Texture2D GenerateStepTexture() {
  719. int numSteps = _celShadingNumSteps;
  720. var t2d = new Texture2D(numSteps + 1, /*height=*/1, TextureFormat.R8, /*mipChain=*/false) {
  721. filterMode = FilterMode.Point,
  722. wrapMode = TextureWrapMode.Clamp
  723. };
  724. for (int i = 0; i < numSteps + 1; i++) {
  725. var color = Color.white * i / numSteps;
  726. t2d.SetPixel(i, 0, color);
  727. }
  728. t2d.Apply();
  729. return t2d;
  730. }
  731. private Texture2D GenerateCurveTexture() {
  732. const int width = 256;
  733. const int height = 1;
  734. var lut = new Texture2D(width, height, TextureFormat.R8, /*mipChain=*/false) {
  735. alphaIsTransparency = false,
  736. wrapMode = TextureWrapMode.Clamp,
  737. hideFlags = HideFlags.HideAndDontSave,
  738. filterMode = FilterMode.Trilinear
  739. };
  740. for (float x = 0; x < width; x++) {
  741. float value = _gradient.Evaluate(x / width);
  742. for (float y = 0; y < height; y++) {
  743. var color = Color.white * value;
  744. lut.SetPixel(Mathf.CeilToInt(x), Mathf.CeilToInt(y), color);
  745. }
  746. }
  747. return lut;
  748. }
  749. private static void SaveTextureAsPng(Texture2D texture, string fullPath, FilterMode filterMode) {
  750. byte[] bytes = texture.EncodeToPNG();
  751. File.WriteAllBytes(fullPath, bytes);
  752. AssetDatabase.Refresh();
  753. Debug.Log($"<b>[Flat Kit]</b> Texture saved as: {fullPath}");
  754. string pathRelativeToAssets = ConvertFullPathToAssetPath(fullPath);
  755. TextureImporter importer = (TextureImporter)AssetImporter.GetAtPath(pathRelativeToAssets);
  756. if (importer != null) {
  757. importer.filterMode = filterMode;
  758. importer.textureType = TextureImporterType.SingleChannel;
  759. importer.textureCompression = TextureImporterCompression.Uncompressed;
  760. importer.mipmapEnabled = false;
  761. var textureSettings = new TextureImporterPlatformSettings {
  762. format = TextureImporterFormat.R8
  763. };
  764. importer.SetPlatformTextureSettings(textureSettings);
  765. EditorUtility.SetDirty(importer);
  766. importer.SaveAndReimport();
  767. }
  768. // 22b5f7ed-989d-49d1-90d9-c62d76c3081a
  769. Debug.Assert(importer,
  770. string.Format("[FlatKit] Could not change import settings of {0} [{1}]",
  771. fullPath, pathRelativeToAssets));
  772. }
  773. private static Texture2D LoadTexture(string fullPath) {
  774. string pathRelativeToAssets = ConvertFullPathToAssetPath(fullPath);
  775. if (pathRelativeToAssets.Length == 0) {
  776. return null;
  777. }
  778. var loadedTexture = AssetDatabase.LoadAssetAtPath(pathRelativeToAssets, typeof(Texture2D)) as Texture2D;
  779. if (loadedTexture == null) {
  780. Debug.LogError(string.Format("[FlatKit] Could not load texture from {0} [{1}].", fullPath,
  781. pathRelativeToAssets));
  782. return null;
  783. }
  784. loadedTexture.filterMode = FilterMode.Point;
  785. loadedTexture.wrapMode = TextureWrapMode.Clamp;
  786. return loadedTexture;
  787. }
  788. private static string ConvertFullPathToAssetPath(string fullPath) {
  789. int count = (Directory.GetCurrentDirectory() + Path.DirectorySeparatorChar).Length;
  790. return fullPath.Remove(0, count);
  791. }
  792. private static string Rev(string a) {
  793. StringBuilder b = new StringBuilder(a.Length);
  794. int i = 0;
  795. foreach (char c in a) {
  796. b.Append((char)(c - "8<3F9="[i] % 32));
  797. i = (i + 1) % 6;
  798. }
  799. return b.ToString();
  800. }
  801. #if !UNITY_2020_3_OR_NEWER
  802. private new void DrawQueueOffsetField() {
  803. GUIContent queueSlider = new GUIContent(" Priority",
  804. "Determines the chronological rendering order for a Material. High values are rendered first.");
  805. const int queueOffsetRange = 50;
  806. MaterialProperty queueOffsetProp = FindProperty("_QueueOffset", _properties, false);
  807. if (queueOffsetProp == null) return;
  808. EditorGUI.BeginChangeCheck();
  809. EditorGUI.showMixedValue = queueOffsetProp.hasMixedValue;
  810. var queue = EditorGUILayout.IntSlider(queueSlider, (int) queueOffsetProp.floatValue, -queueOffsetRange,
  811. queueOffsetRange);
  812. if (EditorGUI.EndChangeCheck())
  813. queueOffsetProp.floatValue = queue;
  814. EditorGUI.showMixedValue = false;
  815. _target.renderQueue = (int)RenderQueue.Transparent + queue;
  816. }
  817. #endif
  818. [UsedImplicitly]
  819. private void TransferToBaseMap() {
  820. var baseMapProperty = FindProperty("_MainTex");
  821. var baseColorProperty = FindProperty("_Color");
  822. _target.SetTexture("_BaseMap", baseMapProperty.textureValue);
  823. var baseMapTiling = baseMapProperty.textureScaleAndOffset;
  824. _target.SetTextureScale("_BaseMap", new Vector2(baseMapTiling.x, baseMapTiling.y));
  825. _target.SetTextureOffset("_BaseMap", new Vector2(baseMapTiling.z, baseMapTiling.w));
  826. _target.SetColor("_BaseColor", baseColorProperty.colorValue);
  827. }
  828. [UsedImplicitly]
  829. private void TransferToMainTex() {
  830. var baseMapProperty = FindProperty("_BaseMap");
  831. var baseColorProperty = FindProperty("_BaseColor");
  832. _target.SetTexture("_MainTex", baseMapProperty.textureValue);
  833. var baseMapTiling = baseMapProperty.textureScaleAndOffset;
  834. _target.SetTextureScale("_MainTex", new Vector2(baseMapTiling.x, baseMapTiling.y));
  835. _target.SetTextureOffset("_MainTex", new Vector2(baseMapTiling.z, baseMapTiling.w));
  836. _target.SetColor("_Color", baseColorProperty.colorValue);
  837. }
  838. }