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

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

[Unity uGUI] グレースケール化シェーダー(Gray Scale Shader)。
画像のモノクロ化、単純平均法とNTSC加重平均法の仕組みの解説。

関連ページ
参考URL
色相反転 シルエット化 に続いて実装が簡単な部類のグレースケール化シェーダーを作りました。
これまでと同じように
BlendRate
を作って変化までの割合を調整できるようにしてあります。
また世のグレースケール化の計算式には
単純平均法
NTSC加重平均法
というのがあるらしく、今回 Variant によってこの2タイプを選べるようにしました。

※コードが古くなっていたのでURP用に記事を改修しました(2026/03/22)。

グレースケール化シェーダー概要

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

コード全文

以下グレースケール化シェーダーの全文です。uGUI Imageへの反映方法は こちらの記事 参照してください。
基礎知識として次の記事にも目を通すと分かり易いです。 p.57 : シェーダー基礎、最もシンプルなuGUI用 URP Shader
Shader "UI/GrayScale"
{
    Properties
    {
        [KeywordEnum(Simple, NTSC)] _GrayType("GrayType", Int) = 1 
//グレースケール化する際の計算タイプ

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

    }

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

        Pass
        {
            HLSLPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            #pragma multi_compile _GRAYTYPE_SIMPLE _GRAYTYPE_NTSC
            #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl"                

            struct Attributes
            {
                half2 uv : TEXCOORD0;
                float4 positionOS : POSITION;
                half4 color : COLOR;
            };

            struct Varyings
            {
                half2 uv : TEXCOORD0;
                float4 positionCS : SV_POSITION;
                half4 color : COLOR;
            };

            
//uGUI Image > Source Imageにアタッチされた画像を参照する

            TEXTURE2D(_MainTex);
            SAMPLER(sampler_MainTex);

            
//SRP Batcherを適用

            CBUFFER_START(UnityPerMaterial)               
                
//プロパティに対応する変数の宣言

                half _GrayType;
                half _BlendRate;
            CBUFFER_END

            Varyings vert (Attributes input)
            {
                Varyings output;
                output.positionCS = TransformObjectToHClip(input.positionOS.xyz);
                output.uv = input.uv;
                output.color = input.color;
                return output;
            }

            half4 frag (Varyings input) : SV_Target
            {
                half4 col = SAMPLE_TEXTURE2D(_MainTex, sampler_MainTex, input.uv);
                col *= input.color;

                #if defined(_GRAYTYPE_SIMPLE)
                    
//単純平均法で完全なグレースケール化した値を取得

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

                    half gray = dot(col.rgb, half3(0.299, 0.587, 0.114));
                    half3 grayCol = half3(gray, gray, gray);
                #endif

                
//_BlendRateを参照し、lerpでオリジナル色と完全なグレースケール化の間の値を設定

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

                return col;
            }
            ENDHLSL
        }
    }
}

プロパティ解説

用意してあるプロパティは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