2

I have been learning OpenGL recently and I have got bit confused about shaders and the VBOs. Basically, I am confused on how my vertex shader knows about the colour and position data in my VBOs. To my knowledge glBindAttribLocation(program, attribute, variable); is what sets the attribute in the shader but when I comment out that code, the triangle still renders fine with it's colours.

Here is my code:

public static void main(String[] args) {
    String vertexSource = "#version 400 core\n"
            + "in vec3 position;\n"
            + "in vec3 colour;\n"
            + "out vec3 vertexColour;\n"
            + "uniform mat4 model;\n"
            + "uniform mat4 view;\n"
            + "uniform mat4 projection;\n"
            + "void main() {\n"
            + "vertexColour = colour;\n"
            + "mat4 mvp = projection * view * model;\n"
            + "gl_Position = vec4(position, 1.0);\n"
            + "}\n";
    
    String fragmentSource = "#version 400 core\n"
            + "in vec3 vertexColour;\n"
            + "out vec4 colours;\n"
            + "void main()\n"
            + "{\n"
            + "    colours = vec4(vertexColour, 1);\n"
            + "}";
    
    glfwSetErrorCallback(errorCallBack);
    
    if (!glfwInit()) {
        throw new IllegalStateException("Unable to initialize GLFW");
    }
        
    glfwDefaultWindowHints();
    glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
    glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
    glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
    glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GLFW_TRUE);
    
    glfwWindowHint(GLFW_VISIBLE, GL_TRUE);
    glfwWindowHint(GLFW_RESIZABLE, GL_FALSE);
    
    long window = glfwCreateWindow(640, 480, "my window!", 0, 0);

    glfwSetKeyCallback(window, keyCallback);
    if (window == 0) {
        glfwTerminate();
        throw new RuntimeException("Failed to create the GLFW window");
    }
    
    glfwMakeContextCurrent(window);
    GL.createCapabilities();
    
    float[] vertexPoints = {
        0f,0.5f,0f,
        -0.5f,-0.5f,0f,
        0.5f,-0.5f,0f
    };
    float[] colours = {
            0.1f,0.5f,0.5f,
            0.3f,0.7f,0.9f,
            0.4f,0.9f,0.1f
        };
    
    ArrayList<Integer> vaos = new ArrayList<Integer>();
    ArrayList<Integer> vbos = new ArrayList<Integer>();
    
    int vaoID = glGenVertexArrays();
    vaos.add(vaoID);
    glBindVertexArray(vaoID);
    
    
    FloatBuffer vertices = BufferUtils.createFloatBuffer(vertexPoints.length);
    vertices.put(vertexPoints);
    vertices.flip();
   
    int vboID = GL33.glGenBuffers();
    vbos.add(vboID);
    glBindBuffer(GL_ARRAY_BUFFER, vboID);
    glBufferData(GL_ARRAY_BUFFER, vertices, GL_STATIC_DRAW);
    
    //add it to the vao at position 0, size 3, with data types of float, normalised = false, 
    glVertexAttribPointer(0, 3, GL_FLOAT, false, 0, 0);
    //unbind currently bound vbo
    glBindBuffer(GL_ARRAY_BUFFER, 0);
    
    //and one for the colour data
    vboID = glGenBuffers();
    glBindBuffer(GL_ARRAY_BUFFER, vboID);
    glBufferData(GL_ARRAY_BUFFER, colours, GL_STATIC_DRAW);
    glVertexAttribPointer(1, 3, GL_FLOAT, false, 0, 0);
    glBindBuffer(GL_ARRAY_BUFFER, 0);
    
    //finished with vao now unbind it
    glBindVertexArray(0);

    //vao and vbo stuff is now finished with
    //now for the shader stuff
    
    int vertexShaderID = glCreateShader(GL_VERTEX_SHADER);
    glShaderSource(vertexShaderID, vertexSource);
    glCompileShader(vertexShaderID);
    
    if (!checkForSuccess(vertexShaderID)) {
        
        glfwTerminate();
        throw new IllegalStateException("vertex shader failed: " + glGetShaderInfoLog(vertexShaderID));
    }
    
    int fragmentShaderID = glCreateShader(GL_FRAGMENT_SHADER);
    glShaderSource(fragmentShaderID, fragmentSource);
    glCompileShader(fragmentShaderID);
    
    if (!checkForSuccess(vertexShaderID)) {
        glGetShaderInfoLog(vertexShaderID);
        glfwTerminate();
        throw new IllegalStateException("fragment shader failed");
    }
    
    //shader program
    int shaderProgramID = glCreateProgram();
    glAttachShader(shaderProgramID, vertexShaderID);
    glAttachShader(shaderProgramID, fragmentShaderID);
    glLinkProgram(shaderProgramID);
    //error test
    int status = glGetProgrami(shaderProgramID, GL_LINK_STATUS);
    if (status != GL_TRUE) {
        glfwTerminate();
        throw new RuntimeException(glGetProgramInfoLog(shaderProgramID));
    }
    
    glUseProgram(shaderProgramID);

    
    //setting position attribute in the shader:
    int positionAttribute = glGetAttribLocation(shaderProgramID, "position");
    glEnableVertexAttribArray(positionAttribute);
    glBindAttribLocation(shaderProgramID, positionAttribute, "position");
    
    //setting colour attribute in the shader:
    int colourAttribute = glGetAttribLocation(shaderProgramID, "colour");
    glEnableVertexAttribArray(colourAttribute);
    glBindAttribLocation(shaderProgramID, colourAttribute, "colour");
    
    
    glClearColor(0.5f,0.5f,0.5f,1f);
    
    //this will create a wire frame view
    //glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
    
    glfwShowWindow(window); //optional
    
    while (!glfwWindowShouldClose(window)) {
        double time = glfwGetTime();
        glfwPollEvents();
        glClear(GL_COLOR_BUFFER_BIT);
        
        glBindVertexArray(vaoID);
        
        //enables the position attribute in the vertex shader
        glEnableVertexAttribArray(0);
        //enables the colour attribute in the vertex shader
        glEnableVertexAttribArray(1);
        
        glDrawArrays(GL_TRIANGLES, 0, vertexPoints.length);
        //disables the position and colour attribute in the vertex shader
        glDisableVertexAttribArray(0);
        glDisableVertexAttribArray(1);
        
        glfwSwapBuffers(window);
    }
    
    //clear vaos and vbos
    for (int vao : vaos) {
        glDeleteVertexArrays(vao);
    }
    for (int vbo : vbos) {
        glDeleteBuffers(vbo);
    }
    //delete shaders
    glDetachShader(shaderProgramID, vertexShaderID);
    glDetachShader(shaderProgramID, fragmentShaderID);
    glDeleteShader(vertexShaderID);
    glDeleteShader(fragmentShaderID);
    glDeleteProgram(shaderProgramID);
    
    Callbacks.glfwFreeCallbacks(window);
    
    glfwDestroyWindow(window);
    glfwTerminate();
   
}

I have provided my full code file on github (250 lines) here: https://github.com/OneEgg42/opengl

Would someone be so kind and explain to me how this works?

Thanks in advance!

BDL
  • 21,052
  • 22
  • 49
  • 55
Egg42
  • 65
  • 5

1 Answers1

1

The relation between a VBO and a shader is not established by glBindAttribLocation.

The relevant lines are actually

glBindBuffer(GL_ARRAY_BUFFER, vboID);
glVertexAttribPointer(0, 3, GL_FLOAT, false, 0, 0);

where you tell OpenGL that the attribute with location 0 (the first parameter in the first glVertexAttribPointer call) should read the first three floats from vboID.

glBindAttribLocation is responsible for binding a certain attribute to a specific location. Since this has to be done before linking the shader, they aren't doing anything in your code. Even more so, because this code tells the attribute to use the location that it already has:

int positionAttribute = glGetAttribLocation(shaderProgramID, "position");
glEnableVertexAttribArray(positionAttribute);
glBindAttribLocation(shaderProgramID, positionAttribute, "position");

You basically query the location, and then assign the location again to the attribute.

Two side notes:

  • At the moment your code works by pure luck. You assume in the glVertexAttribPointer(0, 3, GL_FLOAT, false, 0, 0); line that the "position" attribute is at location 0, which it probably is as long as it is the first attribute written in the shader. Same for colors. It is very likely that your code breaks when you exchange the order of the two attribute definitions in your shader. Either query the attribute location with glGetAttribLocation and use the result instead of 0 and 1, or use glBindAttribLocation before linking to make sure that the attributes are at the location you expect them.

  • The calls to glEnableVertexAttribArray should be in the VAO setup code and not in the render loop.

Setup:

glBindBuffer(GL_ARRAY_BUFFER, vboID);
glBufferData(GL_ARRAY_BUFFER, vertices, GL_STATIC_DRAW);
glEnableVertexAttribArray(0);
glVertexAttribPointer(0, 3, GL_FLOAT, false, 0, 0);

Render:

glBindVertexArray(vaoID);
glDrawArrays(GL_TRIANGLES, 0, vertexPoints.length);
BDL
  • 21,052
  • 22
  • 49
  • 55
  • Thank you so much for you detailed response; I really appreciate it! I'm still slightly confused on glBindAttribLocation. You said: "to make sure that the attributes are at the location you expect them", so does that line place the data into the into the shader and why do you have to have the line before you link up the shaders? – Egg42 Jul 13 '21 at 12:22
  • This line has nothing to do with the data. It only affects the shader itself by defining which location number belongs to which attribute name. By default the locations are assigned randomly. – BDL Jul 13 '21 at 12:49
  • So if you don't assign the values to the attributes in the shader, does the shader just read the data from the VAO or VBOs? – Egg42 Jul 13 '21 at 15:00
  • By values you mean locations? If you don't assign them, then they are automatically assigned by the driver when it links the shader. But again, setting locations has nothing to do with VBO or VAOs. Which data gets read **only** depends on what you set with `glBindAttribLocation`. – BDL Jul 13 '21 at 15:42
  • Thanks for your patience with me lol - I'm very new to OpenGL as you probably can tell! I think I understand now. I thought that you had to manually set the attributes (such as position data in the vertex shader) by passing the VBO data into the shader. – Egg42 Jul 13 '21 at 17:12
  • Hi sorry, I have another question: what would be the correct way to use glBindAttribLocation() ? – Egg42 Jul 13 '21 at 19:49
  • Please ask a new question. Don't think I can answer it in a comment. – BDL Jul 13 '21 at 22:19