【Unity】【シェーダ】カラーマスクの使い方とインスペクタ拡張

レンダーターゲットに書き込むチャンネルを制御するカラーマスクの使い方を紹介します。
またマテリアルのインスペクタからカラーマスクを指定できるようにしてみます。

カラーマスク?

カラーマスクは、フラグメントシェーダで計算し終えた最終的な色を、
レンダーターゲットのどのチャンネルに書き込むか、を指定するものです。

たとえば最終的な色が白のとき、カラーマスクをRだけにすると赤色が書き込まれます。

全チャンネルに書き込む

全チャンネルに書き込むにはColorMask RGBA と書きます。
ただしこれはデフォルトの挙動なので書かなくても挙動は同じです。

Shader "ColorMask"
{
    SubShader
    {
        Tags { "RenderType"="Transparent" "Queue"="Transparent" }

        Pass
        {
            // カラーマスク(全チャンネル書き込む)
            ColorMask RGBA

            CGPROGRAM
           #pragma vertex vert
           #pragma fragment frag
            
           #include "UnityCG.cginc"

            struct appdata
            {
                float4 vertex : POSITION;
            };

            struct v2f
            {
                float4 vertex : SV_POSITION;
            };
            
            v2f vert (appdata v)
            {
                v2f o;
                o.vertex = UnityObjectToClipPos(v.vertex);
                return o;
            }
            
            fixed4 frag (v2f i) : SV_Target
            {
                return 1;
            }
            ENDCG
        }
    }
}

結果はこんな感じ。普通です。

f:id:halya_11:20180911123238p:plain

Rチャンネルだけ書き込む

Rチャンネルにだけ書き込む場合は次のようにします。

Shader "ColorMask"
{
    SubShader
    {
        Tags { "RenderType"="Transparent" "Queue"="Transparent" }

        Pass
        {
            // ColorMaskだけ変更した
            ColorMask R

            CGPROGRAM
           #pragma vertex vert
           #pragma fragment frag
            
           #include "UnityCG.cginc"

            struct appdata
            {
                float4 vertex : POSITION;
            };

            struct v2f
            {
                float4 vertex : SV_POSITION;
            };
            
            v2f vert (appdata v)
            {
                v2f o;
                o.vertex = UnityObjectToClipPos(v.vertex);
                return o;
            }
            
            fixed4 frag (v2f i) : SV_Target
            {
                return 1;
            }
            ENDCG
        }
    }
}

結果はこの通り。赤いです。

f:id:halya_11:20180911123341p:plain

プロパティでマスクを指定する

次にプロパティでマスクを指定してみます。
RGBAがそれぞれ3,2,1,0ビット目を表すビットマスクを指定します。

後述しますが、人間がビット演算するという何ともイケてない方法です。

Shader "ColorMask"
{
    Properties
    {
        // プロパティを定義
        // RGBAがそれぞれ3,2,1,0ビット目を表す
        _ColorMask ("Color Mask", Float) = 15
    }
    SubShader
    {
        Tags { "RenderType"="Transparent" "Queue"="Transparent" }

        Pass
        {
            // プロパティの値をColorMaskとして適用
            ColorMask [_ColorMask]

            CGPROGRAM
           #pragma vertex vert
           #pragma fragment frag
            
           #include "UnityCG.cginc"

            struct appdata
            {
                float4 vertex : POSITION;
            };

            struct v2f
            {
                float4 vertex : SV_POSITION;
            };
            
            v2f vert (appdata v)
            {
                v2f o;
                o.vertex = UnityObjectToClipPos(v.vertex);
                return o;
            }
            
            fixed4 frag (v2f i) : SV_Target
            {
                return 1;
            }
            ENDCG
        }
    }
}

これはこれで動きますが、マテリアルのインスペクタにはfloatのプロパティが表示されてしまいます。
カラーマスクを指定しようとしてfloatを入力させられたら混乱すること請け合いです。

インスペクタからマスクを指定できるようにする

というわけでインスペクタからマスクを指定できるようにします。
まずShaderGUIを次のように定義します。

using UnityEngine;
using UnityEditor;

public class ColorMaskGUI : ShaderGUI {

    /// <summary>
    /// マスク用カラーチャンネル
    /// </summary>
    [System.Flags]
    private enum ColorChannel
    {
        R   = 1 << 3,
        G   = 1 << 2,
        B   = 1 << 1,
        A   = 1 << 0,
    }

    public override void OnGUI(MaterialEditor materialEditor, MaterialProperty[] properties)
    {
        var colorMaskProp           = FindProperty("_ColorMask", properties);
        colorMaskProp.floatValue    = (int)(ColorChannel)EditorGUILayout.EnumFlagsField(colorMaskProp.displayName, (ColorChannel)colorMaskProp.floatValue);
        // EditorGUILayout.EnumFlagsFieldの結果として-1が返ってきた場合は「Everything」ということなのですべてのフラグを立てる
        if (colorMaskProp.floatValue < 0) {
            colorMaskProp.floatValue    = (int)(ColorChannel.R | ColorChannel.G | ColorChannel.B | ColorChannel.A);
        }
    }
}

これをシェーダのCustomEditorとして指定します。

CustomEditor "ColorMaskGUI"

インスペクタは次のようになります。

f:id:halya_11:20180911131659p:plain

マスクを指定できるようになりました。