SPARKCREATIVE Tech Blog

https://www.spark-creative.jp/

【UE5】 Customノードについて触れてみよう!

はじめまして! そしてこんにちは!
エンジニアの桒村です。
私自身、今回が初めてのブログ投稿になるのですが、今回はUnrealEngin5におけるCustomノードについて話していきたいと思います。

まず初めに...

Customノードとは?
簡潔に説明するとシェーダーコードを使用することができるノードのことになります。
※注意すべきこととして、記述にはHLSLのシェーダーコードで書く必要があるという点があります。
↓ 以下がCustomノードになります。

ちなみにCustomノードの詳細ではこのように構成されています。

順を追って説明すると...

【Code】
実際にHLSLコードを書く場所になります。

【Output Type】
返り値になります。現在では、以下の返り値があらかじめ用意されています。
※ちなみに、設定できる返り値は1つだけになりますのでご注意ください。

【Description】
Customノード自体の名前を変更することができます。

【Inputs】
入力値になります。(変数)

【Additional Outputs】
2つ目以降の返り値を使用したい場合に使用します。

【Additional Defines】
Defineの宣言をしたい時に使用します。(マクロや静的型の宣言)
#define ←よく見るやつですね

【Include File Paths】
『ush』や『usf』のIncludeを宣言したいときに使用します。
※『ush』や『usf』とは作成しているプロジェクトで使用されるShader用のファイルになります。

【Desc】
Customノードにコメントを追加することができます。
こちらは他のノードにもついている機能になりますね。

より詳しく知りたい方へ... (公式さんが公開しているドキュメントになります)
https://docs.unrealengine.com/4.27/ja/RenderingAndGraphics/Materials/ExpressionReference/Custom/


ちなみにHLSLコードって何かな?と思った方へ
HLSLコードとは...
High Level Shading Language(ハイレベル シェーディング ランゲージ)の略称で、
Direct3D で使われるプログラマブルシェーダーのためのプロプライエタリなシェーディング言語になります。(※Wikipediaから一部抜粋)

わかりにくいな! ということで、もっとわかりやすく説明すると...
CやC++といったプログラム言語に近い表記方法で書くことができる言語であり、様々なグラフィカルな挙動や計算を実行することを可能にするものになります。
とりあえずHLSLが使えこなせれば、シェーダー達人になります!(多分)

検索エンジンにて、『HLSLコード』などで調べるとたくさん情報が載っていますので、もし興味がある方はぜひ調べてみてください。

それでは、本題に入りたいと思います。

作業環境

windows 10
visual studio 2019
・UnrealEngine 5.0.3

プロジェクト設定

まず、UnrealEngine5.0.3を起動して、プロジェクトを作成してください。

プロジェクト名は適当で大丈夫ですが、今回はCustom用のプロジェクトということで、Projects_CustomNodeにしてみました!


次に、プロジェクトを開いたら以下のような画面になると思います。

左下のコンテンツドロワーから作業用のフォルダを新規作成し、作成フォルダから、右クリックでマテリアルを選択します。
マテリアルファイルをM_CustomNodeというファイル名にしてから、マテリアルファイルを作成します。
今回は、ほぼこちらのマテリアルファイル上で処理を行うようになります。

プロジェクト設定は以上になります。

実際にCustomノードを触ってみよう!

では、実際に触って見ようと思います。

今回は、以下のような魔法陣っぽいエフェクトみたいなものを作成していきます。

Customノードに直接コードを書くことは可能ですが、ノードに直接書くには少し扱いずらいと思ったため、今回はコードの下書き用にvisual studioを使用しました。
visual studio側で処理を組み、Customノード側にコピペするような流れになります。

以下がノード内の最終的な完成図になります。

ぱっと見で自分で作っておきながら処理が多いな!って思いました(^▽^;)
部分的に、もう少し簡略化できそうな雰囲気ですね...

[Scale]

return p * r;

