はなちるのマイノート

Unityをメインとした技術ブログ。自分らしくまったりやっていきたいと思いますー!

【Unity】ShaderGraphでCustomFunctionNodeを用いてガウシアンフィルタを作成する

はじめに

今回はShaderGraphCustomFunctionNodeを使ってみようという記事になります。

docs.unity3d.com

CustomFunctionNodeは一言で言えば、自分で動作を記述することができるノードということでしょうか。

細かい説明はそれぞれの箇所でしますが、今回目的となるガウシアンフィルタを適用したみた画像は以下になります。

f:id:hanaaaaaachiru:20210616195159p:plain
左:入力画像,右:出力画像

画像の引用 : https://github.com/yoyoyo-yo/Gasyori100knock

そもそもガウシアンフィルタってなんやねんみたいになる方もいるかもしれませんが、以前Compute Shaderを使ってガウシアンフィルタを実装する記事を書いた際に詳しく書いたので、そちらを参照してみてください。
www.hanachiru-blog.com

CustomFunctionNode

Custom Function Nodeとは名前の通り、ノードの挙動をHLSLという言語を用いて記述することができるノードです。
docs.unity3d.com

Custom Function Nodeへのコードの書き込み方としては、stringモードを使用して小さな関数を直接グラフ内に記述する方法と、外部のHLSLのインクルードファイルを参照する方法の2つがあります。

前者はお手軽にShaderGraph内にコードを書くのに対して、後者はShaderGraph外にあるファイルを読み込んできます。

f:id:hanaaaaaachiru:20210616201347p:plain
stringモードとfileモード

stringモード

まずはstringモードで作ってみましょう。

stringモードでもfileモードでも明示的に入出力の変数を定義しておかなければなりません。

種類 変数名
tex 入力 Texture2D
uv 入力 Vector2
sam 入力 Sampler State
color 出力 Vector4

今回は以上のものを定義してみました。Sampler Stateは見慣れないかもしれませんが,テクスチャの細かい仕様(フィルタリングモードとラップモードの設定)を決めるもので、テクスチャの特定のピクセルの色を取ってくるときなんかに必要になるので入力に入れておきます。
Sampler State ノード | Shader Graph | 10.0.0-preview.27


入出力・関数名を記入したらコードを書いていきます。

color = float4(0, 0, 0, 1);

color += (1.0 / 16.0) * SAMPLE_TEXTURE2D(tex, sam, uv + int2(-1, -1));
color += (2.0 / 16.0) * SAMPLE_TEXTURE2D(tex, sam, uv + int2(-1, 0));
color += (1.0 / 16.0) * SAMPLE_TEXTURE2D(tex, sam, uv + int2(-1, 1));
color += (2.0 / 16.0) * SAMPLE_TEXTURE2D(tex, sam, uv + int2(0, -1));
color += (4.0 / 16.0) * SAMPLE_TEXTURE2D(tex, sam, uv + int2(0, 0));
color += (2.0 / 16.0) * SAMPLE_TEXTURE2D(tex, sam, uv + int2(0, 1));
color += (1.0 / 16.0) * SAMPLE_TEXTURE2D(tex, sam, uv + int2(1, -1));
color += (2.0 / 16.0) * SAMPLE_TEXTURE2D(tex, sam, uv + int2(1, 0));
color += (1.0 / 16.0) * SAMPLE_TEXTURE2D(tex, sam, uv + int2(1, 1));

SAMPLE_TEXTURE2DはUnity側であらかじめ定義されている関数で,Textureの特定の位置にあるピクセルの色を取得できます。

#define SAMPLE_TEXTURE2D(textureName, samplerName, coord2)

HLSLに慣れている方はtex2D()を使いたくなりますが、どうやらエラーが出てしまうようなので注意してください。

あとfloat4であったりint2とかになっているのを初めて見るかたは驚くかもしれませんが、HLSLC#とは型の書き方が違うからですね。

fileモード

fileモードの場合は拡張子が.hlslとなるおようにファイルを作成(.cgincでもいけるっぽい?)して、参照します。

#ifndef SAMPLE_OUTLINE_INCLUDED
#define SAMPLE_OUTLINE_INCLUDED

void GaussianFilter_float(Texture2D tex, float2 uv, SamplerState sam, out float4 color)
{
    color = float4(0, 0, 0, 1);
    
    color += (1.0 / 16.0) * SAMPLE_TEXTURE2D(tex, sam, uv + int2(-1, -1));
    color += (2.0 / 16.0) * SAMPLE_TEXTURE2D(tex, sam, uv + int2(-1, 0));
    color += (1.0 / 16.0) * SAMPLE_TEXTURE2D(tex, sam, uv + int2(-1, 1));
    color += (2.0 / 16.0) * SAMPLE_TEXTURE2D(tex, sam, uv + int2(0, -1));
    color += (4.0 / 16.0) * SAMPLE_TEXTURE2D(tex, sam, uv + int2(0, 0));
    color += (2.0 / 16.0) * SAMPLE_TEXTURE2D(tex, sam, uv + int2(0, 1));
    color += (1.0 / 16.0) * SAMPLE_TEXTURE2D(tex, sam, uv + int2(1, -1));
    color += (2.0 / 16.0) * SAMPLE_TEXTURE2D(tex, sam, uv + int2(1, 0));
    color += (1.0 / 16.0) * SAMPLE_TEXTURE2D(tex, sam, uv + int2(1, 1));
}

#endif    

結構御作法が厳しくやりずらいところもありますが、uniform変数を定義できたりとメリットもあるようです。
Custom Function ノード | Shader Graph | 10.0.0-preview.27

細かい箇所は公式ドキュメントをご覧ください。

最終形

作成したCustom Function Nodeを活用して冒頭の貼ったような描画を行えるようにノードを組み合わせます。

f:id:hanaaaaaachiru:20210616210817p:plain
最終形

さいごに

どんな組み込み関数が定義されているとか、Unity側でどんな関数が定義されているとかがわかりやすくまとまった記事がなかなかないようで大変でした。

マイクロソフトのHLSLのドキュメントは非常に読みづらいし、CustomFunctionNodeでは動作しないような関数等(例えばtex2Dとか)も含まれていてうーんといった感じです。
組み込み関数 - Win32 apps | Microsoft Docs

もっと色々な情報が出回ればShaderGraph界隈も盛り上がるような気もしますが、参入の敷居がやや高めで手が出しづらいような気がします。

天上人の方がお慈悲でわかりやすい記事を書いてくださることを祈ってます。

ではまた。