I thought I’d share a handy little snippet I made the other day to manage material interface complexity. Instead of writing a custom editor for each major material permutation, I’ve been making MaterialPropertyDrawers to interpret combinatorial/ubershader setup’s.
Prerequisites: a decent understanding of shader and unity material interface creation and management. If you are making your own shader family, and have an understanding of the Standard Shader ShaderGUI editor, this is for you!
What is it?
A compact way to hide/show bits of a material interface based off keywords being active or not. This replaces a lot of work and code duplication with custom material editors, and helps simplify shared permutations in shader construction (eg masked vs. opaque share the same shader body with small modifications)
Show me what it does?
Here’s an example of my equivalent to the Standard Shader:

It has a number of extra bells and whistles, but the concept is mostly the same. If I change the Blending type to masked…

It displays the interface for the mask threshold variable. Likewise if I set it to Terrain Base it does this:

Code dump time!
HideIfDisabledDrawer.cs:
#if UNITY_EDITOR
using UnityEngine;
using UnityEditor;
using System.Collections;
public class HideIfDisabledDrawer : MaterialPropertyDrawer
{
protected string[] argValue;
bool bElementHidden;
//constructor permutations -- params doesn't seem to work for property drawer inputs :( -----------
public HideIfDisabledDrawer(string name1)
{
argValue = new string[] { name1 };
}
public HideIfDisabledDrawer(string name1, string name2)
{
argValue = new string[] { name1, name2 };
}
public HideIfDisabledDrawer(string name1, string name2, string name3)
{
argValue = new string[] { name1, name2, name3 };
}
public HideIfDisabledDrawer(string name1, string name2, string name3, string name4)
{
argValue = new string[] { name1, name2, name3, name4 };
}
//-------------------------------------------------------------------------------------------------
public override void OnGUI (Rect position, MaterialProperty prop, string label, MaterialEditor editor)
{
bElementHidden = false;
for(int i=0; i<editor.targets.Length; i++)
{
//material object that we're targetting...
Material mat = editor.targets[i] as Material;
if(mat != null)
{
//check for the dependencies:
for(int j=0; j<argValue.Length; j++)
bElementHidden |= !mat.IsKeywordEnabled(argValue[j]);
}
}
if(!bElementHidden)
editor.DefaultShaderProperty(prop, label);
}
//We need to override the height so it's not adding any extra (unfortunately texture drawers will still add an extra bit of padding regardless):
public override float GetPropertyHeight (MaterialProperty prop, string label, MaterialEditor editor)
{
//@TODO: manually standardise element compaction
// float height = base.GetPropertyHeight (prop, label, editor);
// return bElementHidden ? 0.0f : height-16;
return 0;
}
}
#endif
And to use it, you simply call it like any other attribute style on the shader property. Eg for that mask value example:
[HideIfDisabled(_MATERIAL_MASKED)] _maskThreshold (“Mask threshold”, Float) = 0.333
NOTE: this is dependent on enable/disable of keywords, so the above would show the mask value when this is set to enabled:
#pragma shader_feature _MATERIAL_MASKED
In my setup, this is done in my common shaderGUI editor with Material.EnableKeyword() and Material.DisableKeyword()
