Take the 2-minute tour ×
Game Development Stack Exchange is a question and answer site for professional and independent game developers. It's 100% free, no registration required.

I am new to OpenGL 3 and graphics programming, and want to create some basic 2d graphics. I have the following scenario of how I might go about drawing a basic (but general) 2d rectangle. I'm not sure if this is the correct way to think about it, or, if it is, how to implement it.

In my head, here's how I imagine doing it:

  1. t = make_rectangle(width, height)
    • build general VBO, centered at 0, 0
  2. optionally: t.set_scale(2)
  3. optionally: t.set_angle(30)
  4. t.draw_at(x, y)
    • calculates some sort of scale/rotate/translate matrix (or matrices), passes the VBO and the matrix to a shader program
  5. Something happens to clip the world to the view visible on screen.

I'm really unclear on how 4 and 5 will work. The main problem is that all the tutorials I find either: use fixed function pipeline, are for 3d, or are unclear how to do something this "simple".

Can someone provide me with either a better way to think of / do this, or some concrete code detailing performing the transformations in a shader and constructing and passing the data required for this shader transformation?

share|improve this question

2 Answers 2

up vote 2 down vote accepted

The basics of drawing are really not much different than with 3D. You need a set of vertices, a transformation matrix, and material properties. For a sprite-based game, the vertices will always be a simple quad, and the material will usually just be a texture (though there are often other properties, e.g. to make sprites blink different colors when an enemy takes damage, or the like).

In this case, you can create a single VBO for all sprites, and reuse it.

If you have complex shapes, you're going to want to use a batching approach. Push all your pre-transformed vertices into a single large VBO and render them all in one go. Doing a separate GL draw call for each object is slow.

The transformation matrix at its simplest is a 3x3 matrix representing the homogeneous transformation. You have your translate (location), rotation, and scale. The matrix is composed by concatenating (multiplying) the matrices together. GL prefers column-major column matrices (but you are free to use any other representation, if you wish, but I won't get into how).

Your 2D transformation then is the combination of the Rotation, Scale, and Translation matrices:

|1, 0, tx|   |cos(theta), -sin(theta), 0|   |sx, 0,  0|
|0, 1, ty| * |sin(theta), cos(theta),  9| * |0,  sy, 0|
|0, 0, 1 |   |0,          0,           1|   |0,  0,  1|

The matrix can either be applied to your vertices on the CPU before uploading, if you're pushing all your vertices into a single VBO.

If you're using raw sprites, instancing can be a lot faster. In this case, you will use a single draw call to render a number of copies of your quad VBO. To access the matrices, they must be stored in either a texture or what's called a Texture Buffer Object. The latter is better. The idea there is that you upload all your transformation matrices for your objects into a VBO, which is bound to a Texture, which is then bound to the shader. The vertex shader uses the special variable gl_InstanceID and the texelFetch() command to read the matrices out of the Buffer Texture, and then you apply it to the vertices.

You can read more about how to use Buffer Texture Objects here:

http://www.opengl.org/wiki/Buffer_Texture

You can read more about instancing here:

http://www.opengl.org/wiki/Vertex_Specification#Instancing

With the instancing approach, you also want to pack in the UV coordinates, and you'll need to use a texture atlas for rendering. I recommend using 2D texture arrays for your atlases, assuming all your sprites have the same width and height. It has a lot of advantages over a traditional texture atlas. The traditional kind works just fine in many cases, if that's what you'd prefer.

The end result is that you'll only have a single draw call for every "material" (e.g. texture), which with atlasing might well be hundreds or thousands of individual objects.

A rough pseudo-code overview then:

init():
  vbo_quad = createVBO()
  pushQuadVerts(vbo_quad)

  tex_atlas = createTextureAtlas()

  vbo_objects = createVBO()
  tex_objects = createTextureBuffer(vbo_objects)

  shader = loadShaders("vertex.glsl", "fragment.glsl")

update():
  for each object:
    calculateObjectTransform()
    pushTransformOntoVBO(vbo_objects)

draw():
  bindVBO(vbo_quad)
  bindTexture(UNIT0, tex_atlas)
  bindTexture(UNIT1, tex_objects)

  drawArraysInstanced(GL_QUADS, 0, 4, number_of_objects)

As a final hint, if you're going the instanced route, you might find it a bit quicker and easier to avoid calculating the entire transformation matrix on the CPU, and instead calculate it on the GPU. You can pass in the rotation, scale, and translation. If you do the math, you can take those inputs and calculate the matrix directly. Then instead of needing to eat up 9 floats in memory bandwidth to the GPU and doing a lot of work on the serial CPU, you can pass in 5 floats (4 if you only have uniform scale) and do the work on the parallel GPU.

The math comes out something like this (off the top of my head, so double check):

|sx * cos(theta), -sy * sin(theta), tx|
|sx * sin(theta),  sy * cos(theta), ty|
|0,                0,               1 |

Nice and easy.

share|improve this answer

What you'll want to do is create a vertex shader that will pass the VBO directly on it's way down the pipe. This way, any transformations you make to you VBO will be applied. This however, will cause you to have to relearn transformations if you ever switch to 3D as you will want to impliment them in the shader instead. How I would break it up is, using your steps:

1) Create the VBO of your basic rectangle.

2 & 3) Edit each element in your VBO, or if you want to learn to do it in the shader, create a transformation matrix of what you want to do to the rectangle.

4)Here you will need to about the glDrawArrays command. If you wish to do your transformation in the shader, you will need to learn about glUniformMatrix4fv command or one of it's many cousins as well as how to implment it on the GLSL side.

5)You will want to learn about perspective matrix, want kinds there are, like orthoginal views and frustum, well those are for 3D, but you'll get the idea. But, working in two dimentsions makes things a little easier as your limits will be -1.0 to 1.0 unless you change them.

I recommend looking up a book by Edward Angel, namely his Interactive Computer Graphics book, the latest edition will assist you, though local librarys may only have older versions that are centered on older versions of OpenGL, it will help you build the basics. If your wanting a more free-bee lession approach, the http://openglbook.com/ is decently done. It helps get a simple implimentation going in both 2D and 3D and will help with the basic first steps, though it is not the best implimentation. And once you get going, fool around with the code and shaders, best way to learn.

share|improve this answer
    
I want to calculate the transformation matrix, then pass it to the shader to do the work. What I need help with is a) making the transformation matrix, and b) getting it to the shader and using it. I don't care about 3D at all right now, which is why I'm having trouble finding an existing explanation of what I want. –  davidism Mar 12 '12 at 18:20
    
@davidism: Are you using homogeneous corrodinates i.e for 2d space is it vec3(x, y, w) where w is a 1 if it is a point or 0 if it a vector. You will should impliment that. Then all your transformations become a 3x3 matrix. Translation: [1 0 X 0 1 Y 0 0 1] Rotation: [cos(T) -sin(T) 0 sin(T) cos(T) 0 0 0 1] Though I am not full confident on those matricies but should be close. In the shader you will need to declare this new matrix and multiply it by the VBO. I can't include in the comment the shader, but impliment a 2D shader like a 3D shader when working like this. –  Kreutzwj Mar 12 '12 at 18:37

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

Not the answer you're looking for? Browse other questions tagged or ask your own question.