OpenGL Efficient Rendering of 2D Sprites

Started by
11 comments, last by bwhiting 8 years, 4 months ago

Hi,

It's my first post here so sorry if this is in the wrong place. I am currently trying to make a basic 2D game in c++ and opengl, to help familiarise myself with using opengl practically (putting it into an object orientated context). My current rendering system (which I implemented yesterday, so it is very easy to remove/modify) involves a 'Sprite' class which holds a position, a texture and a texture rectangle, and whenever one of these are modified, I generate the new vertex data:


GLfloat vertices[] = {
     mPosition.x, mPosition.y, (mTextureRect.x)/mTexture->getWidth(), (mTextureRect.y)/mTexture->getHeight(),
    mPosition.x + mTextureRect.width, (mTextureRect.x + mTextureRect.width)/mTexture->getWidth, mTextureRect.y/mTexture->getHeight()

    //And so on for all 6 points (2 triangles) that make up the texture rectangle. (first two vertices are the position, second two are the UV coordinates).
}

and stick it in a VAO which the sprite owns. Basically this means that whenever the sprites position changes, I have to update the vertex array. The renderer class then just binds the vao, draws the texture and unbinds the vao.

Now this is working fine at the moment, but in terms of performance when I've got 30+ sprites on the screen which positions are constantly being updated, then I feel this method may be too slow. Another thing I don't like about this implementation is that the sprite has a position attribute, but without this I would have to update the VAO every draw call which kinda defeats the whole point. Obviously my implementation is nowhere near optimal, and I'm not looking for insane performance, but I feel like there are methods which are just as easy to implement yet would yield lots better performance.

What I'm asking is what alternative methods are there to rendering 2D sprites efficiently, and does anyone have any examples of 2D rendering systems (or 3D ones that are fairly simple - most 3D examples that I've looked at seem very complicated and way out of the scope of my little 2d game). Sorry if this isn't formatted right, in wrong place or doesn't really make sense, just ask if you need clarification on what I'm asking.

Thanks in advance

Advertisement

and stick it in a VAO which the sprite owns

...

I've got 30+ sprites on the screen which positions are constantly being updated,

Means you're issuing draw call on every sprite?

It should be much faster to upload data from all visible sprites into single vertex buffer and draw it.

Even if that buffer will be rebuilt from scratch every frame.

Just make sure you're not updating same buffer, that would stall cpu, since it will have to wait until gpu finished rendering.

Best would be to have 2 buffers. While one buffer is being rendered, you're filling data in next buffer, then swap them.


Another thing I don't like about this implementation is that the sprite has a position attribute, but without this I would have to update the VAO

It's totally ok to continuously update data in vbo. You're not changing vao, you're only updating data in vbo that is already part of vao.

There's no overheads related to vao in that way.

Also you can split your scene in few chunks, either by sprite position or by movement update frequency, and update only those chunks that has sprites moved.

Hi, thanks for the reply. Firstly, yes, my current implementation does involve a draw call for every single sprite. So, if I've understood correctly, I should only need one VAO which is owned by the renderer, and in the draw method instead of directly drawing to the screen, get the vertex data from the sprite and stick it into a vbo. Then, after this has been done for every sprite, send this data over to the GPU.

Now, how would I go about binding different textures throughout all of this? Currently my draw call looks something like this:


void Renderer::draw(Sprite sprite)
{
    mShader.enable();


    sprite.getTexture()->bind();
    glBindVertexArray(sprite.mVAO);
    glDrawArrays(GL_TRIANGLES, 0, 6);
    sprite.getTexture()->unbind();


}

So how would the renderer know which texture to bind for different vertex data? Sorry if these questions seem trivial, im just trying to wrap my head around this stuff. Also, do you know any simple examples of 2D renderers which I could at for reference?

Look up texture atlases - there's plenty of information on them, and they're one way of solving your problem.

A related tip - that unbind is wasteful. You frequently see it in tutorials, but it doesn't actually achieve anything useful, and if you end up rendering two or more sprites with the same texture you'll only end up losing more performance by introducing unnecessary state changes.

