Unity色反転シェーダー

Unity色反転シェーダー

[Unity uGUI] 色反転シェーダー(Invert Color Shader)。
URPで色が白浮きする理由、リニア空間とガンマ空間について解説。

関連ページ
たぶんシェーダーで作るのが簡単な部類の色反転シェーダー。写真業界ではネガポジ反転とか言ったりするらしいです。
ShaderGraphだとInvertColorという名前のノードがデフォルトで搭載されています。
単純に反転するだけだとやや簡単なので、BlendRateという外出しのプロパティを用意して、その値に応じて反転率を調整できるようにしました。

※コードが古くなっていたのでURP用に記事を改修しました(2026/03/20)。
URP特有の問題となるリニア空間とガンマ空間の問題についても解説を追記しました。

色反転シェーダー概要

マテリアルの
BlendRate
のスライダーで、反転率を0から1までのfloat値で指定できます。


今回URP用にコードを改修したのですが、不思議だったのはビルトインシェーダー(旧Unity標準シェーダー)に比べて、画像が白浮きすることでした。
左はビルトインで作ったシェーダー、右はURPで作ったシェーダーです。


最終的にはURP環境でも白浮きを修正できたので、それも含めたコードを紹介します。

コード全文

以下色反転シェーダーの全文です。uGUI Imageへの反映方法は こちらの記事 参照してください。
基礎知識として次の記事にも目を通すと分かり易いです。 p.57 : シェーダー基礎、最もシンプルなuGUI用 URP Shader
Shader "UI/InvertColor"
{
    
//Propertiesに_BlendRateを指定してInspectorからアクセスできるようにする

    Properties
    {
        _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

            #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl"
            
//後述のLinearToSRGB、SRGBToLinearで必要なライブラリ

            #include "Packages/com.unity.render-pipelines.core/ShaderLibrary/Color.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 _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;

                
//ピクセル色をリニア空間ルールから一旦ガンマ空間ルールへ変換

                half3 sRGB = LinearToSRGB(col.rgb);
                
//ガンマ空間ルール上で色を反転

                sRGB = abs(_BlendRate - sRGB);
                
//色をガンマ空間ルールからリニア空間ルールに戻す

                col.rgb = SRGBToLinear(sRGB);

                return col;
            }
            ENDHLSL
        }
    }
}

コード解説

本ピクセルシェーダー内ではリニア空間、ガンマ空間の変換処理もやっていますが、一旦それは置いて最初に色反転の仕組みを解説します。

色反転の仕組みについて

まずshaderで色は0~255の値ではなく0~1のfloat値で管理しています。
赤なら(1, 0, 0)、青なら(0, 0, 1)、紫なら(1, 0, 1)といった具合です。なのでそれぞれのピクセルのRGBを1から引けば完全な色反転になります。
赤の反転色は(0, 1, 1)で水色に、青の反転色は(1, 1, 0)で黄色になります。
BlendRate
を使用しないシンプルな色反転の場合、ピクセルシェーダーのコードは次の通りです。
                col.rgb = 1 - col.rgb;

今回のシェーダーの場合は1という固定値ではなく、ユーザーが指定する
BlendRate
を参照します。
ただ1より低い値からRGBの値を引くと、結果がマイナスになってしまう可能性があります。
なので
abs
使う事によって絶対値にして、マイナスの値は出力しないようにしています。
                col.rgb = abs(_BlendRate - col.rgb);

リニア空間とガンマ空間について

実際のピクセルシェーダーの中身では、色反転処理以外にもリニア空間やらガンマ空間といったややこしい事をやっています。
            half4 frag (Varyings input) : SV_Target
            {
                half4 col = SAMPLE_TEXTURE2D(_MainTex, sampler_MainTex, input.uv);
                col *= input.color;

                
//ピクセル色をリニア空間ルールから一旦ガンマ空間ルールへ変換

                half3 sRGB = LinearToSRGB(col.rgb);
                
//ガンマ空間ルール上で色を反転

                sRGB = abs(_BlendRate - sRGB);
                
//色をガンマ空間ルールからリニア空間ルールに戻す

                col.rgb = SRGBToLinear(sRGB);

                return col;
            }
簡単に言うと、リニア空間は数学的に正しい明るさの設定、ガンマ空間は人間の目には正しく見える明るさの設定です。
リニア空間(Linear Space)
: 数値が「物理的な光の強さ」に正比例して並んでいる世界。
ガンマ空間(Gamma Space)
: 数値が「人間の目の感じ方」に合わせて歪められて並んでいる世界。

人間は「暗い部分の変化」にとても敏感で、「明るい部分の変化」には鈍感です。
そのため画像データなどは、暗い部分にたくさんの情報(階調)を割り当てて保存しています。これが「ガンマ空間」です。
私たちが普段見ている「中間のグレー」は、ガンマ空間の数値で言うと0.5くらいです


コンピュータ(URPのリニア設定など)は、光の物理的な強さをそのまま数値として計算します。
ガンマ空間で言う0.5は、リニア空間では0.2に相当します。


つまりURPのリニア設定のまま色反転させてしまうと、人間の目にはかなり明るく見えてしまうという事です。
見た目の中間(0.2相当)を反転させた結果(0.8相当)が、本来の半分(0.5)よりずっと明るい値になってしまうのが白浮きが激しくなります。


このピクセルシェーダーでは、一旦リニア空間をガンマ空間に変換し、ガンマ空間ルール上で色反転して、最後にまたリニア空間に戻しています。
                
//ピクセル色をリニア空間ルールから一旦ガンマ空間ルールへ変換

                half3 sRGB = LinearToSRGB(col.rgb);
                
//ガンマ空間ルール上で色を反転

                sRGB = abs(_BlendRate - sRGB);
                
//色をガンマ空間ルールからリニア空間ルールに戻す

                col.rgb = SRGBToLinear(sRGB);

またこの処理で使っている関数
LinearToSRGB
及び
SRGBToLinear
を使うために、上の方で以下のライブラリを読み込んでいます。
            
//後述のLinearToSRGB、SRGBToLinearで必要なライブラリ

            #include "Packages/com.unity.render-pipelines.core/ShaderLibrary/Color.hlsl"
0
0