QOpenGLWidget动态加载功能实现教程(Qt+OpenGL)

发布于:2025-03-29 ⋅ 阅读:(30) ⋅ 点赞:(0)

QOpenGLWidget动态加载功能实现教程

我需要在Qt里面使用QOpenGLWidget显示OpenGL窗口,并且需要实现加载模型后重新渲染更新窗口的功能,但是一直无法更新被卡住了,现在把问题解决了总结一下整个实现过程。


创建一个自己的OpenGLWidget类

QOpenGLWidget提供的是接口,我们需要继承该接口类来实现自己的OpenGLWidget类,我命名为MyGLWidget。

另外还需要继承QOpenGLFunctions类,这是提供封装好的OpenGL相关功能,免去我们大量的gl代码。该类模板在官方示例中也能找到。

我现在要实现这个功能:创建好OpenGL窗口后,不提供任何顶点数据,我加载数据后再去重新渲染。为了把演示功能简化,我把加载模型的函数改为添加三角形addTriangle函数替代。

完整的h文件如下

#ifndef MYGLWIDGET_H
#define MYGLWIDGET_H

#include <QOpenGLWidget>
#include <QOpenGLFunctions>
#include <QOpenGLShaderProgram>
#include <QOpenGLBuffer>
#include <QOpenGLVertexArrayObject>

class MyGLWidget : public QOpenGLWidget, protected QOpenGLFunctions
{
    Q_OBJECT

public:
    explicit MyGLWidget(QWidget* parent = nullptr);
    ~MyGLWidget();

public slots:
    void addTriangle(); // 用于添加三角形

protected:
    void initializeGL() override;
    void paintGL() override;
    void resizeGL(int w, int h) override;

private:
    bool showTriangle = false;  // 是否显示三角形
    QOpenGLShaderProgram shaderProgram;
    QOpenGLBuffer vbo;
    QOpenGLVertexArrayObject vao;
};

#endif // MYGLWIDGET_H

继承QOpenGLWidget后必须需要实现以下三个函数

  • void initializeGL() override;
  • void paintGL() override;
  • void resizeGL(int w, int h) override;

三个函数的作用顾名思义不再赘叙。

成员变量里面使用的都是Qt帮我封装好的:

  • QOpenGLShaderProgram
  • QOpenGLBuffer
  • QOpenGLVertexArrayObject

包括创建绑定的这些功能都直接帮我们封装好的,适合我们用面向对象的编程风格。

加载模型(三角形)数据的函数,我用addTriangle普通函数实现,即可作为普通函数,可以作为槽函数,看你的具体调用方式


实现接口给定的三个虚函数

我们重点要实现的initializeGL初始化和paintGL绘制函数:

  • initializeGL()
void MyGLWidget::initializeGL()
{
    initializeOpenGLFunctions();

    // 创建着色器
    shaderProgram.addShaderFromSourceCode(QOpenGLShader::Vertex,
        "#version 330 core\n"
        "layout(location = 0) in vec3 position;\n"
        "void main() {\n"
        "   gl_Position = vec4(position, 1.0);\n"
        "}");
    shaderProgram.addShaderFromSourceCode(QOpenGLShader::Fragment,
        "#version 330 core\n"
        "out vec4 FragColor;\n"
        "void main() {\n"
        "   FragColor = vec4(1.0, 0.5, 0.2, 1.0);\n"
        "}");
    shaderProgram.link();

    vao.create();
    vao.bind();

    vbo.create();
    vbo.bind();
    vbo.setUsagePattern(QOpenGLBuffer::DynamicDraw);

    vao.release();
    vbo.release();
}

其中initializeOpenGLFunctions()这个一开头就需要使用,算是固定格式。然后对shaderProgram,vao和vbo初始化,而vbo不需要分配任何数据。

  • paintGL()
void MyGLWidget::paintGL()
{
    glClear(GL_COLOR_BUFFER_BIT);

    if (showTriangle) {
        shaderProgram.bind();
        vao.bind();
        glDrawArrays(GL_TRIANGLES, 0, 3);
        vao.release();
        shaderProgram.release();
    }
}

绘制函数也可以很基础,我们根据判断如果加载三角形了再进行绘制。

  • resizeGL(int w, int h)
void MyGLWidget::resizeGL(int w, int h)
{
    glViewport(0, 0, w, h);
}

resizeGL没有特别需求的话就只需要这样写即可。


重点:加载函数的书写

这一步对于刚用的人来说简直是神坑,我不卖关子了,先抛出神坑的点:

