Materialエディタの拡張

エフェクト統一シェーダーがあると捗るよね

の記事を読んで「よし、俺も統一シェーダ作るぞ!」と思って実際にやってみると、
シェーダのコードを#ifdefで切り刻むこと自体はそこまで大変ではないですが、
「エディタのほうはしんどそう」…と思っていました。

たとえば、画像を別画像の形状で切り抜くオプションを付けたとします。 f:id:enrike3:20180606230850p:plain

あくまでオプションなのでこの機能を使わないときはインスペクタにマスク画像を表示したくない。 機能をONにしたときだけ表示してほしい。

f:id:enrike3:20180606232215p:plain f:id:enrike3:20180606232152p:plain

オプションの機能が一つだけなら表示しちゃって気にしないという手はあるのですが、オプションが増えてきて、UVScrollだとかブレンド方式切り替えだとか、カラーチャンネルも切り替えだとかやりはじめるとやはり限界でして、表示のON/OFFを切り替えたい。ぜひに切り替えたい。

これをやるには、MaterialのInspectorを乗っ取る必要があるだろうから気が重かったのですが、Unity5以降のUnityEditor.ShaderGUIクラスを拡張すると意外にも簡単にできました。UnityEditor.MaterialEditorを継承する方式だと難しそうだったのに。ShaderGUI最高か。

方法

まずシェーダのCustomEditorに、ShaderGUIを継承するオレオレエディタのクラス名を名前空間つきで書きます。

Shader "Hoge/OreOreSprite"
{
    Properties
    {
        …
    }
    SubShader
    {
        …
    }
    CustomEditor "HogeHoge.OreOreShaderGUI"
}

MaterialEditorは名前空間使えなかったので、この時点で好感度急上昇。

そして、実装を書きます。

using UnityEditor;

namespace HogeHoge
{
    public class OreOreShaderGUI : ShaderGUI
    {
        public override void OnGUI(MaterialEditor materialEditor, MaterialProperty[] properties)
        {
        }
    }
}

これだけで、インスペクタの乗っ取りは済んでいます。OnGUIが空なのでインスペクタはまっさらになります。

f:id:enrike3:20180606233917p:plain

乗っ取れたので次は中身を書いていくわけですが、
引数で渡ってくるMaterialEditorとMaterialPropertyを使えばプロパティの表示はとても楽です。
たとえば、MainTexを表示するには、propertiesからMainTexを探してmaterialEditor.ShaderProperty()に食べさせるだけで済みます。

using System.Linq;
using UnityEditor;

namespace HogeHoge
{
    public class OreOreShaderGUI : ShaderGUI
    {
        public override void OnGUI(MaterialEditor materialEditor, MaterialProperty[] properties)
        {
            var mainTexProp = properties.First(x => x.name == "_MainTex");
            materialEditor.ShaderProperty(mainTexProp, mainTexProp.displayName);
        }
    }
}

f:id:enrike3:20180606234852p:plain

ここまでくれば、あとは普通のエディタ拡張と変わりません。
Toggleを使って表示非表示を制御するだけです。
色気をだして、Boxでかこったり、Toggleのラベルを太字にしたり、ということもできますね。

using System.Linq;
using UnityEngine;
using UnityEditor;

namespace HogeHoge
{
    public class OreOreShaderGUI : ShaderGUI
    {
        const string AlphaTexEnabledKeyword = "_ENABLE_ALPHA_TEX";

        public override void OnGUI(MaterialEditor materialEditor, MaterialProperty[] properties)
        {
            var material = (Material)materialEditor.target;

            var mainTexProp = properties.First(x => x.name == "_MainTex");
            materialEditor.ShaderProperty(mainTexProp, mainTexProp.displayName);

            var colorProp = properties.First(x => x.name == "_Color");
            materialEditor.ShaderProperty(colorProp, colorProp.displayName);

            //
            // Alpha Texture
            //
            using (new EditorGUILayout.VerticalScope("box"))
            {
                EditorGUI.BeginChangeCheck();

                var origFontStyle = EditorStyles.label.fontStyle;
                EditorStyles.label.fontStyle = FontStyle.Bold;
                var alphaTexEnabled = EditorGUILayout.Toggle("Enable Alpha Texture", material.IsKeywordEnabled(AlphaTexEnabledKeyword));
                EditorStyles.label.fontStyle = origFontStyle;

                if (EditorGUI.EndChangeCheck())
                {
                    if (alphaTexEnabled)
                        material.EnableKeyword(AlphaTexEnabledKeyword);
                    else
                        material.DisableKeyword(AlphaTexEnabledKeyword);
                }
                if (alphaTexEnabled)
                {
                    var alphaTexProp = properties.First(x => x.name == "_AlphaTex");
                    materialEditor.ShaderProperty(alphaTexProp, alphaTexProp.displayName);
                }
            }
        }
    }
}

f:id:enrike3:20180607000059p:plain

なんかテクスチャ設定するところが細長くなってるのが気になるけど、まあ普段使いには支障ないレベルでしょう。