2
\$\begingroup\$

I've been learning OpenGL for the past week, following tutorials here and there. I was looking to create a SpriteBatch-type of solution for 2D rendering, and for it I thought of utilizing instanced arrays, examples of which could be found in a couple of places.

To render the instances of my sprites, I'm using the function glDrawArraysInstanced, utilizing the glVertexAttribDivisor to get the correct instance in the shaders. I must be missing something however, because when ever I set my vertex attributes' divisors to 1 (to increment them each instance), nothing renders on the screen. When I don't set the divisors, only the first instance of sprites renders.

Additionally, I should probably mention that I am using OpenGL version 3.3 and SDL2.

For the relevant code, the SpriteBatch's initializer which set's the projection, creates the single VBO and VAO and whatnot:

#define BATCH_SIZE 2048

struct SpriteBatchItem
    {
    Texture * texture = NULL;
    GLfloat position[2];
    GLfloat dimensions[2];
    };


struct SpriteBatch
    {
    bool batching_started = false;
    unsigned int sprite_count = 0;

    glm::mat4 projection;
    GLuint shader;
    GLuint vao;
    GLuint vbo;

    SpriteBatchItem sprite_buffer[BATCH_SIZE];
    GLfloat vertex_buffer[BATCH_SIZE * 18];

    static constexpr GLfloat quad_data[18] = {
        //Position      //ID
        -1.0f, -1.0f,   0,
         1.0f, -1.0,    1,
        -1.0f,  1.0f,   2,

        -1.0f,  1.0f,   3,
         1.0f, -1.0f,   4,
         1.0f,  1.0f,   5};

    void init()
        {
        projection = glm::ortho(0.0f, (GLfloat)SCREEN_WIDTH, (GLfloat)SCREEN_HEIGHT, 0.0f, -1.0f, 1.0f);
        shader = createShaderProgram("VertexShader.glsl", "FragmentShader.glsl");

        glUseProgram(shader);

        glGenVertexArrays(1, &vao);
        glGenBuffers(1, &vbo);

        glBindVertexArray(vao);

            glBindBuffer(GL_ARRAY_BUFFER, vbo);

            //Position
            glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 3 * sizeof(GLfloat), (GLvoid*)0);
            glEnableVertexAttribArray(0);
            glVertexAttribDivisor(0, 1);

            //ID, for texture positioning
            glVertexAttribPointer(1, 1, GL_FLOAT, GL_FALSE, 3 * sizeof(GLfloat), (GLvoid*)(2 * sizeof(GLfloat)));
            glEnableVertexAttribArray(1);
            glVertexAttribDivisor(1, 1);

            glBindBuffer(GL_ARRAY_BUFFER, 0);

    glBindVertexArray(0);
    glUseProgram(0);
    }
...

The begin() method starts the batching process:

void begin()
    {
    if (batching_started) return;

    sprite_count = 0;
    batching_started = true;
    }

The draw method can be used after begin() has been called, and it's intention is to simply queue a sprite, remembering which texture it was using and what was it's position (I haven't gotten around to texturing yet, so only the position is really relevant.)

void draw(Texture& texture, GLfloat x, GLfloat y)
    {
    if (!batching_started) return;

    SpriteBatchItem& item = sprite_buffer[sprite_count];

    item.texture = &texture;
    item.position[0] = x;
    item.position[1] = y;
    item.dimensions[0] = texture.getWidth();
    item.dimensions[1] = texture.getHeight();

    ++sprite_count;
    }

Finally, the end() method is called. It calculates the vertices' final positions and sends the data to the GPU:

void end()
    {
    if (!batching_started || sprite_count < 1) return;

    glUseProgram(shader);

    for (int i = 0; i < sprite_count; ++i)
        {
        SpriteBatchItem& item = sprite_buffer[i];

        GLfloat tex_w = item.dimensions[0];
        GLfloat tex_h = item.dimensions[1];

        glm::mat4 model;
        model = glm::translate(model, glm::vec3(item.position[0], item.position[1], 0.0f));
        model = glm::translate(model, glm::vec3(1.0f * tex_w, 1.0f * tex_h, 0.0f));
        model = glm::rotate(model, 0.0f, glm::vec3(0.0f, 0.0f, 1.0f));
        model = glm::translate(model, glm::vec3(-0.5f * tex_w, -0.5f * tex_h, 0.0f));
        model = glm::scale(model, glm::vec3(glm::vec2(tex_w/2, tex_h/2), 1.0f));
        //For each vertex...
        for (int j = 0; j < 6; ++j)
            {
            glm::vec4 pos = projection * model * glm::vec4(quad_data[j*3], quad_data[j*3+1], 0.0f, 1.0f);
            vertex_buffer[i*18 + j*3    ] = pos.x;
            vertex_buffer[i*18 + j*3 + 1] = pos.y;
            vertex_buffer[i*18 + j*3 + 2] = (GLfloat)j;
            }
        }

    //Send the data to the GPU
    glBindVertexArray(vao);

        glBindBuffer(GL_ARRAY_BUFFER, vbo);
        glBufferData(GL_ARRAY_BUFFER, sprite_count * sizeof(quad_data), vertex_buffer, GL_DYNAMIC_DRAW);
        glBindBuffer(GL_ARRAY_BUFFER, 0);

        glDrawArraysInstanced(GL_TRIANGLES, 0, 6, sprite_count);

    glBindVertexArray(0);

    glUseProgram(0);

    batching_started = false;
    }

The VertexShader looks like this:

#version 330 core

layout (location = 0) in vec2 position;
layout (location = 1) in float index;

out float out_index;

void main(void)
    {
    gl_Position = vec4(position, 0.0f, 1.0f);
    out_index = index;
    }

The fragment shader I don't suppose is relevant at this stage as it simply sets everything to the same colour in it's current state.

Now, to get to the crux of the problem, the above code causes no output. Nothing renders on the screen with the following render function in the main loop:

void render()
    {
    glClearColor(0.0f, 1.0f, 0.0f, 1.0f);
    glClear(GL_COLOR_BUFFER_BIT);

    batch.begin();
        batch.draw(snow_tex, 375, 275);
        batch.draw(snow_tex, 50, 45);
        batch.draw(snow_tex, 155, 55);
        batch.draw(snow_tex, 200, 2);
    batch.end();

    SDL_GL_SwapWindow(WINDOW);
    }

What I would expect it to do is to render four rectangles on the screen. But this does not happen. Curiously, if I remove the following line from the batch init function...

glVertexAttribDivisor(0, 1);

...the first rectangle does render. However, the rest three do not. So, there's obviously something fundamental I am not quite understanding about instancing and how it's intended to work. Some help would be damn greatly appreciated, even if its a suggestion of a change of approach or something similar.

\$\endgroup\$

2 Answers 2

1
\$\begingroup\$

So the problem might be in this place:

 glBindVertexArray(vao);

        glBindBuffer(GL_ARRAY_BUFFER, vbo);

        //Position
        glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 3 * sizeof(GLfloat), (GLvoid*)0);
        glEnableVertexAttribArray(0);
        glVertexAttribDivisor(0, 1);

        //ID, for texture positioning
        glVertexAttribPointer(1, 1, GL_FLOAT, GL_FALSE, 3 * sizeof(GLfloat), (GLvoid*)(2 * sizeof(GLfloat)));
        glEnableVertexAttribArray(1);
        glVertexAttribDivisor(1, 1);

        glBindBuffer(GL_ARRAY_BUFFER, 0);

