発光表現を行うには?
下の画像のように光源をシェーダーで実装したいと思った時にフラグメントシェーダーで出力するRGBの値は、一番明るくしても白色(RGB = 1,1,1)なので、強い光を表現をすることができません。
このようなRGBAのそれぞれのチャンネルで0~255の範囲で正規化して、0.0~1.0の範囲の色を表現する8bit固定小数精度のことをLDR(Low Dynamic Range)と言います。
これ以上明るくしたい場合はLDRより大きな範囲の値を使わなければなりません。 そこで登場するのがHDR(High Dynamic Range)です。
HDRはRGBAで浮動小数点精度の値を持ち、ハードよって大きさは異なりますが、16bit浮動小数ならば~65504までの範囲を表現することができます。
HDRはカメラコンポーネントの設定を使用して有効化することができます。
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で設定できます。
// albedoにEmissionの色を加算 return _MainColor + tex2D(_EmissionTex,i.uv) * _EmissionColor;
明るさを増やしたいので加算しています。
プロパティを以下のように設定しているので、フラグメントシェーダーで1以上を出力していることになります。
描画結果はこのようになりました。
フラグメントシェーダーで1以上を出力しても画面はLDRなので、上限の白色になっています。
これを白飛びと言います。
トーンマッピング
トーンマッピングとはポストエフェクトの一種で、HDRの範囲からLDRの範囲に良い感じに圧縮して、白飛びを防ぐ技術で、いくつかの種類があります。
Neutral
Neutralは色相や彩度への変化を最小限に抑えたトーンマッピングです。
ACES
ACESはコントラストが強めです。 こちらは明るい色は白色になっていきます。
ACESを実装
今回はACESを実装します。 公式は以下のようになっており、HDRの値がグラフのようにSDRに変換されます。
この式をシェーダーで書きます。
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で出力した部分を綺麗に見せることができます。