Thoughts on Shader Nodes

I’ve been thinking of several enhancements for Shader Nodes and I’d like shader nodes users to chime in.

There are a lot of nice things about the Shader Nodes system, but other things that are not so great.
My main concern is that they can be really cumbersome to write, because they are described in a very declarative way. Without a proper visual editor it’s frankly easier to code a good old shader from scratch than making a shader node based material definition.
And since there is no official core team provided editor, I’d like to address this, by providing an easier way to code them, and build them from java code.

I was thinking of changing several things:
First the j3sn files. They are shader node definitions files that describes the inputs, output, shade code files, and optionally some documentation for a shader node.
For those unfamiliar with it looks like this :

ShaderNodeDefinitions{
    ShaderNodeDefinition Dash {
        //Vertex/Fragment
        Type: Fragment
        //Shader GLSL<version>: <Path to shader>
        Shader GLSL100: MatDefs/dash100.frag    
        Documentation{
            //@input <glsltype> <varName> <description>
            @input vec4 inColor The input color
            //@output <glslType> <varName> <description>
            @output vec4 outColor The modified output color
        }
        Input {
            //<glslType> <varName>            
            vec4 inColor           
        }
        Output {
            //<glslType> <varName>
            vec4 outColor
        }
    }
}

then you have the actual shader code in the dash100.frag

void main(){
	float len = length(gl_FragCoord.xy);
	outColor = inColor;
	float factor = float(int(len * 0.25));
    if(mod(factor, 2.0) > 0.0){
        discard;
    }
}

Quite verbose, several separated files, and very declarative (except for the shader code part of course).

At generation, the code in the main() part will be added to the main function of the generated shader, all inputs and outputs will be “wired” with actual variables, and names will be prefixed by their shader node name to avoid name collision across several nodes etc etc…
Also for example here the len variable will stay as is, and that can be a problem if you declared another len variable in another node.
To avoid this we need more complex mechanism that parse the shader in an abstract syntax tree to detect variables and so on. @javasabr already proposed a solution for this, but I’m not sure we should go on this perilous path.

I was thinking of removing j3sn files altogether, just keep the glsl code file. It would look like this in a dash100.frag file:

/**
*  inColor The input color
*  outColor The modified output color
*/
#pragma ShaderNode
void Dash(const in vec4 inColor, out vec4 outColor) {
	float len = length(gl_FragCoord.xy);
	outColor = inColor;
	float factor = float(int(len * 0.25));
    if(mod(factor, 2.0) > 0.0){
        discard;
    }
}

We have the same information than with previous way, but in a more concise and programmatic way:
the name of the defintion → the function name following the #pragma ShaderNode
the type is in the file name → file.frag, then it’s a fragment type of node
the documentation as a standard “javadoc” style doc
input and outputs handled by the glsl in and out keywords in the function declaration. Note that the function can also return a non void result that would also be handled as an output.

Along with an easier way to declare a Node, this also presents several huge advantages:

  • The content of the frag file can be added as is in the generated shader code. And the call to the node in the main method is just a classic function call, no more variable “wiring”.
  • No need for name spacing anymore. All is properly scoped in a standard glsl function.
    The only missing feature we’d have compared to now is default values for inputs, but that could be handled in the #pragma directive. (note that the pragma directive is ignored by the shader compiler if it is unknown to it).

I’d really like to try that out.

The second thing I’d like to do is to add a java api to build a shader with shader nodes.
I have no clear idea yet, but something like:

Variable color = new Variable("vec4", "color").set("vec4(0.0)");
Shader.node("dash100.frag", "Dash")  // finds the Dash node
     .inputs(
            new Variable("vec4", "inColor").set("vec4(1.0)"), 
            ...
     )
     .outputs(color);
// then use color as an input for another node for example

What do you guys think?

5 Likes

I think the main reason to have Shader Node System is to allow artists to create new shaders, so we should have a visual editor in both cases.
Also, I prefer to have more declarative style for this. I have a visual editor to edit j3sn files and shader nodes materials, it’s fine for me :slight_smile:

