OpenGL Programming/Modern OpenGL Tutorial 2D

From Wikibooks, open books for an open world
Jump to: navigation, search

Even if you do not plan to make a 3D game, and stick to 2D, OpenGL will still bring invaluable tools.

Hardware acceleration[edit]

In the past, 2D graphic card provided hardware acceleration by allowing the programmer to store bitmaps and sprites directly in the card, along with a few primitives to perform basic copies (blits), with or without alpha blending.

Nowadays, these features are being replaced by OpenGL and its (more generic) textures. Programming in 2D in OpenGL is basically displaying textures facing the screen, with z coordinates always set to 0. This also introduces a greatly needed standardization for 2D acceleration (for instance, there is essentially no way to get 2D acceleration under GNU/Linux + X11).

This technique is used by several graphics libraries, including SFML, ClanLib or Gnash.

Setting up the 2D space[edit]

We will use an orthographic projection matrix, where there is no perspective (objects far away look as big as near objects - you may have already seen this in technical drawing, or by typing Numpad 5 in Blender).

GLM provides glm::ortho to compute such a projection. Since we'll be manipulating pixels directly, let's use the size of the physical screen in pixels, rather than [-1, 1] as we previously did.

In addition, we've seen that OpenGL's vertical axis is bottom-to-top, while traditional 2D screens are top-to-bottom, so let's reverse the Y axis:

  glm::mat4 projection = glm::ortho(0, screen_width, screen_height, 0);

But, since we're programming modern, shouldn't we forget about old-style 2D coordinates? Well, it happens that most graphic formats are top-to-bottom, too. TGA and BMP are bottom-to-top, but almost all other formats, and above all formats libraries (JPG, PNG, etc.), will give you top-to-bottom pictures. And we saw in the texture tutorial that OpenGL uses bottom-to-top for textures, so it would display them upside-down!

Reversing the OpenGL screen means that our pictures can be uploaded to the OpenGL graphic card as-is, and will be displayed in the right direction. The alternative would be to reverse texture coordinates.

The only point of attention is that the rotation angle on the Z axis need to be reversed, too.

But maybe the main reason behind reversing coordinates is that most users expect Y coordinates at top-to-bottom: check Gimp and Dia for instance; also check other 2D game frameworks and libraries. One notable exception is Inkscape (vector drawing) where coordinates start at the bottom-left like OpenGL.

Uploading textures[edit]

Graphic cards used to have odd limits, such as only allowing power-of-two dimensions. Since we are targetting OpenGL 2 and later, we can safely assume that this limitation is now lifted.

Displaying a sprite[edit]

To "blit" the texture to the OpenGL buffer the simplest way is to draw a pair of triangles with a texture:

/* code here */

You could however perform incremental display updates, by not calling glClear and using techniques such as dirty rectangles - although nowdays the GPU is usually fast enough to avoid implementing this kind of optimization.

Blitting on a texture[edit]

framebuffer / renderbuffer / reuse-that-as-textures / ... ?


Optimizing for 2D[edit]

Since we work in 2D, we can remove:

  • shrink transformation matrices from 4x4 to 3x3
  • the z coordinates in the vertices
  • back-face culling is not necessary
  • depth-test is not necessary

GLM only provides 4x4 transformation matrices. We'd need a different set of function to work with 3x3 matrices[1], so let's keep using 4x4 translations.

  glm::mat4 mvp = glm::mat4(...);

For the vertices, we can pass a 2d vector, and convert it later to multiply it by our 4x4 matrix.

gl_Position is still a vec4, so:

  gl_Position = mvp * vec4(v_coord, 1.0);

Zooming the whole screen[edit]

You can perform a zoom effect by adjusting the projection. Here's a progressive zoom out, for instance:

  float scale = glutGet(GLUT_ELAPSED_TIME) / 1000.0 * .2;  // 20% per second
  glm::mat4 projection = glm::ortho(0.0f, 1.0f*screen_width*scale, 1.0f*screen_height*scale, 0.0f);

Exact / perfect pixelization[edit]

OpenGL has a special rule to draw fragments at the center of pixel screens, called "diamond rule" [2] [3]. Consequently, it is recommended to add a small translation in X,Y before drawing 2D sprite:

glm::translate(glm::mat4(1), glm::vec3(0.375, 0.375, 0.));

TODO: provide an example


  • Simple implementation of 2D blit in the GLtron project:
  • glSDL is not actively developed anymore but offers a way to treat OpenGL textures as SDL hardware surfaces


  1. For instance, if converting with glm::mat3(mvp), we'll lose the 4th matrix column that contains the translation operations
  2. "OpenGL ES Common Profile Specification Version 2.0.25". 2010-11-02. Retrieved 2011-11-12.  - section 3.4.1 Basic Line Segment Rasterization
  3. "OpenGL FAQ and Troubleshooting Guide v1.2001.11.01 - 9.030 How do I draw 2D controls over my 3D rendering?". Retrieved 2011-11-12.  - If exact pixelization is required, you might want to put a small translation in the ModelView matrix

< OpenGL Programming

Browse & download complete code