周辺減光の実装

周辺減光

カメラで撮った絵の画面中心から離れるほど暗く見えるような効果を周辺減光と言います。
最近のゲームでもよく見かける効果です。
アーティスティックな効果の他にも,グレア効果など スクリーンスペースのエフェクトにおいて,画面端の境界によるアーティファクトを軽減する役割があります。

周辺減光を及ぼす原因としては,大きく分けて,「口径食」と「コサイン四乗則」があるようです。

https://en.wikipedia.org/wiki/Vignetting
https://www.ccs-inc.co.jp/guide/column/light_color/vol08.html

口径食

レンズの経や絞りによって,光が遮られることによって周辺部が暗くなる効果を指します。
さらにMechanical VignettingとOptical Vignettingに分類できるそうですが,イマイチ使い分け方がよくわかりません。
結局のところレンズ系や物理的な制約によって周辺が暗くなる効果ですので,適当に画面中心から遠ざかるほど暗くなるようなシェーダーを書いてみます。

const float2 d = abs(uv - float2(0.5f, 0.5f)) * float2(2.0f*ASPECT_RATIO, 2.0f);
const float r2 = dot(d, d);
float vignettingFactor = 0.0f;
// 口径食の効果
vignettingFactor += pow(min(1.0f, r2 / VIGNETTING_MECHANICAL_RADIUS2), VIGNETTING_MECHANICAL_INV_SMOOTHNESS) * VIGNETTING_MECHANICAL_SCALE;
// 合成
outputColor.rgb *= lerp(float3(1.0f, 1.0f, 1.0f), VIGNETTING_COLOR, saturate(vignettingFactor));

UnityのPostProcessingStackの周辺減光のコードを若干参考にしました。 これで再現した口径食はこんな感じです。

f:id:hikita12312:20180120165716p:plain:w300
周辺減光OFF
f:id:hikita12312:20180120165448p:plain:w300
口径食ON
f:id:hikita12312:20180120165448p:plain:w300
ImageCircle 小さめ
f:id:hikita12312:20180120165559p:plain:w300
ImageCircle 大きめ

コサイン四乗則

Natural Vignettingと呼ばれるものがこちらのコサイン四乗則になります。
光軸とレンズへの入射光のなす角{\theta}のときに以下のような性質を満たすと考えます

f:id:hikita12312:20180120162544p:plain:w240
  • 斜めに入射する光は単位面積あたりの強度が{\cos{\theta}}倍で小さくなる
  • 斜めに入射する光は,直進する光と比べて光源からの距離が{\cos{\theta}}倍長い。光源から逆二乗で単位面積あたりの強度が下がるので{\cos^2{\theta}}
  • 世の中の光源は斜めから見ると{\cos{\theta}}倍で減衰するものが多い

これらの要素を乗算すると,結局{\cos^4{\theta}}で画面端の色が減衰すると考えることが出来ます。

{\cos{\theta}}の計算を詳しく考えてみます。 0~1空間でのUV空間において,アスペクト比を考慮した画面中心からの距離を{r}とします。ただし縦方向画面端で1.0となるように正規化しています。 (口径食のシェーダーで書いたdot(d,d)が{r^2}です)
このときに,イメージセンサーまでの距離(=焦点距離)を{f},さらにイメージセンサーの縦サイズを{h}とすると,{\cos{\theta}}

{ \displaystyle
\begin{eqnarray}
\cos{\theta} &=& \frac{f}{\sqrt{r^2h^2 + f^2}} \\
&=& \frac{1}{\sqrt{ (h/f)^2 r^2 + 1}}
\end{eqnarray}
}

となります。{(h/f)^2}がコサイン四乗則による減光度合いを決めるパラメータとなります。 これを先程のシェーダーに組み込んだ結果です。

const float2 d = abs(uv - float2(0.5f, 0.5f)) * float2(2.0f*ASPECT_RATIO, 2.0f);
const float r2 = dot(d, d);
float vignettingFactor = 0.0f;
// 口径食の効果
vignettingFactor += pow(min(1.0f, r2 / VIGNETTING_MECHANICAL_RADIUS2), VIGNETTING_MECHANICAL_INV_SMOOTHNESS) * VIGNETTING_MECHANICAL_SCALE;
// コサイン4乗則
const float cosTheta = 1.0f / sqrt(r2*VIGNETTING_NATURAL_COS_FACTOR + 1.0f);
vignettingFactor += (1.0f-pow(cosTheta , VIGNETTING_NATURAL_COS_POWER)) * VIGNETTING_NATURAL_SCALE;
// 合成
outputColor.rgb *= lerp(float3(1.0f, 1.0f, 1.0f), VIGNETTING_COLOR, saturate(vignettingFactor));
f:id:hikita12312:20180120165716p:plain:w300
周辺減光OFF
f:id:hikita12312:20180120170133p:plain:w300
コサイン四乗則ON

{(h/f)^2}は4.0程度にしています。口径食はOFFです。それっぽい気がします。

結果

口径食もコサイン四乗則も入れた結果です。

f:id:hikita12312:20180120170240p:plain