3
votes

I'm currently trying to setup a 2D sprite animation with OpenGL 4. For example, I've designed a ball smoothly rotating with Gimp. There are about 32 frames ( 8 frames on 4 rows).

I aim to create a sprite atlas within a 2D texture and store my sprite data in buffers (VBO). My sprite rectangle would be always the same ( i.e. rect(0,0,32,32) ) but my texture coordinates will change each time the frame index is incremented.

I wonder how to modify the coordinates.

  1. As the sprite tiles are stored on several rows if appears to be difficult to manage it in the shader.
  2. Modify the sprite texture coordinate within the buffer using glBufferSubData() ?

I spent a lot of time with OpenGL 1.x....and I get back to OpenGL few months ago and I realized many things changed though. I will try several options though, but your suggestions and experience are welcome.

3

3 Answers

4
votes

As the sprite tiles are stored on several rows if appears to be difficult to manage it in the shader.

Not really, all your sprites are the same size, so you get a perfect uniform grid, and going from some 1D index to 2D is just a matter of division and modulo. Not really hard.

However, why do you even store the single frames in an mxn grid? Now you could store them just in one row. However, in modern GL, we have array textures. These are basically a set of independent 2D layers, all of the same size. You just access them by a 3D coordinate, with the third coordinate being the layer from o to n-1. This is ideally suited for your use case, and will eliminate any issues of texture filtering/bleeding at the borders, and it also will work well with mipmapping (if you need that). When array textures were introduced, the minumim number of layers an implementation is required to support was 64 (it is much higher nowadays), so 32 frames will be a piece of cake even for old GPUs.

2
votes

You could do this a million ways but I'm going to propose a naive solution:

Create a VBO with 32(frame squares)*2(triangles per frame square)*3(triangle vertices)*5(x,y,z, u,v per vertex) = 960 floats of space. Fill it in with the vertices of all your sprites in a 2 triangler-per frame fashion.

Now according to the docs of glDrawArrays, you can specify where you start and how long you render for. Using this you can specify the following:

int indicesPerFrame = 960/32;
int indexToStart = indicesPerFrame*currentBallFrame;
glDrawArrays( GL_TRIANGLES, indexToStart, indicesPerFrame);

No need to modify the VBO. Now from my point of view, this is overkill to just render 32 frames 1 frame at a time. There are better solutions to this problem but this is the simplest for learning OpenGL4.

0
votes

In OpenGL 2.1, I'm using your 2nd option:

void setActiveRegion(int regionIndex)
{
    UVs.clear();

    int numberOfRegions = (int) textureSize / spriteWidth;

    float uv_x = (regionIndex % numberOfRegions)/numberOfRegions;
    float uv_y = (regionIndex / numberOfRegions)/numberOfRegions;

    glm::vec2 uv_up_left    = glm::vec2( uv_x                     , uv_y );
    glm::vec2 uv_up_right   = glm::vec2( uv_x+1.0f/numberOfRegions, uv_y );
    glm::vec2 uv_down_right = glm::vec2( uv_x+1.0f/numberOfRegions, (uv_y + 1.0f/numberOfRegions) );
    glm::vec2 uv_down_left  = glm::vec2( uv_x                     , (uv_y + 1.0f/numberOfRegions) );


    UVs.push_back(uv_up_left   );
    UVs.push_back(uv_down_left );
    UVs.push_back(uv_up_right  );

    UVs.push_back(uv_down_right);
    UVs.push_back(uv_up_right);
    UVs.push_back(uv_down_left);

    glBindBuffer(GL_ARRAY_BUFFER, uvBuffer);
    glBufferSubData(GL_ARRAY_BUFFER, 0, UVs.size() * sizeof(glm::vec2), &UVs[0]);
    glBindBuffer(GL_ARRAY_BUFFER, 0);

}

Source: http://www.opengl-tutorial.org/intermediate-tutorials/tutorial-11-2d-text/

He implemented it to render 2D Text but it's the same concept!

I hope have helped!