MaterialのShaderプロパティーを表示 その2(Editor拡張)

MaterialのShaderプロパティーを表示 その2(Editor拡張)

コントロールの作成

 プロパティの表示に必要なデータの取得で取得したプロパティのタイプ(prop_type)を用いて、プロパティのタイプによって作成するコントロールを変更します。

ShaderUtil.ShaderPropertyType prop_type = ShaderUtil.GetPropertyType(target_shader, i);
・・・
switch (prop_type)
{
・・・
}

Colorプロパティ

 colorプロパティに作用するAttributeはありません。そのため、以下に示すように色を選択するコントロールの作成のみを行っています。

case ShaderUtil.ShaderPropertyType.Color:
    Color col = CustomEditorGUI.ColorField(description, target_mat.GetColor(prop_name), style_label);
    target_mat.SetColor(prop_name, col);
    break;

Vectorプロパティ

 Vectorプロパティもcolorプロパティと同様にAttributeはありません。そのため、Vectorを選択するコントロールの作成のみを行っています。

case ShaderUtil.ShaderPropertyType.Vector:
    Vector4 vec = CustomEditorGUI.Vector4Field(description, target_mat.GetVector(prop_name), style_label, style_text_field, event_current);
    target_mat.SetVector(prop_name, vec);
    break;

Floatプロパティ

 Floatプロパティに有効なAttributeはToggle、Enum及びKeywordEnumがあります。これらが指定されて場合、Attributeによってコントロールの表示方法などを変更します。

case ShaderUtil.ShaderPropertyType.Float:
・・・
    break;

Toggle

 AttributeにToggleもしくはMaterialToggleが指定されていた場合、FloatプロパティはToggleで表示されます。

if (attribs[j] == "Toggle" || attribs[j] == "MaterialToggle")
{
    val = target_mat.GetFloat(prop_name);
    bool flg = (val != 0) ? true : false;   //shaderから得られた数値によりbool
    flg = CustomEditorGUI.Toggle(description, flg, style_label, style_toggle);
    val = (flg) ? 1 : 0;    //コントロールで変更されたboolに応じて数値を決定
    //キーワードが設定されてない場合
    if (param[j] == null)
    {
        if (flg)
        {
            target_mat.EnableKeyword(prop_name.ToUpper() + "_ON");
        }
        else
        {
            target_mat.DisableKeyword(prop_name.ToUpper() + "_ON");
        }
    }
    //キーワードが設定されている場合
    else
    {
        if (flg)
        {
            target_mat.EnableKeyword(param[j]);
        }
        else
        {
            target_mat.DisableKeyword(param[j]);
        }
    }
    control_created = true;
    break;
}

GetFloatによりプロパティの数値を取得します。取得した数値をbool型へ変換します。(0→false、0以外→true)このbool値を用いてToggleを作成します。Toggleの結果はそのままshaderへ渡すことができないので、bool値に応じた数値(true→1、false→0)へ変換します。

val = target_mat.GetFloat(prop_name);
bool flg = (val != 0) ? true : false;
flg = CustomEditorGUI.Toggle(description, flg, style_label, style_toggle);
val = (flg) ? 1 : 0;

Attributeが[Toggle]または[MaterialToggle]であった場合、プロパティ名_ONのようなキーワードを用いてバリアントを作成できます。よって、Toggleから取得したbool値を用いてキーワードの状態を変更します。また、キーワードは全て大文字にしなければならないのでstring.ToUpperによってプロパティ名を大文字へ変更しています。

if (param[j] == null)
{
    if (flg)
    {
        target_mat.EnableKeyword(prop_name.ToUpper() + "_ON");
    }
    else
    {
        target_mat.DisableKeyword(prop_name.ToUpper() + "_ON");
    }
}

Attributeが[Toggle(param)]であった場合、バリアントのキーワードにparamを使用することができます。よって、以下のようにparamでキーワードの状態を変更しています。最後に、control_created=trueとすることでコントロールが作成されたことを知らせます。

