转自https://www.jianshu.com/p/a727120b3c7b
在图形图像应用程序中,经常需要使用QPainter进行二维图像图像的绘制,当扩展到三维可视化应用时,还需要使用OpenGL进行渲染。因此本文着重讨论QPainter与OpenGL混合绘制,同时介绍Graphics-View框架的OpenGL渲染。
QOpenGLWidget下的QPainter与OpenGL混合绘制
首先,我们先说说QOpenGLWidget下的QPainter与OpenGL混合绘制,Qt帮助文档中是这么说明的:
如果只做纯OpenGL绘制,在QOpenGLWidget重写initializeGL()和 resizeGL(),建立OpenGL状态和透视变换。重写paintGL(),用于绘制3D场景。
使用QPainter和OpenGL混合绘制时,OpenGL指令必须被QPainter的
beginNativePainting和endNativePainting函数包围。
如果只做QPainter绘制,可以重写paintEvent。
混合绘制示例
void OpenGLWidget::paintGL()
{
QPainter painter(this);
// 使用OpenGL渲染
painter.beginNativePainting();
glEnable(GL_DEPTH_TEST);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
// 绑定着色器程序
m_shader->bind();
// 设置模型矩阵
m_model.setToIdentity();
m_model.rotate(m_angle, 0.0f, 1.0f, 0.0f);
m_shader->setUniformValue("model", m_model);
// 渲染
m_texture->bind();
m_vao.bind();
glDrawArrays(GL_TRIANGLES, 0, 36);
glDisable(GL_DEPTH_TEST);
painter.endNativePainting();
// 使用QPainter绘制
painter.setPen(Qt::green);
painter.drawText(rect(), QString("Rotation Angle: %1").arg(m_angle));
m_angle += 1.5f;
update();
}
Graphics-View框架下的OpenGL渲染
只需简单的使用setViewport将QGLWidget或QOpenGLWidget设置为QGraphicsView的视口即可。Qt帮助文档中的示例Boxes演示了如何在Graphics-View框架下使用QGLWidget作为视口。因为Qt推荐在新的软件中使用QOpenGLxxx系列类,所以这里使用QOpenGLWidget作为QGraphicsView的视口。在编写代码过程中,发现必须将OpenGL配置成核心模式时,才能正确的进行OpenGL和QPainter混合绘制。可以使用OpenGLWidget的setFormat函数进行配置OpenGL版本和模式等。
OpenGLWidget w;
QSurfaceFormat format;
format.setVersion(3, 3);
format.setProfile(QSurfaceFormat::CoreProfile);
setFormat(format);
或者在程序的入口处为应用程序设置一个默认的格式:
QSurfaceFormat format = QSurfaceFormat::defaultFormat();
format.setProfile(QSurfaceFormat::CoreProfile);
format.setVersion(3, 3);
QSurfaceFormat::setDefaultFormat(format);
使用QOpenGLWidget作为QGraphicsView的视口,以便在QGraphicsItem实例中使用OpenGL和QPainter进行混合绘制。
QGraphicsView* view = new QGraphicsView;
// 使用QOpenGLWidget实例作为GraphicsView的视口
QOpenGLWidget* w = new QOpenGLWidget(this);
view->setViewport(w);
示例
使用QOpenGLWidget作为QGraphicsView的视口,子类化一个QGraphicsItem,在该子类中使用OpenGL绘制一个3D模型,并且使用QPainter绘制文字和椭圆。具体代码如下:
#pragma once
#include <QGraphicsView>
class QGraphicsScene;
class QGraphicsWidget;
class GraphicsView : public QGraphicsView
{
Q_OBJECT
public:
GraphicsView(QWidget *parent = nullptr);
~GraphicsView();
protected:
void resizeEvent(QResizeEvent *event);
private:
QGraphicsWidget* m_widget;
QGraphicsScene* m_scene;
};
#include "GraphicsView.h"
#include <QGraphicsScene>
#include <QResizeEvent>
#include <QGraphicsGridLayout>
#include "OpenGLWidget.h"
#include "BoxItem.h"
GraphicsView::GraphicsView(QWidget *parent)
: QGraphicsView(parent)
{
setViewport(new QOpenGLWidget(this));
m_widget = new QGraphicsWidget;
QGraphicsGridLayout *layout = new QGraphicsGridLayout(m_widget);
BoxItem* box = new BoxItem;
layout->addItem(box, 0, 0);
m_scene = new QGraphicsScene(this);
m_scene->addItem(m_widget);
this->setScene(m_scene);
resize(800, 600);
}
GraphicsView::~GraphicsView()
{
// 释放资源
...
}
void GraphicsView::resizeEvent(QResizeEvent *event)
{
if (scene())
{
m_widget->resize(event->size());
scene()->setSceneRect(0, 0, event->size().width(), event->size().height());
}
QGraphicsView::resizeEvent(event);
}
#pragma once
#include <QGraphicsWidget>
#include <QOpenGLFunctions>
#include <QOpenGLVertexArrayObject>
#include <QMatrix4x4>
class Shader;
class QOpenGLBuffer;
class QOpenGLTexture;
class BoxItem : public QGraphicsWidget, QOpenGLFunctions
{
public:
BoxItem(QGraphicsItem *parent = nullptr);
~BoxItem();
protected:
void resizeEvent(QGraphicsSceneResizeEvent *event) override;
void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) override;
private:
void makeObject();
void paintGL();
void cleanup();
private:
bool m_bInited;
Shader* m_shader;
QOpenGLBuffer* m_vbo;
QOpenGLTexture* m_texture;
QOpenGLVertexArrayObject m_vao;
QMatrix4x4 m_model, m_view, m_projection;
float m_angle;
QRectF m_vp;
};
#include "BoxItem.h"
#include <QPainter>
#include <QOpenGLBuffer>
#include <QOpenGLTexture>
#include "Shader.h"
BoxItem::BoxItem(QGraphicsItem *parent /*= nullptr*/) : m_bInited(false), m_angle(0.0f)
{
}
BoxItem::~BoxItem()
{
}
// 重写painter函数,实现具体的绘制动作
void BoxItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget)
{
// 使用QPainter绘制文字
painter->setPen(Qt::darkMagenta);
painter->drawText(rect(), QString("Rotation angle:%1").arg(m_angle));
// 使用OpenGL绘制3D模型
painter->beginNativePainting();
paintGLWithoutFBO();
painter->endNativePainting();
// 使用QPainter绘制椭圆
painter->setPen(Qt::darkGreen);
painter->drawEllipse(rect());
m_angle += 1.0f;
update();
}
void BoxItem::paintGL()
{
makeObject();
// 设置视口
glViewport(m_vp.x(), m_vp.y(), m_vp.width(), m_vp.height());
m_shader->bind();
m_model.setToIdentity();
m_model.rotate(m_angle, 0.0, 1.0, 0.0);
m_shader->setUniformValue("model", m_model);
m_shader->setUniformValue("view", m_view);
m_shader->setUniformValue("projection", m_projection);
glEnable(GL_DEPTH_TEST);
glEnable(GL_CULL_FACE);
m_texture->bind();
m_vao.bind();
glDrawArrays(GL_TRIANGLES, 0, 36);
glDisable(GL_CULL_FACE);
glDisable(GL_DEPTH_TEST);
}
void BoxItem::cleanup()
{
// 释放资源
m_vao.destroy();
delete m_vbo;
delete m_shader;
...
}
void BoxItem::makeObject()
{
if (!m_bInited)
{
m_bInited = true;
initializeOpenGLFunctions();
m_shader = new Shader(":/Qgl/shaders/box.vert", ":/Qgl/shaders/box.frag", nullptr);
m_texture = new QOpenGLTexture(QImage(":/Qgl/images/container.png"));
m_texture->setMinificationFilter(QOpenGLTexture::Nearest);
m_texture->setMagnificationFilter(QOpenGLTexture::Linear);
m_texture->setWrapMode(QOpenGLTexture::Repeat);
if (m_vao.create())
{
float vertices[] = {
// Back face
-0.5f, -0.5f, -0.5f, 0.0f, 0.0f, // Bottom-left
0.5f, 0.5f, -0.5f, 1.0f, 1.0f, // top-right
0.5f, -0.5f, -0.5f, 1.0f, 0.0f, // bottom-right
0.5f, 0.5f, -0.5f, 1.0f, 1.0f, // top-right
-0.5f, -0.5f, -0.5f, 0.0f, 0.0f, // bottom-left
-0.5f, 0.5f, -0.5f, 0.0f, 1.0f, // top-left
// Front face
-0.5f, -0.5f, 0.5f, 0.0f, 0.0f, // bottom-left
0.5f, -0.5f, 0.5f, 1.0f, 0.0f, // bottom-right
0.5f, 0.5f, 0.5f, 1.0f, 1.0f, // top-right
0.5f, 0.5f, 0.5f, 1.0f, 1.0f, // top-right
-0.5f, 0.5f, 0.5f, 0.0f, 1.0f, // top-left
-0.5f, -0.5f, 0.5f, 0.0f, 0.0f, // bottom-left
// Left face
-0.5f, 0.5f, 0.5f, 1.0f, 0.0f, // top-right
-0.5f, 0.5f, -0.5f, 1.0f, 1.0f, // top-left
-0.5f, -0.5f, -0.5f, 0.0f, 1.0f, // bottom-left
-0.5f, -0.5f, -0.5f, 0.0f, 1.0f, // bottom-left
-0.5f, -0.5f, 0.5f, 0.0f, 0.0f, // bottom-right
-0.5f, 0.5f, 0.5f, 1.0f, 0.0f, // top-right
// Right face
0.5f, 0.5f, 0.5f, 1.0f, 0.0f, // top-left
0.5f, -0.5f, -0.5f, 0.0f, 1.0f, // bottom-right
0.5f, 0.5f, -0.5f, 1.0f, 1.0f, // top-right
0.5f, -0.5f, -0.5f, 0.0f, 1.0f, // bottom-right
0.5f, 0.5f, 0.5f, 1.0f, 0.0f, // top-left
0.5f, -0.5f, 0.5f, 0.0f, 0.0f, // bottom-left
// Bottom face
-0.5f, -0.5f, -0.5f, 0.0f, 1.0f, // top-right
0.5f, -0.5f, -0.5f, 1.0f, 1.0f, // top-left
0.5f, -0.5f, 0.5f, 1.0f, 0.0f, // bottom-left
0.5f, -0.5f, 0.5f, 1.0f, 0.0f, // bottom-left
-0.5f, -0.5f, 0.5f, 0.0f, 0.0f, // bottom-right
-0.5f, -0.5f, -0.5f, 0.0f, 1.0f, // top-right
// Top face
-0.5f, 0.5f, -0.5f, 0.0f, 1.0f, // top-left
0.5f, 0.5f, 0.5f, 1.0f, 0.0f, // bottom-right
0.5f, 0.5f, -0.5f, 1.0f, 1.0f, // top-right
0.5f, 0.5f, 0.5f, 1.0f, 0.0f, // bottom-right
-0.5f, 0.5f, -0.5f, 0.0f, 1.0f, // top-left
-0.5f, 0.5f, 0.5f, 0.0f, 0.0f // bottom-left
};
m_vao.bind();
m_vbo = new QOpenGLBuffer(QOpenGLBuffer::VertexBuffer);
m_vbo->create();
m_vbo->bind();
m_vbo->allocate(vertices, sizeof(vertices));
m_shader->setAttributeBuffer("aPos", GL_FLOAT, 0, 3, 5 * sizeof(float));
m_shader->enableAttributeArray("aPos");
m_shader->setAttributeBuffer("aTexCoords", GL_FLOAT, 3 * sizeof(float), 2, 5 * sizeof(float));
m_shader->enableAttributeArray("aTexCoords");
}
QObject::connect(QOpenGLContext::currentContext(), &QOpenGLContext::aboutToBeDestroyed, this, &BoxItem::cleanup);
}
}
// 尺寸被修改时,视口也做相应的修改
void BoxItem::resizeEvent(QGraphicsSceneResizeEvent *event)
{
QRectF pr = parentLayoutItem()->geometry();
QRectF vp = geometry();
m_vp = QRectF(vp.x(), pr.height() - vp.height() - vp.y(), vp.width(), vp.height());
m_projection.setToIdentity();
m_projection.perspective(35.0f, m_vp.width() / m_vp.height(), 0.1, 100);
m_view.setToIdentity();
m_view.lookAt(QVector3D(0.0f, 0.0f, 3.0f), QVector3D(0.0f, 0.0f, 0.0f), QVector3D(0.0f, 1.0f, 0.0f));
}
运行效果: