当前位置: 首页 > news >正文

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

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();
}


运行效果:

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

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

相关文章:

  • 如何同步fork的更新
  • 【IDEA的个性化配置】
  • 备赛蓝桥杯之第十六届模拟赛2期职业院校组第五题:文本自动生成器
  • SQL Server 2022常见问题解答
  • Mybatis笔记(上)
  • VsCode启用右括号自动跳过(自动重写) - 自录制gif演示
  • 26考研——栈、队列和数组_数组和特殊矩阵(3)
  • 力扣刷题-热题100题-第24题(c++、python)
  • 代码规范之Variable Names变量名
  • 如何使用 CSS 实现多列布局,有哪些注意事项
  • 一款可查看手机详细配置信息的小工具,简单直观,自动识别硬件信息
  • 创建一个服务器启动自动执行的脚本,设置默认路由
  • LUMOS: Language-Conditioned Imitation Learning with World Models
  • QT三 自定义控件,自定义控件的事件处理自定义事件过滤,原始事件过滤
  • 爬虫——将数据保存到MongoDB中
  • conda极速上手记录
  • 如何部署自己的本地大模型
  • Hadoop三 分布式sql计算hive入门
  • 基于PyTorch的艺术风格迁移系统:卷积神经网络与迁移学习在图像生成的应用
  • 【Node.js入门教程:从零到精通】
  • 美国务院宣布新一轮与伊朗相关的制裁
  • 国务院食安办:加强五一假期食品生产、销售、餐饮服务环节监管
  • 中青旅:第一季度营业收入约20.54亿元,乌镇景区接待游客数量同比减少6.7%
  • 媒体:黑话烂梗包围小学生,“有话好好说”很难吗?
  • 昂立教育:去年减亏1.39亿元,今年以“利润持续增长”为核心目标
  • 张元济和百日维新