[Rotate]

float2x2 m = float2x2(cos(rad), sin(rad), -sin(rad), cos(rad));
return mul(m , p);

[Circle]

float leng = length(p);
float d = min(abs(leng - r1), abs(leng - r2));
if (r1 < leng && leng < r2) pre /= exp(d) / r2;
float res = power / d;
return clamp(pre + res, 0.0, 1.0);

[Radiation]

float angle = 2.0*PI/float(num);
float d = 1e10;
for(int i = 0; i < 360; i++)
{
     if (i >= num) break;
     float _d = (r1 < p.y && p.y < r2) ? abs(p.x) : min(length(p - float2(0.0, r1)), length(p - float2(0.0, r2)));
     d = min(d, _d);
      
    // 回転
     float2x2 m = float2x2(cos(angle), sin(angle), -sin(angle), cos(angle));
     p = mul(m , p);
}
float res = power / d;
return clamp(pre + res, 0.0, 1.0);

[Calc]

 float angle = 2.0 * PI / float(n);
 for(int i = 0; i < n; i++)
 {
    float leng = length(q - float2(0.0, 0.875));
    float d = min(abs(leng - r1), abs(leng - r2));
    if (r1 < leng && leng < r2) pre /= exp(d) / r2;
    float res = power / d;
    pre = clamp(pre + res,0.0, 1.0);

    float leng_2 = length(q - float2(0.0, 0.875));
    float d_2 = min(abs(leng - r1_2), abs(leng - r2_2));
    if (r1_2 < leng_2 && leng_2 < r2_2) pre /= exp(d_2) / r2_2;
    float res_2 = power_2 / d;
    pre = clamp(pre + res_2, 0.0, 1.0);

    float2x2 m = float2x2(cos(angle), sin(angle), -sin(angle), cos(angle));
    q = mul(m , q);
 } 
return pre;

[RectAngle]

for(int i = 0; i < n; i++)
{
   float2 half1 = (0.85/sqrt(2.0));
   float2 half2 = (0.85/sqrt(2.0));
   q = abs(q);
   if ((half1.x < q.x || half1.y < q.y) && (q.x < half2.x && q.y < half2.y))
   {
     pre = max(0.01, pre);
   }
   float dx1 = (q.y < half1.y) ? abs(half1.x - q.x) : length(q - half1);
   float dx2 = (q.y < half2.y) ? abs(half2.x - q.x) : length(q - half2);
   float dy1 = (q.x < half1.x) ? abs(half1.y - q.y) : length(q - half1);
   float dy2 = (q.x < half2.x) ? abs(half2.y - q.y) : length(q - half2);
   float d = min(min(dx1, dx2), min(dy1, dy2));
   float res = power / d;
   pre = clamp(pre + res, 0.0, 1.0);

   float2x2 m = float2x2(cos(angle), sin(angle), -sin(angle), cos(angle));
   q = mul(m , q);
}
return pre;

ノード内の処理は以上になります。
所々、同じ内容で分けている箇所がありますが、処理自体は同じ内容になりますので今回は省略させていただきました。

最後に

今回は、出力先をポストプロセス側にしており、表示だけできるように作成しましたが、エフェクトとして使用したい場合は、出力先を変更する必要があります。また、MaterialInstanceを使用していないため、所々固定値を使用していますが、形を変更したい場合は、MaterialInstanceにてパラメーター化ができる形にしたほうがよいと思われます。
Customノードについて話しましたが、個人的にCustomノードを使うことで既存のノード以外で新しく機能を追加することも可能ですので、工夫次第で表現できるものは広がるように思いました。
もしグラフィックス周りに興味がある方 or マテリアルで何か自作してみたい方は、Customノードで作成してみるのもありだと思いますので、ぜひぜひ試してみてもらいたいです。
それでは、今回はここまでになります。また次回よろしくお願いいたします!


今回の魔法陣エフェクトの参考元:
www.youtube.com