Search Unity

Sharing is caring: Hiding optional material parameters

Discussion in 'Shaders' started by jistyles, Aug 25, 2015.

  1. jistyles

    jistyles

    Joined:
    Nov 6, 2013
    Posts:
    34
    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.

    upload_2015-8-25_10-25-2.png

    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:
    upload_2015-8-25_10-30-2.png
    It has a number of extra bells and whistles, but the concept is mostly the same. If I change the Blending type to masked...
    upload_2015-8-25_10-32-45.png

    It displays the interface for the mask threshold variable. Likewise if I set it to Terrain Base it does this:
    upload_2015-8-25_10-41-28.png

    Code dump time!
    HideIfDisabledDrawer.cs:
    Code (csharp):
    1.  
    2. #if UNITY_EDITOR
    3. using UnityEngine;
    4. using UnityEditor;
    5. using System.Collections;
    6.  
    7. public class HideIfDisabledDrawer : MaterialPropertyDrawer
    8. {
    9.    protected string[] argValue;
    10.    bool bElementHidden;
    11.  
    12.    //constructor permutations -- params doesn't seem to work for property drawer inputs :( -----------
    13.    public HideIfDisabledDrawer(string name1)
    14.    {
    15.      argValue = new string[] { name1 };
    16.    }
    17.  
    18.    public HideIfDisabledDrawer(string name1, string name2)
    19.    {
    20.      argValue = new string[] { name1, name2 };
    21.    }
    22.  
    23.    public HideIfDisabledDrawer(string name1, string name2, string name3)
    24.    {
    25.      argValue = new string[] { name1, name2, name3 };
    26.    }
    27.  
    28.    public HideIfDisabledDrawer(string name1, string name2, string name3, string name4)
    29.    {
    30.      argValue = new string[] { name1, name2, name3, name4 };
    31.    }
    32.  
    33.    //-------------------------------------------------------------------------------------------------
    34.  
    35.    public override void OnGUI (Rect position, MaterialProperty prop, string label, MaterialEditor editor)
    36.    {
    37.      bElementHidden = false;
    38.      for(int i=0; i<editor.targets.Length; i++)
    39.      {
    40.        //material object that we're targetting...
    41.        Material mat = editor.targets[i] as Material;
    42.        if(mat != null)
    43.        {
    44.          //check for the dependencies:
    45.          for(int j=0; j<argValue.Length; j++)
    46.            bElementHidden |= !mat.IsKeywordEnabled(argValue[j]);
    47.        }
    48.      }
    49.  
    50.      if(!bElementHidden)
    51.        editor.DefaultShaderProperty(prop, label);
    52.    }
    53.  
    54.    //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):
    55.    public override float GetPropertyHeight (MaterialProperty prop, string label, MaterialEditor editor)
    56.    {
    57.      //@TODO: manually standardise element compaction
    58. //     float height = base.GetPropertyHeight (prop, label, editor);
    59. //     return bElementHidden ? 0.0f : height-16;
    60.  
    61.      return 0;
    62.    }
    63.    
    64. }
    65. #endif
    66.  
    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()
     
    zanouk, monark, Invertex and 2 others like this.
  2. Phantomx

    Phantomx

    Joined:
    Oct 30, 2012
    Posts:
    202
    Wow this is really awesome! can I use this at work for the shaders I make for the art team at work? And Assets I sell on the asset store?
    Thanks!
     
    Last edited: Aug 25, 2015
  3. jistyles

    jistyles

    Joined:
    Nov 6, 2013
    Posts:
    34
    Go for it - consider it public domain, no restrictions, no requirements, (no support ;)).
     
  4. shanecelis

    shanecelis

    Joined:
    Mar 26, 2014
    Posts:
    22
    Hi @jistyles, thanks for this code. Allow me to necrobump this thread. I ended up modifying it so that one could write it like this:

    [HideIf(X)]
    [HideIf(X, true)]
    [HideIf(X, false)] // Same behavior as HideIfDisabled(X)


    Or you can specify it another way like so:

    [ShowIf(X)] // Same behavior as HideIfDisabled(X)
    [ShowIf(X, true)] // Same behavior as HideIfDisabled(X)
    [ShowIf(X, false)]


    Here is the code crediting you, and here's a tweet thread that shows it in action.
     
  5. aybeone

    aybeone

    Joined:
    May 24, 2015
    Posts:
    107
    To remove extra space:

    Code (CSharp):
    1.     public override float GetPropertyHeight(MaterialProperty prop, string label, MaterialEditor editor)
    2.     {
    3.         return -EditorGUIUtility.standardVerticalSpacing;
    4.     }
    :D
     
  6. madGlory

    madGlory

    Joined:
    Jan 12, 2016
    Posts:
    44
    I am using this script and it is awesome! There is however 1 issue that would be nice to solve.

    When using other shader properties like this, it doesn't draw the MaterialToggle
    Code (CSharp):
    1. [MaterialToggle][HideIfDisabled(INVERT)] _ShouldInvert ("Invert", Float) = 1
    How could this drawer accomodate MaterialToggles or KeywordEnum?
     
  7. shelim

    shelim

    Joined:
    Aug 26, 2017
    Posts:
    29
    Actually not that tested, but I would change a single line inside OnGUI. Apparently in editor mode IsKeywordEnabled returns false for shader keywords specified by toggles. Remember to add using System.Linq at the top!

    Code (CSharp):
    1.  
    2.       public override void OnGUI(Rect position, MaterialProperty prop, string label, MaterialEditor editor)
    3.         {
    4.             bElementMatches = false;
    5.             for (int i = 0; i < editor.targets.Length; i++)
    6.             {
    7.                 //material object that we're targetting...
    8.                 Material mat = editor.targets[i] as Material;
    9.                 if (mat != null)
    10.                 {
    11.                     //check for the dependencies:
    12.                     for (int j = 0; j < argValue.Length; j++)
    13.                         bElementMatches |= (mat.IsKeywordEnabled(argValue[j]) == enabled) || (mat.shaderKeywords.Any(x => x == argValue[j]) == enabled);
    14.                    // Above line was changed. Since we are in editor, Any seems ok here.
    15.                 }
    16.             }
    17.  
    18.             if (ShowElement(bElementMatches))
    19.                 editor.DefaultShaderProperty(prop, label);
    20.         }
    21.