A long time ago, when I wrote the game under the adroid, I had to deal with OpenGL ES 1.1. It seems to be nothing complicated, 2D graphics, you just had to draw PNG images, with the ability to scale, rotate, add transparency. Then I spent about a week on it, and maybe even more, I don’t remember. It was difficult, because I never had an affair with OpenGL. The source code
libgdx helped a
lot , in which, by the way, all low-level OpenGL functions are hidden from the developer.
Times change, instead of OpenGL ES 1.1 comes version 2.0, which is quite different. You have to figure out what these
shaders are , and why there is no way without them. It takes several days again. It would seem to be easy, because 2D, everything is simple. For example, if you use QML, it is done like this:
Image { source: "brick.png" opacity: 0.8 rotation: 90 }
And if you write everything in C ++, you get many, many lines of code that are difficult to understand if you are not familiar with OpenGL. I tried to find some kind of library wrapper over OpenGL, like libgdx, only for Qt, but without success. So I decided, after everything will work for me, I will write a small wrapper that hides all OpenGL calls and allows you to conveniently work with 2D graphics.
Wrapper code on githubGitHub usage exampleIn order to use the wrapper in your application, you need to add one line to the .pro file:
include (opengl-qt/opengl-qt.pri)
Inherit the application window from OpenGLQt :: Widget:
class Widget : public OpenGLQt::Widget { public: Widget(); protected: virtual void drawSprites(); };
Pass the path to the file and the number of images in it in the constructor. Start timer:
Widget::Widget() : OpenGLQt::Widget(10, "spritesheet.png") , m_counter(0) { startTimer(); }
Override the drawSprites () method:
void Widget::drawSprites() { static Sprite apple = {86, 118, 30, 30};
And that's all. Pretty easy, right? If you want to use this wrapper, you will need to fit all your graphics into one PNG file. This can be done, for example, using the
TexturePacker . The point of this is to increase the speed: the texture is loaded into the video memory once, at the start, and remains there until the end of the program. When you want to draw an image, you need to specify its coordinates in this texture.

Some OpenGL details. Vertex shader:
uniform mat4 mvp_matrix; attribute vec4 a_position; attribute vec2 a_texcoord; attribute vec4 a_color; varying vec4 v_color; varying vec2 v_texcoord; void main() { gl_Position = mvp_matrix * a_position; v_texcoord = a_texcoord; v_color = a_color; }
Fragment shader:
#ifdef GL_ES #define LOWP lowp precision mediump float; #else #define LOWP #endif varying vec2 v_texcoord; varying LOWP vec4 v_color; uniform sampler2D texture; void main() { gl_FragColor = v_color * texture2D(texture, v_texcoord); }
Most OpenGL functions are called once during program initialization. After that, a timer starts, which causes the screen refresh 60 times per second:
void Widget::startTimer() { m_timer.start(1000.0f / 60.0f, this); } void Widget::timerEvent(QTimerEvent *) { updateGL(); } void Widget::paintGL() { beginDraw(); drawSprites(); endDraw(); } void Widget::beginDraw() { m_idx = 0; glClear(GL_COLOR_BUFFER_BIT); glClearColor(m_background.r, m_background.g, m_background.b, m_background.a); } void Widget::endDraw() { glDrawArrays(GL_TRIANGLES, 0, m_idx / 2); }
All drawing occurs in the drawSprites () method, an example of which is shown above. It consists in filling in the DrawSpriteArgs structure and calling the drawSprite method for each image that needs to be drawn. If you want something non-standard, for example, to rotate the image not around the center, you can manually fill in arrays of coordinates, textures, and colors. The maximum size of the arrays must be set when the program starts in the constructor, because the QVector can move to another memory location when zoomed in, and OpenGL will need to be notified about this:
int vertexLocation = m_program->attributeLocation("a_position"); m_program->enableAttributeArray(vertexLocation); glVertexAttribPointer(vertexLocation, 2, GL_FLOAT, GL_FALSE, 0, &m_verts[0]); int texcoordLocation = m_program->attributeLocation("a_texcoord"); m_program->enableAttributeArray(texcoordLocation); glVertexAttribPointer(texcoordLocation, 2, GL_FLOAT, GL_FALSE, 0, &m_texCoords[0]); int colorLocation = m_program->attributeLocation("a_color"); m_program->enableAttributeArray(colorLocation); glVertexAttribPointer(colorLocation, 4, GL_FLOAT, GL_FALSE, 0, &m_colors[0]);
The projection matrix was taken
from here . It is set during window resizing:
void Widget::resizeGL(int w, int h) { glViewport(0, 0, w, h); QMatrix4x4 mat( 2.0f/w, 0, 0, -1, 0, 2.0f/h, 0, -1, 0, 0, -1, 0, 0, 0, 0, 1 ); m_program->setUniformValue("mvp_matrix", mat); }
')
That's probably all. For those who really want to use a wrapper, I strongly advise you to compile an example and figure out how it works. The code has been tested on Windows 7 (Qt 4.8 MinGW, MSVC 2008), Mac OS X 10.8 (Qt 5.1 Clang), Nokia N9 (Qt 4.7), BlackBerry 10 (Qt 4.8), and it runs correctly everywhere.
Conclusion
I think that not only did I have such problems, and I hope that this article will help someone, that this little wrapper will become a brick in some project and make life easier for someone. Or maybe someone will just pick up something from the code - please. Since I am not an OpenGL expert, it is possible that there are errors in the implementation. If you have corrections or suggestions for improvement, feel free to write in the comments.