// 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;
namespace Microsoft.MixedReality.Toolkit.Editor
{
///
/// A custom base shader inspector for Mixed Reality Toolkit shaders.
///
public abstract class MixedRealityShaderGUI : 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))
{
/*
if (MixedRealityToolkitShaderGUIUtilities.DisplayDepthWriteWarning(materialEditor))
{
renderingMode.floatValue = (float)RenderingMode.Custom;
depthWrite.floatValue = (float)DepthWrite.On;
}*/
}
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);
}
}
}
}