OpenGL ES 2.0 обертка для Qt

от автора

Давным давно, когда я писал игру под адроид, нужно было мне разобраться с OpenGL ES 1.1. Вроде бы ничего сложного, 2D графика, нужно было просто рисовать PNG изображения, с возможностью масштабирования, поворота, добавления прозрачности. Тогда я потратил на это около недели, а может даже и больше, уже не помню. Было сложно, поскольку с OpenGL я никогда дела не имел. Сильно помог исходный код libgdx, в котором, кстати, все низкоуровневые OpenGL функции спрятаны от разработчика.
Времена меняются, на смену OpenGL ES 1.1 приходит версия 2.0, которая довольно сильно отличается. Приходится разбираться, что это за шейдеры, и почему без них никак. На это опять уходит несколько дней. Казалось бы, должно быть легко, ведь 2D, все просто. Например, если использовать QML, это делается вот так:

Image {     source: "brick.png"     opacity: 0.8     rotation: 90 } 

А если писать все на С++, то получается много-много строк кода, которые сложно понять, если не знаком с OpenGL. Я пытался найти какую-нибудь библиотеку, обертку над OpenGL, как libgdx, только для Qt, но безуспешно. Поэтому решил, после того, как у меня все заработает, я напишу небольшую обертку, которая прячет все OpenGL вызовы и позволяет удобно работать с 2D графикой.

Код обертки на GitHub
Пример использования на GitHub

Для того, чтобы использовать обертку в своем приложении, необходимо добавить одну строчку в .pro файл:

include (opengl-qt/opengl-qt.pri) 

Унаследовать окно приложения от OpenGLQt::Widget:

class Widget : public OpenGLQt::Widget { public:     Widget();  protected:     virtual void drawSprites(); }; 

Передать в конструкторе путь к файлу и количество изображений в нем. Запустить таймер:

Widget::Widget()     : OpenGLQt::Widget(10, "spritesheet.png")     , m_counter(0) {     startTimer(); } 

Переопределить метод drawSprites():

void Widget::drawSprites() {     static Sprite apple = {86, 118, 30, 30}; // участок изображения, который нужно нарисовать     static Color color = {1, 1, 1, 1};       // оттенок цвета     static DrawSpriteArgs args;                   args.sprite = apple;     args.color = color;     args.x = 10;                             // центр изображения по оси х     args.y = 15;                             // и по оси у     args.angle = 3.14f / 2.0f;               // поворот на 90 против часовой стрелки     args.scaleX = 0.5f;                      // уменьшение на 50% по оси х     args.scaleY = -1.0f;                     // зеркальное отражение по оси у      drawSprite(args); } 

И все. Довольно легко, правда? Если вы хотите использовать эту обертку, вам нужно будет вместить всю свою графику в один PNG файл. Это можно сделать, например, с помощью TexturePacker. Смысл этого в увеличении быстродействия: текстура загружается в видеопамять один раз, при старте, и остается там до конца работы программы. Когда вы хотите нарисовать изображение, вам нужно указать его координаты в этой текстуре.

Немного OpenGL подробностей. 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); } 

Большинство OpenGL функциий вызывается один раз при инициализации программы. После этого запускается таймер, который 60 раз в секунду вызывает обновление экрана:

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); } 

Все рисование происходит в методе drawSprites(), пример которого приведен выше. Оно заключается в заполнении структуры DrawSpriteArgs и вызове метода drawSprite для каждого изображения, которое необходимо нарисовать. Если хочется чего-то нестандартного, например, вращать изображение не вокруг центра, можно вручную заполнять массивы координат, текстур, и цветов. Максимальный размер массивов необходимо задать при старте программы в конструкторе, потому что QVector при увеличении может переехать на другой участок памяти, и нужно будет оповестить об этом OpenGL:

    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]); 

Проекционная матрица была взята отсюда. Она устанавливается во время изменения размеров окна:

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); } 

Вот пожалуй и все. Тем, кто действительно захочет использовать обертку, очень советую скомпилировать пример и разобраться, как он работает. Код был протестирован на 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), везде выполняется корректно.

Заключение

Я думаю, что не только у меня возникали подобные проблемы и надеюсь, что эта статья поможет кому-нибудь, что эта маленькая оберка станет кирпичиком в каком-нибудь проекте и облегчит кому-то жизнь. А может быть кто-то просто подчерпнет себе что-то из кода — пожалуйста. Поскольку я не являюсь специалистом по OpenGL, не исключено что в реализации присутствуют ошибки. Если у вас есть исправления или предложения по улучшению, не стесняйтесь, пишите в комментариях.

ссылка на оригинал статьи http://habrahabr.ru/post/178599/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *