【UE5】SNN(Symmetric Nearest Neighbor)フィルタを作ってみた – Unreal Engine 5.4

Unreal Engine

水ノ茉の宣伝

準備中...
ゲームを作る予定なの
水ノ茉こおり

始まり

背景をベタ塗りしたい気分になりました。

テクスチャを書き換えるのは面倒なので、ポストエフェクトで簡易的にやります。

ということで手軽なところから、SNNフィルタを作りました。

SNN(Symmetric Nearest Neighbor)とは

分かりやすく言うと平均化フィルタの一種かな。

雰囲気で知りたい場合はブルプロの資料がおすすめ。

セル調表現で世界展開を目指す! 『BLUE PROTOCOL』におけるアニメ表現技法〜CEDEC 2020(2)
オンラインアクションRPGとして世界展開を目指して開発中の『BLUE PROTOCOL』では、...

計算方法の分かりやすい説明はこちらかな。

【Unity】【シェーダ】SNNフィルタのポストエフェクトで絵画風に加工する - LIGHT11
UnityでSNNフィルタのポストエフェクトを掛けてレンダリング結果を絵画風にする方法をまとめました。

ポストプロセスマテリアル編

ポストプロセスマテリアルの作り方が分からない場合はアウトラインから見るのおすすめ。

【UE5】アウトラインを描いてみた ポストプロセスマテリアル編 - Unreal Engine 5.3
始まり描画エンジニアになりたい方の入門編にもちょうどよいコンテンツ。そう、アウトライン(輪郭線)(尚主観。異論は認めよう。)それを幾つかの実装方法で描いていきます。アウトライン(輪郭線)とは読んで字の如く。描画まわりの専門用語が一切必要の無...

ノードを組む

こんな感じ。

カスタムノードを書く

こんな感じ。

#define CalcDistanceSquared(X, Y) (length2(X - Y))
#define ViewportUVToClampedBufferUV(UV, U, V, InvSize) (ClampSceneTextureUV(ViewportUVToSceneTextureUV(UV + float2(U, V) * InvSize, PPI_PostProcessInput0), PPI_PostProcessInput0))

float KernelFullSize = Kernel + (((int)Kernel % 2 == 0) ? 1.0 : 0.0);
int KernelHalfSize = (int)floor(KernelFullSize * 0.5);

float3 AccColor = SceneColor;
float  AccW = 1.0;

for (int x = 1; x <= KernelHalfSize; ++x)
{
    float3 c1 = SceneTextureLookup(ViewportUVToClampedBufferUV(UV, -x, 0.0, InvSize), PPI_PostProcessInput0, 0).rgb;
    float3 c2 = SceneTextureLookup(ViewportUVToClampedBufferUV(UV,  x, 0.0, InvSize), PPI_PostProcessInput0, 0).rgb;

    float d1 = CalcDistanceSquared(c1, SceneColor);
    float d2 = CalcDistanceSquared(c2, SceneColor);

    float d = step(d2, d1);

    // Non-optimized version:
    // FLATTEN
    // if (d1 < d2)
    // {
    //     AccColor += c1;
    // }
    // else
    // {
    //     AccColor += c2;
    // }

    AccColor += c1 * (1.0 - d);
    AccColor += c2 * d;
    AccW += 1.0;
}

for (int y = 1; y <= KernelHalfSize; ++y)
{
    for (int x = -KernelHalfSize; x <= KernelHalfSize; ++x)
    {
        float3 c1 = SceneTextureLookup(ViewportUVToClampedBufferUV(UV,  x, -y, InvSize), PPI_PostProcessInput0, 0).rgb;
        float3 c2 = SceneTextureLookup(ViewportUVToClampedBufferUV(UV, -x,  y, InvSize), PPI_PostProcessInput0, 0).rgb;

        float d1 = CalcDistanceSquared(c1, SceneColor);
        float d2 = CalcDistanceSquared(c2, SceneColor);

        float d = step(d2, d1);

        // Non-optimized version:
        // FLATTEN
        // if (d1 < d2)
        // {
        //     AccColor += c1;
        // }
        // else
        // {
        //     AccColor += c2;
        // }

        AccColor += c1 * (1.0 - d);
        AccColor += c2 * d;
        AccW += 1.0;
    }
}

return AccColor / AccW;

対角線比較の順番の可視化

赤色が対角線の始点、青色が対角線の終点です。

中心ピクセルの行だけ中央から端に探索しています。

最上下の行から1段ずつ降りる、上がるスタイルは、デクリメントが好きじゃないという理由で不採用です。

