Qt Graphics-View框架下的OpenGL渲染

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

运行效果:
在这里插入图片描述

  • 1
    点赞
  • 24
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值