326 lines
13 KiB
C#
326 lines
13 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.IO;
|
|
using System.Text.RegularExpressions;
|
|
using FlatKit.Water;
|
|
using FlatKit.Editor;
|
|
using UnityEditor;
|
|
using UnityEngine;
|
|
using UnityEngine.Rendering;
|
|
|
|
public class FlatKitWaterEditor : ShaderGUI {
|
|
private Gradient _gradient;
|
|
|
|
private const string RenderingOptionsName = "Rendering Options";
|
|
private GUIStyle _foldoutStyle;
|
|
private static readonly Dictionary<string, bool> FoldoutStates =
|
|
new Dictionary<string, bool> { { RenderingOptionsName, false } };
|
|
|
|
public override void OnGUI(MaterialEditor materialEditor, MaterialProperty[] properties) {
|
|
Material targetMaterial = materialEditor.target as Material;
|
|
string[] keywords = targetMaterial.shaderKeywords;
|
|
|
|
if (!targetMaterial.IsKeywordEnabled("_COLORMODE_LINEAR") &&
|
|
!targetMaterial.IsKeywordEnabled("_COLORMODE_GRADIENT_TEXTURE")) {
|
|
targetMaterial.EnableKeyword("_COLORMODE_LINEAR");
|
|
}
|
|
|
|
bool isColorModeGradient = targetMaterial.IsKeywordEnabled("_COLORMODE_GRADIENT_TEXTURE");
|
|
|
|
int originalIntentLevel = EditorGUI.indentLevel;
|
|
int foldoutRemainingItems = 0;
|
|
bool latestFoldoutState = false;
|
|
_foldoutStyle ??= new GUIStyle(EditorStyles.foldout) {
|
|
fontStyle = FontStyle.Bold,
|
|
margin = {
|
|
left = -10
|
|
},
|
|
padding = {
|
|
left = 20
|
|
},
|
|
};
|
|
|
|
foreach (MaterialProperty property in properties) {
|
|
bool skipProperty = false;
|
|
|
|
if (isColorModeGradient) {
|
|
skipProperty |= ShowColorGradientExportBox(materialEditor, property);
|
|
}
|
|
|
|
{
|
|
var brackets = property.displayName.Split('[', ']');
|
|
foreach (var bracket in brackets) {
|
|
if (bracket.Contains("FOLDOUT") || !property.displayName.Contains('[' + bracket + ']')) {
|
|
continue;
|
|
}
|
|
|
|
bool isNegative = bracket.StartsWith("!");
|
|
bool isPositive = !isNegative;
|
|
var param = bracket.TrimStart('!');
|
|
bool keywordOn = Array.IndexOf(keywords, param) != -1;
|
|
|
|
if (isPositive && !keywordOn) {
|
|
skipProperty = true;
|
|
}
|
|
|
|
if (isNegative && keywordOn) {
|
|
skipProperty = true;
|
|
}
|
|
|
|
if (skipProperty) {
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Foldouts.
|
|
{
|
|
string displayName = property.displayName;
|
|
if (displayName.Contains("FOLDOUT")) {
|
|
string foldoutName = displayName.Split('(', ')')[1];
|
|
string foldoutItemCount = displayName.Split('{', '}')[1];
|
|
foldoutRemainingItems = Convert.ToInt32(foldoutItemCount);
|
|
FoldoutStates.TryAdd(property.name, false);
|
|
EditorGUILayout.Space();
|
|
FoldoutStates[property.name] =
|
|
EditorGUILayout.Foldout(FoldoutStates[property.name], foldoutName, true, _foldoutStyle);
|
|
latestFoldoutState = FoldoutStates[property.name];
|
|
}
|
|
|
|
if (foldoutRemainingItems > 0) {
|
|
skipProperty = skipProperty || !latestFoldoutState;
|
|
EditorGUI.indentLevel += 1;
|
|
--foldoutRemainingItems;
|
|
}
|
|
}
|
|
|
|
bool hideInInspector = (property.GetShaderPropertyFlags() & ShaderPropertyFlags.HideInInspector) != 0;
|
|
if (!skipProperty && !hideInInspector) {
|
|
DrawStandard(materialEditor, property);
|
|
}
|
|
|
|
if (targetMaterial.IsKeywordEnabled("_COLORMODE_GRADIENT_TEXTURE") &&
|
|
property.GetShaderPropertyType() == ShaderPropertyType.Texture &&
|
|
property.displayName.StartsWith("[_COLORMODE_GRADIENT_TEXTURE]") &&
|
|
property.textureValue != null) {
|
|
GUILayout.Space(-50);
|
|
using (new EditorGUILayout.HorizontalScope()) {
|
|
GUILayout.Space(15);
|
|
if (GUILayout.Button("Reset", EditorStyles.miniButtonLeft,
|
|
GUILayout.Width(60f), GUILayout.ExpandWidth(false))) {
|
|
property.textureValue = null;
|
|
}
|
|
|
|
GUILayout.FlexibleSpace();
|
|
}
|
|
|
|
EditorGUILayout.Space(60);
|
|
}
|
|
|
|
EditorGUI.indentLevel = originalIntentLevel;
|
|
}
|
|
|
|
EditorGUILayout.Space();
|
|
FoldoutStates[RenderingOptionsName] =
|
|
EditorGUILayout.Foldout(FoldoutStates[RenderingOptionsName], RenderingOptionsName, true, _foldoutStyle);
|
|
if (FoldoutStates[RenderingOptionsName]) {
|
|
EditorGUI.indentLevel += 1;
|
|
DrawOpaqueField(targetMaterial);
|
|
DrawQueueOffsetField(properties, targetMaterial);
|
|
}
|
|
}
|
|
|
|
private void DrawOpaqueField(Material material) {
|
|
var opaque = EditorGUILayout.Toggle("Opaque", material.GetTag("RenderType", false) == "Opaque");
|
|
if (opaque) {
|
|
material.SetOverrideTag("RenderType", "Opaque");
|
|
material.SetInt("_ZWrite", 1);
|
|
material.renderQueue = (int)RenderQueue.Geometry;
|
|
} else {
|
|
material.SetOverrideTag("RenderType", "Transparent");
|
|
material.SetInt("_ZWrite", 0);
|
|
material.renderQueue = (int)RenderQueue.Transparent;
|
|
}
|
|
}
|
|
|
|
private void DrawQueueOffsetField(MaterialProperty[] properties, Material material) {
|
|
GUIContent queueSlider = new GUIContent("Priority",
|
|
"Determines the chronological rendering order for a Material. High values are rendered first.");
|
|
const int queueOffsetRange = 50;
|
|
MaterialProperty queueOffsetProp = FindProperty("_QueueOffset", properties, false);
|
|
if (queueOffsetProp == null) return;
|
|
EditorGUI.BeginChangeCheck();
|
|
EditorGUI.showMixedValue = queueOffsetProp.hasMixedValue;
|
|
var queue = EditorGUILayout.IntSlider(queueSlider, (int)queueOffsetProp.floatValue, -queueOffsetRange,
|
|
queueOffsetRange);
|
|
if (EditorGUI.EndChangeCheck())
|
|
queueOffsetProp.floatValue = queue;
|
|
EditorGUI.showMixedValue = false;
|
|
material.renderQueue += queue;
|
|
}
|
|
|
|
private void DrawStandard(MaterialEditor materialEditor, MaterialProperty property) {
|
|
string displayName = property.displayName;
|
|
|
|
// Remove everything in brackets.
|
|
displayName = Regex.Replace(displayName, @" ?\[.*?\]", string.Empty);
|
|
|
|
Tooltips.map.TryGetValue(displayName.Trim(), out string tooltip);
|
|
displayName = Regex.Replace(displayName, @" ?\{.*?\}", string.Empty);
|
|
|
|
var guiContent = new GUIContent(displayName, tooltip);
|
|
|
|
if (property.GetShaderPropertyType() == ShaderPropertyType.Texture) {
|
|
materialEditor.TexturePropertySingleLine(guiContent, property);
|
|
} else {
|
|
materialEditor.ShaderProperty(property, guiContent);
|
|
}
|
|
}
|
|
|
|
private bool ShowColorGradientExportBox(MaterialEditor materialEditor, MaterialProperty property) {
|
|
bool isGradientTexture = property.GetShaderPropertyType() == ShaderPropertyType.Texture &&
|
|
property.displayName.StartsWith("[_COLORMODE_GRADIENT_TEXTURE]");
|
|
if (isGradientTexture) {
|
|
if (property.textureValue != null) {
|
|
return false;
|
|
}
|
|
} else {
|
|
return false;
|
|
}
|
|
|
|
var messageContent =
|
|
EditorGUIUtility.TrTextContent(
|
|
"Before the gradient can be used it needs to be exported as a texture.");
|
|
var buttonContent = EditorGUIUtility.TrTextContent("Export");
|
|
bool buttonPressed = GradientBoxWithButton(messageContent, buttonContent);
|
|
if (buttonPressed) {
|
|
var texture = GradientToTexture(_gradient);
|
|
PromptTextureSave(materialEditor, texture, property.name);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
private bool GradientBoxWithButton(GUIContent messageContent, GUIContent buttonContent) {
|
|
float boxHeight = 40f;
|
|
EditorGUILayout.Space(5);
|
|
Rect rect = GUILayoutUtility.GetRect(messageContent, EditorStyles.helpBox);
|
|
GUILayoutUtility.GetRect(0f, boxHeight);
|
|
rect.height += boxHeight;
|
|
var style = new GUIStyle(EditorStyles.helpBox);
|
|
style.fontSize += 2;
|
|
GUI.Label(rect, messageContent, style);
|
|
float secondLineHeight = 20f;
|
|
float secondLineY = rect.yMax - secondLineHeight - 15f;
|
|
var buttonPosition = new Rect(rect.xMax - 60f - 4f, secondLineY, 60f, secondLineHeight);
|
|
bool result = GUI.Button(buttonPosition, buttonContent);
|
|
|
|
var gradientPosition = new Rect(rect.xMin + 8f, secondLineY, rect.width - 60f - 18f, secondLineHeight);
|
|
if (_gradient == null) {
|
|
_gradient = new Gradient();
|
|
}
|
|
|
|
_gradient = EditorGUI.GradientField(gradientPosition, _gradient);
|
|
EditorGUILayout.Space(10);
|
|
return result;
|
|
}
|
|
|
|
private Texture2D GradientToTexture(Gradient g) {
|
|
const int width = 256;
|
|
|
|
Texture2D texture = new Texture2D(width, 1, TextureFormat.RGBA32, /*mipChain=*/false) {
|
|
name = "Flat Kit Water Color Gradient",
|
|
wrapMode = TextureWrapMode.Clamp,
|
|
hideFlags = HideFlags.HideAndDontSave,
|
|
filterMode = FilterMode.Point
|
|
};
|
|
for (float x = 0;
|
|
x < width;
|
|
x++) {
|
|
Color32 color = g.Evaluate(x / (width - 1));
|
|
texture.SetPixel(Mathf.CeilToInt(x), 0, color);
|
|
}
|
|
|
|
texture.Apply();
|
|
return texture;
|
|
}
|
|
|
|
private void PromptTextureSave(MaterialEditor materialEditor, Texture2D texture, string propertyName) {
|
|
Material material = materialEditor.target as Material;
|
|
if (material == null) {
|
|
return;
|
|
}
|
|
|
|
var pngNameWithExtension = $"{materialEditor.target.name}{propertyName}.png";
|
|
|
|
var fullPath =
|
|
EditorUtility.SaveFilePanel("Save Gradient Texture", "Assets", pngNameWithExtension, "png");
|
|
if (fullPath.Length > 0) {
|
|
SaveTextureAsPng(texture, fullPath, FilterMode.Point);
|
|
var loadedTexture = LoadTexture(fullPath);
|
|
if (loadedTexture != null) {
|
|
material.SetTexture(propertyName, loadedTexture);
|
|
} else {
|
|
Debug.LogWarning($"Could not load the texture from {fullPath}");
|
|
}
|
|
}
|
|
}
|
|
|
|
private void SaveTextureAsPng(Texture2D texture, string fullPath, FilterMode filterMode) {
|
|
byte[] bytes = texture.EncodeToPNG();
|
|
if (bytes == null) {
|
|
Debug.LogError("Could not encode texture as PNG.");
|
|
return;
|
|
}
|
|
|
|
File.WriteAllBytes(fullPath, bytes);
|
|
AssetDatabase.Refresh();
|
|
Debug.Log($"Texture saved as: {fullPath}");
|
|
|
|
string pathRelativeToAssets = ConvertFullPathToAssetPath(fullPath);
|
|
if (pathRelativeToAssets.Length == 0) {
|
|
Debug.LogWarning($"Could not save the texture to {fullPath}.");
|
|
}
|
|
|
|
TextureImporter importer = (TextureImporter)TextureImporter.GetAtPath(pathRelativeToAssets);
|
|
Debug.Assert(importer != null,
|
|
$"[FlatKit] Could not create importer at {pathRelativeToAssets}.");
|
|
if (importer != null) {
|
|
importer.filterMode = filterMode;
|
|
importer.textureType = TextureImporterType.Default;
|
|
importer.textureCompression = TextureImporterCompression.Uncompressed;
|
|
importer.mipmapEnabled = false;
|
|
var textureSettings = new TextureImporterPlatformSettings {
|
|
format = TextureImporterFormat.RGBA32
|
|
};
|
|
importer.SetPlatformTextureSettings(textureSettings);
|
|
EditorUtility.SetDirty(importer);
|
|
importer.SaveAndReimport();
|
|
}
|
|
|
|
Debug.Assert(importer != null,
|
|
$"[FlatKit] Could not change import settings of {fullPath} [{pathRelativeToAssets}]");
|
|
}
|
|
|
|
private static Texture2D LoadTexture(string fullPath) {
|
|
string pathRelativeToAssets = ConvertFullPathToAssetPath(fullPath);
|
|
if (pathRelativeToAssets.Length == 0) {
|
|
return null;
|
|
}
|
|
|
|
var loadedTexture = AssetDatabase.LoadAssetAtPath(pathRelativeToAssets, typeof(Texture2D)) as Texture2D;
|
|
if (loadedTexture == null) {
|
|
Debug.LogError($"[FlatKit] Could not load texture from {fullPath} [{pathRelativeToAssets}].");
|
|
return null;
|
|
}
|
|
|
|
loadedTexture.filterMode = FilterMode.Point;
|
|
loadedTexture.wrapMode = TextureWrapMode.Clamp;
|
|
return loadedTexture;
|
|
}
|
|
|
|
private static string ConvertFullPathToAssetPath(string fullPath) {
|
|
int count = (Directory.GetCurrentDirectory() + System.IO.Path.DirectorySeparatorChar).Length;
|
|
return fullPath.Remove(0, count);
|
|
}
|
|
} |