Pull to refresh

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

Reading time4 min
Views16K
Давным давно, когда я писал игру под адроид, нужно было мне разобраться с 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, не исключено что в реализации присутствуют ошибки. Если у вас есть исправления или предложения по улучшению, не стесняйтесь, пишите в комментариях.
Tags:
Hubs:
+9
Comments5

Articles