AST based ShaderNode Generator

Hi guys,
I work on improving shader node system in jME from the last month to make it easier to use for artists. I’m going to post my progress here and to discuss my changes :slight_smile:
I do it as an alternative option of shader generator which a user can activate itself. What do I want to have?:

  1. Possibility to have default values.
  2. Auto-resolving names of additional methods in shader nodes to avoid name collisions.
  3. Auto-resolving some defines inside shader nodes.
  4. Auto-resolving names of local variables of shader nodes to avoid name collisions.
  5. Auto-resolving world bindings from imported shaders inside shader nodes.
  6. Сonvenient solution to use imports/extensions inside the shader nodes.
  7. Possibility to visualize binding material parameter to custom defines of shader nodes.
    and something else…
1 Like

One example:
We have the difficult the vertex shader node with 3 imports, 1 additional local methods, some defines and local variables:


And we have using of this like this:

In the result we have:

#define PBRMainVertex_SD_IS_SET_inPosition 1
#define PBRMainVertex_SD_IS_SET_normalMap 1
#define PBRMainVertex_SD_IS_SET_parallaxMap 1
#define PBRMainVertex_SD_IS_SET_baseColor 1

// ------ Started Common/ShaderLib/GLSLCompat.glsllib ------ //
#if defined GL_ES
#  define hfloat highp float
#  define hvec2  highp vec2
#  define hvec3  highp vec3
#  define hvec4  highp vec4
#  define lfloat lowp float
#  define lvec2 lowp vec2
#  define lvec3 lowp vec3
#  define lvec4 lowp vec4
#else
#  define hfloat float
#  define hvec2  vec2
#  define hvec3  vec3
#  define hvec4  vec4
#  define lfloat float
#  define lvec2  vec2
#  define lvec3  vec3
#  define lvec4  vec4
#endif

#if __VERSION__ >= 130
out vec4 outFragColor;
#  define texture1D texture
#  define texture2D texture
#  define texture3D texture
#  define textureCube texture
#  define texture2DLod textureLod
#  define textureCubeLod textureLod
#  if defined VERTEX_SHADER
#    define varying out
#    define attribute in
#  elif defined FRAGMENT_SHADER
#    define varying in
#    define gl_FragColor outFragColor
#  endif
#endif
// ------ Finished Common/ShaderLib/GLSLCompat.glsllib ------ //

// ------ Started Common/ShaderLib/Instancing.glsllib ------ //
// Instancing GLSL library.
// 
// When the INSTANCING define is set in the shader, 
// all global matrices are replaced with "instanced" versions.
// One exception is g_NormalMatrix which becomes unusable,
// instead the function ApplyNormalTransform is used to transform
// the normal and tangent vectors into world view space.

// The world matrix and normal transform quaternion need to be passed
// as vertex attributes "inWorldMatrix" and "inNormalRotationQuaternion"
// respectively. 
// The VertexBuffers for those two attributes 
// need to be configured into instanced mode (VertexBuffer.setInstanced(true)). 
//  - inWorldMatrix should have 12 * numInstances floats.
//  - inNormalRotationQuaternion should have 4 * numInstances.
// Thus, instancing data occupies 4 vertex attributes (16 / 4 = 4).
// 
// The GL_ARB_draw_instanced and GL_ARB_instanced_arrays extensions 
// are required (OGL 3.3).

uniform mat4 g_WorldMatrix;
uniform mat4 g_ViewMatrix;
uniform mat4 g_ProjectionMatrix;
uniform mat4 g_WorldViewMatrix;
uniform mat4 g_WorldViewProjectionMatrix;
uniform mat4 g_ViewProjectionMatrix;
uniform mat3 g_NormalMatrix;

#if defined INSTANCING

// World Matrix + Normal Rotation Quaternion. 
// The World Matrix is the top 3 rows - 
//     since the bottom row is always 0,0,0,1 for this transform.
// The bottom row is the transpose of the inverse of WorldView Transform 
//     as a quaternion. i.e. g_NormalMatrix converted to a quaternion.
//
// Using a quaternion instead of a matrix here allows saving approximately
// 2 vertex attributes which now can be used for additional per-vertex data.
attribute mat4 inInstanceData;



vec4 TransformWorld(vec4 position)
{
    // Extract the world matrix out of the instance data, leaving out the
    // quaternion at the end.
    mat4 worldMatrix = mat4(vec4(inInstanceData[0].xyz, 0.0),
    vec4(inInstanceData[1].xyz, 0.0),
    vec4(inInstanceData[2].xyz, 0.0),
    vec4(inInstanceData[3].xyz, 1.0));
    return (worldMatrix * position);
}

vec4 TransformWorldView(vec4 position)
{
    return g_ViewMatrix * TransformWorld(position);
}

