Unity
Unity拡張

StandardShaderのMode選択機能をカスタムシェーダーに入れたい!

カスタムシェーダーのInspectorもこだわりたい。

StandardShaderの一番上についているRenderting Modeのプルダウンメニュー
自作シェーダーのBlendModeをいじるたびにコレつけられたらかっこいいのに!
と思ってました。

↓コレ
StandardMode.png

というわけで、ちょっとチャレンジしてみることにしました。
Unity2017.2.0f3で動作確認しています。

参考

・builtin_shadersの中に含まれるeditor/StandardShaderGUI.cs
・安藤圭吾さんのエディター拡張入門
第6章 EditorGUI (EdirotGUILayout)

まずは準備

shadersample.png

Create > Shader>Unlit Shader 名前をGUITest
Create >Material できたMaterialに先ほどのShaderをアタッチ
Editorフォルダを作って中にCreate > C# Script
名前は適当に付けました。 一応画像の通りにしてあります。

Shaderの最後にCustomEditor "BlendTestGUI" (名前は任意)
こうすることでこのShaderはMaterial経由でInspectorに表示される時は指定されたScriptを
参照するようになるみたいです。
ちなみに、Standard.Shaderは末尾にCustomEditor "StandardShaderGUI"と書いてありますね。

調べたこと

StandardShaderGUI.csの中にModeの設定について記載がありました。
public enum BlendMode
public static void SetupMaterialWithBlendMode(Material material, BlendMode blendMode)

BlendModeの定義を使ってSetupMaterialWithBlendModeで値をセットしているみたいです。
StandardShaderGUIは長いので後はEditorGUIを調べつつシンプルに実装していきます。

※ソースは下記参照してください

Inspectorの表示物はこの中↓に記載します
OnInspectorGUI()

流れとしては。
Material targetMat = target as Material;
ここで扱うMaterialを確保しておいて

EditorGUI.BeginChangeCheck();
これでGUI内の変更を監視しつつ、表示部本体を表示

if (EditorGUI.EndChangeCheck())
ここで変更があった場合にパラメータをMaterialにセットしていきます。
セット忘れると、GUIはいじれても値がセットされません。

Materialの設定項目そのものはStandardShaderからコピペになっています。

結果

さあどうでしょうか。無事モードセレクトが表示されました。
このあとは設定項目にAdditiveを追加してみたりとか夢がひろがります。
Materialを設定する人が使いやすいGUI設計ができそうな気がしてきました。

kekka1.png

kekka2.png

それぞれのScriptの中身

Shaderの中はこんな感じにしています。

Shader "Custom/GuiTest"
{
Properties
{
    _Color("Color",color) = (1,1,1,1)
    _MainTex ("Texture", 2D) = "white" {}

    [HideInInspector] _Mode("Mode", Float) = 0.0
    [HideInInspector]_SrcBlend("Blend Src", Float) = 0
    [HideInInspector]_DstBlend("Blend Dst", Float) = 0
    [HideInInspector]_ZWrite("ZWrite", Float) = 0
}
SubShader
{
    LOD 100

    Pass
    {
        Blend[_SrcBlend][_DstBlend]
        ZWrite[_ZWrite]

        CGPROGRAM
        #pragma vertex vert
        #pragma fragment frag
        // make fog work
        #pragma multi_compile_fog

        #include "UnityCG.cginc"

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

        struct v2f
        {
            float2 uv : TEXCOORD0;
            UNITY_FOG_COORDS(1)
            float4 vertex : SV_POSITION;
        };

        sampler2D _MainTex;
        float4 _MainTex_ST;
        float4 _Color;

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

        fixed4 frag (v2f i) : SV_Target
        {
            // sample the texture
            fixed4 col = tex2D(_MainTex, i.uv) * _Color;
            // apply fog
            UNITY_APPLY_FOG(i.fogCoord, col);
            return col;
        }
        ENDCG
    }
}
    CustomEditor "BlendTestGUI"
}

Edirorの中に作成したBlendTestGUI.csはこんな感じです。

