Search code examples
openglglsl

Rewriting my vector shader to accommodate a slightly different fragment shader


I have this vector shader (called "Vector Shader") that is all fine and dandy:

#version 330 core

layout (location = 0) in vec4 values;
layout (location = 1) in vec4 rect;
layout (location = 2) in int depth;
layout (location = 3) in float radians;
layout (location = 4) in float effect;

layout (std140) uniform Matrices
{
    mat4 projection;
    mat4 view;
};


out vec2 texCoord;

void main()
{
    vec2 transformed = vec2(values.xy * rect.zw*.5); //scale
    transformed = vec2(cos(radians)*transformed.x - sin(radians)*transformed.y,sin(radians)*transformed.x + cos(radians)*transformed.y); //rotate
    transformed += rect.xy + rect.zw*.5; //move
    gl_Position = projection*view*vec4(transformed,depth,1);
    texCoord = vec2(values.z,values.a);
}

This shader often passes data to this fragment shader (call it "Fragment Shader"):

#version 330 core

out vec4 fragColor;
in vec2 texCoord;
uniform sampler2D sprite;

void main()
{
    vec4 text = texture(sprite,texCoord);
    fragColor = text;
}

Together, my vertex and fragment shaders help me render sprites, cool! Now I have another fragment shader (called "Paint Shader") that has one additional vec4 input, called "tint":

#version 330 core

//renders a sprite in a specific color

out vec4 fragColor;
in vec2 texCoord;
in vec4 tint;
uniform sampler2D sprite;

void main()
{
    vec4 text = texture(sprite,texCoord);
    if (text.w ==0)
    {
        fragColor = vec4(0);
    }
    else
    {
        fragColor = tint;
    }

}

I want to use Paint Shader with Vector Shader, but as far as I am aware (please correct if wrong), all my inputs into the fragment shader are either uniforms or outputs from the vertex shader. I may need to call Paint Shader a lot using a lot of different values, which doesn't seem to match the idea of a uniform. But if I want to use the vertex shader, I have to create a whole new vertex shader that uses the same code as before with the only difference being that it now outputs the vec4 that Paint Shader will use, like so:

#version 330 core

layout (location = 0) in vec4 values;
layout (location = 1) in vec4 rect;
layout (location = 2) in int depth;
layout (location = 3) in float radians;
layout (location = 4) in float effect;
layout (location = 5) in vec4 tint_;

layout (std140) uniform Matrices
{
    mat4 projection;
    mat4 view;
};


out vec2 texCoord;
out vec4 tint;

void main()
{
    vec2 transformed = vec2(values.xy * rect.zw*.5); //scale
    transformed = vec2(cos(radians)*transformed.x - sin(radians)*transformed.y,sin(radians)*transformed.x + cos(radians)*transformed.y); //rotate
    transformed += rect.xy + rect.zw*.5; //move
    gl_Position = projection*view*vec4(transformed,depth,1);
    texCoord = vec2(values.z,values.a);
    tint = tint_;
}

Now I have two vertex shaders that do effectively the same thing but because of the fragment shader they differ by 3 lines. If I ever decide to change how my vertex shaders work, I now have to change both of them rather than just one.

Is there a way to do this without creating a 2nd vertex shader? Could I use this 2nd iteration of a vertex shader with the original fragment shader but just ignore the "tint" output?


Solution

  • To avoid rewriting code, use the preprocessor.

    e.g. in you shader code use something like this

    #ifdef THIS_CONSTANT
    //some code
    #elif THAT_CONSTANT
    //some other code
    #else
    //alternative code, or #error "requirements are not met"
    #endif
    

    then, when you're creating your shader, you simply need to pass two (or more) source strings to glShaderSource

    const char *constants = 
        "#version 330 core\n"
        "#define THIS_CONSTANT\n"
        "//or #define THAT_CONSTANT\n";
    
    const char *vs_or_fs_src = "your main code";
    
    //pass this to glShaderSource
    const char *shader_src[] = {
        constants,
        vs_or_fs_src
    };
    

    A simple, effortless method is to use a bitmask and let your shader manager create a specific shader program on the fly (if not exists) based on the bits that are set in the mask.

    And yes, you'll end up with multiple shader programs but with only one single source file (for each shader type).