FSAA with Blur Effects

2000/07/20(木)  − FSAA with Blur Effects − 動いたらモーションブラー止まったらアンティエイリアス

 もう随分前に考えてた方法だが、これまで実際に試して評価するのを忘れていたアイディア。 という程大したモノではなく、単に ブラーエフェクトの各サンプル描画時に、 射影行列もアンティエイリアス用にジッタ処理(Jittering:画像を微妙にずらして描画) してしまうだけである。
 おそらく既にやろうとしている事が判ってしまった方も多いと思うが、 これによって 画面上での動きが止まっている部分には、 自動的にアンティエイリアスがかかる 訳だ。 アンティエイリアスのジッタ処理は元々 1ピクセル未満のずらしであるため、 動いている部分にはほとんど影響を与えない。 従って、動いている部分については 通常のモーションブラーとなる(下図参照)。

6 sample Motion-Blur (non-movement)
Standard method With FSAA Jittering

6 sample Motion-Blur (movement)

  動いている部分は元々ジャギーが目立ちにくい事と、 この処理による 速度的なオーバヘッドはほとんど皆無(射影行列のセットのみ) である事から、処理速度が速ければなかなか有効に使えそうである。


 シーン・アンティエイリアスのジッタ処理は非常に簡単で、 視錐体を1ピクセル未満で変形(射影行列を変化) させるだけである。 ただ、 射影行列(Projection Matrix) は意外に理解されていない事が多いようなので、 ここで簡単に説明することにする。

 Direct3D や OpenGL の 射影変換は、ビューボリュームに含まれる空間を 正規化デバイス座標系(クリップ座標系)に射影する座標変換である。 この変換により、 ビューボリューム内のx、y、z座標は -1 〜 1 の範囲 (ただし Direct3D ではz座標のみ 0 〜 1)に射影される。 そしてこの範囲に納まらない(ビューボリュームの外の)頂点はクリップの対象となる。
 Direct3D と OpenGL では扱う座標系が異なり(右手または左手座標系)、 クリップ座標のzの範囲も異なり、さらに行列の表現(縦横)も異なるため、 射影行列も当然異なる。 とりあえず、 Direct3D と OpenGL の透視射影行列 を以下に示す。

 ただし、行列中の

f はファークリップ平面の位置(視点座標系でのz座標)
n はニアクリップ平面の位置(同じくz座標)
r はニアクリップ平面の右端(x座標)
l はニアクリップ平面の左端(x座標)
t はニアクリップ平面の上端(y座標)
b はニアクリップ平面の下端(y座標)
とする。

Direct3D OpenGL

式で書くなら

Direct3D OpenGL
である。 Direct3D と OpenGL では、 z と w のみ 異なっている。

 念の為に書いておくが、これによる変換後の座標(x, y, z, w)はあくまで 同次座標であり、 これをw除算した値(x/w, y/w, z/w)が実際の座標 となる。 透視投影の場合wには視点座標系のz座標(OpenGL では -z)が入っていることから、 透視変換も同時に行われていることが判るだろう。 各自、f,n, r,l, t,b に適当な値をセットし、 与えた頂点(x, y, z, w)がどのような値に変換されるか試してみると良いだろう。 また、 ビューボリューム内の座標を変換した場合、(x/w, y/w, z/w)はかならず -1 〜 1 の範囲に含まれる ことを確認しよう(Direct3D の z/w のみ 0 〜 1)。

 参考までに、W-Buffering に使用されるw値とは、 視点座標系での頂点を上の射影行列で変換した後のw値のことである (内部的にはw値の取り得る最大値によってスケーリングされるため、 この値そのものではない)。 また、w値は実際にはその逆数である rhw によって渡される。 rhw はつまりは透視変換されたz座標 であるため、 ラスタライズ時には rhw 値を線形補間し、ピクセル毎にその逆数を計算することで、 補正された正確なw値(視点座標系でのz値といっても良い)を復元できる。

 さて本題に戻り、射影行列に与えるパラメタの内、 投影面の矩形を定義する l,r, b,t を、1ピクセル未満の量でずらして複数回レンダリングし、 サンプリング不足によるジャギーを軽減するのが、 (フル)シーン・アンティエイリアス である。
 ビューポートのサイズが判っていれば、 ビューポート上での1ピクセルの大きさ(範囲)が、 視点座標系での投影面(l 〜 r, b 〜 t によって定義される矩形)に対して どの程度の大きさを持つかを逆算 できる。 例えばビューポートのサイズが(640x480)で、ニアクリップ平面(投影面)が (l=-8, b=-6)〜(r=8, t=6)までの範囲(16x12 のサイズ)であるとすると、 投影面上での1ピクセルに相当するサイズは(16/640, 12/480)で求まる。 当たり前ですね。

 後は、各サンプル毎に、 1ピクセルの範囲内(上で求めた範囲)でパラメタ r,l, t,b にオフセットを 与えてずらし、 射影行列を変化(ビューボリュームを変形)させてやれば良い訳だ。 最後に全ての画像を平均化すれば、シーン・アンティエイリアスの完成である。

 と言う訳でサンプルプログラムっていうか glclock で実装してみただけです。

jittertest.zip(139KB) for win32
ちなみに実行ファイルしか入っていないので、 動かない場合はまず glclock 本体が動く環境にしてください(無責任)。 あと、現時点で公開している最新版の glclock-6.0 Beta 6.0 では実装されてませんので、 試したい場合は上の奴の実行ファイルが必須です。

glclock.exe -NOANTI
で実行すると従来のジッタを与えない方法になり、
glclock.exe
のように -NOANTI を付けなければ(デフォルトで)FSAA ジッタが有効になります。

 適当に実行して、m キーでモーションブラーをかけてみてください。 ちなみに、GeForce シリーズの nVIDIA reference driver 5.x だと高速になります。