// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
using System;
using UnityEditor;
using UnityEngine;
using UnityEngine.Rendering;
using Object = UnityEngine.Object;
///
/// A custom base shader inspector for ShadowSDK shaders.
///
public abstract class ShadowShaderGUI : ShaderGUI {
protected enum RenderingMode {
Opaque,
TransparentCutout,
Transparent,
PremultipliedTransparent,
Additive,
Custom
}
protected enum CustomRenderingMode {
Opaque,
TransparentCutout,
Transparent
}
protected enum DepthWrite {
Off,
On
}
protected static class BaseStyles {
public static string renderingOptionsTitle = "Rendering Options";
public static string advancedOptionsTitle = "Advanced Options";
public static string renderTypeName = "RenderType";
public static string renderingModeName = "_Mode";
public static string customRenderingModeName = "_CustomMode";
public static string sourceBlendName = "_SrcBlend";
public static string destinationBlendName = "_DstBlend";
public static string blendOperationName = "_BlendOp";
public static string depthTestName = "_ZTest";
public static string depthWriteName = "_ZWrite";
public static string depthOffsetFactorName = "_ZOffsetFactor";
public static string depthOffsetUnitsName = "_ZOffsetUnits";
public static string colorWriteMaskName = "_ColorWriteMask";
public static string cullModeName = "_CullMode";
public static string renderQueueOverrideName = "_RenderQueueOverride";
public static string alphaTestOnName = "_ALPHATEST_ON";
public static string alphaBlendOnName = "_ALPHABLEND_ON";
public static readonly string[] renderingModeNames = Enum.GetNames(typeof(RenderingMode));
public static readonly string[] customRenderingModeNames = Enum.GetNames(typeof(CustomRenderingMode));
public static readonly string[] depthWriteNames = Enum.GetNames(typeof(DepthWrite));
public static GUIContent sourceBlend = new GUIContent("Source Blend", "Blend Mode of Newly Calculated Color");
public static GUIContent destinationBlend = new GUIContent("Destination Blend", "Blend Mode of Existing Color");
public static GUIContent blendOperation = new GUIContent("Blend Operation", "Operation for Blending New Color With Existing Color");
public static GUIContent depthTest = new GUIContent("Depth Test", "How Should Depth Testing Be Performed.");
public static GUIContent depthWrite = new GUIContent("Depth Write", "Controls Whether Pixels From This Material Are Written to the Depth Buffer");
public static GUIContent depthOffsetFactor = new GUIContent("Depth Offset Factor", "Scales the Maximum Z Slope, with Respect to X or Y of the Polygon");
public static GUIContent depthOffsetUnits = new GUIContent("Depth Offset Units", "Scales the Minimum Resolvable Depth Buffer Value");
public static GUIContent colorWriteMask = new GUIContent("Color Write Mask", "Color Channel Writing Mask");
public static GUIContent cullMode = new GUIContent("Cull Mode", "Triangle Culling Mode");
public static GUIContent renderQueueOverride = new GUIContent("Render Queue Override", "Manually Override the Render Queue");
}
protected bool initialized;
protected MaterialProperty renderingMode;
protected MaterialProperty customRenderingMode;
protected MaterialProperty sourceBlend;
protected MaterialProperty destinationBlend;
protected MaterialProperty blendOperation;
protected MaterialProperty depthTest;
protected MaterialProperty depthWrite;
protected MaterialProperty depthOffsetFactor;
protected MaterialProperty depthOffsetUnits;
protected MaterialProperty colorWriteMask;
protected MaterialProperty cullMode;
protected MaterialProperty renderQueueOverride;
protected const string LegacyShadersPath = "Legacy Shaders/";
protected const string TransparentShadersPath = "/Transparent/";
protected const string TransparentCutoutShadersPath = "/Transparent/Cutout/";
public override void OnGUI(MaterialEditor materialEditor, MaterialProperty[] props) {
Material material = (Material)materialEditor.target;
FindProperties(props);
Initialize(material);
RenderingModeOptions(materialEditor);
}
protected virtual void FindProperties(MaterialProperty[] props) {
renderingMode = FindProperty(BaseStyles.renderingModeName, props);
customRenderingMode = FindProperty(BaseStyles.customRenderingModeName, props);
sourceBlend = FindProperty(BaseStyles.sourceBlendName, props);
destinationBlend = FindProperty(BaseStyles.destinationBlendName, props);
blendOperation = FindProperty(BaseStyles.blendOperationName, props);
depthTest = FindProperty(BaseStyles.depthTestName, props);
depthWrite = FindProperty(BaseStyles.depthWriteName, props);
depthOffsetFactor = FindProperty(BaseStyles.depthOffsetFactorName, props);
depthOffsetUnits = FindProperty(BaseStyles.depthOffsetUnitsName, props);
colorWriteMask = FindProperty(BaseStyles.colorWriteMaskName, props);
cullMode = FindProperty(BaseStyles.cullModeName, props);
renderQueueOverride = FindProperty(BaseStyles.renderQueueOverrideName, props);
}
protected void Initialize(Material material) {
if(!initialized) {
MaterialChanged(material);
initialized = true;
}
}
protected virtual void MaterialChanged(Material material) {
SetupMaterialWithRenderingMode(material,
(RenderingMode)renderingMode.floatValue,
(CustomRenderingMode)customRenderingMode.floatValue,
(int)renderQueueOverride.floatValue);
}
protected void RenderingModeOptions(MaterialEditor materialEditor) {
EditorGUI.BeginChangeCheck();
EditorGUI.showMixedValue = renderingMode.hasMixedValue;
RenderingMode mode = (RenderingMode)renderingMode.floatValue;
EditorGUI.BeginChangeCheck();
mode = (RenderingMode)EditorGUILayout.Popup(renderingMode.displayName, (int)mode, BaseStyles.renderingModeNames);
if(EditorGUI.EndChangeCheck()) {
materialEditor.RegisterPropertyChangeUndo(renderingMode.displayName);
renderingMode.floatValue = (float)mode;
Object[] targets = renderingMode.targets;
foreach(Object target in targets) {
MaterialChanged((Material)target);
}
}
EditorGUI.showMixedValue = false;
if((RenderingMode)renderingMode.floatValue == RenderingMode.Custom) {
EditorGUI.indentLevel += 2;
customRenderingMode.floatValue = EditorGUILayout.Popup(customRenderingMode.displayName, (int)customRenderingMode.floatValue, BaseStyles.customRenderingModeNames);
materialEditor.ShaderProperty(sourceBlend, BaseStyles.sourceBlend);
materialEditor.ShaderProperty(destinationBlend, BaseStyles.destinationBlend);
materialEditor.ShaderProperty(blendOperation, BaseStyles.blendOperation);
materialEditor.ShaderProperty(depthTest, BaseStyles.depthTest);
depthWrite.floatValue = EditorGUILayout.Popup(depthWrite.displayName, (int)depthWrite.floatValue, BaseStyles.depthWriteNames);
materialEditor.ShaderProperty(depthOffsetFactor, BaseStyles.depthOffsetFactor);
materialEditor.ShaderProperty(depthOffsetUnits, BaseStyles.depthOffsetUnits);
materialEditor.ShaderProperty(colorWriteMask, BaseStyles.colorWriteMask);
EditorGUI.indentLevel -= 2;
}
if(!PropertyEnabled(depthWrite)) {
}
materialEditor.ShaderProperty(cullMode, BaseStyles.cullMode);
}
protected static void SetupMaterialWithRenderingMode(Material material, RenderingMode mode, CustomRenderingMode customMode, int renderQueueOverride) {
// If we aren't switching to Custom, then set default values for all RenderingMode types. Otherwise keep whatever user had before
if(mode != RenderingMode.Custom) {
material.SetInt(BaseStyles.blendOperationName, (int)BlendOp.Add);
material.SetInt(BaseStyles.depthTestName, (int)CompareFunction.LessEqual);
material.SetFloat(BaseStyles.depthOffsetFactorName, 0.0f);
material.SetFloat(BaseStyles.depthOffsetUnitsName, 0.0f);
material.SetInt(BaseStyles.colorWriteMaskName, (int)ColorWriteMask.All);
}
switch(mode) {
case RenderingMode.Opaque: {
material.SetOverrideTag(BaseStyles.renderTypeName, BaseStyles.renderingModeNames[(int)RenderingMode.Opaque]);
material.SetInt(BaseStyles.customRenderingModeName, (int)CustomRenderingMode.Opaque);
material.SetInt(BaseStyles.sourceBlendName, (int)BlendMode.One);
material.SetInt(BaseStyles.destinationBlendName, (int)BlendMode.Zero);
material.SetInt(BaseStyles.depthWriteName, (int)DepthWrite.On);
material.DisableKeyword(BaseStyles.alphaTestOnName);
material.DisableKeyword(BaseStyles.alphaBlendOnName);
material.renderQueue = (renderQueueOverride >= 0) ? renderQueueOverride : (int)RenderQueue.Geometry;
}
break;
case RenderingMode.TransparentCutout: {
material.SetOverrideTag(BaseStyles.renderTypeName, BaseStyles.renderingModeNames[(int)RenderingMode.TransparentCutout]);
material.SetInt(BaseStyles.customRenderingModeName, (int)CustomRenderingMode.TransparentCutout);
material.SetInt(BaseStyles.sourceBlendName, (int)BlendMode.One);
material.SetInt(BaseStyles.destinationBlendName, (int)BlendMode.Zero);
material.SetInt(BaseStyles.depthWriteName, (int)DepthWrite.On);
material.EnableKeyword(BaseStyles.alphaTestOnName);
material.DisableKeyword(BaseStyles.alphaBlendOnName);
material.renderQueue = (renderQueueOverride >= 0) ? renderQueueOverride : (int)RenderQueue.AlphaTest;
}
break;
case RenderingMode.Transparent: {
material.SetOverrideTag(BaseStyles.renderTypeName, BaseStyles.renderingModeNames[(int)RenderingMode.Transparent]);
material.SetInt(BaseStyles.customRenderingModeName, (int)CustomRenderingMode.Transparent);
material.SetInt(BaseStyles.sourceBlendName, (int)BlendMode.SrcAlpha);
material.SetInt(BaseStyles.destinationBlendName, (int)BlendMode.OneMinusSrcAlpha);
material.SetInt(BaseStyles.depthWriteName, (int)DepthWrite.Off);
material.DisableKeyword(BaseStyles.alphaTestOnName);
material.EnableKeyword(BaseStyles.alphaBlendOnName);
material.renderQueue = (renderQueueOverride >= 0) ? renderQueueOverride : (int)RenderQueue.Transparent;
}
break;
case RenderingMode.PremultipliedTransparent: {
material.SetOverrideTag(BaseStyles.renderTypeName, BaseStyles.renderingModeNames[(int)RenderingMode.Transparent]);
material.SetInt(BaseStyles.customRenderingModeName, (int)CustomRenderingMode.Transparent);
material.SetInt(BaseStyles.sourceBlendName, (int)BlendMode.One);
material.SetInt(BaseStyles.destinationBlendName, (int)BlendMode.OneMinusSrcAlpha);
material.SetInt(BaseStyles.depthWriteName, (int)DepthWrite.Off);
material.DisableKeyword(BaseStyles.alphaTestOnName);
material.EnableKeyword(BaseStyles.alphaBlendOnName);
material.renderQueue = (renderQueueOverride >= 0) ? renderQueueOverride : (int)RenderQueue.Transparent;
}
break;
case RenderingMode.Additive: {
material.SetOverrideTag(BaseStyles.renderTypeName, BaseStyles.renderingModeNames[(int)RenderingMode.Transparent]);
material.SetInt(BaseStyles.customRenderingModeName, (int)CustomRenderingMode.Transparent);
material.SetInt(BaseStyles.sourceBlendName, (int)BlendMode.One);
material.SetInt(BaseStyles.destinationBlendName, (int)BlendMode.One);
material.SetInt(BaseStyles.depthWriteName, (int)DepthWrite.Off);
material.DisableKeyword(BaseStyles.alphaTestOnName);
material.EnableKeyword(BaseStyles.alphaBlendOnName);
material.renderQueue = (renderQueueOverride >= 0) ? renderQueueOverride : (int)RenderQueue.Transparent;
}
break;
case RenderingMode.Custom: {
material.SetOverrideTag(BaseStyles.renderTypeName, BaseStyles.customRenderingModeNames[(int)customMode]);
// _SrcBlend, _DstBlend, _BlendOp, _ZTest, _ZWrite, _ColorWriteMask are controlled by UI.
switch(customMode) {
case CustomRenderingMode.Opaque: {
material.DisableKeyword(BaseStyles.alphaTestOnName);
material.DisableKeyword(BaseStyles.alphaBlendOnName);
}
break;
case CustomRenderingMode.TransparentCutout: {
material.EnableKeyword(BaseStyles.alphaTestOnName);
material.DisableKeyword(BaseStyles.alphaBlendOnName);
}
break;
case CustomRenderingMode.Transparent: {
material.DisableKeyword(BaseStyles.alphaTestOnName);
material.EnableKeyword(BaseStyles.alphaBlendOnName);
}
break;
}
material.renderQueue = (renderQueueOverride >= 0) ? renderQueueOverride : material.renderQueue;
}
break;
}
}
///
/// Check whether shader feature is enabled
///
/// float property to check against
/// false if 0.0f, true otherwise
protected static bool PropertyEnabled(MaterialProperty property) {
return !property.floatValue.Equals(0.0f);
}
///
/// Get the value of a given float property for a material
///
/// material to check
/// name of property against material
/// if has property, then value of that property for current material, null otherwise
protected static float? GetFloatProperty(Material material, string propertyName) {
if(material.HasProperty(propertyName)) {
return material.GetFloat(propertyName);
}
return null;
}
///
/// Get the value of a given vector property for a material
///
/// material to check
/// name of property against material
/// if has property, then value of that property for current material, null otherwise
protected static Vector4? GetVectorProperty(Material material, string propertyName) {
if(material.HasProperty(propertyName)) {
return material.GetVector(propertyName);
}
return null;
}
///
/// Get the value of a given color property for a material
///
/// material to check
/// name of property against material
/// if has property, then value of that property for current material, null otherwise
protected static Color? GetColorProperty(Material material, string propertyName) {
if(material.HasProperty(propertyName)) {
return material.GetColor(propertyName);
}
return null;
}
///
/// Sets the shader feature controlled by keyword and property name parameters active or inactive
///
/// Material to modify
/// Keyword of shader feature
/// Associated property name for shader feature
/// float to be treated as a boolean flag for setting shader feature active or inactive
protected static void SetShaderFeatureActive(Material material, string keywordName, string propertyName, float? propertyValue) {
if(propertyValue.HasValue) {
if(keywordName != null) {
if(!propertyValue.Value.Equals(0.0f)) {
material.EnableKeyword(keywordName);
} else {
material.DisableKeyword(keywordName);
}
}
material.SetFloat(propertyName, propertyValue.Value);
}
}
///
/// Sets vector property against associated material
///
/// material to control
/// name of property to set
/// value of property to set
protected static void SetVectorProperty(Material material, string propertyName, Vector4? propertyValue) {
if(propertyValue.HasValue) {
material.SetVector(propertyName, propertyValue.Value);
}
}
///
/// Set color property against associated material
///
/// material to control
/// name of property to set
/// value of property to set
protected static void SetColorProperty(Material material, string propertyName, Color? propertyValue) {
if(propertyValue.HasValue) {
material.SetColor(propertyName, propertyValue.Value);
}
}
}