else
{
    if (flg)
    {
        target_mat.EnableKeyword(param[j]);
    }
    else
    {
        target_mat.DisableKeyword(param[j]);
    }
}
control_created = true;

Enum

 FloatプロパティのAttributeにEnumを指定することができます。Enumはキーワードを指定すると、そのキーワードに指定されている数値を設定することができます。

else if (attribs[j] == "Enum")
{
    val = target_mat.GetFloat(prop_name);

    string temp = Regex.Replace(param[j], @"\s", "");   //スペースの削除
    string[] enum_temp = temp.Split(',');

    if (enum_temp.Length % 2 != 0) break;   //名前と数値が組になっていない場合は無効なAttributeとして処理する。

    int enum_length = enum_temp.Length / 2;

    //名前と数値に分ける。数値部分の文字がfloatへ変更できなかった場合は無効なAttributeとして処理する。
    string[] enum_name = new string[enum_length];
    float[] enum_value = new float[enum_length];
    bool error = false;

    int selected = 0;
    for (int k = 0; k < enum_length; k++)
    {
        enum_name[k] = enum_temp[k * 2];
        try
        {
            enum_value[k] = float.Parse(enum_temp[k * 2 + 1]);
        }
        catch
        {
            error = true;
        }

        if (val == enum_value[k])
        {
            selected = k;
        }
    }
    if (error) continue;

    //Enumの表示
    selected = EditorPopUpWindow.Create(description, selected, enum_name, style_label, style_button, style_popup_label, color_popup_background, PopUpPosition.Center, this);
    val = enum_value[selected];

    control_created = true;
    break;
}

EnumのAttributeは[Enum(ZERO, 0, HALF, 0.5, ONE, 1)]のように記述されています。よって取得したparam(ZERO, 0, HALF, 0.5, ONE, 1)をキーワードと数値へ分ける必要があります。始めに、空白の削除を行い、カンマによって文字列を分割します。次に、取得したparamが妥当か検討します。名前と数値が組となっている場合は、分割された文字列(enum_temp)の数は偶数となるはずです。よって、偶数でなかった場合は正しくAttributeが記述できていません。この場合は、breakにより処理を抜け、通常のFloatプロパティを表示します。

string temp = Regex.Replace(param[j], @"\s", ""); 
string[] enum_temp = temp.Split(',');

if (enum_temp.Length % 2 != 0) break; 

次に、分割した文字列をキーワードと数値へ分けます。数値部分はstring型で取得しているのでfloat.Parseを用いてfloat型へ変換します。float型へ変更できなかった場合はerror=trueとすることで処理を抜け、次のAttributeの処理を行います。また、選択されているキーワードの順番をselectedで受け取ります。

string[] enum_name = new string[enum_length];
float[] enum_value = new float[enum_length];
bool error = false;

int selected = 0;
for (int k = 0; k < enum_length; k++)
{
    enum_name[k] = enum_temp[k * 2];
    try
    {
        enum_value[k] = float.Parse(enum_temp[k * 2 + 1]);
    }
    catch
    {
        error = true;
    }

    if (val == enum_value[k])
    {
        selected = k;
    }
}
if (error) continue;

これらのキーワードと数値を用いてEnum用のコントロールを作成ます。また、選択されたキーワードの数値をvalへ代入し、後にマテリアルへ渡します。最後に、control_created=trueとすることでコントロールが作成されたことを知らせます。

selected = EditorPopUpWindow.Create(description, selected, enum_name, style_label, style_button, style_popup_label, color_popup_background, PopUpPosition.Center, this);
val = enum_value[selected];

control_created = true;

KeywordEnum

 AttributeにKeywordEnumを指定することでUIから有効なバリアントのキーワードを変更することができます。

