ShaderGraph自定义节点

原文:Custom Nodes with CodeFunctionNode@Unity Docs

随着Unity发布了ShaderGraph,创建一个Shader比以往都要更加容易。然而无论提供多少个默认节点,都不能满足用户的自定义需求。因此,我们开发了一个自定义节点的API,可以在C#中创建新节点。或许能满足用户们刁钻的需求。

本文将介绍一种最简单的实现方法,称之为Code Function Node,接下来将展示详细细节。

让我们先创建一个C#脚本,在这个例子中,我们将脚本命名为MyCustomNode。为了使用Code Function Node API,需要引入UnityEditor.ShaderGraph并继承父类CodeFunctionNode

1
2
3
using UnityEngine;
using UnityEditor.ShaderGraph;
public class MyCustomNode:CodeFunctionNode{}

由于CodeFunctionNode仍是虚类,故接下来我们将实现其中要求我们实现的函数。

方法GetFunctionToConvert使用反射将一段方法转化为ShaderGraph可用的方法信息(MethodInfo)。

如下添加引用System.Reflection并重写GetFunctionToConvert方法,注意”MyCustomFunction”这段字符串正对应着我们要实现的Shader函数名,我们将在稍后实现。至此一个节点便完成了,同时在构造函数中设置name成员可以指定该Node在ShderGraph中显示的标题。

1
2
3
4
5
6
7
8
9
10
11
12
using UnityEngine;
using UnityEditor.ShaderGraph;
using System.Reflection;
public class MyCustomNode:CodeFunctionNode
{
public MyCustomNode(){
name = "My Custom Node";
}
protected override MethodInfo GetFunctionToConvert(){
return GetType().GetMethod("MyCustomFunction",BindingFlags.Static | BindingFlags.NonPublic);
}
}

接下来我们实现MyCustomFunction函数。

首先创建一个返回字符串的静态方法,在参数中我们可以定义当前Node的端口(Port)。这些端口名将直接映射到最终实现Shader中的参数名。这个例子中我们将设置两个输入的端口AB,数据格式都是动态维度向量(Dynamic Dimension Vector),同时输出一个动态维度向量Out。这一组端口便构成了一个Node的所有节点。同时我们需要添加Slot属性以区分不同的id。

1
2
3
4
5
static string MyCustomFunction(
[Slot(0, Binding.Node)] DynamicDimensionVector A,
[Slot(1, Binding.Node)] DynamicDimensionVector B,
[Slot(2, Binding.Node)] out DynamicDimensionVector Out)
{

更详细的数据格式列表请查阅API文档或文末附录。

这个函数需要返回一个包含着色器程序的字符串,如HLSL语句。本例中我们将定义一个Out=A+B的简单函数,这个函数大致如下:

1
2
3
4
5
6
7
8
9
10
11
12
static string MyCustomFunction(
[Slot(0, Binding.Node)] DynamicDimensionVector A,
[Slot(1, Binding.Node)] DynamicDimensionVector B,
[Slot(2, Binding.Node)] out DynamicDimensionVector Out)
{
return
@"
{
Out = A + B;
}
";
}

最后我们需要将这个节点显示再节点列表中,以供选择。这要求我们为类添加一个属性。如下添加,最后一个字符串将作为节点在节点列表中的显示名字(区别于显示在节点上方的节点标题)其余为分类文件夹。

1
2
3
4
[Title("Custom", "My Custom Node")]

public class MyCustomNode:CodeFunctionNode
{

这样一来我们就创建好了一个自定义节点,可以在ShaderGraph中尽情使用了!

其他例子

除了Out = A + B,我们还可以定义很多东西。下面这个例子展示了如何在三个多维向量中选取最小值。

1
2
3
4
5
6
7
8
9
10
11
12
13
static string Min3(
[Slot(0, Binding.None)] DynamicDimensionVector A,
[Slot(1, Binding.None)] DynamicDimensionVector B,
[Slot(2, Binding.None)] DynamicDimensionVector C,
[Slot(3, Binding.None)] out DynamicDimensionVector Out)
{
return
@"
{
Out = min(min(A,B),C);
}
";
}

下面这个例子是通过一个布尔值决定是否反转法向量。其中Normal端口被绑定为世界坐标法线(World Space Normal),即没有线段连入这个端口是,默认使用世界坐标法线。

1
2
3
4
5
6
7
8
9
10
11
12
static string Min3(
[Slot(0, Binding.WorldSpaceNormal)] Vector3 Normal,
[Slot(1, Binding.None)] Boolean Predicate,
[Slot(2, Binding.None)] out Vector3 Out)
{
return
@"
{
Out = Predicate == 1 ? -1 * Normal : Normal;
}
";
}

附录

Port Types

Argument Type Data Type
Boolean Boolean
Vector1 Vector1
Vector2 Vector2
Vector3 Vector3
Vector4 Vector4
Color Vector4 (with a ColorRGBA Port Binding)
ColorRGBA Vector4 (with a ColorRGBA Port Binding)
ColorRGB Vector3 (with a ColorRGB Port Binding)
Texture2D Texture2D
Texture2DArray Texture2DArray
Texture3D Texture3D
Cubemap Cubemap
SamplerState SamplerState
DynamicDimensionVector DynamicVector
Matrix4x4 Matrix4
Matrix3x3 Matrix3
Matrix2x2 Matrix2
DynamicDimensionMatrix DynamicMatrix
Share