public class BlendTestGUI : MaterialEditor
{
    public override void OnInspectorGUI()
    {
        if (!isVisible)
            return;

        //Materialを所得
        Material targetMat = target as Material;

        //GUIの変更をチェック開始
        EditorGUI.BeginChangeCheck();

        //表示物本体
        //Mode選択
        BlendMode SelectedMode = (BlendMode)EditorGUILayout.EnumPopup("ブレンドモード", (BlendMode)targetMat.GetFloat("_Mode"));
        Color color = EditorGUILayout.ColorField("MainColor",targetMat.color);
        Texture Texture =  EditorGUILayout.ObjectField(targetMat.GetTexture("_MainTex"), 
            typeof(Texture),false, GUILayout.Width(64), GUILayout.Height(64)) as Texture;
        Vector2 Tiling = EditorGUILayout.Vector2Field("Tiling", targetMat.GetTextureScale("_MainTex"),
            GUILayout.Width(200), GUILayout.Height(32));
        Vector2 Offset = EditorGUILayout.Vector2Field("Offset", targetMat.GetTextureOffset("_MainTex"),
            GUILayout.Width(200), GUILayout.Height(32));

        //GUIの変更点をMaterialに反映
        if (EditorGUI.EndChangeCheck())
        {
            targetMat.SetFloat("_Mode", (float)SelectedMode);
            SetupMaterialWithBlendMode(targetMat, SelectedMode);//BlendModeも保存
            targetMat.SetColor("_Color", color);
            targetMat.SetTexture("_MainTex", Texture);
            targetMat.SetTextureScale("_MainTex", Tiling);
            targetMat.SetTextureOffset("_MainTex", Offset);
        }
    }

    //StandardShaderGUI.csから引っこ抜き
    public enum BlendMode
    {
        Opaque,
        Cutout,
        Fade,       // Old school alpha-blending mode, fresnel does not affect amount of transparency
        Transparent // Physically plausible transparency mode, implemented as alpha pre-multiply
    }

    public static void SetupMaterialWithBlendMode(Material material, BlendMode blendMode)
    {
        switch (blendMode)
        {
            case BlendMode.Opaque:
                material.SetOverrideTag("RenderType", "");
                material.SetInt("_SrcBlend", (int)UnityEngine.Rendering.BlendMode.One);
                material.SetInt("_DstBlend", (int)UnityEngine.Rendering.BlendMode.Zero);
                material.SetInt("_ZWrite", 1);
                material.DisableKeyword("_ALPHATEST_ON");
                material.DisableKeyword("_ALPHABLEND_ON");
                material.DisableKeyword("_ALPHAPREMULTIPLY_ON");
                material.renderQueue = -1;
                break;
            case BlendMode.Cutout:
                material.SetOverrideTag("RenderType", "TransparentCutout");
                material.SetInt("_SrcBlend", (int)UnityEngine.Rendering.BlendMode.One);
                material.SetInt("_DstBlend", (int)UnityEngine.Rendering.BlendMode.Zero);
                material.SetInt("_ZWrite", 1);
                material.EnableKeyword("_ALPHATEST_ON");
                material.DisableKeyword("_ALPHABLEND_ON");
                material.DisableKeyword("_ALPHAPREMULTIPLY_ON");
                material.renderQueue = (int)UnityEngine.Rendering.RenderQueue.AlphaTest;
                break;
            case BlendMode.Fade:
                material.SetOverrideTag("RenderType", "Transparent");
                material.SetInt("_SrcBlend", (int)UnityEngine.Rendering.BlendMode.SrcAlpha);
                material.SetInt("_DstBlend", (int)UnityEngine.Rendering.BlendMode.OneMinusSrcAlpha);
                material.SetInt("_ZWrite", 0);
                material.DisableKeyword("_ALPHATEST_ON");
                material.EnableKeyword("_ALPHABLEND_ON");
                material.DisableKeyword("_ALPHAPREMULTIPLY_ON");
                material.renderQueue = (int)UnityEngine.Rendering.RenderQueue.Transparent;
                break;
            case BlendMode.Transparent:
                material.SetOverrideTag("RenderType", "Transparent");
                material.SetInt("_SrcBlend", (int)UnityEngine.Rendering.BlendMode.One);
                material.SetInt("_DstBlend", (int)UnityEngine.Rendering.BlendMode.OneMinusSrcAlpha);
                material.SetInt("_ZWrite", 0);
                material.DisableKeyword("_ALPHATEST_ON");
                material.DisableKeyword("_ALPHABLEND_ON");
                material.EnableKeyword("_ALPHAPREMULTIPLY_ON");
                material.renderQueue = (int)UnityEngine.Rendering.RenderQueue.Transparent;
                break;
        }
    }

}