【Unity】【シェーダ】色空間設定がガンマでもリニアでも同じ計算結果を示すシェーダを書く方法

Unityのシェーダで色空間設定がガンマでもリニアでも同じ計算結果を示すシェーダを書く方法についてまとめました。

Unity2019.2.18

やりたいこと

sRGB色空間では人間の目に50%のグレーとして見える色がRGB=(0.5, 0.5, 0.5)として表されます。
これに対してリニア色空間では人間の目に50%のグレーとして見える色がRGB=約(0.22, 0.22, 0.22)として表されます。
このあたりのリニアワークフローの基礎知識は以下の記事にまとめていますので、必要に応じて参照してください。

light11.hatenadiary.com

さてこの違いによりリニアワークフローではより物理的に正しいレンダリング結果が得られるわけですが、
例えば「sRGBのカラーテクスチャをプロシージャルに生成するツール」を作ることを考えると、
Unityの色空間の設定にかかわらず同じ計算結果を得られないとツールとして機能しません。

本記事ではこのようなケースのために、色空間設定が違っても同じ計算結果を示すシェーダを作ります。

シェーダ

やるべきことはシンプルで、色空間に合わせてフラグメントシェーダの最初と最後で変換を掛けるだけです。

今回は色空間がリニア設定だった場合にフラグメントシェーダに渡ってきた値をsRGB空間に変換し、
またフラグメントシェーダから出力する際にリニア空間の値に変換する実装にしました。

Shader "Example"
{
    Properties
    {
        _ExampleColor("Example Color", Color) = (1,1,1,1)
    }
    SubShader
    {
        Tags { "RenderType"="Opaque" }

        Pass
        {
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag

            #include "UnityCG.cginc"

            struct appdata
            {
                float4 vertex : POSITION;
            };

            struct v2f
            {
                float4 vertex : SV_POSITION;
            };

            float4 _ExampleColor;
            
            #define FLT_EPSILON 1.192092896e-07

            float3 PositivePow(float3 base, float3 power)
            {
                return pow(max(abs(base), float3(FLT_EPSILON, FLT_EPSILON, FLT_EPSILON)), power);
            }
            
            half3 LinearToSRGB(half3 c)
            {
                half3 sRGBLo = c * 12.92;
                half3 sRGBHi = (PositivePow(c, half3(1.0 / 2.4, 1.0 / 2.4, 1.0 / 2.4)) * 1.055) - 0.055;
                half3 sRGB = (c <= 0.0031308) ? sRGBLo : sRGBHi;
                return sRGB;
            }

            half3 SRGBToLinear(half3 c)
            {
                half3 linearRGBLo = c / 12.92;
                half3 linearRGBHi = PositivePow((c + 0.055) / 1.055, half3(2.4, 2.4, 2.4));
                half3 linearRGB = (c <= 0.04045) ? linearRGBLo : linearRGBHi;
                return linearRGB;
            }

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

            fixed4 frag (v2f i) : SV_Target
            {
#if !UNITY_COLORSPACE_GAMMA
                // 入力値がリニアの値で渡ってくるのでSRGBに変換
                _ExampleColor.rgb = LinearToSRGB(_ExampleColor.rgb);
#endif
                float4 color = _ExampleColor;
                // 何かしらの計算
                color.rgb = color.rgb + color.rgb;
                
#if !UNITY_COLORSPACE_GAMMA
                // SRGBで計算した値をリニアで返す
                color.rgb = SRGBToLinear(color.rgb);
#endif
                return color;
            }
            ENDCG
        }
    }
}

Unityはワークフロー設定がガンマになっているとUNITY_COLORSPACE_GAMMAが定義されます。
今回はこれを利用して、リニア設定の場合にsRGBに変換してから計算しています。

色空間の変換関数については以下の記事を参照してください。

light11.hatenadiary.com

関連

light11.hatenadiary.com

light11.hatenadiary.com