OpenGL Programming/Modern OpenGL Tutorial 2D< OpenGL Programming
Even if you do not plan to make a 3D game, and stick to 2D, OpenGL will still bring invaluable tools.
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 spaceEdit
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::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::ortho(left, right, bottom, top, [zNear, zFar]) 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.
Graphic cards used to have odd limits, such as only allowing power-of-two dimensions.
In OpenGL ES 2, non-power-of-two textures are allowed only if:
GL_TEXTURE_MIN_FILTERdoesn't use mipmaps
GL_TEXTURE_WRAP_Tare both set to
otherwise the texture will always return black.
Let's do that for our textures.
Displaying a spriteEdit
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 textureEdit
framebuffer / renderbuffer / reuse-that-as-textures / ... ?
Optimizing for 2DEdit
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, 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 screenEdit
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 pixelizationEdit
Consequently, it is recommended to add a small translation in X,Y before drawing 2D lines, so that the last pixel isn't missed:
glm::translate(glm::mat4(1), glm::vec3(0.375, 0.375, 0.));
TODO: provide an example
Note: this seems limited to drawing primitives, I couldn't reproduce any issue when manipulating textures.
- 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
- "glTexParameter". Khronos.org. https://www.khronos.org/opengles/sdk/docs/man/xhtml/glTexParameter.xml. Retrieved 2015-08-19.
- For instance, if converting with
glm::mat3(mvp), we'll lose the 4th matrix column that contains the translation operations
- "OpenGL ES Common Profile Specification Version 2.0.25". Khronos.org. 2010-11-02. http://www.khronos.org/registry/gles/specs/2.0/es_full_spec_2.0.25.pdf. Retrieved 2011-11-12. - section 3.4.1 Basic Line Segment Rasterization
- "OpenGL FAQ and Troubleshooting Guide v1.2001.11.01 - 9.030 How do I draw 2D controls over my 3D rendering?". http://www.opengl.org/resources/faq/technical/transformations.htm#tran0030. Retrieved 2011-11-12. - If exact pixelization is required, you might want to put a small translation in the ModelView matrix