This wouldn’t prevent to have an editor.
The j3md would stay declarative though, only the j3sn would have a programmatic alternative.
In any case I will keep j3sn files for backward compatibility.

1 Like

As an artist, I would love to have simple ability to manipulate with a shader. Shader nodes - modern word for us regular non-programmer-artists. I understand that they were developed for simplification of coding process but this idea of being user-friendly made shader nodes popular among us, artists. Example: UE4, Blender BGE, Armory engine Godot is currently throwing all the power to create easy to work node system for the artists and even Unity made their own shader nodes for artists.

Just a simple example:
I would never ever write a shader for this and I don’t have a programmer who has free time to help me out, so I made it in blender shader nodes and use it in my projects

Fake SSS

1 Like

Yeah but you didn’t make the nodes code. Behind those nodes you use, there is code.
My point is to make the way to create a new node more programmatic. But the way to assemble nodes will stay declarative, and editor friendly.

Also there is a need for a programmatic way to create shaders through nodes, but both way are not incompatible.

But it requires full reimplementing on editor’s side :frowning: also, I think we will throw AST shader generator…

All I care about is only a final picture and product. Thre is no matter how good or bad the code is while we have: decent framerate and nice visuals.

So for me:
I would love to have a shovel with the shaft built in to dig a nice hole I planned, rather than trying to understand how to assemble shovel and create shaft for it from wood. I hope my analogy is understandable.

2 things:

  • first: it won’t. as said the old system will stay in place.
  • second: I will never refrain to make JME evolve because it would imply some work on a third party. Versions are made for this.
    And note that the idea behind talking about it here, is precisely to not break your stuff by pushing drastic changes in the core.

You will have the exact same shovel as before, except that for the manufacturer, the process to make the shaft will have been easier.

Do you like to work on improving deprecated things? I think no :wink: You are going to make .j3sn deprecated, so I will need to migrate to the new system.

I like it. It’s exactly the way I original proposed the idea all of those years ago as “shaderlets”.

It also makes it really easy to evolve shaders into shader nodes since you can just start breaking your main method up into separate functions and then eventually spin them off into separate nodes.

What exactly is the point of the #pragma versus just a different extension? Is your thinking that a single file could contain multiple ‘nodes’ just by having multiple functions?

Well so be it.
This won’t have a huge impact on the current system. The difference will be in the way to load the J3md, and for this I guess you already rely on the engine.

Ok, so we’ll stop improving things because it will make a little more work for you… Probably not.

The issue now is that it’s waaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaay too hard to write nodes.

It needs to be a lot easier to write the nodes themselves or you will never have things to use in your editor anyway. I think the changes will be small on your part.

#pragma is a known directive of the compiler, that knows that it can ignore it if it doesn’t know what it’s made for. So I don’t have to remove it from the shader node code to append it the the generated code.
EDIT: ha by extension you don’t mean a custom #something like for imports? you mean the file extension?

and that yes, why not?

Makes sense.

I will actually use shader nodes if you make these changes. I’ve avoid it up until now because I felt it too complicated.

In my 30 or so years of software dev experience, anything that virtually requires a special editor to something simple is a broken design. Every case (even the ones I wrote myself) I’ve encountered they always turn out to be more pain than they are worth. Like, the first time you just want to search/replace something… etc… just awful.

1 Like

Yeah… .shaderlet or whatever. I don’t remember what my original extension suggestion was when I originally proposed this as an alternative to shader injection or whatever. That was years and years ago… probably before we moved to slack even.

In any case the point is to append the code without modifying it. I guess we’ll still move things around like extensions and imports but that should be it.

I find the j3sn cumbersome too. Its data could be shrinked and moved on top of the .glsl file.

Also there should be away to export the unified shader files. I tried to implement this last feature, unsuccessfully :frowning:.

Yeah, I agree with that.

Though on the road to development, if done properly you start with a shader “all together” during development and then can break it up into shader nodes… maybe even just by commenting out its main().

That can be done already, but it’s a bit tricky. Basically you create a generator instance and you generate the shader for a given technique with it.

Do you mean about right side on the screen?

1 Like