今回は、レンダリングした後の画像の輪郭をとるエッジフィルターです。
このエフェクトは、DOUBLE-S.T.E.A.Lで有名になりましたが、
私の知り合いは、DOUBLE-S.T.E.A.Lの広告を見て、「リアルタイムフォトショップだね」といっていました。
まぁ、だからどうしたという感じですが、実装してみました。
まぁ、いつものように適当にファイルが入っています。
final.vsh | 頂点シェーダー。 |
final.psh | ピクセルシェーダー。 |
draw.cpp | メインの描画部分。 |
draw.h | 描画の各関数の定義。 |
bg.cpp | 背景の描画。 |
main.h | 基本的な定数など。 |
main.cpp | 描画に関係しないシステム的な部分。 |
load.cpp | ロード。 |
load.h | ロードのインターフェイス。 |
あと、実行ファイル及び、プロジェクトファイルが入っています。
どのような加工をしているか順を追って説明しましょう。
先ずは、元画像です。 テクスチャーにレンダリングして、画像を作り上げます。
隣のピクセルの「輝度」の差でエッジを抽出します。
輝度を
輝度 = 0.299f*赤 + 0.587f*緑 + 0.114f*青
で、計算します。
次に、近くのピクセルとの輝度の差を取って、エッジを抽出します。
輝度の差をとるポイントは、対角的にとります。
中心から、前後、左右に0.5離れた4点で、対角的に差をとります。
差をとったピクセルですが、本当は、
差の大きさ = |ピクセルの差1|+|ピクセルの差2|
と、絶対値でとらないと、差をとった符号によって、一部が消えてしまいます。
ただし、絶対値の計算は大変なので、近似的に符号を消すための演算である絶対値の2乗
差の大きさ = (ピクセルの差1)2+(ピクセルの差2)2
をとって、近似します。
あっ、上の絵は、色のスケーリングをして、強弱を強めています。
さて、輪郭にするために、ネガポジ変換して、輪郭を抽出します。
これを、元の画像と合成(乗算)をすれば、今回の画像が求まります。
以上で、出来上がりです。
さて、どのようにして、以上の手続きを実現するか、ピクセルシェーダーのプログラムを見てみましょう。
0001: ; final.psh 0002: ; (c) 2002 IMAGIRE Takashi 0003: 0004: ps.1.0 0005: 0006: def c0, 0.299f, 0.587f, 0.114f, 0.0f ; 輝度の重み 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: dp3 r0, t0, c0 ; rgb a 0014: dp3 r0.rgb, t1, c0 ; r0 = (t0の輝度、t1の輝度) 0015: dp3 r1, t3, c0 ; r1 = (t3の輝度、t2の輝度) 0016: dp3 r1.rgb, t2, c0 0017: 0018: add_x4 r0, r0,-r1 ; r0 = 4( t3-t0の輝度, t2-t1の輝度) 0019: mul_x4 r0, r0, r0 ; r0 = 64((t3-t0の輝度)^2, (t2-t1の輝度)^2) 0020: add_x4_sat r0, 1-r0,-r0.a ; r0 = 4*(1-64((t2-t0の輝度)^2+(t3-t1の輝度)^2)) 0021: mul_sat r0, r0, t1 ; 求めたエッジに色を乗せる
テクスチャー座標は、あらかじめずらしておきます(頂点シェーダーで被写界深度をやった時と同じ方法です)。
そのテクスチャーをロードた後、内積命令を使って輝度を求めます。
テンポラリレジスタは2つしかないので、アルファ成分と色成分にそれぞれ違うピクセルの輝度を格納します。
次に、足し算命令を使って、引き算して、エッジをしらべます。また、強弱を強調するために、結果を4倍します。
次に、掛け算命令をして、2乗します。これで、負の値が消えます。
その後、足し算をして、(ネガポジ反転しつつ)輝度を合成します。これで、輪郭が得られました。
最後に、色をかけて出来上がりです。
シェーダ部分しか説明しませんでしたが、それ以外は今までにやってきたことなので、すぐわかると思います。
このフィルター自体は、そんなに綺麗でもないので、使いどころは微妙ですが、
エッジを検出できるということは、いろいろな計算ができるので、使いどころは満載でしょう。