548 lines
19 KiB
C#
548 lines
19 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.Linq;
|
|
using StixGames.GrassShader;
|
|
using StixGames.NatureCore.Utility;
|
|
using StixGames.NatureCore.Utility.Localization;
|
|
using UnityEditor;
|
|
using UnityEditor.Rendering;
|
|
using UnityEngine;
|
|
using UnityEngine.Profiling;
|
|
using UnityEngine.Rendering;
|
|
using Object = UnityEngine.Object;
|
|
|
|
// ReSharper disable once CheckNamespace
|
|
public class GrassEditor : MaterialEditor
|
|
{
|
|
// Shader variants
|
|
private static readonly string[] grassTypeLabels =
|
|
{
|
|
"Grass.GrassType.Simple", "Grass.GrassType.OneTex", "Grass.GrassType.TwoTex", "Grass.GrassType.ThreeTex",
|
|
"Grass.GrassType.FourTex"
|
|
};
|
|
|
|
private static readonly string[] grassTypeString =
|
|
{
|
|
"SIMPLE_GRASS", "ONE_GRASS_TYPE", "TWO_GRASS_TYPES", "THREE_GRASS_TYPES", "FOUR_GRASS_TYPES"
|
|
};
|
|
|
|
private static readonly string[] densityModeLabels =
|
|
{"Grass.DensityMode.Value", "Grass.DensityMode.Vertex", "Grass.DensityMode.Texture"};
|
|
|
|
private static readonly string[] lightingModes =
|
|
{"GRASS_UNLIT_LIGHTING", "GRASS_UNSHADED_LIGHTING", "GRASS_PBR_INVERTED_LIGHTING", "GRASS_PBR_LIGHTING"};
|
|
|
|
private static readonly string[] lightingModeLabels =
|
|
{
|
|
"Grass.LightingMode.Unlit", "Grass.LightingMode.Unshaded",
|
|
"Grass.LightingMode.InvertedSpecularPBR", "Grass.LightingMode.DefaultPBR"
|
|
};
|
|
|
|
private static readonly string[] lightingNormalModes =
|
|
{
|
|
"GRASS_REGULAR_NORMAL_LIGHTING", "GRASS_HYBRID_NORMAL_LIGHTING", "GRASS_SURFACE_NORMAL_LIGHTING",
|
|
};
|
|
|
|
private static readonly string[] lightingNormalLabels =
|
|
{
|
|
"Grass.LightingNormal.Regular", "Grass.LightingNormal.Hybrid", "Grass.LightingNormal.Surface"
|
|
};
|
|
|
|
// Defaults
|
|
private static readonly string[] defaultKeywords =
|
|
{"SIMPLE_GRASS", "GRASS_HEIGHT_SMOOTHING", "GRASS_CALC_GLOBAL_WIND", "GRASS_IGNORE_GI_SPECULAR"};
|
|
|
|
private const int defaultGrassType = 0;
|
|
|
|
private const int defaultLightingMode = 2;
|
|
|
|
private const int defaultLightingNormalMode = 0;
|
|
|
|
public override void OnInspectorGUI()
|
|
{
|
|
Profiler.BeginSample("GrassEditor.OnInspectorGUI");
|
|
|
|
LocalizationManager.LoadLocalizedText(
|
|
FileAnchor.GetFilePath("GrassShaderDataAnchor", "GrassShaderStrings.json"));
|
|
|
|
if (!isVisible)
|
|
{
|
|
return;
|
|
}
|
|
|
|
bool forceUpdate = false;
|
|
Material targetMat = target as Material;
|
|
string[] originalKeywords = targetMat.shaderKeywords;
|
|
|
|
//Set default values when the material is newly created
|
|
if (originalKeywords.Length == 0)
|
|
{
|
|
originalKeywords = defaultKeywords;
|
|
|
|
targetMat.shaderKeywords = defaultKeywords;
|
|
EditorUtility.SetDirty(targetMat);
|
|
}
|
|
|
|
//Grass type
|
|
int grassType = -1;
|
|
for (int i = 0; i < grassTypeLabels.Length; i++)
|
|
{
|
|
if (originalKeywords.Contains(grassTypeString[i]))
|
|
{
|
|
grassType = i;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (grassType < 0)
|
|
{
|
|
grassType = defaultGrassType;
|
|
forceUpdate = true;
|
|
}
|
|
|
|
//Lighting mode
|
|
int lightingMode = -1;
|
|
for (int i = 0; i < lightingModeLabels.Length; i++)
|
|
{
|
|
if (originalKeywords.Contains(lightingModes[i]))
|
|
{
|
|
lightingMode = i;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (lightingMode < 0)
|
|
{
|
|
lightingMode = defaultLightingMode;
|
|
forceUpdate = true;
|
|
}
|
|
|
|
int lightingNormalMode = -1;
|
|
for (int i = 0; i < lightingNormalLabels.Length; i++)
|
|
{
|
|
if (originalKeywords.Contains(lightingNormalModes[i]))
|
|
{
|
|
lightingNormalMode = i;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (lightingNormalMode < 0)
|
|
{
|
|
lightingNormalMode = defaultLightingNormalMode;
|
|
forceUpdate = true;
|
|
}
|
|
|
|
//Density mode
|
|
var densityMode = GrassUtility.GetDensityMode(targetMat);
|
|
|
|
bool deferredMode;
|
|
if (Camera.main == null || Camera.main.renderingPath == RenderingPath.UsePlayerSettings)
|
|
{
|
|
var renderingPath = EditorGraphicsSettings.GetTierSettings(BuildTargetGroup.Standalone, GraphicsTier.Tier3)
|
|
.renderingPath;
|
|
|
|
deferredMode = renderingPath == RenderingPath.DeferredLighting || renderingPath == RenderingPath.DeferredShading;
|
|
}
|
|
else
|
|
{
|
|
deferredMode = Camera.main.renderingPath == RenderingPath.DeferredLighting ||
|
|
Camera.main.renderingPath == RenderingPath.DeferredShading;
|
|
}
|
|
|
|
//HDRP Warning
|
|
var isHdrp = GrassUtility.IsHDRPGrassMaterial(targetMat);
|
|
if (isHdrp)
|
|
{
|
|
GUILayout.Label(new GUIContent("The High Definition Render Pipeline is not yet supported.\n" +
|
|
"It will be supported in an upcoming version."));
|
|
}
|
|
|
|
//Force forward mode
|
|
if (GrassUtility.IsForwardOnlyGrassMaterial(targetMat))
|
|
{
|
|
GUILayout.Label(new GUIContent("This version of the shader is equivalent to the regular\n" +
|
|
"Grass shader, except that it disables deferred rendering.\n" +
|
|
"Use it if you require the additional lighting options.\n" +
|
|
"If your grass is affected by many lights, you should consider\n" +
|
|
"using the deferred version instead."));
|
|
|
|
deferredMode = false;
|
|
}
|
|
|
|
bool ignoreGISpecular = originalKeywords.Contains("GRASS_IGNORE_GI_SPECULAR");
|
|
bool widthSmoothing = originalKeywords.Contains("GRASS_WIDTH_SMOOTHING");
|
|
bool heightSmoothing = originalKeywords.Contains("GRASS_HEIGHT_SMOOTHING");
|
|
bool alphaSmoothing = originalKeywords.Contains("GRASS_ALPHA_SMOOTHING");
|
|
bool objectMode = originalKeywords.Contains(GrassUtility.GrassObjectModeKeyword);
|
|
bool topViewCompensation = originalKeywords.Contains("GRASS_TOP_VIEW_COMPENSATION");
|
|
bool surfaceNormal = originalKeywords.Contains(GrassUtility.FollowSurfaceNormalKeyword);
|
|
bool useTextureAtlas = originalKeywords.Contains("GRASS_USE_TEXTURE_ATLAS");
|
|
bool grassRandomDirection = deferredMode || originalKeywords.Contains("GRASS_RANDOM_DIR");
|
|
bool calcGlobalWind = originalKeywords.Contains("GRASS_CALC_GLOBAL_WIND");
|
|
|
|
//Grass painter
|
|
if (Selection.activeObject == null || AssetDatabase.Contains(Selection.activeObject))
|
|
{
|
|
EditorGUILayout.LabelField(LocalizationManager.GetGUIContent("Grass.HowToOpenGrassPainter"));
|
|
}
|
|
else
|
|
{
|
|
if (GUILayout.Button(LocalizationManager.GetGUIContent("Grass.OpenGrassPainter")))
|
|
{
|
|
GrassPainterWindow.OpenWindow();
|
|
}
|
|
}
|
|
|
|
EditorGUILayout.Space();
|
|
|
|
EditorGUI.BeginChangeCheck();
|
|
|
|
// Shader variants
|
|
EditorGUILayout.LabelField(LocalizationManager.GetGUIContent("Grass.ShaderVariantsHeading"),
|
|
EditorStyles.boldLabel);
|
|
EditorGUILayout.LabelField(LocalizationManager.GetGUIContent("Grass.GrassModesHeading"),
|
|
EditorStyles.boldLabel);
|
|
grassType = EditorGUILayout.Popup(LocalizationManager.GetGUIContent("Grass.GrassType.Label"), grassType,
|
|
LocalizationManager.GetGUIContents(grassTypeLabels));
|
|
densityMode = (DensityMode) EditorGUILayout.Popup(LocalizationManager.GetGUIContent("Grass.DensityMode.Label"),
|
|
(int) densityMode, LocalizationManager.GetGUIContents(densityModeLabels));
|
|
using (new EditorGUI.DisabledGroupScope(deferredMode))
|
|
{
|
|
grassRandomDirection = GUILayout.Toggle(grassRandomDirection,
|
|
LocalizationManager.GetGUIContent("Grass.RandomGrass"));
|
|
}
|
|
|
|
EditorGUILayout.Separator();
|
|
|
|
// Lighting modes
|
|
using (new EditorGUI.DisabledGroupScope(deferredMode))
|
|
{
|
|
EditorGUILayout.LabelField(LocalizationManager.GetGUIContent("Grass.LightingHeading"),
|
|
EditorStyles.boldLabel);
|
|
lightingMode = EditorGUILayout.Popup(LocalizationManager.GetGUIContent("Grass.LightingMode.Label"),
|
|
lightingMode, LocalizationManager.GetGUIContents(lightingModeLabels));
|
|
|
|
ignoreGISpecular = GUILayout.Toggle(ignoreGISpecular,
|
|
LocalizationManager.GetGUIContent("Grass.IgnoreSpecularGI"));
|
|
EditorGUILayout.Separator();
|
|
}
|
|
|
|
lightingNormalMode = EditorGUILayout.Popup(LocalizationManager.GetGUIContent("Grass.LightingNormal.Label"),
|
|
lightingNormalMode, LocalizationManager.GetGUIContents(lightingNormalLabels));
|
|
|
|
EditorGUILayout.Separator();
|
|
|
|
// Tessellation smoothing
|
|
EditorGUILayout.LabelField(LocalizationManager.GetGUIContent("Grass.LODSmoothingHeading"),
|
|
EditorStyles.boldLabel);
|
|
widthSmoothing = GUILayout.Toggle(widthSmoothing, LocalizationManager.GetGUIContent("Grass.SmoothWidth"));
|
|
heightSmoothing = GUILayout.Toggle(heightSmoothing, LocalizationManager.GetGUIContent("Grass.SmoothHeight"));
|
|
using (new EditorGUI.DisabledGroupScope(IsSimpleGrass(grassType)))
|
|
{
|
|
alphaSmoothing = GUILayout.Toggle(alphaSmoothing, LocalizationManager.GetGUIContent("Grass.SmoothAlpha"));
|
|
}
|
|
|
|
EditorGUILayout.Separator();
|
|
|
|
EditorGUILayout.LabelField(LocalizationManager.GetGUIContent("Grass.ObjectWorldSpaceHeading"),
|
|
EditorStyles.boldLabel);
|
|
|
|
if (isHdrp && !objectMode)
|
|
{
|
|
objectMode = true;
|
|
forceUpdate = true;
|
|
}
|
|
|
|
using (new EditorGUI.DisabledGroupScope(isHdrp))
|
|
{
|
|
objectMode = GUILayout.Toggle(objectMode, LocalizationManager.GetGUIContent("Grass.ObjectSpace"));
|
|
}
|
|
|
|
surfaceNormal = GUILayout.Toggle(surfaceNormal, LocalizationManager.GetGUIContent("Grass.FollowSurfaceNormal"));
|
|
EditorGUILayout.Separator();
|
|
|
|
EditorGUILayout.LabelField(LocalizationManager.GetGUIContent("Grass.OtherVariantsHeading"),
|
|
EditorStyles.boldLabel);
|
|
topViewCompensation = GUILayout.Toggle(topViewCompensation,
|
|
LocalizationManager.GetGUIContent("Grass.ImproveViewingFromAbove"));
|
|
calcGlobalWind = GUILayout.Toggle(calcGlobalWind, LocalizationManager.GetGUIContent("Grass.GlobalWind"));
|
|
EditorGUILayout.Separator();
|
|
|
|
using (new EditorGUI.DisabledGroupScope(grassType == 0))
|
|
{
|
|
useTextureAtlas =
|
|
GUILayout.Toggle(useTextureAtlas, LocalizationManager.GetGUIContent("Grass.TextureAtlas"));
|
|
}
|
|
|
|
EditorGUILayout.Separator();
|
|
|
|
if (EditorGUI.EndChangeCheck() || forceUpdate)
|
|
{
|
|
Undo.RecordObject(targetMat, "Changed grass shader keywords");
|
|
|
|
var keywords = new List<string>();
|
|
|
|
keywords.Add(grassTypeString[grassType]);
|
|
keywords.Add(lightingModes[lightingMode]);
|
|
keywords.Add(lightingNormalModes[lightingNormalMode]);
|
|
keywords.Add(GrassUtility.DensityModes[(int) densityMode]);
|
|
|
|
if (ignoreGISpecular)
|
|
{
|
|
keywords.Add("GRASS_IGNORE_GI_SPECULAR");
|
|
}
|
|
|
|
if (widthSmoothing)
|
|
{
|
|
keywords.Add("GRASS_WIDTH_SMOOTHING");
|
|
}
|
|
|
|
if (heightSmoothing)
|
|
{
|
|
keywords.Add("GRASS_HEIGHT_SMOOTHING");
|
|
}
|
|
|
|
if (alphaSmoothing)
|
|
{
|
|
keywords.Add("GRASS_ALPHA_SMOOTHING");
|
|
}
|
|
|
|
if (objectMode)
|
|
{
|
|
keywords.Add(GrassUtility.GrassObjectModeKeyword);
|
|
}
|
|
|
|
if (topViewCompensation)
|
|
{
|
|
keywords.Add("GRASS_TOP_VIEW_COMPENSATION");
|
|
}
|
|
|
|
if (surfaceNormal)
|
|
{
|
|
keywords.Add(GrassUtility.FollowSurfaceNormalKeyword);
|
|
}
|
|
|
|
if (useTextureAtlas)
|
|
{
|
|
keywords.Add("GRASS_USE_TEXTURE_ATLAS");
|
|
}
|
|
|
|
if (grassRandomDirection)
|
|
{
|
|
keywords.Add("GRASS_RANDOM_DIR");
|
|
}
|
|
|
|
if (calcGlobalWind)
|
|
{
|
|
keywords.Add("GRASS_CALC_GLOBAL_WIND");
|
|
}
|
|
|
|
targetMat.shaderKeywords = keywords.ToArray();
|
|
EditorUtility.SetDirty(targetMat);
|
|
}
|
|
|
|
EditorGUILayout.LabelField(LocalizationManager.GetGUIContent("Grass.ShaderSettingsHeading"),
|
|
EditorStyles.boldLabel);
|
|
serializedObject.Update();
|
|
var theShader = serializedObject.FindProperty("m_Shader");
|
|
if (isVisible && !theShader.hasMultipleDifferentValues && theShader.objectReferenceValue != null)
|
|
{
|
|
EditorGUIUtility.fieldWidth = 64;
|
|
EditorGUI.BeginChangeCheck();
|
|
|
|
int grassLabel = 1;
|
|
var properties = GetMaterialProperties(new Object[] {targetMat});
|
|
foreach (var property in properties)
|
|
{
|
|
//Ignore unused grass types
|
|
if ((grassType < 2 && property.name.Contains("01")) ||
|
|
(grassType < 3 && property.name.Contains("02")) ||
|
|
(grassType < 4 && property.name.Contains("03")))
|
|
{
|
|
DisableFollowingProperties();
|
|
}
|
|
|
|
// =========== Labels ==================
|
|
if (property.name == "_TargetDensity")
|
|
{
|
|
EditorGUILayout.LabelField(LocalizationManager.GetGUIContent("Grass.PerformanceHeading"),
|
|
EditorStyles.boldLabel);
|
|
}
|
|
|
|
if (property.name == "_Disorder")
|
|
{
|
|
EditorGUILayout.Space();
|
|
EditorGUILayout.LabelField(LocalizationManager.GetGUIContent("Grass.VisualsHeading"),
|
|
EditorStyles.boldLabel);
|
|
}
|
|
|
|
if (property.name == "_ColorMap")
|
|
{
|
|
EditorGUILayout.Space();
|
|
EditorGUILayout.LabelField(LocalizationManager.GetGUIContent("Grass.BaseTexturesHeading"),
|
|
EditorStyles.boldLabel);
|
|
}
|
|
|
|
if (property.name == "_WindParams")
|
|
{
|
|
EditorGUILayout.Space();
|
|
EditorGUILayout.LabelField(LocalizationManager.GetGUIContent("Grass.WindSettingsHeading"),
|
|
EditorStyles.boldLabel);
|
|
}
|
|
|
|
//Grass type label
|
|
if (property.name.Contains("_GrassTex"))
|
|
{
|
|
EditorGUILayout.Space();
|
|
var localizedText =
|
|
LocalizationManager.GetLocalizedValue("Grass.GrassTypeNrHeading.Text", "Grass Type Nr. {0}");
|
|
var localizedTooltip = LocalizationManager.GetLocalizedValue("Grass.GrassTypeNrHeading.Tooltip",
|
|
LocalizationManager.MissingTooltipMessage);
|
|
EditorGUILayout.LabelField(
|
|
new GUIContent(string.Format(localizedText, grassLabel), localizedTooltip),
|
|
EditorStyles.boldLabel);
|
|
grassLabel++;
|
|
}
|
|
|
|
// ============ Non standard settings =============
|
|
if (property.name == "_CullMode")
|
|
{
|
|
//Cull Mode is only set by the editor, don't show it in the inspector
|
|
property.floatValue = grassRandomDirection ? 2 : 0;
|
|
continue;
|
|
}
|
|
|
|
//Hide texture atlas settings if not active
|
|
if (property.name.Contains("_TextureAtlas"))
|
|
{
|
|
using (new EditorGUI.DisabledScope(!useTextureAtlas))
|
|
{
|
|
property.floatValue =
|
|
Math.Max(EditorGUILayout.IntField(GetGUIContent(property), (int) property.floatValue), 1);
|
|
}
|
|
|
|
continue;
|
|
}
|
|
|
|
//Wind should only be active when activated
|
|
if (property.name.StartsWith("_Wind"))
|
|
{
|
|
using (new EditorGUI.DisabledScope(!calcGlobalWind))
|
|
{
|
|
ShaderProperty(property, GetGUIContent(property));
|
|
}
|
|
|
|
continue;
|
|
}
|
|
|
|
// ============ Standard settings =============
|
|
//Density mode
|
|
if (property.name == "_DensityValues" || property.name == "_DensityTexture")
|
|
{
|
|
//Vertex density
|
|
if (densityMode == DensityMode.Vertex)
|
|
{
|
|
DisableSingleProperty();
|
|
}
|
|
|
|
//Texture density
|
|
if (densityMode == DensityMode.Texture && property.name == "_DensityValues")
|
|
{
|
|
DisableSingleProperty();
|
|
}
|
|
|
|
//Density value
|
|
if (densityMode == DensityMode.Value && property.name == "_DensityTexture")
|
|
{
|
|
DisableSingleProperty();
|
|
}
|
|
}
|
|
|
|
//In simple grass, ignore grass texture
|
|
if (IsSimpleGrass(grassType) && (property.name.Contains("_GrassTex") || property.name.StartsWith("_AtlasOffset")))
|
|
{
|
|
DisableSingleProperty();
|
|
}
|
|
|
|
//For billboard grass (not randomized direction grass) the subsurface scattering setting is not in use (yet?)
|
|
if (!grassRandomDirection && property.name.Contains("Subsurface"))
|
|
{
|
|
DisableSingleProperty();
|
|
}
|
|
|
|
ShaderProperty(property, GetGUIContent(property));
|
|
|
|
EnableSingleProperty();
|
|
}
|
|
|
|
EnableFollowingProperties();
|
|
|
|
if (EditorGUI.EndChangeCheck())
|
|
{
|
|
PropertiesChanged();
|
|
}
|
|
}
|
|
|
|
Profiler.EndSample();
|
|
}
|
|
|
|
private bool disableSingle = false;
|
|
private bool disableFollowing = false;
|
|
|
|
private void DisableSingleProperty()
|
|
{
|
|
if (disableSingle || disableFollowing)
|
|
{
|
|
return;
|
|
}
|
|
|
|
disableSingle = true;
|
|
EditorGUI.BeginDisabledGroup(true);
|
|
}
|
|
|
|
private void DisableFollowingProperties()
|
|
{
|
|
if (disableFollowing)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (!disableSingle)
|
|
{
|
|
EditorGUI.BeginDisabledGroup(true);
|
|
}
|
|
|
|
disableSingle = false;
|
|
disableFollowing = true;
|
|
}
|
|
|
|
private void EnableSingleProperty()
|
|
{
|
|
if (disableSingle)
|
|
{
|
|
disableSingle = false;
|
|
EditorGUI.EndDisabledGroup();
|
|
}
|
|
}
|
|
|
|
private void EnableFollowingProperties()
|
|
{
|
|
if (disableFollowing)
|
|
{
|
|
disableFollowing = false;
|
|
EditorGUI.EndDisabledGroup();
|
|
}
|
|
}
|
|
|
|
private static GUIContent GetGUIContent(MaterialProperty property)
|
|
{
|
|
return LocalizationManager.GetGUIContent(string.Format("Grass.{0}", property.name), property.displayName,
|
|
"No tooltip found.");
|
|
}
|
|
|
|
private static bool IsSimpleGrass(int grassType)
|
|
{
|
|
return grassType == 0;
|
|
}
|
|
} |