被写界深度表現の実装

被写界深度とは

今度はカメラで撮った写真のような,ボケのある表現を実装してみたいと思います。 UnrealEngineのドキュメントを見ると,どんな感じの表現なのか掴みやすいでしょう。
Unreal Engine | 被写界深度

錯乱円の公式

ある点からレンズを通ってフィルムに投影される円の大きさ(錯乱円,Circle of Confusion,CoC)を計算します。
https://en.wikipedia.org/wiki/Circle_of_confusion
詳しい計算はWikipedia。レンズの公式から導けます。

{ \displaystyle
\begin{eqnarray}
f &=& \mathrm{Focal \ Length}\\\
F &=& \mathrm{Fnumber}\\\
d_0 &=& \mathrm{focus \ length}\\\
\epsilon &=& \mathrm{pixel \ size}\\\
\\\
\mathrm{CoC}(d) &=& \frac{|d-d_0|}{d}\frac{f^2}{F(d_0-f)}\frac{1}{\epsilon}
\end{eqnarray}
}


{f}焦点距離,{F}はカメラのF値,{d_0}はカメラのフォーカス距離です。
錯乱円(以下CoC)を求める式としては本来必要ないですが,現実のカメラの撮像素子の画素サイズを表す{\epsilon}も定義しておきます。 実際に被写界深度処理をかけるテクスチャの1pixelを,仮想的にカメラの撮像素子とみなすということに対応します。 {\mathrm{CoC}(d)\leq1}のときは,1pixelの中にCoCが収まるので,ボケない(=被写界)と考えることができて, {\mathrm{CoC}(d)>1}の場合はそのCoCのサイズに応じた直径のブラーをかけることで,被写界深度表現を行います。

f:id:hikita12312:20170902185332p:plain:w300
元画像
f:id:hikita12312:20170902200259p:plain:w300
CoCマップ

CoCを実際に図示した結果です。前ボケのCoCは負の値を格納していたり,後ボケの1.0以上の大きさのCoCが格納されていたりするので,図示するとサチってしまってあまり意味はないのですが…

ボケフィルタによるギャザーベース被写界深度表現

CoCのサイズに従って,前回ブルームで行ったようなガウスブラーをかければ,距離に従ったボケの表現が可能です。
幾つかの代表的なCoCサイズに対応するガウスブラー結果をフィルタとして用意してます。 実際のピクセルの深度値からCoCサイズに変換し,CoC値でフィルタ間のボカされたテクセルをブレンドする方法で被写界深度表現を実装してみます。

f:id:hikita12312:20170902183551p:plain

この図は,実際にCoCごとのフィルタを作成して,それを補間して被写界深度表現を行う際の模式的な図です。 今回は補間を行うときに,smoothstepによる三次補間を行っています。 線形補間でも良さそうですが,フィルタ間が折れ線グラフ的になると,微分値が不連続でなんとなくカクカクした感じに見えたので,smoothstepを使っています。

大きいCoCサイズを表現するフィルタでは,大きなブラーをかけることになり,画像の情報量を減らすということに対応するので,画像を縮小して負担を減らす方法が有効です。 1/2サイズのバッファを基本的に利用していますが,大きいブラーをかけるフィルタでは,1/4サイズぐらいまでの縮小バッファを利用して高速化しています。

f:id:hikita12312:20170902185025p:plain

上の画像が実際に被写界深度処理を適用したものです。前ボケにフィルタを3枚,後ボケにフィルタを4枚使っています。

f:id:hikita12312:20170902185332p:plain
DoF:OFF
f:id:hikita12312:20170902185025p:plain
DoF:ON

ONとOFFを比べてみるとわかりやすいですね。

後ボケの滲みの軽減

実はさっきの被写界深度の実装はダメダメです。 例えば,凄くF値を小さくして,ボケボケにした状態で,明るいオブジェクトにピントを合わせてみます。

f:id:hikita12312:20170902190243p:plain

ピントが合っているピクセルの色が,後ボケに滲み出ていて,本来存在しないはずの色が後ボケに乗っています。
これは,ガウスブラーにおいてサンプリングをするときに,本来光学的に遮蔽されてブレンドされない色が乗っていることが原因です。
そこで,後ボケフィルタにおいて,手前にあると見なせるCoCの値のピクセルについてはマスクをするという処理を行います。

f:id:hikita12312:20170902192025p:plain:w400

マスクをされたテクセルはサンプリングされず,ガウスブラーの正規化する際の重みにも反映されません。 なぜその1つ前のフィルタの距離からマスクを行うかというと,ボケを補間して合成をするときに,中間の距離の情報がなくなってしまって,黒くなってしまうからです。

f:id:hikita12312:20170902192650p:plain

後ボケのマスクを行った結果が上の画像です。後ボケの滲みが軽減されました。

f:id:hikita12312:20170902190243p:plain
後ボケマスク:OFF
f:id:hikita12312:20170902192650p:plain
後ボケマスク:ON

前ボケのエッジブラー

この章はでの実装は,少し遅いのと,もっとキレイにやる方法がありそうなので,まだまだ検証中です(2017/09/02)

前ボケも実はダメダメです。

f:id:hikita12312:20170902193251p:plain

前ボケのブラーされた結果と,ピントの合ってボケていない領域の境界がとてもハッキリ見えています。もっとぼんやりとボケて欲しいと思います。
今回のガウスブラーのようなブラーはギャザーベースのブラーと呼ばれていて, 周囲のピクセルのをサンプリングして集めて,出力するピクセルの色を決めるという手法においてのボケの境界は,一つの問題です。

エッジをボカシたい,ということで,とりあえずSobelフィルターを利用して,エッジ検出をします。

10-1
20-2
10-1

{\Delta x}
121
000
-1-2-1

{\Delta y}


このエッジをマスクとみて,この範囲の前ボケエッジをボカスようにガウスブラーを適用します。

マスクはガウスブラー範囲よりも大きくとらなければ行けないのですが,Sobelフィルターをそのまま使うと,検出されるエッジの太さは1ピクセルにしかなりません。 そこで,本来はSobelフィルタのカーネルは3x3サイズですが,適当にカーネル間のテクセルサイズを5テクセルぐらいに拡大してサンプリングをしました。(上の画像)

前の章までの,ボケフィルタの合成処理の後に,エッジへのガウスブラーを適用したものが以下の画像です。

f:id:hikita12312:20170902195649p:plain

f:id:hikita12312:20170902193251p:plain
前ボケブラー:OFF
f:id:hikita12312:20170902195649p:plain
前ボケブラー:ON

終結

後ボケマスク,前ボケブラーを適用した被写界深度表現の結果です。

f:id:hikita12312:20170902195926p:plain

f:id:hikita12312:20170902185025p:plain
後ボケマスク,前ボケブラー:OFF
f:id:hikita12312:20170902195926p:plain
後ボケマスク,前ボケブラー:ON