(翻译)使用代码创建自定义Shader Graph自定义节点

发布于 11 天前  14 次阅读


本篇文章为Unity官方博客的翻译整理
原文链接
日期:2018年5月27日
作者:Matt Dean
翻译:咲夜詩

序言

在新增的Shader Graph中,创建自定义shader变得前所未有的简单。
但是,无论我们提供多少个节点,都可能无法满足您的需求。
因此,我们研发了一套自定义节点的api用于使用C#创建新的节点。
这将帮助您拓展Shader Graph。

最简单的创建自定义节点的方式是使用Code Function Node。
创建一个新的C#脚本,命名为MyCustomNode。
包含Code Function Node需要使用到的域名UnityEditor.ShaderGraph。
然后继承CodeFunctionNode基类。

using UnityEngine;
using UnityEditor.ShaderGraph;

public class MyCustomNode : CodeFunctionNode
{

}

一开始MyCustomNode会报错,需要继承并填充GetFunctionToConvert方法。
CodeFunctionNode处理大部分工作,包括告诉Shader Graph如何处理这个节点,我们需要指定输出方法。

GetFunctionToConvert:使用反射将另一个指定方法转化为MehodInfo的实例,以用于Shader Graph。
添加新的域名System.Reflection

下例中演示了GetFunctionToConvert。
注意其中MyCustomFunction字符串,其将被写入到最终的shader。
这个名称可以被任意命名,不能是数字开头。

using UnityEngine;
using UnityEditor.ShaderGraph;
using System.Reflection;

public class MyCustomNode : CodeFunctionNode
{
    protected override MethodInfo GetFunctionToConvert()
    {
        return GetType().GetMethod("MyCustomFunction",
            BindingFlags.Static | BindingFlags.NonPublic);
    }
}

现在报错被解决了,我们开始写节点函数。

我们先命名这个节点。首先添加一个无参自构方法,用name变量来包含节点标题,name将显示在图形编辑界面的节点的标题栏上。例如:My Custom Node。

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);
    }
}

接下来,我们来定义节点函数。
反射方法GetFunctionToConvert会访问名叫“MyCustomFunction”的方法,也就是我们将要定义的shader方法。

我们创建一个静态的方法,返回string类型,名称与刚才的字符串相同,也就是“MyCustomFunction”。
在参数中,我们定义想要的输入/输出,这些参数会映射到最终的shader。
添加的参数需要是Shader Graph支持的类型,其带有一个Slot特性。
现在我们添加2个DynamicDimensionVector参数,叫做A和B,以及Out类型的Out。
现在我们添加一个默认的Slot特性给每个参数,Slot特性需要一个唯一编号和一个binding,这里我们设置binding为None。

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

}

Github的CodeFunctionNode API中可以查看完整的变量类型和支持的binding。

我们需要在返回字符串中定义shader函数。这需要包含shader函数的括号和include的HLSL代码。
例如我们定义Out=A+B;我们需要创建的方法如下:

    static string MyCustomFunction(
        [Slot(0, Binding.None)] DynamicDimensionVector A,
        [Slot(1, Binding.None)] DynamicDimensionVector B,
        [Slot(2, Binding.None)] out DynamicDimensionVector Out)
    {
        return
            @"
{
    Out = A + B;
} 
";
    }
}

这和我们在Shader Graph中添加节点是使用的一样的代码。

最后,我们要在“创建节点目录”中确定新创建的节点的路径,在class上方添加一个Title特性。
例如,我们需要在文件夹Custom中放置节点My Custom Node:

[Title("Custom", "My Custom Node")]
public class MyCustomNode : CodeFunctionNode
{

现在节点可以被访问了,返回Unity等待脚本编译完成,打开Shader Graph,在Create Node Menu中查看新节点。

在Shader Graph中创建一个节点实例后,可以看到我们刚才定义的名字相同的参数作为输入/输出。

现在我们使用不停的输入/输出类型和binding创建不同的节点。
返回的string中可以包含任何常规shader中的HLSL语法。
下面是一个返回“三个输入中的最小值”的节点:

    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);
} 
";
        }
}

这里有一个节点,通过bool输入反转法线。
注意本例中Normal输入的binding为WorldSpaceNormal。
当这个输入端没有被链接时,会默认使用世界空间,更多信息参见Port Binding documentation
注意,使用具体类型的输出,比如Vector 3,需要在return前先定义参数以表明是一个未经使用的值。

    static string FlipNormal(
        [Slot(0, Binding.WorldSpaceNormal)] Vector3 Normal,
        [Slot(1, Binding.None)] Boolean Predicate,
        [Slot(2, Binding.None)] out Vector 3 Out)
    {
        Out = Vector3.zero;
        return
            @"
{
    Out = Predicate == 1 ? -1 * Normal : Normal;;
} 
";
        }
}

现在 我们已经为使用Code Function Node创建Shader Graph自定义节点做好了准备。
这只是一个开始,你在Shader Graph中还可以自定义更多。


关注成长,注重因果。