Direct3D has need of instancing, but we do not. We have plenty of glVertexAttrib calls.

Ok, thanks for the reply, texture atlases seem to be the kind of thing I'm looking for. I found a nice thread on stackexchange here which seems to outline what I'm looking for: http://gamedev.stackexchange.com/questions/30362/drawing-lots-of-tiles-with-opengl-the-modern-way

One more quick question - I feel like packing every single sprite in the game into one texture atlas is going to get very messy, especially if I choose to change the atlas or add a new texture later. Would it be worth having a few atlases, for example, one for the tiled background, one for the player, and one for other entities (enemies etc)?

EDIT: Further researching and people are suggesting TEXTURE_2D_ARRAY as an alternative to texture atlases - is this a good idea?

One thing to remember is that it's usually not too bad with a drawcall for each sprite unless you have on the order of at least several thousand. GL draw-calls are pretty fast, assuming you still use a texture-atlas and don't switch textures for each one (+ sort by texture either way, even with the atlas).

It's uncommon that sprites have any other bottle-neck than fillrate, unless you do something like very many small particles. If your sprites are reasonably large, then sorting by texture or even area of a texture to improve cache usage can give much more benefit in itself than every possible optimization of the vertices does.

When sprites are sorted by texture then changing textures won't matter too much, so several atlases isn't a bad idea, and if you have sprites with many animation frames you will probably end up with one texture per sprite for the animated ones to fit all the frames.

Count the number of texture switches, if you only save a few texture switches by combining an atlas with another then it probably isn't worth it, but if you need to switch atlas between every other sprite then combining them has a large benefit.

One thing to remember is that it's usually not too bad with a drawcall for each sprite unless you have on the order of at least several thousand. GL draw-calls are pretty fast, assuming you still use a texture-atlas and don't switch textures for each one (+ sort by texture either way, even with the atlas).

It's uncommon that sprites have any other bottle-neck than fillrate, unless you do something like very many small particles. If your sprites are reasonably large, then sorting by texture or even area of a texture to improve cache usage can give much more benefit in itself than every possible optimization of the vertices does.

When sprites are sorted by texture then changing textures won't matter too much, so several atlases isn't a bad idea, and if you have sprites with many animation frames you will probably end up with one texture per sprite for the animated ones to fit all the frames.

Count the number of texture switches, if you only save a few texture switches by combining an atlas with another then it probably isn't worth it, but if you need to switch atlas between every other sprite then combining them has a large benefit.

Ok thanks for the reply - my current thought is to use 3 texture atlases and vbos - one for each of : the player (dynamic), the tilemap (static), and miscellaneous objects such as enemies and moving platforms ( dynamic - cant put moving platforms into the tilemap vbo as the platforms' positions will be constantly updated). This way there is only 3 texture switches and 3 vbo switches.

Have a look at the batch renderer article that I published here: http://www.gamedev.net/page/resources/_/technical/opengl/opengl-batch-rendering-r3900

Basically when I want to render sprites, I send them down to the batch renderer. It is responsible for grouping the vert's into buckets and when I'm ready to render my frame, it just empties all the render buckets that it has stored.

Have a look at the batch renderer article that I published here: http://www.gamedev.net/page/resources/_/technical/opengl/opengl-batch-rendering-r3900

Basically when I want to render sprites, I send them down to the batch renderer. It is responsible for grouping the vert's into buckets and when I'm ready to render my frame, it just empties all the render buckets that it has stored.

Ahh thankyou that is also of some use to me. Ill look into batching techniques and see how I could implement them in my renderer.

Bindless would make this whole texture atlas thing wholly unnecessary, but we're getting into very sophisticated (and audience-limited) uses of OpenGL that are probably not needed. I stream my 2D vertices into persistent mapped buffers and don't atlas anything, one draw call per element, and it's never been a problem for UI code. Atlases are definitely better, though.

SlimDX | Ventspace Blog | Twitter | Diverse teams make better games. I am currently hiring capable C++ engine developers in Baltimore, MD.

This topic is closed to new replies.

Advertisement