glBindVertexArray(0);

There is no GL call that feeds vbo with data (quad_data). Try calling glBufferData() (with proper parameters) after binding the vbo buffer.


Above might not be totally correct since I didn't notice the following code:

//Send the data to the GPU
glBindVertexArray(vao);

    glBindBuffer(GL_ARRAY_BUFFER, vbo);
    glBufferData(GL_ARRAY_BUFFER, sprite_count * sizeof(quad_data), vertex_buffer, GL_DYNAMIC_DRAW);
    glBindBuffer(GL_ARRAY_BUFFER, 0);

    glDrawArraysInstanced(GL_TRIANGLES, 0, 6, sprite_count);

glBindVertexArray(0);

First of all this is not allowed to call glBufferData() every frame. Since this call creates and initializes data store on GPU. What you want to do is to create and allocate memory on GPU once (tip above the line) and in this piece of code you want to do something similar to (to update data in the buffer vbo):

//Send the data to the GPU
glBindVertexArray(vao);

    glBindBuffer(GL_ARRAY_BUFFER, vbo);
    glBufferSubData(GL_ARRAY_BUFFER, 0 /* offset */, sprite_count * sizeof(quad_data), vertex_buffer);

    glDrawArraysInstanced(GL_TRIANGLES, 0, 6, sprite_count);

glBindVertexArray(0);
\$\endgroup\$
1
  • \$\begingroup\$ Hey, I actually already made many of those improvements as, really, the code was and still isn't very optimized as I'm experimenting and learning OpenGL on the go. But thanks! Offtopic, I wonder if it matters whether or not I use glBufferSubData or glBufferData if I update all of the vertices in a single frame. Anyway, I do have a working implementation with glDrawElements and an element array buffer now and I'm pretty happy with my SpriteBatch. But the original question, why the instancing part of my code was not working, remains a question to me, so I haven't edited the original post. \$\endgroup\$
    – Salajouni
    Jun 22, 2016 at 12:20
0
\$\begingroup\$

http://www.learnopengl.com/#!Advanced-OpenGL/Instancing

Debugging someone's code when nothing obvious leaps out takes too long to reasonably expect someone to do (especially in OGL where there's many things to go wrong) but this tutorial on the subject should make it easy to get something on the screen if no one immediately spots your error. From there you should be able to get things actually going in your own code.

What makes this code interesting is the last line where we call glVertexAttribDivisor. This function tells OpenGL when to update the content of a vertex attribute to the next element. Its first parameter is the vertex attribute in question and the second parameter the attribute divisor. By default the attribute divisor is 0 which tells OpenGL to update the content of the vertex attribute each iteration of the vertex shader. By setting this attribute to 1 we're telling OpenGL that we want to update the content of the vertex attribute when we start to render a new instance. By setting it to 2 we'd update the content every 2 instances and so on. By setting the attribute divisor to 1 we're effectively telling OpenGL that the vertex attribute at attribute location 2 is an instanced array.

Looks like you are sending in the IDs manually which is not necessary.

By itself this function is a bit useless. Rendering the same object a thousand times is of no use to us since each of the rendered objects are rendered exactly the same and thus also at the same location; we would only see one object! For this reason GLSL embedded another built-in variable in the vertex shader called gl_InstanceID.

When drawing via one of the instanced rendering calls, gl_InstanceID is incremented for each instance being rendered starting from 0. If we were to render the 43th instance for example, gl_InstanceID would have the value 42 in the vertex shader. Having a unique value per instance means we could now for example index into a large array of position values to position each instance at a different location in the world.

Seems you set an attribute for them as well but you do not actually send them.

I won't be surprised if you remove this issue things start working as expected.

Anyway, regardless of where the problem lies (or problems), should be easy to get it going with that tutorial as a starting place. Worst comes to worst just implement the tutorial first then add in the functionality you want afterwards. Trying to do batch objcts and such before you even have the basic rendering happening is just too much.

\$\endgroup\$

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .