-100p

-10p

+10p

+100p

Unityグレースケール化シェーダー

UnityuGUI用、グレースケール化シェーダー。画像をモノクロ化して灰色に限定。
(Gray Scale Shader for Unity uGUI)

関連ページ
参考URL
色相反転 シルエット化 に続いて実装が簡単な部類のグレースケール化シェーダーを作った。
これまでと同じようにBlendRateを作って変化までの割合を調整できるようにしてある。

また世のグレースケール化の計算式には単純平均法とNTSC加重平均法というのがあるらしく、今回 Variant によってこの2タイプを選べるようにした。

グレースケール化シェーダー、コード全文

プロパティに外出ししてあるBlendRateを弄ることで、徐々にグレースケール化していく。
下がサンプル動画。


下がコード全文。uGUI Imageへの反映方法は こちらの記事 参照。
Shader "UI/GrayScale"
{
    Properties
    {
        [KeywordEnum(Simple, NTSC)] _GrayType("GrayType", Int) = 1 
//グレースケール化する際の計算タイプ

        _BlendRate ("BlendRate", Range(0.0, 1.0)) = 1 
//完全にグレースケール化するまでの割合

    }

    SubShader
    {
        Tags { "Queue" = "Transparent" }
        Cull Off
        ZWrite Off
        Blend SrcAlpha OneMinusSrcAlpha

        Pass
        {
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            #pragma multi_compile _GRAYTYPE_SIMPLE _GRAYTYPE_NTSC

            struct appdata
            {
                fixed2 uv : TEXCOORD0;
                fixed4 vertex : POSITION;
                fixed4 color : COLOR;
            };

            struct v2f
            {
                fixed2 uv : TEXCOORD0;
                fixed4 vertex : SV_POSITION;
                fixed4 color : COLOR;
            };

            sampler2D _MainTex;
            
//プロパティに対応する変数の宣言

            fixed _GrayType;
            fixed _BlendRate;

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

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

                fixed3 grayCol;
#ifdef _GRAYTYPE_SIMPLE
                
//単純平均法で完全なグレースケール化した値を取得

                fixed gray = (col.r + col.g + col.b) / 3;
                grayCol = fixed3(gray, gray, gray);
#elif _GRAYTYPE_NTSC
                
//NTSC加重平均法で完全なグレースケール化した値を取得

                fixed gray = dot(col.rgb, fixed3(0.299, 0.587, 0.114));
                grayCol = fixed3(gray, gray, gray);
#endif
                
//_BlendRateを参照し、lerpでオリジナル色と完全なグレースケール化の間の値を設定

                col.rgb = lerp(col.rgb, grayCol, _BlendRate);

                return col;
            }
            ENDCG
        }
    }
}

プロパティの解説

用意してあるプロパティは2つ。
グレースケール化シェーダーのプロパティ
 GrayType 
: グレースケール化する際の計算タイプ。単純平均法のSimpleとNTSC加重平均法のNTSCが選択できる。
 BlendRate 
: 完全にグレースケール化するまでの割合。0でオリジナルの色、1で完全にグレースケール化した色。

下はBlendRateが1(完全にグレースケール化した状態)で、単純平均とNTSC加重平均法を切り替えた動画。

テストの画像では、単純平均法の方がやや黒っぽく見える。

コードを少し解説

まず前提としてグレーとは何かと言うと、R(赤)G(緑)B(青)が全て一緒の値のことを言う。
(1, 1, 1)が白、(0, 0, 0)が黒なので、(0.7, 0.7, 0.7)であれば白めの灰色、(0.3, 0.3, 0.3)であれば黒めの灰色になる。
グレーのRGBの説明

単純平均法のグレースケール化

単純平均法とは、対象ピクセルのRGBの値を全て足して、それを3で割ったものでそれぞれのRGB値を上書きする処理のことを言う。
コードで言うと次の部分。
#ifdef _GRAYTYPE_SIMPLE
                
//単純平均法で完全なグレースケール化した値を取得

                fixed gray = (col.r + col.g + col.b) / 3;
                grayCol = fixed3(gray, gray, gray);

例えば対象ピクセルの色が真っ赤(1, 0, 0)だと仮定し、それをこの式に掛けると(0.333.., 0.333.., 0.333..)になる。
対象ピクセルの色が緑(0, 1, 0)でも青(0, 0, 1)でも(0.333.., 0.333.., 0.333..)と同じ値になる。
もし紫(1, 0, 1)なら(0.666.., 0.666.., 0.666..)で、白(1, 1, 1)なら変わらず白(1, 1, 1)である。

NTSC加重平均法のグレースケール化

これに対しNTSC加重平均法というのは、より人間の目に自然に見えるように、RGBの修正値に適切な比重を加えた計算式になる。
たしかに美術の鉛筆デッサンなどでは、黄色は薄く、赤は濃く塗った方がより自然に見えると教わった。
機械的に平均化しただけでは返って人間の目には違和感が出てしまうと言うことだと思う。

NTSC加重平均法のコードは次の通り。
#elif _GRAYTYPE_NTSC
                
//NTSC加重平均法で完全なグレースケール化した値を取得

                fixed gray = dot(col.rgb, fixed3(0.299, 0.587, 0.114));
                grayCol = fixed3(gray, gray, gray);
dotはHLSLの関数で、2つのベクトルの内積を算出する関数となる。
自分のように数学を勉強してこなかった人間からすると、ベクトルや内積と言われるとかなり複雑なことをやってるように見える。
しかしここでやってる事はとてもシンプルになる。

まずNTSC加重平均法のfixed3(0.299, 0.587, 0.114)の数式をよく見てみると、すべて足すと1になるのが分かる。
0.299 + 0.587 + 0.114 = 1
そしてdotの計算式をもっと分かりやすく、その代わり長く書くと次のようなものになる。
#elif _GRAYTYPE_NTSC
                
//NTSC加重平均法で完全なグレースケール化した値を取得

                
//fixed gray = dot(col.rgb, fixed3(0.299, 0.587, 0.114));

                fixed r = col.r * 0.299;
                fixed g = col.g * 0.587;
                fixed b = col.b * 0.114;
                fixed gray = r + g + b;
                grayCol = fixed3(gray, gray, gray);

0.299, 0.587, 0.114は全て足すと1なので、仮に対象ピクセルの色が最大値の白(1, 1, 1)であっても、grayが1を超えることはない。
r = 1 * 0.299 = 0.299
g = 1 * 0.587 = 0.587
b = 1 * 0.114 = 0.114
gray = 0.299 + 0.587 + 0.114 = 1
grayCol = (1, 1, 1)

仮に対象ピクセルの色が赤(1, 0, 0)の場合は次の通りで、最終的にグレースケール化した値は(0.299, 0.299, 0.299)になる。
r = 1 * 0.299 = 0.299
g = 0 * 0.587 = 0
b = 0 * 0.114 = 0
gray = 0.299 + 0 + 0 = 0.299
grayCol = (0.299, 0.299, 0.299)
同じ要領で計算し、緑(0, 1, 0)の場合は(0.587, 0.587, 0.587)、青(0, 0, 1)の場合は(0.114, 0.114, 0.114)となる事が分かる。

これらを纏めると、つまりNTSC加重平均法というのは、赤と青は黒めの灰色と判定し、逆に緑は明るめの灰色と判定してブレンドする数式となる。
0
0

-100p

-10p

+10p

+100p