在上述三个虚函数以为的函数或者构造函数,要使得OpenGL API的函数起作用,必须要先调用makeCurrent()函数,以确保使用的都是当前的上下文。

不调用makeCurrent()的话,你往vbo里面存放数据也是不会被绘制出来的。

void MyGLWidget::addTriangle()
{
    makeCurrent();		//重中之重!!!!!!!!!!!
    showTriangle = true; // 标记显示三角形
    qDebug() << "add Triangle";
    GLfloat vertices[] = {
        -0.5f, -0.5f, 0.0f,
         0.5f, -0.5f, 0.0f,
         0.0f,  0.5f, 0.0f
    };

    vao.bind();
    vbo.bind();
    vbo.allocate(vertices, sizeof(vertices));

    shaderProgram.bind();
    shaderProgram.enableAttributeArray(0);
    shaderProgram.setAttributeBuffer(0, GL_FLOAT, 0, 3);

    for (GLenum err; (err = glGetError()) != GL_NO_ERROR;) {
        qDebug() << "error:" << err;
    }

    vao.release();
    vbo.release();
    shaderProgram.release();

    update(); // 触发重绘
    doneCurrent();	//结束后调用doneCurrent
}

完整的cpp文件代码

#include "MyGLWidget.h"
#include <QOpenGLShader>
#include <QDebug>

MyGLWidget::MyGLWidget(QWidget* parent)
    : QOpenGLWidget(parent), vbo(QOpenGLBuffer::VertexBuffer)
{
}

MyGLWidget::~MyGLWidget()
{
    makeCurrent();
    vbo.destroy();
    vao.destroy();
    shaderProgram.removeAllShaders();
    doneCurrent();
}

void MyGLWidget::initializeGL()
{
    initializeOpenGLFunctions();

    // 创建着色器
    shaderProgram.addShaderFromSourceCode(QOpenGLShader::Vertex,
        "#version 330 core\n"
        "layout(location = 0) in vec3 position;\n"
        "void main() {\n"
        "   gl_Position = vec4(position, 1.0);\n"
        "}");
    shaderProgram.addShaderFromSourceCode(QOpenGLShader::Fragment,
        "#version 330 core\n"
        "out vec4 FragColor;\n"
        "void main() {\n"
        "   FragColor = vec4(1.0, 0.5, 0.2, 1.0);\n"
        "}");
    shaderProgram.link();

    vao.create();
    vao.bind();

    vbo.create();
    vbo.bind();
    vbo.setUsagePattern(QOpenGLBuffer::DynamicDraw);

    vao.release();
    vbo.release();
}

void MyGLWidget::paintGL()
{
    glClear(GL_COLOR_BUFFER_BIT);

    if (showTriangle) {
        shaderProgram.bind();
        vao.bind();
        glDrawArrays(GL_TRIANGLES, 0, 3);
        vao.release();
        shaderProgram.release();
    }
}

void MyGLWidget::resizeGL(int w, int h)
{
    glViewport(0, 0, w, h);
}

void MyGLWidget::addTriangle()
{
    makeCurrent();
    showTriangle = true; // 标记显示三角形
    qDebug() << "add Triangle";
    GLfloat vertices[] = {
        -0.5f, -0.5f, 0.0f,
         0.5f, -0.5f, 0.0f,
         0.0f,  0.5f, 0.0f
    };

    vao.bind();
    vbo.bind();
    vbo.allocate(vertices, sizeof(vertices));

    shaderProgram.bind();
    shaderProgram.enableAttributeArray(0);
    shaderProgram.setAttributeBuffer(0, GL_FLOAT, 0, 3);

    for (GLenum err; (err = glGetError()) != GL_NO_ERROR;) {
        qDebug() << "error:" << err;
    }

    vao.release();
    vbo.release();
    shaderProgram.release();

    update(); // 触发重绘
    doneCurrent();
}


主程序调用方法

main.cpp

#include <QApplication>
#include <QPushButton>
#include <QVBoxLayout>
#include "MyGLWidget.h"

int main(int argc, char* argv[])
{
    QApplication app(argc, argv);

    QWidget window;
    QVBoxLayout* layout = new QVBoxLayout(&window);

    MyGLWidget* glWidget = new MyGLWidget();
    QPushButton* button = new QPushButton("添加三角形");

    layout->addWidget(glWidget);
    layout->addWidget(button);

    QObject::connect(button, &QPushButton::clicked, glWidget, &MyGLWidget::addTriangle);

    window.show();
    return app.exec();
}


运行效果:

加载前:
在这里插入图片描述

加载后:
在这里插入图片描述