C#
Unity
UnityDay 10

[Unity]CustomShaderGUIによるBlend Mode指定

本文
この記事は、Unity Advent Calendar 2017 の10日目の記事になります。
前日の記事は @lycoris102 さんの [Unity] Editor拡張でInspectorの"戻る"を実現する です。gifを用いた非常に丁寧な投稿でした。

本内容ですが、以前にShaderのCullingやZTest等のMaterialPorpertyをEnumにて指定できるようにしましたが、
コメントにてMaterialのEditor拡張を行うCustomShaderGUIを教えて頂きました。

大分期間が空いてしまいましたが、今回それを用いてBlend Modeのプリセットを指定できるよう拡張しました。
サンプルとしてSpriteRenderer用のシンプルなシェーダーを作ります。

プリセットのSprite-Defaultシェーダーは加算ブレンドがない為、
Particle/Additive等を代用したり自前で用意したりする必要がありますが
もうこの際シンプルなシェーダーはコレ一つで足りるようにします。

ShaderBasic.shader
// 標準スプライトシェーダー
Shader "Custom/SpriteBasic" {
    Properties {
        _MainTex ("Base (RGB), Alpha (A)", 2D) = "white" {}
        [Enum(UnityEngine.Rendering.CullMode)]
        _Cull("Cull", Float) = 0        // Off
        [Enum(UnityEngine.Rendering.CompareFunction)]
        _ZTest("Z Test", Float) = 4     // LEqual

        [Enum(UnityEngine.Rendering.BlendMode)]
        _SrcBlend("Src Factor", Float) = 5  // SrcAlpha
        [Enum(UnityEngine.Rendering.BlendMode)]
        _DstBlend("Dst Factor", Float) = 10 // OneMinusSrcAlpha

        [HideInInspector] _Mode("__mode", Float) = 0.0  // ブレンドモードキャッシュ
    }

    SubShader {
        Tags {
            "Queue" = "Transparent"
            "IgnoreProjector" = "True"
            "RenderType" = "Transparent"
            "PreviewType" = "Plane"
            "CanUseSpriteAtlas"="True"
        }

        Cull [_Cull] ZTest [_ZTest] ZWrite Off
        Blend[_SrcBlend][_DstBlend]

        Pass {
            CGPROGRAM
            struct appdata_t {
                float4 vertex : POSITION;
                half2 texcoord : TEXCOORD0;
                fixed4 color : COLOR;
            };

            struct v2f {
                float4 pos : SV_POSITION;
                half2 uv : TEXCOORD0;
                fixed4 color : COLOR;
            };

            sampler2D _MainTex;

            v2f vert(appdata_t v) {
                v2f o;
                o.pos = mul(UNITY_MATRIX_VP, mul(unity_ObjectToWorld, float4(v.vertex.xyz, 1.0)));
                o.uv = v.texcoord;
                o.color = v.color;

#ifdef PIXELSNAP_ON
                o.pos = UnityPixelSnap(o.pos);
#endif
                return o;
            }

            fixed4 frag(v2f i) : SV_Target {
                return tex2D(_MainTex, i.uv) * i.color;
            }

            #pragma vertex vert
            #pragma fragment frag
            #pragma fragmentoption ARB_precision_hint_fastest
            ENDCG
        }
    }

    // ShaderGUI指定
    CustomEditor "SpriteBasicShaderGUI"
}
SpriteBasicShaderGUI.cs
using UnityEngine;
using UnityEditor;
using System;


/// <summary>
/// 標準スプライトシェーダー
/// </summary>
public sealed class SpriteBasicShaderGUI : ShaderGUI {
    /// <summary>
    /// ブレンド方法
    /// </summary>
    public enum BlendMode {
        Opaque,                 // 不透明
        Transparent,            // 半透明
        Additive,               // 加算
        AdditiveTransparent,    // 加算半透明
    }

    private MaterialProperty blendProp, cullProp, ztestProp, snapProp;

    /// <summary>
    /// Inspector表示
    /// </summary>
    public override void OnGUI(MaterialEditor materialEditor, MaterialProperty[] props) {
        this.blendProp = ShaderGUI.FindProperty("_BlendMode", props);
        this.cullProp = ShaderGUI.FindProperty("_Cull", props);
        this.ztestProp = ShaderGUI.FindProperty("_ZTest", props);
        this.snapProp = ShaderGUI.FindProperty("_PixelSnap", props);

        materialEditor.ShaderProperty(this.cullProp, "Culling");
        materialEditor.ShaderProperty(this.ztestProp, "Z Test");

        BlendMode mode = (BlendMode)this.blendProp.floatValue;
        EditorGUI.BeginChangeCheck();
        mode = (BlendMode)EditorGUILayout.Popup("Blend Mode", (int)mode, Enum.GetNames(typeof(BlendMode)));
        if (EditorGUI.EndChangeCheck()) {
            this.blendProp.floatValue = (float)mode;
            foreach (UnityEngine.Object obj in this.blendProp.targets)
                this.SetupBlendMode(obj as Material, mode);
        }

        materialEditor.ShaderProperty(this.snapProp, "Pixel Snap");
        materialEditor.RenderQueueField();
        //materialEditor.EnableInstancingField();   // GPU Instancing未対応
    }

    /// <summary>
    /// Shader切り替えコールバック
    /// </summary>
    public override void AssignNewShaderToMaterial(Material material, Shader oldShader, Shader newShader) {
        base.AssignNewShaderToMaterial(material, oldShader, newShader);


        // MaterialのShader切り替え時にBlend指定が変更されてしまうので再設定
        this.SetupBlendMode(material, (BlendMode)material.GetFloat("_BlendMode"));
    }

    /// <summary>
    /// ブレンド種類設定
    /// </summary>
    private void SetupBlendMode(Material material, BlendMode blendMode) {
        switch (blendMode) {
            case BlendMode.Opaque:
                material.SetFloat("_SrcBlend", (float)UnityEngine.Rendering.BlendMode.One);
                material.SetFloat("_DstBlend", (float)UnityEngine.Rendering.BlendMode.Zero);
                break;
            case BlendMode.Transparent:
                material.SetFloat("_SrcBlend", (float)UnityEngine.Rendering.BlendMode.SrcAlpha);
                material.SetFloat("_DstBlend", (float)UnityEngine.Rendering.BlendMode.OneMinusSrcAlpha);
                break;
            case BlendMode.Additive:
                material.SetFloat("_SrcBlend", (float)UnityEngine.Rendering.BlendMode.One);
                material.SetFloat("_DstBlend", (float)UnityEngine.Rendering.BlendMode.One);
                break;
            case BlendMode.AdditiveTransparent:
                material.SetFloat("_SrcBlend", (float)UnityEngine.Rendering.BlendMode.SrcAlpha);
                material.SetFloat("_DstBlend", (float)UnityEngine.Rendering.BlendMode.One);
                break;
        }
    }
}

これで下記画像のように半透明・加算のマテリアル等を用意すればこのShaderひとつで対応できます。
SpriteMaterial.PNG

今回Sprite用のマテリアルなのでMainTexの表記はInspector上に表示しないようにしています。
Tintカラーもやや蛇足な処理なので今回は無視しています。

GPU Instancingを対応したい場合はビルトインシェーダーのUnitySprite.cgincを参考にすればよいでしょう。
※ビルトインシェーダーはMITライセンスが明記されていますのでその旨ご注意ください。


次の記事は @Kan_Kikuchi さんによる 日本語から変数や関数名を生成するエディタ拡張になります!