Search Unity

With the recent addition of Shader Graph, it’s now easier than ever to create custom shaders in Unity. However, regardless of how many different Nodes we offer by default, we can’t possibly cater for everything you might want to make. For this reason, we have developed a Custom Node API for you to use to make new Nodes in C#. This allows you to extend Shader Graph to suit your needs.

In this blog post, we will take a look at one of the ways you can do this in Unity 2018.1 beta. The simplest way to create custom Nodes that create shader functions is the Code Function Node. Let’s take a look at how to create a new Node using this method.

Let’s start by creating a new C# script. For this example, I have named the script MyCustomNode. To use the Code Function Node API you need to include (or add the class to) the namespace UnityEditor.ShaderGraph and inherit from the base class CodeFunctionNode.

The first thing you will notice is that MyCustomNode is highlighted with an error. If we hover over the message we see that we need to implement an inherited member called GetFunctionToConvert. The base class CodeFunctionNode handles most of the work that needs to be done to tell the Shader Graph how to process this Node, but we still need to tell it what the resulting function should be.

The method GetFunctionToConvert uses Reflection to convert another method into an instance of MethodInfo that CodeFunctionNode can convert for use in Shader Graph. This simply allows us to write the shader function we want in a more intuitive way.

For more information on Reflection see Microsoft’s Programming Guide on Reflection (C#).

Add the namespace System.Reflection and the override function GetFunctionToConvert as shown in the example below. Note the string that reads MyCustomFunction. This will be the name of the function that is written into the final shader. This can be named whatever you wish to suit the function you are writing and can be anything that doesn’t begin with a numeric character, but for the rest of this article we will assume its name is MyCustomFunction.

Now that our script errors are resolved, we can start working on the functionality of our new Node! Before we continue we should name it. To do this add a public constructor for the class with no arguments. In it, set the variable name to a string that contains the title of your Node. This will be displayed in the title bar of the Node when it appears in a graph. For this example, we will call the Node My Custom Node.

Next, we will define the Node’s function itself. If you are familiar with Reflection you will notice that the method GetFunctionToConvert is attempting to access a method in this class called MyCustomFunction. This is the method that will define the shader function itself.

Let’s create a new static method of return type string with the same name as the string in the method GetFunctionToConvert. In this case it’s MyCustomFunction. In the arguments of this method we can define what Ports we want the Node to have. These will map directly to the arguments in the final shader function. We do this by adding an argument of a type supported in Shader Graph with a Slot Attribute. For now, let’s add two arguments of type DynamicDimensionVector called A and B and another out argument of type DynamicDimensionVector called Out. Then we will add a default Slot Attribute to each of these arguments. Each Slot Attribute needs a unique index and a binding, which we will set to None.

For a full list of types and bindings that are supported see the CodeFunctionNode API documentation on GitHub.

In the following method we will define the contents of the shader function in the return string. This needs to contain the braces of the shader function and the HLSL code we wish to include. For this example let’s define Out = A + B;. The method we just created should look like this:

This is exactly the same C# code that is used in the Add Node that comes with Shader Graph.

There is one last thing we need to do before we have a working Node: tell it where to appear in the Create Node Menu. We do this by adding the Title Attribute above the class.This defines a string array that describes where it should appear in the hierarchy in the menu. The last string in this array defines what the Node should be called in the Create Node Menu. For this example, we will call the Node My Custom Node and place it in the folder Custom.

Now we have a working Node! If we return to Unity, let the script compile then open Shader Graph we will see the new Node in the Create Node Menu.

Create an instance of the Node in the Shader Graph. You will see it has the Ports we defined with the same names and types as the arguments to the MyCustomFunction class.

Now we can create all kinds of different Nodes by using different Port types and bindings. The return string of the method can contain any valid HLSL in a regular Unity shader. Here is a Node that returns the smallest of the three input values:

And here is a Node that inverts normals based on a Boolean input. Note in this example how the Port Normal has a binding for WorldSpaceNormal. When there is no Edge connected to this Port it will use the mesh’s world space normal vector by default. For more information see the Port Binding documentation on GitHub. Also note how when using a concrete output type like Vector 3 we have to define it before we return the shader function. Note that this value is not used.

Now you are ready to try making Nodes in Shader Graph using Code Function Node! But this is, of course, just the beginning. There is much more you can do in Shader Graph to customize the system.

Stay tuned to this blog and talk to us on the Forums!

Добавить комментарий

Вы можете использовать эти теги и атрибуты HTML: <a href=""> <b> <code> <pre>

  1. WHERE IS THE BEST PLACE TO PUT THIS SCRIPT?

    1. Looks like this uses the UnityEditor namespace (using UnityEditor.ShaderGraphs;), so I’ve put it «Assets>[ProjectName]>Shaders>Editor». Regardless if your overall hierarchy, I believe the namespace part requires it live in a folder titled «Editor» so this code can be left out from builds.

  2. has anyone moaning about the sting part here actually even bothered to look at the examples posted by andy touch and similar postings?

    I really think you are expecting it to be far more unusable than it is.

    Also you are defining the bindings using a string, not the entire shader. Calm down.

    1. It has little to do with how «usable» it is and everything to do with how this is an alpha/proof-of-concept approach being passed off as a production-ready system. If you *ever* have the developer writing anything script-related as a plain string (and embedded in another script file, no less), then someone, somewhere, royally screwed up. That is because writing script as a string within another script is highly-coupled, virtually-untestable, and difficult to maintain — the three most common signs of bad code design.

      And it’s not like this is the only way to do this, either. Myself and others have posted numerous alternative approaches in other comments. This system is nothing short of lazy and sloppy, and it will be addressed within a month (if not a week) of this feature’s official release by an editor add-on on the Asset Store. And while the problem will be effectively solved at that point, the issue is that the Unity Dev Team created the problem in the first place seemingly without even recognizing that it *is* a problem.

  3. Is it still possible to write shaders the old boring way? I don’t see how nodes really help with writing anything requiring custom algebra. I don’t mind the node system, as long as it doesn’t get in the way of my boring old HLSL approuch. I’m not to fond of creating code in a huge string like I have brain damage or something.

    1. Yes, absolutely. Shader Graph is a system on top of the existing shader system in Unity. You won’t be able to use your shaders written the old way in Shader Graph, but it also won’t prevent you from doing what you’ve always been doing.

  4. Interesting! But indeed seems like the wrong way around — you should concentrate on the hlsl first — have an hlsl file full of functions and nodes are created from that — if u need a little C# to create the nodes then ok but can you not parse the hlsl to get function names, input/output to a degree?

  5. It’s not so much that you released an API based on writing HLSL in a string literal that bothers me. We’ve all done worse things when necessary.

    It’s the fact that the announcement of this aspect of the API wasn’t wrapped in shameful apologies and embarrassed justifications. It concerns me — not that you’ve done it like this, but that you don’t seem to think it’s anything to be bothered about.

  6. Could you expand more on the «why» to this approach? I echo the other comments here, I feel this is long winded and prone to error writing the HLSL as a string… beyond simple stuff like in this blog, in production (using proprietary engines that have shader graphs with custom nodes), these custom nodes can get quiet elaborate, like, it might be a lengthy «Draw Object Outline» node…

    So — hearing the why (and how you arrived at this approach compared to all these others suggested/attempted) would be great as it would help put some context to it all. Cheers!

  7. This is the worst workflow I’ve ever seen in Unity.

  8. Why not just define the inputs and outputs as class members and have a virtual GenerateCode function? In any case the awkward GetFunctionToConvert reflection stuff could be hidden by having a ShaderFunction attribute that you put on whatever function you want.

  9. So. I am liking. strings’s not a problem for me. I’m used to writing shaders without autocomplitionDD
    I’ve been eagerly awaiting for 2018 release.

  10. writing shader function body in a string ? seriously ? this is what you released ?
    did you ever think what this might look like exposed for the user when you were designing shader graph ?
    that’s laughable

  11. It’s cool and simple but I dislike the fact that I will be forced to either use C# code highlighting&autocompletion or HLSL. Unless I missed something and there are options to do that, in let’s just say, VS Code.

  12. Binding the method via a reflection reference? Returning the entire shader function code as a string? This feels super clunky, not gonna lie. Why can’t you pass a reference to the method itself instead of reflecting it, for example? Why can’t you just supply a shader file instead of going through the hassle of converting it into a string literal?

    I’m sure there must be technical reasons why it was implemented this way, but for the life of me I can’t imagine what they could be other than the Shader Graph team having designed themselves into a corner on this.

    1. We could do this via a function reference, but it would just tidy the user facing API a bit. Well look into it but its not a huge priority for us. We use reflection to access the function arguments and convert them to ports, all this would do is hide that.

      As for the string literals we do this for simplicity, we current ship ~150 nodes, with the vast majority using this abstraction. Splitting the HLSL into separate files isn’t optimal for us. However, message received. You can still do what you want to do via this API though. Something along the lines of return File.Read("otherfile.hlsl"). This just isnt mentioned in this blog post (maybe it should have been).

      1. I feel like the string is mostly fine — it’s tightly coupled with the names and such of the surrounding function, so it’s easier to have it inline. However, I feel there’s 2 paths forward:

        ->Most ambitious: Generate HLSL from a C# subset, Xenko style (http://doc.xenko.com/latest/en/manual/graphics/effects-and-shaders/shading-language/shader-classes-mixins-and-inheritance.html). It seems unity is doing _some_ of that in their new pipelines already, and the new math library even makes more syntax match. The idea of being able to build shaders out of reusable C# snippets would be an incredible dev experience.

        -> Little more reasonable: Work with the UnityVS team to recognize these snippets as HLSL, and use the HLSL coloring / autocomplete on them.

      2. I don’t really dispute that the existence of this class may or may not be necessary (the mapping needs to be done somewhere) but this strikes me as a very odd way of going about it. Using this approach, the C# class is almost entirely arbitrary boilerplate while the embedded HLSL code is represented as a string literal, which means no syntax coloring, compile-time type safety, Intellisense, or any of that other goodness.

        IMHO, the C# code should be entirely abstracted away, auto-generated for any but the people who have specific reasons to do it manually (whatever those reasons might be). Maybe when the developer chooses the option to create a custom shader node, there is a properties window for the file that displays the ins and outs similarly to how properties for shaders are currently handled, and that is where you can specify what properties the node will have. Or, when they get defined in the shader file, the C# file gets updated with the properties when the shader is «compiled».

        As far as the HLSL code itself, while it might technically be the most «optimal» to have the shader code and the C# code in a single script file, that doesn’t make it the best option. This approach is extremely unfriendly to anyone who doesn’t know HLSL by heart and can code out a shader file in their sleep, and even for those people, the ability to use regular HLSL development tools and not having to essentially write their shader using vanilla Notepad is still a plus. (And yes, while you could write the code in an .hlsl file and load the string with a File.Read, that is an extremely arbitrary step that is just avoiding the problem rather than actually solving it.)