【Unity】Unityにおけるリニアワークフローの挙動を簡単に確認してみる

リニアワークフローについて、以前簡単にまとめました。

light11.hatenadiary.com

今回はUnityにおけるリニアワークフロー周りの挙動を調べてみました。

色空間の変更

Unityでは以下のようにして色空間を変更できます。

  • Edit > Project Settings > Player
  • Other Settings > Rendering > Color Space

これをGammmaにすればガンマ空間に、Linearにすればリニア空間に設定できます。

検証の目的とフロー

今回の目的は、入力した色やテクスチャなどがシェーダ内でどのように扱われているかを知ることです。

そのため、シェーダ内の色の値を視覚化できるように検証用のシーンを作りました。

まず、ワールドx座標が0、2.2、5の位置に板ポリを置いています(yとzは0です)。

次に原点にSphereを置きます。
このSphereのシェーダ内で、入力された色の値をx座標に変換します。

具体的には、0.5という値を様々な方法(テクスチャとかカラーとか)で入力し、その値を10倍したもの値をx座標としています。

f:id:halya_11:20180210162128p:plain

これを使って、内部的な値を調べていきます。

色の入力を検証

まずはマテリアルで入力した色がシェーダ内部でどのように扱われているかを調べます。
RGB(0.5, 0, 0)の入力値に対して、R値 * 10をx座標として出力します。

ガンマの場合

f:id:halya_11:20180210162900p:plain:w500

リニアの場合

f:id:halya_11:20180210163026p:plain:w500

ガンマの場合は0.5、リニアの場合は約0.22となっていることがわかります。

テクスチャを検証

テクスチャも同様の手順です。
RGB(0.5, 0, 0)の単色のテクスチャを用意し、頂点シェーダでサンプリングした値を用います。

ガンマの場合

f:id:halya_11:20180210165115p:plain:w500

リニアの場合

f:id:halya_11:20180210165126p:plain:w500

先ほどと同様の結果となりました。

ただし、テクスチャに関してはリニア空間で作成したデータをそのまま読み込みたいケースもあるはずです。
このようなケースでは、リニア空間設定にすることで入力値をデガンマされてしまっては問題が起こります。

このような場合には、Texture ImporterのsRGBのチェックを外すと、テクスチャデータをリニアの色空間上のものとして取り扱えるようです。
ちなみにTexture Importer のこの辺りはUnity5.5から大きく変わったようなので、それ以前のバージョンを使っている方は手順が異なると思います。

ライトのIntensityを検証

最後に、ライトのIntensityの値を0.5にして同様に検証します。

ライトのIntensityの値は、ディレクショナルライトの色をRGB(1,1,1)にした上で、シェーダで _LightColor0.r で取得しています。
そのため上記2つの検証時とライティングが変わっていますが、その点は気にしないでください。

ガンマの場合

f:id:halya_11:20180210170630p:plain:w500

リニアの場合

f:id:halya_11:20180210170716p:plain:w500

ライトの場合もリニアの場合はデガンマされることがわかりました。

そして下の記事によると、この仕様は特殊なものらしい。

technorgb.blogspot.jp

考えてみれば確かにそうかも。
輝度が1の白いライトを設定したかったらRGB(1,1,1)にしてIntensityを1にしたいところだけどそうはならないということなので。

検証用シェーダ

一応、検証に使ったシェーダも貼っときます。

色の検証用

Shader "Test_LWF"
{
    Properties
    {
        _Color("Color", Color) = (1,0,0,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 _Color;
            
            v2f vert (appdata v)
            {
                v2f o;
                v.vertex.x += _Color.r * 10;
                o.vertex = UnityObjectToClipPos(v.vertex);
                return o;
            }
            
            fixed4 frag (v2f i) : SV_Target
            {
                return half4(1, 0, 0, 1);
            }
            ENDCG
        }
    }
}

テクスチャの検証用

Shader "Test_LWF"
{
    Properties
    {
        _MainTex ("Base (RGB)", 2D) = "white" {}
    }
    SubShader
    {
        Tags { "RenderType"="Opaque" }

        Pass
        {
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag

            #include "UnityCG.cginc"

            struct appdata
            {
                float4 vertex : POSITION;
                float4 texcoord : TEXCOORD;
            };

            struct v2f
            {
                float4 vertex : SV_POSITION;
            };
            
            sampler2D _MainTex;
            float4 _MainTex_ST;
            
            v2f vert (appdata v)
            {
                v2f o;
                float4 tex = tex2Dlod (_MainTex, float4(v.texcoord.xy, 0.0, 0.0));
                v.vertex.x += tex.r * 10;
                o.vertex = UnityObjectToClipPos(v.vertex);
                return o;
            }
            
            fixed4 frag (v2f i) : SV_Target
            {
                return half4(1, 0, 0, 1);
            }
            ENDCG
        }
    }
}

ライトの検証用

Shader "Test_LWF"
{
    SubShader
    {
        Tags { "RenderType"="Opaque" }

        Pass
        {
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag

            #include "UnityCG.cginc"

            struct appdata
            {
                float4 vertex : POSITION;
                float4 texcoord : TEXCOORD;
            };

            struct v2f
            {
                float4 vertex : SV_POSITION;
            };
            
            float4 _LightColor0;
            
            v2f vert (appdata v)
            {
                v2f o;
                v.vertex.x += _LightColor0.r * 10;
                o.vertex = UnityObjectToClipPos(v.vertex);
                return o;
            }
            
            fixed4 frag (v2f i) : SV_Target
            {
                return half4(1, 0, 0, 1);
            }
            ENDCG
        }
    }
}

参考

technorgb.blogspot.jp

www.slideshare.net