vec4 TransformWorldViewProjection(vec4 position)
{
    return g_ViewProjectionMatrix * TransformWorld(position);
}

vec3 TransformWorldNormal(vec3 vec) {
    vec4 quat = vec4(inInstanceData[0].w, inInstanceData[1].w,
                     inInstanceData[2].w, inInstanceData[3].w);

    return vec + vec3(2.0) * cross(cross(vec, quat.xyz) + vec3(quat.w) * vec, quat.xyz);
}

vec3 TransformNormal(vec3 vec)
{
    return (g_ViewMatrix * vec4(TransformWorldNormal(vec), 0.0)).xyz;
}

// Prevent user from using g_** matrices which will have invalid data in this case.
#define g_WorldMatrix               Use_the_instancing_functions_for_this
#define g_WorldViewMatrix           Use_the_instancing_functions_for_this
#define g_WorldViewProjectionMatrix Use_the_instancing_functions_for_this
#define g_NormalMatrix              Use_the_instancing_functions_for_this

#else

vec4 TransformWorld(vec4 position)
{
    return g_WorldMatrix * position;
}

vec4 TransformWorldView(vec4 position)
{
    return g_WorldViewMatrix * position;
}

vec4 TransformWorldViewProjection(vec4 position)
{
    return g_WorldViewProjectionMatrix * position;
}

vec3 TransformNormal(vec3 normal) {
	return g_NormalMatrix * normal;
}

vec3 TransformWorldNormal(vec3 normal) {
    return normalize((g_WorldMatrix * vec4(normal,0.0)).xyz);
}

 
#endif
// ------ Finished Common/ShaderLib/Instancing.glsllib ------ //

// ------ Started Common/ShaderLib/Skinning.glsllib ------ //
#ifdef NUM_BONES

#if NUM_BONES < 1 || NUM_BONES > 255
#error NUM_BONES must be between 1 and 255.
#endif

#define NUM_WEIGHTS_PER_VERT 4
 
attribute vec4 inHWBoneWeight;
attribute vec4 inHWBoneIndex;
uniform mat4 m_BoneMatrices[NUM_BONES];

void Skinning_Compute(inout vec4 position){
    if (inHWBoneWeight.x != 0.0) {
#if NUM_WEIGHTS_PER_VERT == 1
        position = m_BoneMatrices[int(inHWBoneIndex.x)] * position;
#else
        mat4 mat = mat4(0.0);
        mat += m_BoneMatrices[int(inHWBoneIndex.x)] * inHWBoneWeight.x;
        mat += m_BoneMatrices[int(inHWBoneIndex.y)] * inHWBoneWeight.y;
        mat += m_BoneMatrices[int(inHWBoneIndex.z)] * inHWBoneWeight.z;
        mat += m_BoneMatrices[int(inHWBoneIndex.w)] * inHWBoneWeight.w;
        position = mat * position;
#endif
    }
}
 
void Skinning_Compute(inout vec4 position, inout vec3 normal){
    if (inHWBoneWeight.x != 0.0) {
#if NUM_WEIGHTS_PER_VERT == 1
        position = m_BoneMatrices[int(inHWBoneIndex.x)] * position;
        normal = (mat3(m_BoneMatrices[int(inHWBoneIndex.x)][0].xyz,
                       m_BoneMatrices[int(inHWBoneIndex.x)][1].xyz,
                       m_BoneMatrices[int(inHWBoneIndex.x)][2].xyz) * normal);
#else
        mat4 mat = mat4(0.0);
        mat += m_BoneMatrices[int(inHWBoneIndex.x)] * inHWBoneWeight.x;
        mat += m_BoneMatrices[int(inHWBoneIndex.y)] * inHWBoneWeight.y;
        mat += m_BoneMatrices[int(inHWBoneIndex.z)] * inHWBoneWeight.z;
        mat += m_BoneMatrices[int(inHWBoneIndex.w)] * inHWBoneWeight.w;
        position = mat * position;

        mat3 rotMat = mat3(mat[0].xyz, mat[1].xyz, mat[2].xyz);
        normal = rotMat * normal;
#endif
    }
}
 
