開発環境
Unity2018.2.3
ShaderGraph3.0 preview
ShaderGraphの自作ノード(カスタムノード)の作成の仕方
必要な要素としては以下の通り
- ノードの呼び出し収納先の指定
- ノードのタイトル
- ノードの入力と出力の設定
- 実際のシェーダーコードの記述
参考にしたURL
http://r-ngtm.hatenablog.com/entry/2019/01/19/190218
https://blogs.unity3d.com/jp/2018/03/27/shader-graph-custom-node-api-using-the-code-function-node/
参考にしたソースコード
https://gist.github.com/rngtm/d8b4bcd8785f24193bbadcf25325cdb4
今回作成するもの
入力される4つの値をまとめたものを出力するためのものです。
手順
手順としては以下の通り
下準備
C#のソースコードの作成、継承としてCodeFunctionを使用する
using UnityEditorShaderGraph
public class クラス名:CodeFunctionNode
{
・・・
}
といった感じです。
ノードの呼び出し収納先の指定
このコードの上に[Title("階層名","メニューに表示されるノード名")]を付けると
CreateNodeで行いやすくなります。
※ ShaderGraph ver 3.0.0では階層は1段しかもうけれない模様 ("OO/OO"と指定したが反映されず)
ノードのタイトル
ノードのタイトルは、実際のShaderGraphに使用した際に、Nodeのタイトルに当たるものです。
以下の図でなら、「MyIntergrateNode」がそれにあたります。
クラス名()
{
name = "ノードでのタイトル名"
}
ノードの入力と出力の設定
[Slot(0, Binding.None)] Vector1 r,
[Slot(1, Binding.None)] Vector1 g,
[Slot(2, Binding.None)] Vector1 b,
[Slot(3, Binding.None)] Vector1 a,
[Slot(11,Binding.None)] out Vector4 ret
ノードの入力と出力には、Slot[]を使用します。
Slotの第一引数は、SlotIdなので、重複がしなければ問題ありません。
出力には、変数の前に out を使用する事で、出力の扱いになります。
実際のシェーダーコードの記述(今回つくったもの)
using UnityEngine;
using UnityEditor.ShaderGraph;
using System.Reflection;
[Title("Custom","Integration")]
public class IntegrateShaderNode : CodeFunctionNode
{
public IntegrateShaderNode()
{
name = "MyIntegrateNode";
}
protected override MethodInfo GetFunctionToConvert()
{
return GetType().GetMethod("ShaderFunction",
BindingFlags.Static | BindingFlags.NonPublic);
}
private static string ShaderFunction
(
[Slot(0, Binding.None)] Vector1 r,
[Slot(1, Binding.None)] Vector1 g,
[Slot(2, Binding.None)] Vector1 b,
[Slot(3, Binding.None)] Vector1 a,
[Slot(11,Binding.None)] out Vector4 ret
)
{
ret = Vector4.zero;
return @"
{
ret = float4(r,g,b,a);
}"
;
}
}
エラーが出た場合の解決方法 (体験したやつ)
カスタムノードで、ノード自体は問題ないが、中のShaderコードでエラーが出た場合以下のような感じの見た目になります
こうなると正しく動かないので、修正の必要があるのですが、慣れてないと意外とひっかかったりします。
この章では、引っかかった例を記述します。
Vectorの形に引っ張られて、Shaderの書き方から逸脱した
今回のOutがVector4型で記述しているので、それにつられるパターンです
//間違った例
ret = Vector4(1,1,1,1);
//正しい例
ret = float4(1,1,1,1);
out出力が初期化かかっていると思い込んでしまった。
呼び出された関数内で、初期化を行っているので、そのまま使用できると思い込んでしまったものです。
処理自体はあるから、意外と引っかかった人はおおいのではないでしょうか?そうでもない?
//間違った例
ret = ret + float4(1,1,1,1);
//正しい例
ret = 何かしらの初期化や計算して導かれる値