SceneTextureノードは消さないでね

カスタムノードに慣れていて且つエンジン実装を読んでいない方は高確率で踏むであろう仕様について。

慣れてくると「SceneTextureノードを一切使わずにSceneTextureLookupだけでいいじゃん」と思うかもしれません。

残念でした。

それは出来ない仕様なのです。

実際に試してもらえば分かると思いますが、関数の未定義エラーが出力されます。

UEのシェーダーコードジェネレーター(シェーダーコードを文字列で生成する機能)は、ソコソコに優秀でして、SceneColorやGBufferを一切フェッチしていない場合に、これらの関数をバリエーションで閉じちゃうのです。

そのため、カスタムノードだけで完結させてしまうと、未定義エラーが発生するのです。

んで、ついでに言いますと、PostProcessInput0を選択したSceneTextureノードが存在しない場合、RTにアタッチするテクスチャが新たに生成されずに、現在のSceneColorテクスチャをセットするので、これまたフェッチしようとすると、読み込みと書き込みで衝突しちゃうという罠があります。

この辺りは実装を読むと楽しいポイントです。

分からない方向けに要約すると、この通りに組めよってこと。

条件分岐を封印してみた

今時のハード性能なら条件分岐はさておき、三項演算子ぐらいなら、ボトルネックにすらならないと思いますが、先人の知恵であるstepに置き換えでやってみました。

こういう細々とした最適化をする場合は、元の実装を残しましょうね。

最適化後のコードだけ残されるとマジで可読性がゴミ過ぎてイライラするから。

プラグイン編

リポジトリに纏めている&過去にネタにしたので、特に説明はないです。

【UE5】アウトラインを描いてみた プラグイン編 - Unreal Engine 5.3
始まり描画エンジニアになりたい方の入門編にもちょうどよいコンテンツ。そう、アウトライン(輪郭線)(尚主観。異論は認めよう。)それを幾つかの実装方法で描いていきます。アウトライン(輪郭線)とは読んで字の如く。描画まわりの専門用語が一切必要の無...

ついでにポストプロセスマテリアルも入っています。

GitHub - kafues511/StylizedFilterRenderPipeline: This plugin provides a sample implementation for applying stylized filters to objects in the scene using FSceneViewExtensionBase.
This plugin provides a sample implementation for applying stylized filters to objects in the scene using FSceneViewExten...

おわり!!!

次はKuwahara Filterを試したいですね。

関連

雑談

最近、Udemyでインゲームまわりを勉強中なのです。

描画まわりは全部独りで出来るから、後はインゲームを軽く触れるようになれば無敵なのです。

―――と思っていたのですが、モデリングとリギングはどうしようもないことに気付いたのです。

素体と各種パーツの組み合わせで誤魔化せない場合は大人しく外注ですわ。

コード書くのは楽しいけど、どこかで時間作ってエロゲしたい。

ハードエッジな法線をランタイム計算&頂点カラーに書き込み

スカートを綺麗に揺らしたかったのですが、UE純正のクロスシミュレーションはパラメータが多くて怠い。

ということで自前のクロスシミュレーションを作成しようと。
エンジニアの特徴である、楽するために遠回りをするという変態行為です。

その道中で各種バッファに詰めることを覚えたので、アウトラインに流用してみました。

BeginPlayやConstructionScriptのタイミングでバッファに書き込んでいるため、汎用性が地味に高いのがポイントです。

あと筆者にしては珍しくエンジン改造じゃないんです。

当然ですがハードエッジの方が綺麗に出ますね。

想定外の副産物を得られて大変ご機嫌な筆者さんでした。

思い返すと背面法の記事は書いてなかったので、どこかのタイミングで放出するかも。

楓さんのことを忘れていた件

発送おっそ…と思いましたが、そもそも攻略を進めていないことを思い出しました。

しかもノートンのアプデ入ってからエロゲをウイルスと誤認して消したっぽいんだよな。

ファイル欠損で起動できぬ。

こういう事故が怖いからセーブデータは全部クラウド行き。

ちなみにFANZAで10%OFFなってました。

直リンク貼ると怠いことになるので公式に流しますわ。

尚筆者さんは楓さんと学祭まわる途中で力尽きました。

『シークレットラブ(仮)』2024年7月26日(金)発売 - HOOKSOFT
学園生活の「バレない恋」を楽しむ秘密の恋愛ADVゲーム!HOOKSOFT25作『シークレットラブ(仮)』2024年7月26日(金)発売

奏命様の苺大福といい、楓さんの苺クレープといい、最近いちご食べたい欲がすごい。