VBOs vs. immediate mode for 2D sprites

Started by
14 comments, last by Erik Rufelt 13 years, 6 months ago
I'm definitely not an expert, but you could try transforming all of your objects on the CPU rather than using glTranslate.

For the type of objects you're using (2D quads) the math is very light as you only really have to transform one point per object; this way you can build a single large buffer to draw all at once, as everything will be in worldspace.
Advertisement
Quote:Original post by raigan
I'm definitely not an expert, but you could try transforming all of your objects on the CPU rather than using glTranslate.

For the type of objects you're using (2D quads) the math is very light as you only really have to transform one point per object; this way you can build a single large buffer to draw all at once, as everything will be in worldspace.
You can also do this on the GPU - for potentially much greater speed gains.

The trick is that you don't need a full 4x4 matrix transformation just to store a 2-dimensional position and rotation. Rather than building 4x4 rotation matrices CPU-side and then passing them to the GPU (i.e. what glTranslate/glRotate does), just pass a buffer of 2d vectors and rotation angles down to a custom vertex shader, and perform the transformation manually in the shader.

You will have cut out the cost of building all the matrices on the CPU, cut down the bandwidth required to send those matrices to the GPU, *and* made it possible to draw everything which shares the same texture with a single draw call - effectively, it is a specialised form of old-fashioned OpenGL instancing.

Tristam MacDonald. Ex-BigTech Software Engineer. Future farmer. [https://trist.am]

So what I'm currently doing is having an array of "sprite data".

Each instance has X,Y,W,H,angle,framenumber.

The shader turns those into corners. In order to do this, I allocate 4x as many slots as sprites. Each slot also has a "corner" number.

To draw a frame, clone slots 0..(N-1) into N..2N-1, clone them again from 0..2N-1 -> 2N..4N-1

I then write into the corner number elements, 0..N-1 := 0, N..2N-1 := 1 etc.

I've pre-setup an index array which has 0,N,2N,3N, 1, N+1, 2N+1, 3N+1 etc and so rendering the quads is just drawing that array.


The shader then picks up the corner number and the other data and produces corner coords. It also produces a "sheet number" which is passed to the pixel shader (to choose a texture unit to use) and texture coords from the frame number and some uniforms describing the texture data.

I haven't worked out a way to do this without needing to clone the data yet. If anyone knows of a way, I'd be interested it hear it.
Quote:Original post by Katie
I haven't worked out a way to do this without needing to clone the data yet. If anyone knows of a way, I'd be interested it hear it.
Place the data into a texture (instead of a VBO), and sample the texture in the vertex shader to retrieve it. You pass an index along with each vertex, and then the texture coord to sample from is mod(index, 4).

If you are targeting only modern OpenGL you can use a texture-buffer-object for this, but a plain old texture works fine too.

NVidia has a sample along these lines, demonstrating how textures let you do fancy indexing into vertex data arrays.

Tristam MacDonald. Ex-BigTech Software Engineer. Future farmer. [https://trist.am]

Quote:Original post by Erik Rufelt
There are also other ways of drawing the ships in a single draw call. Do you plan to keep your game in 2D so that each ship is a single quad?
Do you rotate the quads to rotate the ships, or is everything handled by changing the sprite texture, so that the quad can always remain a rectangle?
Also, what OpenGL version and hardware are you targeting?
And lastly, are you using shaders, or are you otherwise willing to use them?


I do plan on keeping the game 2D, using a quad for each ship. As of now, I am planning on rotating the quads themselves.

As for OpenGL version/hardware, I just want anyone with a somewhat recent-ish card to be able to play. I was requiring either 1.5 or the ARB_vertex_buffer_object extension when experimenting with VBOs, but at the moment there aren't any requirements. I would certainly be open to upping the requirement some, however, if there was some extension that would really help.

I am not using shaders, although I recall reading somewhere that they could actually be used to render things other than shadows (which is all I thought they were for). I could probably learn how to use them if I needed to.
Quote:Original post by Dark_Oppressor
I do plan on keeping the game 2D, using a quad for each ship. As of now, I am planning on rotating the quads themselves.

As for OpenGL version/hardware, I just want anyone with a somewhat recent-ish card to be able to play. I was requiring either 1.5 or the ARB_vertex_buffer_object extension when experimenting with VBOs, but at the moment there aren't any requirements. I would certainly be open to upping the requirement some, however, if there was some extension that would really help.

I am not using shaders, although I recall reading somewhere that they could actually be used to render things other than shadows (which is all I thought they were for). I could probably learn how to use them if I needed to.


Shaders are used for everything. If you write glTranslatef(...); glVertex3f(...); then when the vertex is actually drawn, a vertex shader will be run that multiplies your vertex with a matrix that includes your translation. If you write these shaders manually you can exploit how your particular game works in order to optimize things.
Whenever a pixel is drawn on the screen, a pixel shader is run that fetches the texture color and multiplies it by the vertex-color.
Shaders are run billions of times per second on modern hardware.

If you learn to write and use shaders, a whole new world opens up, so I would definitely recommend it if you want to implement something more advanced. Learning the basics of how shaders work is not too hard, so you probably won't need to dedicate too much time to it before being able to continue with your game.
Optimizing everything to perfection can be a bit harder, but shouldn't be too difficult once you learn how to pass arrays and data to the vertex-shader. I think a vertex-shader is the single thing that could benefit you the most here. If you target DX10+ class hardware, a geometry shader can make it even easier and possibly more efficient too.

If you want to try without shaders first, create a really large array of 1000*4 vertices or so, and manually put 1000 quads into it. Then upload the data to your VBO and draw the 1000 quads with glDrawElements. Then instead of using glTranslate and glRotate you manually update the array with translated coordinates and call glBufferData each frame before drawing. This could be efficient enough. If you use different textures you might need to use a few glDrawElements, but only as many as you have different sprite sheets, if you sort your objects by texture.
With this you would also need to manually put the texture-coords into the array depending on which animation-frame is to be used.

Instancing can be an easy way to reduce calls, but it also requires shaders.

This topic is closed to new replies.

Advertisement