ゲームエフェクトデザイナーのブログ (新)

レポート記事とかUE4のマテリアルとか。C#とかも触ったり。

UE4でSNNフィルタを適用する

SNN(Symmetric Nearest Neighbor)は Photoshop のフィルタの「面を刻む」のような効果で、絵画調になるというか単純化するというかのっぺりするというか‥そんなフィルタです。

下記のもんしょさんのブログにてSNNの概要やコードでの実装方法が紹介されており、こちらを頼りにポストプロセスマテリアルを作成してシーンに適用してみました(UE4.19)。

DirectXの話 第111回 - もんしょの巣穴


こちらが元のシーンのスクリンショット(2倍に拡大)。

f:id:moko_03_25:20180401033846j:plain

こちらがSNNフィルタ適用後です(2倍に拡大)。

f:id:moko_03_25:20180401033844j:plain

前回の記事で軽くご紹介したように、おかずさん輪郭検出の記事を試していれば、隣接するピクセルの情報を拾う方法が分かるかと思います。

ここからは、ピクセルの情報を拾う方法を知っている前提で話を進めます。

また、いきなりSNNフィルタを試すより、先に平均化フィルタやガウスブラーフィルタを試した方が、理解するのに丁度良いステップアップになると思います。
その際には下記のサイトの記事がとても参考になりました。

平滑化(移動平均、ガウシアン)フィルタ 画像処理ソリューション

 

その後にSNNフィルタを試しました。

こちらのツイートの小さい画像では分かりにくいと思うので、順を追って解説していきます。

     *     *     *     *     *

まずはUV座標をズラすマテリアル関数を作成します。

f:id:moko_03_25:20180401033841j:plain

内容はおかずさんの輪郭検出の記事そのままです。

Scene TextureノードでScene Colorを選択、InvSizeポートからは画面サイズの逆数‥つまり1ピクセル分のUVの幅を取得できるので、Inputノードで入力してきた値を掛けると「何ピクセル分の幅か」が決まります。それをUV座標に足すと、そのピクセル分ズレた座標が出力結果になります。

f:id:moko_03_25:20180401033838j:plain

もう1つマテリアル関数を作成します。
こちらはズラしたUV座標の色を拾うような構成にします。

f:id:moko_03_25:20180401033836j:plain

先ほどのマテリアル関数を Scene Texture ノード(PostProcessInput0 を選択)に接続して、シーンの色を出力するだけの構成です。
こちらもおかずさんの輪郭検出の記事と流れは一緒です。

たったこれだけなのになぜ先ほどのマテリアル関数と分けているのかと言うと、PostProcessInput0 を Scene Depth にして深度を出力したりと用途に応じて構成が変わるため、流用可能なよう分けているのかと思います。

f:id:moko_03_25:20180401033917j:plain

さらにマテリアル関数を作成します。
もんしょさんのSNNフィルタの記事の図入りでの説明をご覧になっている前提で進んでいますが、SNNフィルタの肝となる、範囲(今回は5x5)内の対称(対角線上)となる2つのピクセルの色のうち、自身(中央のピクセル)の色に近い方を出力する構成にします。

f:id:moko_03_25:20180401050651j:plain

ここはもんしょさんの記事をそのまま参考にさせて頂きました。

基本的な構成の意味はもんしょさんの記事で解説されています。
ただ、DotProductノードが使われていて「どゆこと?」と思う方もいらっしゃるかと思います。

私は中学以降の数学を全く勉強しなかったため、DotProductは2つのベクトルの向きがどれくらい違うかを-1~1で取得してくれる程度にしか理解できていないのですが、どうもAとBのベクトルを掛けた後に全てのチャンネルを足すようです。
この点については、RGBのグレースケール変換に便利というもんしょさんの解説がおかずさんのブログ記事で紹介されていたおかげで知りました。

なのでここでのDotノードは「3チャンネルあるRGB値を比較するために1チャンネルに値を統合するのに利用しているのだな」程度には理解できました。。

f:id:moko_03_25:20180401050708j:plain

さて、さらにもう1つマテリアル関数を作成します。
いよいよSNNフィルタを5x5ピクセルの範囲で行う構成を組みます。

f:id:moko_03_25:20180401050401j:plain

最終的にこんな感じになりました。

ノードが多くて線が散らばっているので一瞬ぎょっとするかと思いますが、同じことを繰り返しているだけで、行っていることはとてもシンプルだったりします。

f:id:moko_03_25:20180401033912j:plain

まず左半分は、5x5のピクセルの色を拾う構成になっています。
ここは輪郭検出の構成を試したことがあれば大丈夫ですよね。

f:id:moko_03_25:20180401033909j:plain

右半分では、下図のように対称(対角線上)の2つのピクセルと中央のピクセルを比較して色を決める関数に繋ぎますが、これが全部で12通りの組み合わせになります。
そして12通りの出力結果を順に足していきます。

f:id:moko_03_25:20180401033906j:plain

最後に、25ピクセル分の色が足された値を25で割って平均の色を出力します。
こちらが最終的に決定する色になります。
この構成を少し変えれば、結果も変わって面白いかと思います。

f:id:moko_03_25:20180401033944j:plain

最後にマテリアルを作成します。

f:id:moko_03_25:20180401034045j:plain

Material Domain を Post Process に変更して、最後に作成したSNNフィルタのマテリアル関数をエミッシブカラーに繋いで、PostProcessVolume アクターに登録するだけです。

f:id:moko_03_25:20180401034042j:plain

 

さて、別のシーンで結果を見てみます。SNNフィルタ適用前はこちら。
(画像クリックで倍サイズになります)

f:id:moko_03_25:20180401034039j:plain

SNNフィルタを適用するとこちらのような結果になりました。
(画像クリックで倍サイズになります)

f:id:moko_03_25:20180401034036j:plain

以上になります。

面白い効果ではありますが、アーティスト的には綺麗な見た目にするためにもう一工夫入れたいところ。。そうするとどんどん描画負荷が重くなっていきそうですが‥

という訳で参考になれば幸いです!