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?