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();
}
运行效果:
加载前:
加载后: