Search code examples
c++openglglsl

When instanced rendering, using a transform vbo with glVertexAttribDivisor messes up glDrawElements


I am trying to make instanced rendering work with multiple differently-sized 'quads' (two connected triangles). I am setting up a VAO with a vertex data VBO and a transform data VBO, along side an index EBO to stitch these vertices together. For some reason, using the transform VBO makes the glDrawElementsInstanced ignore the actual sizes of the vertex data and just uses one set for all 'quads'. It looks like this.

The quad on the right should be half the size of the left one. It seems like opengl picks the larger size regardless of how I add the vertex data.

Here is my opengl render init:

Renderer::Renderer(Shader* staticShader)
    : m_ShaderStatic(staticShader)
{
    // Generate all needed buffers
    glGenVertexArrays(1, &m_VAO);
    glGenBuffers(1, &m_VertVBO);
    glGenBuffers(1, &m_TransVBO);
    glGenBuffers(1, &m_EBO);

    // Bind VAO
    glBindVertexArray(m_VAO);
    
    // Bind and set up vertex VBO
    glBindBuffer(GL_ARRAY_BUFFER, m_VertVBO);
    glBufferData(GL_ARRAY_BUFFER, sizeof(GL_FLOAT) * 2, NULL, GL_DYNAMIC_DRAW);
    glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, sizeof(GL_FLOAT) * 2, (void*)0);
    glEnableVertexAttribArray(0);

    // Bind and set up transform VBO
    glBindBuffer(GL_ARRAY_BUFFER, m_TransVBO);
    glBufferData(GL_ARRAY_BUFFER, sizeof(GL_FLOAT) * 2, NULL, GL_DYNAMIC_DRAW);
    glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, sizeof(GL_FLOAT) * 2, (void*)0);
    glEnableVertexAttribArray(1);
    glVertexAttribDivisor(1, 1);

    // Bind and set up EBO
    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, m_EBO);
    glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(unsigned int) * 6, NULL, GL_DYNAMIC_DRAW);

    // Unbind everything
    glBindVertexArray(0);
    glBindBuffer(GL_ARRAY_BUFFER, 0);
    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
}

Here is the AddStatic function to give the buffers data:

void Renderer::AddStatic(float x, float y, float width, float height, float rotation)
{
    // Bind VAO
    glBindVertexArray(m_VAO);

    // Configure vertex data
    for (int i = 0; i < 8; i+=2)
    {
        m_Vertices.push_back(QuadVertices[i] * width);
        m_Vertices.push_back(QuadVertices[i+1] * height);
    }
    // Set vertex data
    glBindBuffer(GL_ARRAY_BUFFER, m_VertVBO);
    glBufferData(GL_ARRAY_BUFFER, sizeof(float) * m_Vertices.size(), m_Vertices.data(), GL_DYNAMIC_DRAW);

    // Configure transform data
    m_Transforms.push_back(x);
    m_Transforms.push_back(y);
    // Set transform data
    glBindBuffer(GL_ARRAY_BUFFER, m_TransVBO);
    glBufferData(GL_ARRAY_BUFFER, sizeof(float) * m_Transforms.size(), m_Transforms.data(), GL_DYNAMIC_DRAW);

    // Configure index data
    m_Indices.push_back(0 + 4 * m_QuadCount);
    m_Indices.push_back(1 + 4 * m_QuadCount);
    m_Indices.push_back(3 + 4 * m_QuadCount);
    m_Indices.push_back(3 + 4 * m_QuadCount);
    m_Indices.push_back(2 + 4 * m_QuadCount);
    m_Indices.push_back(0 + 4 * m_QuadCount);
    // Set index data
    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, m_EBO);
    glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(unsigned int) * m_Indices.size(), m_Indices.data(), GL_DYNAMIC_DRAW);

    // Unbind all
    glBindVertexArray(0);
    glBindBuffer(GL_ARRAY_BUFFER, 0);
    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);

    // Increment the amount of quads loaded
    m_QuadCount++;
}

Here is the actual draw call:

void Renderer::Render()
{
    // Bind VAO
    glBindVertexArray(m_VAO);
    //glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, m_EBO);
    
    // Bind Shader
    m_ShaderStatic->use();

    // Draw
    glDrawElementsInstanced(GL_TRIANGLES, m_Indices.size(), GL_UNSIGNED_INT, 0, m_QuadCount);
}

And finally, here is my vertex shader if that is needed.

#version 330 core

layout (location = 0) in vec2 vertex;
layout (location = 1) in vec2 transform;

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

out vec4 color;

void main()
{
    gl_Position = projection * view * vec4(vertex + transform, 0.0, 1.0);
    color = vec4(1.0, 1.0, 1.0, 1.0);
}

I dont know why a simple transform would affect the actual size of my quads, all it does is just add a set x/y to them.

Edit: If I set the transform values i put into the vector to 0, it renders the squares at the correct width/height, albeit at the wrong location.


Solution

  • I think you're fundamentally misunderstanding instanced rendering. But, as with everything graphics related, a visual explanation is worth more than a thousands words. So:

    enter image description here

    With the following quads:

    void add_quad(const MATH::Vector2f& position, const MATH::Vector2f& size, const MATH::Vector4f& color);
    
    add_quad(MATH::Vector2f(100.0f, 100.0f), MATH::Vector2f(10.0f, 10.0f), MATH::Vector4f(1.0f, 0.0f, 0.0f, 0.5f));
    add_quad(MATH::Vector2f(200.0f, 200.0f), MATH::Vector2f(20.0f, 20.0f), MATH::Vector4f(0.0f, 1.0f, 0.0f, 0.5f));
    add_quad(MATH::Vector2f(300.0f, 300.0f), MATH::Vector2f(30.0f, 30.0f), MATH::Vector4f(0.0f, 0.0f, 1.0f, 0.5f));
    

    And fully additive blending (glBlendFunc(GL_ONE, GL_ONE)).

    As you can see, the three quads are being rendered at every transform/position. This is exactly what instanced rendering does: It renders a mesh (in this case, the three quads together form a single mesh) multiple times, with separate vertex attributes that change for each consecutive mesh (in this case the transform, which is applied to every quad). In simple words, every quad is being rendered at every transform position.

    In order to achieve what you want to do with instanced rendering, you only need a single quad mesh for the usual vertex and index data. The transform (translation AND scaling) would then be the instanced vertex attributes. Using a 3x3 matrix for this would probably be easier than trying to split this up into many different individual transforms. This is essentially the same as making the model matrix be an instanced attribute rather than a uniform.