Node Based Shader Editor
Introduction
I really like creating tools that enables other developers to make their ideas a reality, so when the scripting course came around and we started learning about node based scripting, I was immediately intrigued as it's a very intuitive and refreshing way to create and interact with content. When we started the specialization course I decided to combine my new found love for visual scripting with my passion for shaders. I have always thought about math in a visual way so shaders has been the right forum for me to put my knowledge to use in a fun and creative way.
The combination of these two resulted in what I call:
a Node Based Shader Editor.
Project Info
This project was made during our specialization course 5 Weeks half time.
The back end used is The Game Assembly's in house 2D engine "TGA2D"
and a bare bones Node Editor made by one of our teachers using Dear ImGui as part of our Scripting Course.
Graph Evaluation
The nodes in the graph are evaluated from right to left. Starting with the output node, the input pins are evaluated by requesting a variable to perform an operation on from the node on the other side of the attached link. If no link is present, a value is fetched from an input field on the node. When a node is evaluated, a single line of HLSL code will be written to the shader, resulting in the declaration of a variable. The resulting variable is then passed on to the pins that request a variable from that node.
Variable Name Generation
Because every node results in a declaration of a variable within the shader, we need to make sure that we never use the same variable name twice. So when a node wants to declare a variable, it requests a unique name from the ShaderBuilder. The ShaderBuilder uses a string consisting of the alphabet and a internal counter, which increments once every request, to generate a unique name following this pattering: a, b, c..., aa, ba, ca... ba, bb, bc, ca, cb, cc... and so forth. An implementation of this is shown in the image below.
Code Generation
Each node is responsible for appending a single line of HLSL code to the shader. Each node holds a so called format line which is a line of HLSL with placeholders for the variables used in the line and the resulting declaration.
An example of such format is "float % = min(%, %);". When a node has fetched all relevant information through its input pins, it passes its format and a list of the variables to the ShaderBuilder. The ShaderBuilder then replaces the placeholders with the variables and appends the result to the shader code that's being generated.
Functions/Groups
Selecting a set of nodes and pressing ctrl + F results in a new node containing the selected nodes which can be reused: a function. When a function node is evaluated, it delegates the requests to the sub graph it contains. Implementation wise, I scan all the links in graph, searching for any link that connects a node inside the selection with a node outside the selection. When found, this link can be used for deciding the final input pins and output pins on the function node. This proved to be a powerful feature of the shader editor, resulting in ease of reusing and splitting big complex graphs into small reusable chunks. In the following images, one can see an example of the same shader, but one without functions, and one with functions.
Afterthoughts
Under the development of this tool, I never got the change to have anybody else than myself test the tool. So it is very customized after how I would interact and use this kind of tool. This results in a great loss of very important feedback that could help the tool be more general and intuitive for other developers.