Времена меняются, на смену 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/
Добавить комментарий