- PVSM.RU - https://www.pvsm.ru -
Во времена Qt 4 можно было ускорить рисование QPainter с OpenGL используя класс QGLPixelBuffer [1]: он предоставляет удобный и быстрый способ создания поверхности для рисования, рендеринга на неё (с помощью обычных методов QPainter) и захвата конечного результата как QImage.
В Qt 5 QGLPixelBuffer по-прежнему существует, но он считается устаревшим в пользу объектов кадрового буфера, обёрнутых в Qt в класс QOpenGLFramebufferObject [2]. Однако QOpenGLFramebufferObject это не QPaintDevice, поэтому мы не можем использовать QPainter прямо на нём.
QOpenGLPaintDevice [3] является связующим звеном, которое нам нужно. Создание QOpenGLPaintDevice на активном контексте OpenGL разрешает нам рисовать на нём используя QPainter. Давайте посмотрим на пример его использования:
#include <QtCore>
#include <QtGui>
#include <QtWidgets>
QImage createImageWithFBO() {
QSurfaceFormat format;
format.setMajorVersion(3);
format.setMinorVersion(3);
QWindow window;
window.setSurfaceType(QWindow::OpenGLSurface);
window.setFormat(format);
window.create();
QOpenGLContext context;
context.setFormat(format);
if (!context.create()) qFatal("Cannot create the requested OpenGL context!");
context.makeCurrent(&window);
const QRect drawRect(0, 0, 400, 400);
const QSize drawRectSize = drawRect.size();
QOpenGLFramebufferObjectFormat fboFormat;
fboFormat.setSamples(16);
fboFormat.setAttachment(QOpenGLFramebufferObject::CombinedDepthStencil);
QOpenGLFramebufferObject fbo(drawRectSize, fboFormat);
fbo.bind();
QOpenGLPaintDevice device(drawRectSize);
QPainter painter;
painter.begin(&device);
painter.setRenderHints(QPainter::Antialiasing | QPainter::HighQualityAntialiasing);
painter.fillRect(drawRect, Qt::blue);
painter.drawTiledPixmap(drawRect, QPixmap(":/qt-project.org/qmessagebox/images/qtlogo-64.png"));
painter.setPen(QPen(Qt::green, 5));
painter.setBrush(Qt::red);
painter.drawEllipse(0, 100, 400, 200);
painter.drawEllipse(100, 0, 200, 400);
painter.setPen(QPen(Qt::white, 0));
QFont font;
font.setPointSize(24);
painter.setFont(font);
painter.drawText(drawRect, "Hello FBO", QTextOption(Qt::AlignCenter));
painter.end();
fbo.release();
return fbo.toImage();
}
int main(int argc, char **argv) {
QApplication app(argc, argv);
QImage targetImage = createImageWithFBO();
QLabel label;
label.setPixmap(QPixmap::fromImage(targetImage));
label.show();
return app.exec();
}
Этот небольшой фрагмент кода создаёт QImage, рисуя на временный объект буфера кадров с использованием QPainter, а затем отображает полученное изображение на QLabel. Давайте проанализируем, что делает метод createImageWithFBO
.
QSurfaceFormat format;
format.setMajorVersion(3);
format.setMinorVersion(3);
QWindow window;
window.setSurfaceType(QWindow::OpenGLSurface);
window.setFormat(format);
window.create();
QOpenGLContext context;
context.setFormat(format);
if (!context.create()) qFatal("Cannot create the requested OpenGL context!");
context.makeCurrent(&window);
Вначале мы создаём QWindow (с типом OpenGL) и QOpenGLContext, затем делаем контекст текущим в нашем окне. Они оба пытаются использовать OpenGL 3.3 (учтите, что Mac OS X на данный момент поддерживает OpenGL до 3.2).
Использование QWindow как результирующей поверхности контекста является воркэраундом, потому что на данный момент в Qt 5.0 нет никаких других типов QSurface. Так как нам нужна поверхность для установки текущего контекста, мы просто создаём невидимое окно без определённого размера и этого достаточно, чтобы иметь возможность использовать контекст OpenGL на нём.
const QRect drawRect(0, 0, 400, 400);
const QSize drawRectSize = drawRect.size();
Эти переменные будут содержать размер прямоугольника для рисования.
QOpenGLFramebufferObjectFormat fboFormat;
fboFormat.setSamples(16);
fboFormat.setAttachment(QOpenGLFramebufferObject::CombinedDepthStencil);
QOpenGLFramebufferObject fbo(drawRectSize, fboFormat);
fbo.bind();
Здесь мы создаём QOpenGLFramebufferObject с конечным размером. Прежде чем сдалать это, мы также указываем его формат: мы хотим сделать мультисэмплинг активным на нашем объекте кадрового буфера, таким образом мы получим сглаженные примитивы, поэтому мы задаём 16 сэмплов на пиксель; кроме того, мы просим Qt задействовать также буфер глубины и буфер шаблона. Мы не собираемся использовать их явно, но движок отрисовки Qt может, так как в нём есть вызовы glStencil
[4].
В конце мы привязываем объект QOpenGLFramebufferObject (это переадресовывает все операции рисования на него).
QOpenGLPaintDevice device(drawRectSize);
QPainter painter;
painter.begin(&device);
painter.setRenderHints(QPainter::Antialiasing | QPainter::HighQualityAntialiasing);
painter.fillRect(drawRect, Qt::blue);
painter.drawTiledPixmap(drawRect, QPixmap(":/qt-project.org/qmessagebox/images/qtlogo-64.png"));
painter.setPen(QPen(Qt::green, 5));
painter.setBrush(Qt::red);
painter.drawEllipse(0, 100, 400, 200);
painter.drawEllipse(100, 0, 200, 400);
painter.setPen(QPen(Qt::white, 0));
QFont font;
font.setPointSize(24);
painter.setFont(font);
painter.drawText(drawRect, "Hello FBO", QTextOption(Qt::AlignCenter));
painter.end();
Далее мы приступаем к созданию QOpenGLPaintDevice и рисованию на нём с помощью обычных вызовов QPainter. Обратите внимание на то, что мы должны явно закончить рисование вызовом end()
, после чего наш объект кадрового буфера будет содержать результат.
fbo.release();
return fbo.toImage();
Эти строки завершают магию: мы отвязываем объект кадрового буфера, сбросив таким образом все его настройки к стандартным, и захватываем его содержимое как QImage вызовом метода toImage()
.
Результат:
Мы можем доработать этот простой пример и нарисовать что-нибудь вызовами «чистого» OpenGL во время рисования с QPainter. Достаточно добавить следующие строки в приведённый выше фрагмент, пока painter активен:
painter.beginNativePainting();
nativePainting();
painter.endNativePainting();
Пара вызовов begin/endNativePainting
нужна, чтобы сбросить состояние OpenGL на «чистое», прежде чем мы выполним наше рисование. Функция nativePainting()
:
void nativePainting() {
static const float vertexPositions[] = {
-0.8f, -0.8f, 0.0f,
0.8f, -0.8f, 0.0f,
0.0f, 0.8f, 0.0f
};
static const float vertexColors[] = {
1.0f, 0.0f, 0.0f,
0.0f, 1.0f, 0.0f,
0.0f, 0.0f, 1.0f
};
QOpenGLBuffer vertexPositionBuffer(QOpenGLBuffer::VertexBuffer);
vertexPositionBuffer.create();
vertexPositionBuffer.setUsagePattern(QOpenGLBuffer::StaticDraw);
vertexPositionBuffer.bind();
vertexPositionBuffer.allocate(vertexPositions, 9 * sizeof(float));
QOpenGLBuffer vertexColorBuffer(QOpenGLBuffer::VertexBuffer);
vertexColorBuffer.create();
vertexColorBuffer.setUsagePattern(QOpenGLBuffer::StaticDraw);
vertexColorBuffer.bind();
vertexColorBuffer.allocate(vertexColors, 9 * sizeof(float));
QOpenGLShaderProgram program;
program.addShaderFromSourceCode(QOpenGLShader::Vertex,
"#version 330n"
"in vec3 position;n"
"in vec3 color;n"
"out vec3 fragColor;n"
"void main() {n"
" fragColor = color;n"
" gl_Position = vec4(position, 1.0);n"
"}n"
);
program.addShaderFromSourceCode(QOpenGLShader::Fragment,
"#version 330n"
"in vec3 fragColor;n"
"out vec4 color;n"
"void main() {n"
" color = vec4(fragColor, 1.0);n"
"}n"
);
program.link();
program.bind();
vertexPositionBuffer.bind();
program.enableAttributeArray("position");
program.setAttributeBuffer("position", GL_FLOAT, 0, 3);
vertexColorBuffer.bind();
program.enableAttributeArray("color");
program.setAttributeBuffer("color", GL_FLOAT, 0, 3);
glDrawArrays(GL_TRIANGLES, 0, 3);
Это чересчур подробный (но современный) способ для рисования трёхцветного треугольника в центре нашего изображения. Я не буду анализировать этот фрагмент кода подробно, так как он вполне читаем: вначале мы создаём два объекта буфера вершин, один для хранения позиции вершин (уже в усечённом пространстве), а другой для хранения цветов вершин (в RGB).
Затем мы создаём необходимые для вершин и фрагментов шейдеры, которые являются тривиальными — вершинный шейдер просто выводит (нормированные) позиции на выходе и передаёт цвета фрагментному шейдеру; фрагментный шейдер выводит цвета, которые он получает на входе.
Потом происходит связывание и буферы, созданные вначале устанавливаются как аттрибутные буферы.
Теперь всё готово для рисования нашего треугольника простым вызовом glDrawArrays
. Конечный результат:
Полный исходный код: pastebin.com/pkLA42G0 [5]
Приложив немного больше усилий мы также можем продублировать возможности создания динамических текстур QGLPixelBuffer: QOpenGLFramebufferObject имеет метод texture()
, который возвращает идентификатор текстуры, связанный с буфером цвета. Это означает, что мы можем рисовать текстуры, используя QPainter и накладывать их на любые объекты на нашей сцене OpenGL. Но это потребовало бы написания другой статьи…
Счастливого хакинга!
Автор: epicfailguy93
Источник [6]
Сайт-источник PVSM.RU: https://www.pvsm.ru
Путь до страницы источника: https://www.pvsm.ru/opengl/26804
Ссылки в тексте:
[1] QGLPixelBuffer: http://qt-project.org/doc/qt-4.8/qglpixelbuffer.html
[2] QOpenGLFramebufferObject: http://qt-project.org/doc/qt-5.0/qtgui/qopenglframebufferobject.html
[3] QOpenGLPaintDevice: http://qt-project.org/doc/qt-5.0/qtgui/qopenglpaintdevice.html
[4] так как в нём есть вызовы glStencil
: http://code.woboq.org/qt5/qtbase/src/gui/opengl/qopenglpaintengine.cpp.html#915
[5] pastebin.com/pkLA42G0: http://pastebin.com/pkLA42G0
[6] Источник: http://habrahabr.ru/post/168911/
Нажмите здесь для печати.