DirectXの話 第111回
Symmetric Nearest Neighbor
今回は簡単なポストフィルタでお茶を濁してみます。
画像からわかる…かもしれませんが、ちょっと絵画っぽくなるポストフィルタである Symmetric Nearest Neighbor です。
絵画っぽくなるフィルタとしては、MedianフィルタやKuwaharaフィルタなどが有名ですが、SNNも同種のフィルタです。
エッジを残しつつブラーをかける、といった感じのものと考えていただければよいと思います。
決して軽くはないですが、仕組みが簡単なのでシェーダにおこすのが簡単だったからというだけの理由で作ってみました。
SNNは基本はボックスブラーです。ただし、単純に各ピクセルを足して、足した分だけ割るというものではありません。
ボックスの各ピクセルをピックアップする際、点対称にあるピクセルもピックアップし、中心の色から最も近い色を選択する、というのがこの技術です。
図で描くと以下のようになります。
ブラーをかけるピクセルを P とし、それに対して点対称の2ピクセルを取得します。
上図の緑のピクセル、および赤のピクセルが対称となるピクセルです。
緑のピクセル Q と R について、右にあるような擬似コードを用いて色を選択します。
PとQの距離の方が近いならQを、PとRの距離の方が近いならRを選択し、これをボックス内の全てのピクセルに対して行って最後にボックスのピクセル数で割ります。
ね、簡単でしょ?
シェーダの実装も極めて簡単です。
float4 GetSNN( float4 centerColor, float2 uv, float2 offset )
{
float4 col0 = texAlbedo.Sample( samAlbedo, uv + offset );
float4 col1 = texAlbedo.Sample( samAlbedo, uv - offset );
float3 d0 = col0.rgb - centerColor.rgb;
float3 d1 = col1.rgb - centerColor.rgb;
if( dot(d0, d0) < dot(d1, d1) )
{
return col0;
}
return col1;
}
float4 RenderPS( OutputVS inPixel ) : SV_TARGET
{
const int width = halfWidth.x;
float4 centerColor = texAlbedo.Sample( samAlbedo, inPixel.texCoord );
float4 outColor = centerColor;
float pixels = 1.0f;
for( int y = -width; y < 0; ++y )
{
float yOffset = uvOffset.y * (float)y;
for( int x = -width; x <= width; ++x )
{
float2 offset = { uvOffset.x * (float)x, yOffset };
outColor += GetSNN( centerColor, inPixel.texCoord, offset ) * 2.0f;
pixels += 2.0f;
}
}
for( int x = -width; x < 0; ++x )
{
float2 offset = { uvOffset.x * (float)x, 0.0f };
outColor += GetSNN( centerColor, inPixel.texCoord, offset ) * 2.0f;
pixels += 2.0f;
}
outColor /= pixels;
return outColor;
}
GetSNN() 関数で取得したカラーを2倍しているのは点対称となるもう1つのピクセルでも々処理を行うためです。
当然点対称で処理をするため、対称となるピクセルでは全く々出力がされるので、わざわざ処理をせずに2倍しているわけです。
さて、このフィルタなんですが、割と重いです。
うちの環境(RadeonHD5870)でも9*9の範囲で1msくらいかかります。
画面解像度が低いなら5*5くらいでも十分効果がありますが、解像度が高いとそれなりの範囲でかけないと微妙にそれっぽく見えません。
また、元絵の質感によっても効果のある/なしが変わってきます。
いくつかの写真で試してみたのですが、自然には強く、人工物には弱いという印象です。車とかはかかってるようないないような、って感じになってるものもありました。
毛並みがわかるくらいの猫のアップ写真だと5*5くらいでは変化がわかりにくかったですね。
強くかけようとするとどうしてもテクスチャフェッチの回数が増えてしまい、速度的に厳しかったですね。
もしもゲームに使うのであれば、元の画像がフィルタが効きやすいようにするべきかもしれません。どういうのがいいのかは色々試してみる必要がありそうですが。
そこまでやるならテクスチャにフィルタをかけてしまうという手もありますね。これなら軽くて簡単に実装できますしね!
そういえば、GPU Proには異方性Kuwaharaフィルタの記事がありました。軽く流し読みしただけなんですが、こっちの方が軽かったりするかもしれません。
なんにしても、この手のフィルタは常時かけるってこともあまりないでしょうし(そういうゲームを作るなら別ですが)、ちょっとした映像表現として使う程度なら重さに目をつむってもいいかもしれませんね。
次回はやっぱり未定。
FBX SDKに割と手こずっているので(というか、FBX形式で出力するためのDCCツールに手こずってるんですが)、次も何かお茶を濁すようなものになるかも。