ぼかしフィルター


~線形補間ラッシュラッシュ~




■はじめに

エッジフィルターを行いましたが、他にも使えるフィルターがあります。それがぼかしフィルターです。
画像は、左半分が普通にレンダリングした結果、右半分がぼかした結果になっています。

まぁ、いつものように適当にファイルが入っています。

vs.vsh通常色の頂点シェーダー。
ps.psh通常色のピクセルシェーダー。
final.vshぼかしの頂点シェーダー。
final.pshぼかしのピクセルシェーダー。
draw.cppメインの描画部分。
draw.h描画の各関数の定義。
bg.cpp背景の描画。
main.h基本的な定数など。
main.cpp描画に関係しないシステム的な部分。
load.cppロード。
load.hロードのインターフェイス。
tile.bmp (床デカール)
sky.bmp (空デカール)

あと、実行ファイル及び、プロジェクトファイルが入っています。

■方法

ぼかしフィルターはバイリニアサンプリングを応用します。
ここでいうバイリニアサンプリングとは、テクスチャーからの色を引っ張るときの方法です。
テクスチャー座標がどっしりとテクセル(テクスチャーの画素)の中心に乗っていない時、 近くにある4つのテクセルの色を、テクスチャー座標とテクセルの中心との距離に応じて混ぜ合わせる方法です。
実際の方法は、4つのテクセルのうち、2つの色に関してテクスチャー座標に応じて線形補間します。 線形保管した色及び保管した座標は2つできるので、さらにその2つの点に関して線形補間して最終的な色を決めます。 リニア(線形)な補間を2回行うので、バイリニアとよばれます。
最近のビデオチップはほぼ間違いなくバイリニア補間をサポートしています (サポートしていない場合は、テクスチャー座標の乗っているテクセルの色だけを引っ張るポイントサンプリングが使われます。 (さらに未来にはより高度なサンプリングしかサポートしない場合がありますが、それはまたのお話で…))。

さて、4つのテクセルの中心座標のちょうど中間にテクスチャー座標を配置すると、それぞれの色を等しくサンプリングします。
つまり、それぞれの色を1/4ずつ混ぜ合わせた色が取り出せます。
さらに、2つのテクセルの間を開けた座標を四角形の形に4つサンプリングして、平均を取ると4x4のテクセルの平均した色が求められます。
遠くの色を引っ張ってくるということは、色がにじんだということになります。つまり、色がボケます。

■シェーダープログラム

さて、どのようにして、以上の手続きを実現するか、ピクセルシェーダーのプログラムを見てみましょう。

合成するピクセルシェーダーを見ましょう。 テクスチャーに1度レンダリングします。
レンダリングしたテクスチャーをテクスチャーの0から3まで全てに仕込みます。
頂点シェーダーでテクスチャー座標をずらしておきます。
この後が、ピクセルシェーダーです。
0.5を掛けながら、色を加算して、最後に線形補間命令lrpで、線形補間して4つのテクセルのバイリニアサンプリングを行います。
サンプリングされた色は、tex命令で色をもってくる時点でバイリニアサンプリングされた色が引っ張られるので、この結果は16このテクセルを合成した色になります。
今回のものは、nVIDIA のものよりも1命令多いですが、マッハバンドが小さくなっています。

0001: ; final.psh
0002: ;      (c) 2002 IMAGIRE Takashi
0003: 
0004: ps.1.0
0005: 
0006: def c0, 0.5f, 0.5f, 0.5f, 0.5f
0007: 
0008: tex t0      ; 0:1 0  1:0 1  2:0 0  3:0 0
0009: tex t1      ;   0 0    0 0    1 0    0 1
0010: tex t2
0011: tex t3
0012: 
0013: mul r0, t0, c0       ; r0 = 0.5*t0
0014: mul r1, t1, c0       ; r1 = 0.5*t1
0015: mad r0, t2, c0, r0   ; r0 = 0.5*(t0+t2)
0016: mad r1, t3, c0, r1   ; r1 = 0.5*(t1+t3)
0017: lrp r0, c0, r0, r1   ; r0 = 0.5*0.5*(t0+t2+t1+t3)=(t0+t2+t1+t3)/4

実際にテクスチャー座標をずらす頂点シェーダーでは、定数を足して平行移動を実現します。

0001: ; normaledge.vsh
0002: ; c20-23   -- テクスチャーをずらすオフセット
0003: ;             (0,0), (1,0), (0,1), (1,1)
0004: ; v0    頂点の座標値
0005: ; v7    テクスチャ座標
0006: 
0007: vs.1.0
0008: 
0009: ; 座標を代入
0010: mov oPos, v0
0011: 
0012: ; それぞれテクスチャーの位置をずらして出力
0013: add oT0, v7, c20
0014: add oT1, v7, c21
0015: add oT2, v7, c22
0016: add oT3, v7, c23

ずらす値は、テクスチャーの幅の逆数が1つのテクセルの幅になるので、 その2倍の値を使って、2つのピクセルの間を開けたテクスチャー座標のずれを実現します。

draw.c
0210:     float const inv_w = 1.0f / (float)RENDER_SIZE;
0211:     float const inv_h = 1.0f / (float)RENDER_SIZE;
0212:     lpD3DDev->SetVertexShaderConstant(20, &D3DXVECTOR4  (0.0f*inv_w, 0.0f*inv_h, 0.0f, 0.0f), 1);
0213:     lpD3DDev->SetVertexShaderConstant(21, &D3DXVECTOR4  (2.0f*inv_w, 0.0f*inv_h, 0.0f, 0.0f), 1);
0214:     lpD3DDev->SetVertexShaderConstant(22, &D3DXVECTOR4  (0.0f*inv_w, 2.0f*inv_h, 0.0f, 0.0f), 1);
0215:     lpD3DDev->SetVertexShaderConstant(23, &D3DXVECTOR4  (2.0f*inv_w, 2.0f*inv_h, 0.0f, 0.0f), 1);

■最後に

今回はテクセル2つ分の間を開けてサンプリングしましたが、 1つだけ開けた場合のぼかしを9サンプルコーンフィルタといって、 ボケる量は少なくなりますが、うっすらとぼやけた画像も得られます。

あと、面白いのはエッジ抽出フィルターとかメディアンフィルターですが、そこらへんはちょっとGeForce3じゃ難しくなってきそうですね。
やっぱりRADEONを買うしか・・・




もどる

imagire@gmail.com