ShaderTips

シェーダーTips

主にUnityシェーダーについての記事を書いています。

【HDR】【Emissive】トーンマッピングでオブジェクトを発光させる

発光表現を行うには?

下の画像のように光源をシェーダーで実装したいと思った時にフラグメントシェーダーで出力するRGBの値は、一番明るくしても白色(RGB = 1,1,1)なので、強い光を表現をすることができません。

このようなRGBAのそれぞれのチャンネルで0~255の範囲で正規化して、0.0~1.0の範囲の色を表現する8bit固定小数精度のことをLDR(Low Dynamic Range)と言います。

https://docs.unity3d.com/ja/2018.4/uploads/Main/StandardShaderEmissiveBakedEffect.jpg

これ以上明るくしたい場合はLDRより大きな範囲の値を使わなければなりません。 そこで登場するのがHDR(High Dynamic Range)です。

HDRはRGBAで浮動小数点精度の値を持ち、ハードよって大きさは異なりますが、16bit浮動小数ならば~65504までの範囲を表現することができます。

HDRはカメラコンポーネントの設定を使用して有効化することができます。

https://docs.unity3d.com/ja/2018.4/uploads/Main/Camera-HDR.svg

Emissionシェーダー

オブジェクトを発行させるシェーダーを実装します。

Shader "Custom/Emission" 
{
    Properties 
    {
        _MainColor("Color", Color) = (1,1,1,1)       
        _EmissionTex ("Emission Texture", 2D) = "white" {}
        [HDR] _EmissionColor ("Emission Color", Color) = (0,0,0)    
    }
    SubShader 
    {
        Pass 
        {
            CGPROGRAM
            #pragma vertex vert_img
            #pragma fragment frag

            #include "UnityCG.cginc"

            float4 _MainColor;
            float4 _EmissionColor;      
            sampler2D _EmissionTex;  

            fixed4 frag(v2f_img i) : SV_Target 
            {
                // albedoにEmissionの色を足している
                return _MainColor + tex2D(_EmissionTex,i.uv) * _EmissionColor;  
            }
            ENDCG
        }
    }
}
[HDR] _EmissionColor ("Emission Color", Color) = (0,0,0)    

シェーダープロパティに[HDR]と指定するとマテリアルからカラーをHDRで設定できます。

f:id:Ny_Program:20210708225617p:plain

 // albedoにEmissionの色を加算
 return _MainColor + tex2D(_EmissionTex,i.uv) * _EmissionColor;  

明るさを増やしたいので加算しています。

プロパティを以下のように設定しているので、フラグメントシェーダーで1以上を出力していることになります。 f:id:Ny_Program:20210709090701p:plain

描画結果はこのようになりました。

フラグメントシェーダーで1以上を出力しても画面はLDRなので、上限の白色になっています。

これを白飛びと言います。

トーンマッピング

トーンマッピングとはポストエフェクトの一種で、HDRの範囲からLDRの範囲に良い感じに圧縮して、白飛びを防ぐ技術で、いくつかの種類があります。

Neutral

Neutralは色相や彩度への変化を最小限に抑えたトーンマッピングです。

https://cdn-ak.f.st-hatena.com/images/fotolife/B/Brave345/20200605/20200605142946.jpg

【Unity】トーンマッピングで光らせ方を変える - 武0武/blogより引用

ACES

ACESはコントラストが強めです。 こちらは明るい色は白色になっていきます。

https://cdn-ak.f.st-hatena.com/images/fotolife/B/Brave345/20200605/20200605143011.jpg

【Unity】トーンマッピングで光らせ方を変える - 武0武/blogより引用

ACESを実装

今回はACESを実装します。 公式は以下のようになっており、HDRの値がグラフのようにSDRに変換されます。

{ \displaystyle
a = 2.51 \\
b = 0.03 \\
c = 2.43 \\
d = 0.59 \\
e = 0.14 \\
f(x) = \mathrm{saturate}\Bigl( \frac{x(ax+b)}{x(cx+d)+e} \Bigl)
}

https://cdn-ak.f.st-hatena.com/images/fotolife/h/hikita12312/20170827/20170827000506.png

この式をシェーダーで書きます。

Shader "Custom/NeutralTonemap"
{
    Properties
    {
        _MainTex ("Texture", 2D) = "white" {}
    }
    SubShader
    {
        // No culling or depth
        Cull Off ZWrite Off ZTest Always

        Pass
        {
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag

            #include "UnityCG.cginc"

            struct appdata
            {
                float4 vertex : POSITION;
                float2 uv : TEXCOORD0;
            };

            struct v2f
            {
                float2 uv : TEXCOORD0;
                float4 vertex : SV_POSITION;
            };

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

            sampler2D _MainTex;

            fixed3 ACESToneMap(fixed3 color)
            {
                const float a = 2.51;
                const float b = 0.03;
                const float c = 2.43;
                const float d = 0.59;
                const float e = 0.14;
                return saturate(
                    color * (a * color + b) /
                    (color * (c * color + d) + e) );
            }

            fixed4 frag (v2f i) : SV_Target
            {
                fixed4 col = tex2D(_MainTex, i.uv);
                return fixed4(ACESToneMap(col.rgb),1);
            }
            ENDCG
        }
    }
}

描画結果はこちらになりました。 白飛びしていた部分が軽減されたのがわかると思います。

最後に

トーンマッピングの描画結果がイマイチだったのでBloomで試してみました。 Bloomは明度が強い箇所をブラーをかけて合成しているのでHDRで出力した部分を綺麗に見せることができます。