油彩風フィルタ
明けましておめでとうございます。本年も怠惰にやっていきますがよろしくお願いします。昨年から、あくまで自分用メモとして投げやりな更新をちらちらと再開していましたが、今年は自分なりの勉強やこれまでの学習の成果を、後から読んでもわかる程度に文書にして吐き出していきたいと思います。がんばります。
さて新年一発目は、昨年実践してできた(と思う)油彩風フィルタについてまとめておきます。
概要
kuwaharaフィルターによる実装です。このフィルタをざっくり説明すると、ある点Pの色を決めようとするとき、Pを中心に4象限のエリアを設定し、分散の最も小さいエリアの平均カラーをPの色をして採用する、という感じだそうです。
実際やってみました。↓
ヤッター油彩風フィルターデキタヨー!! pic.twitter.com/Jsjo0NVMj5
— る (@noume_ri) 2020年11月29日
今回はサンプルピクセルを3x3サイズで実装してみました。使用バージョンはUE4.25です。
実装手順
1:カラーをサンプルする
まずはカラーをサンプルする準備をします。2つほどマテリアル関数を作ります。
MF_GetOffsetPixelは、指定したピクセル幅のオフセットをとるためのマテリアル関数です。マテリアル上でテクスチャはUV値0~1で正規化されているので、nピクセル分ズレた位置の情報をとるためにUV値自体をずらします。これをSceneTextureのInvSize(=1/ピクセル数)と掛け合わせて元のTexCoordに足すことで行っています。輪郭線検出などでもよく見る方法ですね。
MF_GetOffsetPixelColorは、さきほどの関数からの戻り値を使って、オフセットされた場所の色情報をとってきます(関数を分けていますが、今回はColorしか使わないのでまとめてしまってもいいかもしれません)。
2:分散値を求める
次に先ほどのサンプルカラーを元に、kuwaharaフィルターにおける各エリアの分散値を求めます。分散の導出はこんな式だったはず。
ごちゃごちゃしてますがやってることは単純です。MF_GetAreaVarienceでは、まず分散の計算のためR、G、Bそれぞれの平均値を出して、サンプルしたカラーとともに分散の実際の処理(MF_GetVarienceV3)に渡しています。その後戻り値をR、G、Bをすべて合算して評価値としています。また、エリアの平均カラーは最終的に採用されるカラーの候補となるため、Outputノードを増やして出力するようにします。
そして分散を求めるマテリアル関数はこうなります。ごちゃってますが、これも単純に各ピクセルのカラーと平均カラーとの差の2乗を足し合わせて最後に割る、という分散の式をノードに置き換えているだけです。
3:エリアの評価と最終カラー決定
先ほどまでようやくエリアの分散と平均カラーを求める準備ができたので、あとはこれを4つのエリア全てで計算して評価するだけです。
Customノードの中の式はこんな感じです。4つの値を比較して最も小さいものを選択する、というやつですが、みたままそのままifだらけなので、これはもっといい書き方がありそうな気がします。
if(AreaA > AreaB){ if(AreaB > AreaC){ if(AreaC > AreaD){ return ColorD; } else{ return ColorC; } } else{ if(AreaB > AreaD){ return ColorD; } else{ return ColorB; } } } else{ if(AreaA > AreaC){ if(AreaC > AreaD){ return ColorD; } else{ return ColorC; } } else{ if(AreaA > AreaD){ return ColorD; } else{ return ColorA; } } }
以上をポストプロセスマテリアルとして設定すれば、以下のような絵ができていると思います。なんとなく平滑化がかかってる気がしますね。
終わりに
本来kuwaharaフィルターは任意のピクセルでのサンプルができるようにするものらしいですが、ご覧の通りノードでやるにはかなりごちゃるのでカラーサンプルからCustomノードを使う必要がありそうです。HLSLにもう少し詳しくなったら、再度挑んでみたいですね。