モーションブラーの実装

モーションブラー

ゲームのフレームとフレームの間の動きに応じて,ブラーを与える効果がモーションブラーです。 レンダリングされた一枚のシーンがカメラによって撮影されたものと考えて,露出時間からブラーを計算します。

https://docs.unity3d.com/ja/current/Manual/PostProcessing-MotionBlur.html

モーションブラーを適用することで,レースゲームのスピード感が出たり, フレームレートが低い場合でも,滑らかに動いているような表現ができます。

今回は,モーションブラーの方法として,「A Reconstruction Filter for Plausible Motion Blur」という論文を内容を実装してみます。

http://casual-effects.com/research/McGuire2012Blur/index.html

「A Reconstruction Filter for Plausible Motion Blur」の方法はUnityでも実装されている方法のようです。

速度マップ

モーションブラーを作成するために,オブジェクトがどのような速度を持っているかの情報を持った速度マップを作成します。
一般に3Dモデルを画面に表示するときには,Projection,View,Modelといった,3つの行列を利用して,ワールド空間からスクリーン空間へと変換することで実現します。 このとき1フレーム前の行列を保存しておいて,現在のフレームとの差分を考えることで,フレーム間のオブジェクトの速度が計算されます。

float4x4 g_CurrentModelMatrix, g_CurrentViewMatrix, g_CurrentProjectionMatrix;
float4x4 g_PrevModelMatrix, g_PrevViewMatrix, g_PrevProjectionMatrix;

float4 currentPosition = position * CurrentModelMatrix * CurrentViewMatrix * CurrentProjectionMatrix;
float4 prevPosition = position * PrevModelMatrix * PrevViewMatrix * PrevProjectionMatrix;

float2 velocity = currentPosition.xy/currentPosition.w - prevPosition.xy/prevPosition.w;
velocity *= 0.5f; // 画面サイズを1.0としたUV座標系での速度に変換

画像は右に移動をするオブジェクトに対して,速度マップを作成した結果です。 見やすくするために,実際の速度の20倍の値を表示しています。

f:id:hikita12312:20171221103726p:plain:w500

A Reconstruction Filter for Plausible Motion Blur

先程の速度マップの速度情報に従ってボカす方向とボカす量を決めればモーションブラーが出来上がります。 しかし,速度マップは,そのフレームでオブジェクトがあった位置にしか,速度情報は書き込まれません。

f:id:hikita12312:20171221104516p:plain:w200

上の図のような状態では,濃い青色のオブジェクトの部分には「右に移動している」という情報が格納されていますが, 本来通過してきたはずのオブジェクトの左側の境界の外の付近の速度マップの値は0です。 単純に現在の速度マップの値を信用してブラーをかけると,境界外部に対してブラーをかけることが出来ません。

A Reconstruction Filter for Plausible Motion Blurの方法では, 注目するピクセルの周辺を含めた支配的な速度を仮定してブラーを行って,境界外部へのブラーを解決しています。 紹介されている方法では3つのパスを利用してモーションブラーを適用します。 まず,{w\times h}サイズの速度マップがあったとして,{w/k \times h/k}サイズのTileMaxと呼ばれるバッファを作成します。

f:id:hikita12312:20171221105201p:plain:w300

TileMaxは,元の速度マップを{k\times k}のタイルに分割して,そのタイル内部で最大の速度を格納します。 図の赤い矢印が中心のタイルが保持する最大の速度です。
さらに,TileMaxから周辺のタイルを含めて最大の速度を持ったTileを自身に格納する,{w/k \times h/k}サイズのNeighborMaxと呼ばれるバッファも作成します。

f:id:hikita12312:20171221105848p:plain:w300

図の赤い矢印が,周辺を含めた最大の速度として,NeighborMaxになります。
この,NeighborMaxがブラーを行うときに,支配的な周辺の速度であるとして,利用されます。

最後に,シーンのカラーテクスチャ,深度テクスチャ,元の速度マップ,そして今作成されたNeighborMaxを利用して,最終的なブラー結果を作成します。 シェーダーは論文のコードをそのまま実装しただけですが,以下のようなものになります。

シェーダーでは,NeighborMaxの速度ベクトル方向に対してサンプリングを行っていき,実際のサンプリングされた地点の速度と深度値からブラー量を決定しています。 この時,注目しているテクセルよりも,サンプルされたテクセルが前にあるか後にあるかでどの重みを採用するか計算を行っているようです。 また,jitterとして,ランダムなオフセットを与えることで,サンプル数が少ない場合でもそれなりに見えるようになっています。

論文ではOpenGL座標系だからか,カメラ奥方向がZ-となっていたので修正しています。 また,ブラー量を無理やりスケールする係数BLUR_SCALEも追加しています。

結果

結果です。BLUR_SCALE=6,K=16,SAMPLE_NUM=16で適用しました。 GPU実行時間は,GTX970の環境で1280x720サイズのテクスチャで.0.25ms程度でした。

f:id:hikita12312:20171221203813p:plain:w300
Motion Blur OFF
f:id:hikita12312:20171221203826p:plain:w300
Motion Blur ON
f:id:hikita12312:20171221212424g:plain
Motion Blur OFF
f:id:hikita12312:20171221212443g:plain
Motion Blur ON

まだまだキレイにする工夫の余地はありそうですが,とりあえずこんな感じです。