void Skinning_Compute(inout vec4 position, inout vec3 normal, inout vec3 tangent){
    if (inHWBoneWeight.x != 0.0) {
#if NUM_WEIGHTS_PER_VERT == 1
        position = m_BoneMatrices[int(inHWBoneIndex.x)] * position;
        tangent = m_BoneMatrices[int(inHWBoneIndex.x)] * tangent;
        normal = (mat3(m_BoneMatrices[int(inHWBoneIndex.x)][0].xyz,
                       m_BoneMatrices[int(inHWBoneIndex.x)][1].xyz,
                       m_BoneMatrices[int(inHWBoneIndex.x)][2].xyz) * normal);
#else
        mat4 mat = mat4(0.0);
        mat += m_BoneMatrices[int(inHWBoneIndex.x)] * inHWBoneWeight.x;
        mat += m_BoneMatrices[int(inHWBoneIndex.y)] * inHWBoneWeight.y;
        mat += m_BoneMatrices[int(inHWBoneIndex.z)] * inHWBoneWeight.z;
        mat += m_BoneMatrices[int(inHWBoneIndex.w)] * inHWBoneWeight.w;
        position = mat * position;

        mat3 rotMat = mat3(mat[0].xyz, mat[1].xyz, mat[2].xyz);
        tangent = rotMat * tangent;
        normal = rotMat * normal;
#endif
    }
}

#endif
// ------ Finished Common/ShaderLib/Skinning.glsllib ------ //


uniform sampler2D m_TestNormalMap;
uniform sampler2D m_TestParall;
uniform vec4 m_BaseColor;
in vec3 inPosition;

out vec4 PBRMainVertex_vertexColor;

vec3 PBRMainVertex_testMethod() {
    return vec3(1.0, 1.0, 1.0);
}



void main() {
    vec4 Global_position = vec4(inPosition,1.0);
    //PBRMainVertex : Begin
    vec3 PBRMainVertex_inPosition = Global_position.xyz;
    vec4 PBRMainVertex_baseColor = m_BaseColor;
    vec2 PBRMainVertex_texCoord;
    vec2 PBRMainVertex_texCoord2;
    vec4 PBRMainVertex_vertexPosition;
    vec3 PBRMainVertex_worldNormal;
    vec4 PBRMainVertex_worldTangent;
    vec3 PBRMainVertex_worldPosition;
    vec3 PBRMainVertex_test = PBRMainVertex_testMethod();

    #ifdef PBRMainVertex_SD_IS_SET_inPosition 
        vec4 PBRMainVertex_modelSpacePosition = vec4(PBRMainVertex_inPosition, 1.0);
    #else
        vec4 PBRMainVertex_modelSpacePosition = vec4(1.0);
    #endif

    #ifdef PBRMainVertex_SD_IS_SET_inNormal 
        vec3 PBRMainVertex_modelSpaceNormal = inNormal;
    #else
        vec3 PBRMainVertex_modelSpaceNormal = vec3(1.0);
    #endif

    #if (defined(PBRMainVertex_SD_IS_SET_normalMap) || defined(PBRMainVertex_SD_IS_SET_parallaxMap)) && !defined(VERTEX_LIGHTING)
        #ifdef PBRMainVertex_SD_IS_SET_inTangent
            vec3 PBRMainVertex_modelSpaceTanget = inTangent.xyz;
        #else
            vec3 PBRMainVertex_modelSpaceTanget = vec3(1.0);
        #endif
    #endif

    #ifdef NUM_BONES
        #if defined(PBRMainVertex_SD_IS_SET_normalMap) && !defined(VERTEX_LIGHTING)
            Skinning_Compute(PBRMainVertex_modelSpacePosition, PBRMainVertex_modelSpaceNormal, PBRMainVertex_modelSpaceTanget);
        #else
            Skinning_Compute(PBRMainVertex_modelSpacePosition, PBRMainVertex_modelSpaceNormal);
        #endif
    #endif

    PBRMainVertex_vertexPosition = TransformWorldViewProjection(PBRMainVertex_modelSpacePosition);
    PBRMainVertex_worldPosition = TransformWorld(PBRMainVertex_modelSpacePosition).xyz;
    PBRMainVertex_worldNormal = TransformWorldNormal(PBRMainVertex_modelSpaceNormal);

    #if defined(PBRMainVertex_SD_IS_SET_normalMap) || defined(PBRMainVertex_SD_IS_SET_parallaxMap)
        #ifdef PBRMainVertex_SD_IS_SET_inTangent
            float PBRMainVertex_inTangentW = inTangent.w;
        #else
            float PBRMainVertex_inTangentW = 1.0;
        #endif
        PBRMainVertex_worldTangent = vec4(TransformWorldNormal(PBRMainVertex_modelSpaceTanget), PBRMainVertex_inTangentW);
    #endif

    #ifdef PBRMainVertex_SD_IS_SET_baseColor
        PBRMainVertex_vertexColor = PBRMainVertex_baseColor;
    #else
        PBRMainVertex_vertexColor = vec4(0.5, 0.5, 0.5, 0.5);
    #endif
    
    #ifdef PBRMainVertex_SD_IS_SET_inColor                    
        PBRMainVertex_vertexColor *= inColor;
    #endif
    Global_position = PBRMainVertex_vertexPosition;
    //PBRMainVertex : End
    gl_Position = Global_position;
}

So you can see updated names of defines, methods, local variables, automaticly added defines:

#define PBRMainVertex_SD_IS_SET_inPosition 1
#define PBRMainVertex_SD_IS_SET_normalMap 1
#define PBRMainVertex_SD_IS_SET_parallaxMap 1
#define PBRMainVertex_SD_IS_SET_baseColor 1

also, the generator added missed world bindings from imported shaders and other things.

@nehon I think we can discuss here :wink:

Well… I’m getting tired discussing this with you… I told you in private messages that the current system already handles most of your points except for point 2. I don’t really know why, but you are just trying to reinvent the wheel here…

Your option assumes that artists and developers should be worried about many things themselves, I don’t agree with it. I try to reduce efforts from them to work with shader nodes.

No, that’s what I told you for 2.
But for the rest, they don’t need to know anything… it’s handled.
Anyway, do whatever you want, that’s what you usually do.

1 Like

what about for example p.3:
I have the block in the source code of the shader node:

    #ifdef SD_IS_SET_inColor                    
        vertexColor *= inColor;
    #endif

in the case with my generator, it transforms it to this:

    #ifdef PBRMainVertex_SD_IS_SET_inColor                    
        PBRMainVertex_vertexColor *= inColor;
    #endif

and adds the define of this in the top of the shader automatically if my shader has a mapping of the parameter.

#define PBRMainVertex_SD_IS_SET_inColor 1

also, for example p.5:

I have these uniforms in the imported shader:

uniform mat4 g_WorldMatrix;
uniform mat4 g_ViewMatrix;
uniform mat4 g_ProjectionMatrix;
uniform mat4 g_WorldViewMatrix;
uniform mat4 g_WorldViewProjectionMatrix;
uniform mat4 g_ViewProjectionMatrix;
uniform mat3 g_NormalMatrix;

How can I handle it in the shader node system now? Do I need to add these parameters manually, right?

I want to add it to jME, so I need to have your approving of this, so I want to discuss about it to find the compromise :slight_smile:

What’s wrong with how it’s done in UnshadedNodes.j3md? you have a useVertexColor param that maps to a define, and that is the condition of the shader node that multiplies the color to the vertex color.
For the life of me I can’t see how it’s more developer or even Artist friendly to have to manually add a ifdef with a “idk where it comes” define.

Well we already talked about it… several times, and we just did it once again now, I said no, and explained why. Your way of just asking the same thing whenever you’re answered ‘no’ is getting really really tiresome.

I hesitate to be the voice of reason… because usually I’m stirring up trouble…

…but I would humbly suggest that there may be a small language barrier here being inflated by some weariness/history.

javasbr, I suggest if you want to make progress in getting your point across that you might want to find a different way to describe it. Especially one that doesn’t imply that the current code all sucks and wasn’t intended for real users… but mostly one that better describes why something is important without resorting to such statements.

I didn’t mean that, my attempts are based on feedback from my artist about which difficulties which he has with shader node system. I just try to solve it, and if I can do something for the community with it, I want to do it.

An artist should set the conditions of parameters manually.
Also, when for example my artist wants to make its own unshader material using the current shader node system, currently it will be like your example:

it’s too difficult for the simple case, my artist expects to see something like this:

it doesn’t require any additional actions from him, hi shouldn’t worry about conditions, hi shouldn’t build a simple case using a lot of small shader nodes and something else…
For my version of the unshaded material, I wrote these nodes:

@8Keep123 @NemesisMate @haze @themiddleman what do you think about this?

That’s basically because you made 1 node for vertex and one node for fragment… What’s the advantage of this versus a standard shader. The idea is that the shader becomes modular… Of course the graph is simpler when you cram all your code in a single node… But it defeats the very purpose of shader nodes.

I just look at this from artist side, I understand your point of view and it’s a good point for developers, but artists want to make their materials using easiest solution. So I try to provide more flexible solution for shader node developers to make easier shader nodes for artists.

It’s kind of a developer tool… and/or at minimum a “technical artist” tool. The kind that would be fine debugging shader code.

…because:

You mean a less flexible solution. What if you want to remove something that fragment node is doing? Well, you are back to square one again because you must build a whole new fragment shader node.

The point was to chain simpler modular mini-shaders (we called them shaderlets before) to achieve functionality. The fully expanded unshaded example is a poor one because it already includes everything “for backwards compataibility”. If I were to build an unshaded shader for my game then I’d pull in just the parts I wanted. No conditionals or any of that stuff because if I don’t want textures then I won’t include that node.

1 Like

Exactly!!! thanks Paul.

I see that not technical artists can work with shader nodes in UE, Unity, Blender and etc, but they can’t work it in jME, is it ok?, I don’t think so.

In my case, you can implement shaders using both approachs.

It’s very cool when only developers work on graphics part, but what about artists?