水ノ茉の宣伝
準備中...
ゲームを作る予定なの水ノ茉
始まり
背景をベタ塗りしたい気分になりました。
テクスチャを書き換えるのは面倒なので、ポストエフェクトで簡易的にやります。
ということで手軽なところから、SNNフィルタを作りました。
SNN(Symmetric Nearest Neighbor)とは
分かりやすく言うと平均化フィルタの一種かな。
雰囲気で知りたい場合はブルプロの資料がおすすめ。

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

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

ノードを組む
こんな感じ。

カスタムノードを書く
こんな感じ。
#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に置き換えでやってみました。
こういう細々とした最適化をする場合は、元の実装を残しましょうね。
最適化後のコードだけ残されるとマジで可読性がゴミ過ぎてイライラするから。
プラグイン編
リポジトリに纏めている&過去にネタにしたので、特に説明はないです。

ついでにポストプロセスマテリアルも入っています。
おわり!!!
次はKuwahara Filterを試したいですね。
関連
雑談
最近、Udemyでインゲームまわりを勉強中なのです。
描画まわりは全部独りで出来るから、後はインゲームを軽く触れるようになれば無敵なのです。
―――と思っていたのですが、モデリングとリギングはどうしようもないことに気付いたのです。
素体と各種パーツの組み合わせで誤魔化せない場合は大人しく外注ですわ。
コード書くのは楽しいけど、どこかで時間作ってエロゲしたい。
ハードエッジな法線をランタイム計算&頂点カラーに書き込み
スカートを綺麗に揺らしたかったのですが、UE純正のクロスシミュレーションはパラメータが多くて怠い。
ということで自前のクロスシミュレーションを作成しようと。
エンジニアの特徴である、楽するために遠回りをするという変態行為です。
その道中で各種バッファに詰めることを覚えたので、アウトラインに流用してみました。
BeginPlayやConstructionScriptのタイミングでバッファに書き込んでいるため、汎用性が地味に高いのがポイントです。
あと筆者にしては珍しくエンジン改造じゃないんです。
当然ですがハードエッジの方が綺麗に出ますね。
想定外の副産物を得られて大変ご機嫌な筆者さんでした。
思い返すと背面法の記事は書いてなかったので、どこかのタイミングで放出するかも。
楓さんのことを忘れていた件
発送おっそ…と思いましたが、そもそも攻略を進めていないことを思い出しました。

しかもノートンのアプデ入ってからエロゲをウイルスと誤認して消したっぽいんだよな。
ファイル欠損で起動できぬ。
こういう事故が怖いからセーブデータは全部クラウド行き。

ちなみにFANZAで10%OFFなってました。
直リンク貼ると怠いことになるので公式に流しますわ。
尚筆者さんは楓さんと学祭まわる途中で力尽きました。

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