else if (attribs[j] == "KeywordEnum")
{
    val = target_mat.GetFloat(prop_name);

    string temp = Regex.Replace(param[j], @"\s", "");
    string[] enum_name = temp.Split(',');

    int selected = (int)val;
    selected = EditorPopUpWindow.Create(description, selected, enum_name, style_label, style_button, style_popup_label, PopUpPosition.Center, this);
    val = selected;

    //有効にするキーワード以外はDisableで無効にする必要がある
    target_mat.SetFloat(prop_name, selected);
    for (int k = 0; k < enum_name.Length; k++)
    {
        if (k == selected)
        {
            target_mat.EnableKeyword(prop_name.ToUpper() + "_" + enum_name[k].ToUpper());
        }
        else
        {
            target_mat.DisableKeyword(prop_name.ToUpper() + "_" + enum_name[k].ToUpper());
        }
    }

    control_created = true;
    break;

Enumの場合と同様に、空白を削除した後にカンマによってparamを分割します。

val = target_mat.GetFloat(prop_name);

string temp = Regex.Replace(param[j], @"\s", "");
string[] enum_name = temp.Split(',');

プロパティより得られる数値は、選択されているキーワードの番号となります。[KeywordEnum(Red, Green, Blue)]の場合は、Redは0、Greenは1、Blueは2となります。よって、取得した文字列と数値を用いてKeywordEnum用のコントロールを作成します。

int selected = (int)val;
selected = EditorPopUpWindow.Create(description, selected, enum_name, style_label, style_button, style_popup_label, PopUpPosition.Center, this);
val = selected;

選択された番号をマテリアルへ渡し、選択されたキーワードを有効にします。この際、選択されなかったキーワードは無効にする必要があります。最後に、control_created=trueとすることでコントロールが作成されたことを知らせます。

target_mat.SetFloat(prop_name, selected);
for (int k = 0; k < enum_name.Length; k++)
{
    if (k == selected)
    {
        target_mat.EnableKeyword(prop_name.ToUpper() + "_" + enum_name[k].ToUpper());
    }
    else
    {
        target_mat.DisableKeyword(prop_name.ToUpper() + "_" + enum_name[k].ToUpper());
    }
}

control_created = true;

Float

 Attributeが無効もしくは指定されていない場合は、通常のFloatプロパティを作成します。最後に、material.SetFloatにより変更された数値をマテリアルへ渡します。

if (control_created == false)
{
    val = CustomEditorGUI.FloatField(description, target_mat.GetFloat(prop_name), style_label, style_text_field, event_current);
}

target_mat.SetFloat(prop_name, val);

Rangeプロパティ

 RangeプロパティはPowerSliderとIntRangeをAttributeへ指定できます。これらが指定された場合、Attributeによってコントロールの表示方法などを変更します。

case ShaderUtil.ShaderPropertyType.Range:
・・・
    break;

PowerSlider

 RangeプロパティのAttributeにはPowerSliderを指定することができます。Shaderには[PowerSlider(数値)]と記述されます。paramには括弧内の数値がstring型で入っています。よって、これをfloat.Parseを用いてfloat型へ変換します。もし、変換できなかった場合は処理を飛ばし、次のAttributeの処理を行います。変換できた場合はPowerSliderを作成し、最後に、control_created=trueとすることでコントロールが作成されたことを知らせます。

if (attribs[j] == "PowerSlider")
{
    try
    {
        power = float.Parse(param[j]);
    }
    catch
    {
        continue;
    }

    slider_val = target_mat.GetFloat(prop_name);
    slider_val = CustomEditorGUI.PowerSlider(description, slider_val, min, max, power, style_label, style_slider, style_slider_thumb, style_text_field, event_current);
    slider_created = true;
    break;
}

IntRnage

 AttributeにIntRangeが指定されていた場合、IntSliderを作成します。今までと同様に、control_created=trueとすることでコントロールが作成されたことを知らせます。

else if (attribs[j] == "IntRange")
{
    slider_val = CustomEditorGUI.IntSlider(description, (int)slider_val, (int)min, (int)max, style_label, style_slider, style_slider_thumb, style_text_field, event_current);
    slider_created = true;
    break;
}

Textureプロパティ

 TextureプロパティにはNoScaleOffsetを指定することができます。このAttributeを指定すると、TextureプロパティのScaleとOffsetを非表示にすることができます。NoScaleOffsetはshader.GetPropertyFlagsより取得することができます。この結果より、ScaleとOffsetのありなしの処理を分けています。

case ShaderUtil.ShaderPropertyType.TexEnv:
    //Texture
    UnityEngine.Rendering.TextureDimension textureDimension = target_shader.GetPropertyTextureDimension(index);

    if ((target_shader.GetPropertyFlags(index) & UnityEngine.Rendering.ShaderPropertyFlags.NoScaleOffset) == UnityEngine.Rendering.ShaderPropertyFlags.NoScaleOffset)
    {
    ・・・
    }
    else
    {
    ・・・
    }
    break;

NoScaleOffset

 AttributeにNoScaleOffsetがあるTextureプロパティの処理です。TextureにはTexture、Cube、Texture3D及びTexture2DArrayがあります。よって、Textureの種類ごとにScale及びOffsetを表示しないTexture用のコントロールを作成しています。

if ((target_shader.GetPropertyFlags(index) & UnityEngine.Rendering.ShaderPropertyFlags.NoScaleOffset) == UnityEngine.Rendering.ShaderPropertyFlags.NoScaleOffset)
{
    switch (textureDimension)
    {
        case UnityEngine.Rendering.TextureDimension.Tex2D:
            Texture tex = CustomEditorGUI.TextureField(description, target_mat.GetTexture(prop_name), style_label);
            target_mat.SetTexture(prop_name, tex);
            break;

        case UnityEngine.Rendering.TextureDimension.Cube:
            Cubemap cubemap = CustomEditorGUI.CubemapField(description, target_mat.GetTexture(prop_name) as Cubemap, style_label);
            target_mat.SetTexture(prop_name, cubemap);
            break;

        case UnityEngine.Rendering.TextureDimension.Tex3D:
            Texture3D tex3d = CustomEditorGUI.VolumeTextureField(description, target_mat.GetTexture(prop_name) as Texture3D, style_label);
            target_mat.SetTexture(prop_name, tex3d);
            break;

        case UnityEngine.Rendering.TextureDimension.Tex2DArray:
            Texture2DArray tex2darray = CustomEditorGUI.Texture2DArrayField(description, target_mat.GetTexture(prop_name) as Texture2DArray, style_label);
            target_mat.SetTexture(prop_name, tex2darray);
            break;
    }
}

NoScaleOffsetがない場合

 TextureプロパティにNoScaleOffsetがない場合の処理です。Scale及びOffsetを表示するTexture用コントロールを作成します。

else
{
    Vector2 scale = target_mat.GetTextureScale(prop_name);
    Vector2 offset = target_mat.GetTextureOffset(prop_name);

    switch (textureDimension)
    {
        case UnityEngine.Rendering.TextureDimension.Tex2D:
            Texture tex = CustomEditorGUI.TextureField(description, target_mat.GetTexture(prop_name), style_label, style_text_field, ref scale, ref offset, event_current);
            target_mat.SetTexture(prop_name, tex);
            break;

        case UnityEngine.Rendering.TextureDimension.Cube:
            Cubemap cubemap = CustomEditorGUI.CubemapField(description, target_mat.GetTexture(prop_name) as Cubemap, style_label, style_text_field, ref scale, ref offset, event_current);
            target_mat.SetTexture(prop_name, cubemap);
            break;

        case UnityEngine.Rendering.TextureDimension.Tex3D:
            Texture3D tex3d = CustomEditorGUI.VolumeTextureField(description, target_mat.GetTexture(prop_name) as Texture3D, style_label, style_text_field, ref scale, ref offset, event_current);
            target_mat.SetTexture(prop_name, tex3d);
            break;

        case UnityEngine.Rendering.TextureDimension.Tex2DArray:
            Texture2DArray tex2darray = CustomEditorGUI.Texture2DArrayField(description, target_mat.GetTexture(prop_name) as Texture2DArray, style_label, style_text_field, ref scale, ref offset, event_current);
            target_mat.SetTexture(prop_name, tex2darray);
            break;
    }
    target_mat.SetTextureScale(prop_name, scale);
    target_mat.SetTextureOffset(prop_name, offset);
}

実行結果

 作成したMaterialのShaderプロパティを表示するコードは以下のScriptで実行できます。以下のScriptではLightSkinが適用されます。DarkSkinはコンメントアウト部分に記述しています。

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEditor;
using System.Text.RegularExpressions;

public class MaterialProperty : EditorWindow
{
    private const string dark_skin_path = "builtin skins/darkskin/images/";

    private Material target_mat;
    private Vector2 scroll_shader = Vector2.zero;

    private GUIStyle style_label, style_text_field, style_slider, style_slider_thumb, style_toggle, style_drop_button,
             style_header, style_popup_label, style_obj;

    private Texture2D tex_background;
    private Color color_background = new Color(0.2f, 0.2f, 0.2f, 1.0f);
    private Color color_popup_background = new Color(0.8f, 0.8f, 0.8f, 1.0f);

    [MenuItem("Tools/MaterialProperty")]
    public static void OpenWindow()
    {
        EditorWindow ed = EditorWindow.GetWindow(typeof(MaterialProperty), false, "MaterialProperty");
        ed.minSize = new Vector2(285, 256);
        ed.wantsMouseMove = true;
    }

    private void OnEnable()
    {
        //GUIStyle

        //dark style
        //GUISkin dark_skin = (GUISkin)EditorGUIUtility.Load("DarkSkin.guiskin");
        //style_label = new GUIStyle(dark_skin.label);
        //style_label.focused.textColor = new Color(0.506f, 0.706f, 1f, 1f);
        //style_label.padding = new RectOffset(1, 1, 0, 0);

        //style_text_field = new GUIStyle(dark_skin.textField);
        //style_text_field.normal.background = (Texture2D)EditorGUIUtility.Load(dark_skin_path + "textfield.png");
        //style_text_field.focused.background = (Texture2D)EditorGUIUtility.Load(dark_skin_path + "textfield focused.png");

        //style_slider = new GUIStyle(dark_skin.horizontalSlider);
        //style_slider_thumb = new GUIStyle(dark_skin.horizontalScrollbarThumb);

        //style_toggle = new GUIStyle(dark_skin.toggle);
        //style_toggle.normal.background = (Texture2D)EditorGUIUtility.Load(dark_skin_path + "toggle.png");
        //style_toggle.focused.background = (Texture2D)EditorGUIUtility.Load(dark_skin_path + "toggle focus.png");
        //style_toggle.active.background = (Texture2D)EditorGUIUtility.Load(dark_skin_path + "toggle act.png");
        //style_toggle.onNormal.background = (Texture2D)EditorGUIUtility.Load(dark_skin_path + "toggle on.png");
        //style_toggle.onFocused.background = (Texture2D)EditorGUIUtility.Load(dark_skin_path + "toggle on focus.png");
        //style_toggle.onActive.background = (Texture2D)EditorGUIUtility.Load(dark_skin_path + "toggle on act.png");

        //style_drop_button = new GUIStyle(dark_skin.button);
        //style_drop_button.alignment = TextAnchor.MiddleLeft;
        //style_drop_button.padding = new RectOffset(5, 2, 1, 2);
        //style_drop_button.normal.background = (Texture2D)EditorGUIUtility.Load(dark_skin_path + "btn.png");
        //style_drop_button.focused.background = (Texture2D)EditorGUIUtility.Load(dark_skin_path + "btn focus.png");
        //style_drop_button.active.background = (Texture2D)EditorGUIUtility.Load(dark_skin_path + "btn act.png");
        //style_drop_button.onNormal.background = (Texture2D)EditorGUIUtility.Load(dark_skin_path + "btn on.png");
        //style_drop_button.onFocused.background = (Texture2D)EditorGUIUtility.Load(dark_skin_path + "btn on focus.png");
        //style_drop_button.onActive.background = (Texture2D)EditorGUIUtility.Load(dark_skin_path + "btn onact.png");

        //style_header = new GUIStyle(style_label);
        //style_header.fontStyle = FontStyle.Bold;
        //style_obj = new GUIStyle(dark_skin.FindStyle("ObjectField"));

        //light style
        GUISkin light_skin = (GUISkin)EditorGUIUtility.Load("LightSkin.guiskin");

        style_label = new GUIStyle(light_skin.label);
        style_label.focused.textColor = new Color(0, 0.235f, 0.533f, 1f);
        style_label.padding = new RectOffset(1, 1, 0, 0);
        style_text_field = new GUIStyle(light_skin.textField);
        style_slider = new GUIStyle(light_skin.horizontalSlider);
        style_slider_thumb = new GUIStyle(light_skin.horizontalSliderThumb);
        style_toggle = new GUIStyle(light_skin.toggle);
        style_drop_button = new GUIStyle(light_skin.FindStyle("DropDownButton"));
        style_drop_button.alignment = TextAnchor.MiddleLeft;
        style_drop_button.padding = new RectOffset(5, 2, 1, 2);
        style_header = new GUIStyle(style_label);
        style_header.fontStyle = FontStyle.Bold;
        style_popup_label = new GUIStyle(style_label);
        style_obj = new GUIStyle(light_skin.FindStyle("ObjectField"));

        //背景用画像
        tex_background = new Texture2D(1, 1, TextureFormat.RGB24, false);
        tex_background.SetPixel(0, 0, color_background);
        tex_background.Apply();

    }

    private void OnGUI()
    {
        //GUI.DrawTexture(new Rect(0, 0, position.width, position.height), tex_background, ScaleMode.StretchToFill);

        target_mat = (Material)CustomEditorGUI.ObjectField("target_mat", target_mat, typeof(Material), style_label, style_obj, false);

        ShaderPropertyWindow();
    }

    void ShaderPropertyWindow()
    {
    ・・・
    }

    private void DrawHeader(string label, GUIStyle style_header)
    {
    ・・・
    }
}

以上のScriptの動作を確認するために以下のShaderを作成しました。

Shader "Unlit/ShaderProperties"
{
    Properties
    {
        [MainColor]
        _MainColor ("main color", Color) = (0.3, 0.8, 0.6, 1.0)
        _Color ("color", Color) = (0.3, 0.8, 0.6, 1.0)
        _Float ("float", Float) = 2.3

        [Space(aaa)]    //()内が数値でなかった場合の確認用

        [Header(Texture)]
        _MainTex ("Texture", 2D) = "white" {}
        _Rectangele ("Rectangle", Rect) = "white" {}
        _Cubemap ("Cubemap", Cube) = "white" {}
        _VolumeTex("3D Texture", 3D) = "white" {}
        _Texture2DArray("Texture 2D Array", 2DArray) = "white" {}

        [NoScaleOffset]
        _MainTexS ("Texture", 2D) = "white" {}
        [NoScaleOffset]
        _CubemapS ("Cubemap", Cube) = "white" {}
        [NoScaleOffset]
        _VolumeTexS("3D Texture", 3D) = "white" {}
        [NoScaleOffset]
        _Texture2DArrayS("Texture 2D Array", 2DArray) = "white" {}

        [Normal]
        _NormalMap ("Normalmap", 2D) = "white" {}
        [HDR]
        _HDRTex ("HDR Texture", 2D) = "white" {}

        [Header(Toggle)]
        [Toggle]
        _Toggle ("toggle", Int) = 0
        [MaterialToggle]
        _MaterialToggle ("material toggle", Int) = 0
        [Toggle(ENABLE_INVERT)]
        _Invert ("toggle", Int) = 0

        [Header(Slider)]
        _Range ("range", Range(-5.0, 5.0)) = 0.5
        [HideInInspector]
        _HideRange ("hide range", Range(0.0, 1.0)) = 0.5
        [PowerSlider(0.5)]
        _PowerSlider_1 ("PowerSlider(0, 1)", Range(0, 1)) = 0.5
        [PowerSlider(0.5)]
        _PowerSlider_2 ("PowerSlider(-1, 0)", Range(-5, 5)) = 0.5
        [IntRange]
        [PowerSlider(TWO)]
        _ErrorPowerSlider ("ErrorSlider(-1, 0)", Range(-5, 5)) = 0.5
        [IntRange]
        _IntRange ("IntRange", Range(-5, 5)) = 0
        _Vector ("vector", Vector) = (0.3, 3.7, 8.6, 7.1)

        [Header(Enum)]
        [Space]
        [Space(100)]
        [Enum(ZERO, 0, HALF, 0.5, ONE, 1)]
        _Enum ("Enum", Float) = 0

        [Toggle]
        [Enum(test, 0, test2, 1)]
        [KeywordEnum(Left, Right)]
        _EnumTest ("EnumTest", Float) = 0

        [KeywordEnum(Red, Green, Blue)]
        _KeywordEnum ("KeywordEnum", Int) = 0

        //()内が文字列と数値の組となっていない場合
        [Enum(Left, 0, Right)]
        _ErrorEnum1( "Error Enum1", Float) = 0

        //()内が文字列だけの場合
        [Enum(Left, ONE, Right, TWO)]
        _ErrorEnum2 ("Error Enum2", Float) = 0

        //不正なAttributeの後に処理できるかの確認用
        [Enum(Left, 1, Right, 2)]
        [Enum(Left, ONE, Right, TWO)]
        _ErrorEnum3 ("Error Enum3", Float) = 0
    }

    SubShader
    {
        Tags { "RenderType"="Opaque" }

        Pass
        {
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag

            #pragma multi_compile _KEYWORDENUM_RED _KEYWORDENUM_BLUE _KEYWORDENUM_GREEN
            #pragma shader_feature _TOGGLE_ON _ _MATERIALTOGGLE_ON _ ENABLE_INVERT

            #include "UnityCG.cginc"

            struct appdata
            {
                float4 vertex : POSITION;
                float2 uv : TEXCOORD0;
            };

            struct v2f
            {
                float2 uv : TEXCOORD0;
                float4 vertex : SV_POSITION;
            };

            sampler2D _MainTex;
            float4 _MainTex_ST;
            float _Enum;
            int _Toggle;

            v2f vert (appdata v)
            {
                v2f o;
                o.vertex = UnityObjectToClipPos(v.vertex);
                o.uv = TRANSFORM_TEX(v.uv, _MainTex);
                return o;
            }

            fixed4 frag (v2f i) : SV_Target
            {
                fixed4 col = tex2D(_MainTex, i.uv);

                float3 result = float3(0, 0, 0);

                #ifdef _KEYWORDENUM_RED
                   result = float3(1.0, 0, 0);
                #elif _KEYWORDENUM_GREEN
                   result = float3(0, 1.0, 0.0);
                #elif _KEYWORDENUM_BLUE
                   result = float3(0, 0.0, 1.0);
                #endif

                #ifdef _TOGGLE_ON
                   result = float3(1, 1, 0);
                #endif

                #ifdef _MATERIALTOGGLE_ON
                   result = float3(1, 0, 1);
                #endif

                #ifdef ENABLE_INVERT
                   result = float3(0, 1, 1);
                #endif

                col = fixed4(result, 1.0) * _Enum;
                return col;
            }
            ENDCG
        }
    }
}

実行結果は以下の通りです。各コントロールの作成が問題なく行われていることが分かります。また、Toggleによるキーワードの状態の変更、Enumによる数値変更やKeywordEnumによるキーワードの変更が行えていることが分かります。