0

I have some code that draws squares by passing points through a geometry shader. I construct an array which is sequences of 3 floats, bind that to the in vec3 vert attribute of my vertex shader, and everything is fine.

However, I want to add another float which the fragment shader will use to calculate color. This is in the vertex shader (to pass through) as in float val. Despite being able to find vert, glGetAttribLocation can't find val (get_program_attrib(): Atrrib val not found (-1)).

Code:

void load_model(GLuint* vao, GLuint* vbo) {
    glGenVertexArrays(1, vao);
    glBindVertexArray(*vao);
    glGenBuffers(1, vbo);
    glBindBuffer(GL_ARRAY_BUFFER, *vbo);

    float data[SQUARES_PER_AXIS_SQ * 4] = {0};
    squares_count = 0;

    for (int i = 0; i < SQUARES_PER_AXIS_SQ; i++) {
        int x_pos = i % SQUARES_PER_AXIS;
        int y_pos = i / SQUARES_PER_AXIS;
        if (fabs(squares[i]) > 0.0) {
            data[squares_count * 4 + 0] = x_pos / ((float)SQUARES_PER_AXIS) * 2 - 1;
            data[squares_count * 4 + 1] = (SQUARES_PER_AXIS - y_pos) / ((float)SQUARES_PER_AXIS) * 2 - 1;
            data[squares_count * 4 + 2] = 0.5f;
            data[squares_count * 4 + 3] = (float)squares[i];
            squares_count++;
        }
    }
    DPRINT("Loaded %d squares\n", squares_count);

    glBufferData(GL_ARRAY_BUFFER, squares_count * 4 * sizeof(float), data, GL_STATIC_DRAW);

    glEnableVertexAttribArray(get_program_attrib(main_shader, "vert"));
    glEnableVertexAttribArray(get_program_attrib(main_shader, "val"));
    glVertexAttribPointer(get_program_attrib(main_shader, "vert"), 3, GL_FLOAT, GL_FALSE, 4, NULL);
    glVertexAttribPointer(get_program_attrib(main_shader, "val"), 1, GL_FLOAT, GL_FALSE, 4, (float*)(3 * sizeof(float)));

    glBindBuffer(GL_ARRAY_BUFFER, 0);
    glBindVertexArray(0);
}

GLuint get_program_attrib(program_t* prog, GLchar* name) {
    if (!name) {
        DPRINT("ERROR: name == NULL\n");
        return -1;
    }
    GLint attrib = glGetAttribLocation(prog->id, name);
    if (attrib < 0)
        DPRINT("Atrrib %s not found (%d)\n", name, attrib);
    return attrib;
}

Vertex shader:

#version 150

in vec3 vert;
in float val;
out float value;

void main() {
    gl_Position = vec4(vert, 1);
    value = val;
}

Fragment shader:

#version 150

in float value;
out vec4 color;

void main() {
    color = vec4(value, 0, 0, 1);
}

Geometry shader:

#version 150

layout (points) in;
layout (triangle_strip, max_vertices=4) out;

uniform float square_size;

void main() {
    vec4 position = gl_in[0].gl_Position;

    gl_Position = vec4(position.x, position.y, position.zw);
    EmitVertex();
    gl_Position = vec4(position.x,  position.y + square_size, position.zw);
    EmitVertex();
    gl_Position = vec4(position.x + square_size, position.y, position.zw);
    EmitVertex();
    gl_Position = vec4(position.x + square_size,  position.y + square_size, position.zw);
    EmitVertex();

    EndPrimitive();
}
vgel
  • 3,225
  • 1
  • 21
  • 35
  • Are you sure you have the fragment shader properly compiled and attached? The fragment shader is the only thing that consumes `value`, which is based on `val`. If it is not attached, then `val` is not an active attribute. `vert`, on the other hand, is used to assign `gl_Position`; anything used to compute `gl_Position` will be active no matter what the contents of your fragment shader looks like. – Andon M. Coleman Jun 13 '14 at 19:02
  • Yes, the fragment shader compiles correctly. – vgel Jun 13 '14 at 19:03
  • If you change `gl_Position` to `gl_Position = vec4 (vert, val)` does that change anything? I still think the problem is to do with your fragment shader. – Andon M. Coleman Jun 13 '14 at 19:05
  • Oh, as a matter of fact - I just re-read your question. You talk of passing things through a geometry shader. I am now absolutely certain that this is your problem. If you can include your geometry shader, I can show you the steps necessary to fix it. The problem basically boils down to the fact that the GS now sits inbetween the VS and FS. For a vertex attribute to be active, it has to go through all 3 of those stages; otherwise it is inactive and you get **-1** for its location. – Andon M. Coleman Jun 13 '14 at 19:10
  • I just added the geometry shader. But you're just saying I have to pass `value` through? – vgel Jun 13 '14 at 19:18

1 Answers1

3

Vertex shader outputs are not passed directly to the fragment shader when you have a geometry shader.

It is this that is causing all of your problems. For a vertex attribute to be active, it has to contribute to the final output of your program. Basically that means something calculated in the fragment shader has to be based off of it.

Unfortunately, that is not happening right now. You have a variable called value that is output from your vertex shader and a variable called value that is input by your fragment shader. Because the geometry shader sits inbetween the two of them, the fragment shader only looks for an output named value in the geometry shader -- no such output exists.

Naturally you might think that the solution would be to create a variable called value in the geometry shader that serves as the input and the output. However, that will not work, you would have to declare it inout value and that is invalid.


Here are the necessary corrections:

Vertex shader:

#version 150

in vec3 vert;
in float val;
out float value_vtx; // Output is fed to the Geometry Shader

void main() {
    gl_Position = vec4(vert, 1);
    value_vtx = val;
}

Fragment shader:

#version 150

in float value_geo; // Takes its input from the Geometry Shader
out vec4 color;

void main() {
    color = vec4(value_geo, 0, 0, 1);
}

Geometry shader:

#version 150

layout (points) in;
layout (triangle_strip, max_vertices=4) out;

uniform float square_size;

in  float value_vtx []; // This was output by the vertex shader
out float value_geo;    // This will be the input to the fragment shader

void main() {
    vec4 position = gl_in[0].gl_Position;

    gl_Position = vec4(position.x, position.y, position.zw);
    value_geo   = value_vtx[0];
    EmitVertex();

    gl_Position = vec4(position.x,  position.y + square_size, position.zw);
    value_geo   = value_vtx[0];
    EmitVertex();

    gl_Position = vec4(position.x + square_size, position.y, position.zw);
    value_geo   = value_vtx[0];
    EmitVertex();

    gl_Position = vec4(position.x + square_size,  position.y + square_size, position.zw);
    value_geo   = value_vtx[0];
    EmitVertex();

    EndPrimitive();
}

You may be asking why I assigned value_geo 4 times when it is constant. That is because EmitVertex (...) causes all output variables to become undefined when it returns, so you have to set it every time.

Andon M. Coleman
  • 42,359
  • 2
  • 81
  • 106
  • `#version 320` is too new for the graphics card I'm using (it's pretty ancient), and 150 seems to work with no issues. However, adding the inputs/outputs to the geometry shader gives me the error `error C7544: OpenGL requires geometry inputs to be arrays`. – vgel Jun 13 '14 at 19:39
  • @Rotten194: Yes, I had to correct a few things, sorry. See the revised answer. – Andon M. Coleman Jun 13 '14 at 19:39
  • @Rotten194: I have been misreading GLSL version numbers < 330 all week long for some reason (150 is GL 3.2 and there is no 320). So `#version 150` is correct ;) – Andon M. Coleman Jun 13 '14 at 19:46