OpenGL with QtWidgets:在Qt中使用OpenGL绘制一个三角形

(本文是LearnOpenGL的学习笔记,教程中文翻译地址https://learnopengl-cn.github.io/(备用地址https://learnopengl-cn.readthedocs.io/zh/latest/),写于 2020-1-30 ,并在 2021-8-10 进行了更新)

0.前言

LearnOpenGL 教程目前使用的是 glfw+glad 的方式来使用 OpenGL,而在 Qt Widgets 框架中也封装了相应的类。QOpenGLWidget 相当于 glfw,QOpenGLFunctions 相当于 glad,配合其他 Qt 工具类,操作起来更加方便。

1.如何实现

在 Qt 旧版本中,提供有 QGLWidget 类,从 Qt5.4 开始提供了 QOpenGLWidget 类,只需要继承并实现相应接口,就可以使用 OpenGL 绘制一个 Qt 组件(其中使用 OpenGL 函数还要借助QOpenGLFunctions 相关的类,可以继承,也可以作为成员变量):

class GLTriangle : public QOpenGLWidget, protected QOpenGLFunctions_3_3_Core
{}

继承 QOpenGLWidget 后,只需要实现三个虚函数接口,就可以实现绘制:

    //设置OpenGL资源和状态。在第一次调用resizeGL或paintGL之前被调用一次
    void initializeGL() override;
    //渲染OpenGL场景,每当需要更新小部件时使用
    void paintGL() override;
    //设置OpenGL视口、投影等,每当尺寸大小改变时调用
    void resizeGL(int width, int height) override;

QOpenGLWidget 是继承自 QWidget 的,所以做好的组件类像其他 Widget 类那样使用就行了。

在教程中,是自己封装着色器程序类,Qt 已经封装好了一个 QOpenGLShaderProgram,我们可以直接用它。此外还提供了 QOpenGLVertexArrayObject、QOpenGLBuffer 等封装好的类供我们使用。剩下的,就是参照 LearnOpenGL 教程配合 Qt 封装的类来使用 OpenGL。

2.实现代码

(项目git链接:https://github.com/gongjianbo/OpenGLwithQtWidgets.git

我的GLTriangle类和GLElement类效果图:

其中三角形的实现代码:

(2020-05-10 修改)将 QSurfaceFormat 设置为 3.3 及以上会出现 gl_FragColor报错:

'variable' : is removed in Forward Compatible context gl_FragColor

因为 OpenGL3.3 的 glsl 移除了这个变量,可以自己声明一个 out vec4 fragColor 。

(2021-08-10修改)析构时判断 QOpenGLWidget::isValid()  ,只有显示过才会初始化,而没初始化就 glDelete 会异常。

 OpenGL4.5 接口示例:

#pragma once
#include <QOpenGLWidget>
#include <QOpenGLFunctions_4_5_Compatibility>

//OpenGL4.5画三角
//QOpenGLWidget窗口上下文
//QOpenGLFunctions访问OpenGL接口,可以不继承作为成员变量使用
class GLTriangle
        : public QOpenGLWidget
        , protected QOpenGLFunctions_4_5_Compatibility
{
public:
    explicit GLTriangle(QWidget *parent = nullptr);
    ~GLTriangle();

protected:
    //【】继承QOpenGLWidget后重写这三个虚函数
    //设置OpenGL资源和状态。在第一次调用resizeGL或paintGL之前被调用一次
    void initializeGL() override;
    //渲染OpenGL场景,每当需要更新小部件时使用
    void paintGL() override;
    //设置OpenGL视口、投影等,每当尺寸大小改变时调用
    void resizeGL(int width, int height) override;

private:
    void checkShaderError(GLuint id, const QString &type);

private:
    //还没使用Qt的封装
    GLuint shaderProgram = 0;
    GLuint vao = 0;
    GLuint vbo = 0;
};
#include "GLTriangle.h"
#include <QDebug>

GLTriangle::GLTriangle(QWidget *parent)
    : QOpenGLWidget(parent)
{

}

GLTriangle::~GLTriangle()
{
    //initializeGL在显示时才调用,释放未初始化的会异常
    if(!isValid())
        return;
    //QOpenGLWidget
    //三个虚函数不需要makeCurrent,对应的操作已由框架完成
    //但是释放时需要设置当前上下文
    makeCurrent();
    glDeleteVertexArrays(1, &vao);
    glDeleteBuffers(1, &vbo);
    glDeleteProgram(shaderProgram);
    doneCurrent();
}

void GLTriangle::initializeGL()
{
    //QOpenGLFunctions
    //为当前上下文初始化opengl函数解析
    initializeOpenGLFunctions();

    //着色器代码
    //in输入,out输出,uniform从cpu向gpu发送
    const char *vertex_str=R"(#version 450
layout (location = 0) in vec3 vertices;
void main() {
gl_Position = vec4(vertices,1.0);
})";
    const char *fragment_str=R"(#version 450
uniform vec3 myColor;
out vec4 fragColor;
void main() {
fragColor = vec4(myColor,1.0);
})";

    //顶点着色器
    //创建着色器对象
    //GLuint glCreateShader(GLenum shaderType​);
    GLuint vertex_shader = glCreateShader(GL_VERTEX_SHADER);
    //设置着色器对象中的的代码
    //void glShaderSource(GLuint shader​, GLsizei count​,
    //                    const GLchar **string​, const GLint *length​);
    //参数1指定着色器对象
    //参数2指定字符串个数
    //参数3指定字符串二维数组指针
    //参数4指定字符串长度数组,为NULL则以'\0'为字符串终止符
    glShaderSource(vertex_shader, 1, &vertex_str, NULL);
    //编译着色器对象
    //void glCompileShader(GLuint shader​);
    glCompileShader(vertex_shader);
    //检测着色器是否异常
    checkShaderError(vertex_shader, "vertex");

    //片段着色器
    GLuint fragment_shader = glCreateShader(GL_FRAGMENT_SHADER);
    glShaderSource(fragment_shader, 1, &fragment_str, NULL);
    glCompileShader(fragment_shader);
    checkShaderError(fragment_shader,"fragment");

    //着色器程序
    //创建一个空的程序对象并返回一个可以被引用的非零值
    //GLuint glCreateProgram(void​);
    shaderProgram = glCreateProgram();
    //将着色器对象附加到程序对象
    //void glAttachShader(GLuint program​, GLuint shader​);
    glAttachShader(shaderProgram, vertex_shader);
    glAttachShader(shaderProgram, fragment_shader);
    //链接程序对象
    //void glLinkProgram(GLuint program​);
    glLinkProgram(shaderProgram);
    checkShaderError(shaderProgram, "program");
    //删除着色器,它们已经链接到我们的着色器程序对象了
    //void glDeleteShader(GLuint shader​);
    glDeleteShader(vertex_shader);
    glDeleteShader(fragment_shader);

    //三角形的三个顶点
    float vertices[] = {
        -0.5f, -0.5f, 0.0f, // left
        0.5f, -0.5f, 0.0f, // right
        0.0f,  0.5f, 0.0f  // top
    };
    //【1】2014年8月12日,Khronos发布了OpenGL 4.5标准规范,
    //其中ARB_direct_state_access扩展进入核心,
    //其允许直接访问和修改OpenGL对象而无需绑定OpenGL对象(bind操作,例如glBindBuffer),
    //提高应用程序和中间件的效率。
    //【2】glGen*系列函数生成的id,内部并没有初始化那个对象的状态。只有到了glBind*的时候才会初始化。
    //而Core/ARB的DSA直接提供了glCreate*系列函数,可以一步到位地建立id和初始化。

    //生成顶点数组对象
    //void glCreateVertexArrays(GLsizei n, GLuint *arrays);
    glCreateVertexArrays(1, &vao);
    //生成缓冲区对象
    //void glCreateBuffers(GLsizei n, GLuint *buffers);
    glCreateBuffers(1, &vbo);
    //分配size个存储单元存储数据或索引
    //glNamedBufferStorage(GLuint buffer, GLsizeiptr size,
    //                     const void *data, GLbitfield flags);
    //参数1缓冲区对象
    //参数2数据块大小
    //参数3如果为NULL则是size个为初始化的数据,否则以data拷贝初始化
    //参数4数据相关的用途
    glNamedBufferStorage(vbo, sizeof(vertices), vertices, GL_DYNAMIC_STORAGE_BIT);
    //vbo绑定到vao
    //glVertexArrayVertexBuffer(GLuint vaobj, GLuint bindingindex,
    //                          GLuint buffer, GLintptr offset, GLsizei stride);
    //参数1顶点数组对象
    //参数2vbo在vao的索引
    //参数3顶点缓冲对象
    //参数4缓冲区第一个元素的偏移
    //参数5缓冲区顶点步进,三角形一个点3个float
    glVertexArrayVertexBuffer(vao, 0, vbo, 0, 3*sizeof(float));
    //启用通用顶点 attribute 数组的 index 索引,对应layout location
    //glEnableVertexArrayAttrib(GLuint vaobj, GLuint index);
    glEnableVertexArrayAttrib(vao, 0);
    //指定顶点数组的组织
    //glVertexArrayAttribFormat(GLuint vaobj, GLuint attribindex,
    //                          GLint size, GLenum type,
    //                          GLboolean normalized, GLuint relativeoffset);
    //参数1顶点数组对象
    //参数2通用顶点 attribute 数组,对应layout location
    //参数3每个顶点几个数据
    //参数4存储类型
    //参数5是否归一化
    //参数6顶点步进
    glVertexArrayAttribFormat(vao, 0, 3, GL_FLOAT, GL_FALSE, 0);
    //关联顶点 attribute 属性和顶点缓冲区的绑定
    //glVertexArrayAttribBinding(GLuint vaobj, GLuint attribindex,
    //                           GLuint bindingindex);
    //参数1顶点数组对象
    //参数2属性 attribute 索引,对应layout location
    //参数3vbo在vao的索引
    glVertexArrayAttribBinding(vao, 0, 0);
}

void GLTriangle::paintGL()
{
    //清屏设置
    glClearColor(0.2f, 0.3f, 0.3f, 1.0f);
    glClear(GL_COLOR_BUFFER_BIT);

    //安装所指定的程序对象程序作为当前再现状态的一部分
    glUseProgram(shaderProgram);
    //传递值
    glUniform3f(glGetUniformLocation(shaderProgram, "myColor"), 1.0f, 0.0f, 0.0f);
    //绑定数组对象
    glBindVertexArray(vao);

    //使用当前激活的着色器和顶点属性配置和VBO(通过VAO间接绑定)来绘制图元
    //void glDrawArrays(GLenum mode​, GLint first​, GLsizei count​);
    //参数1为图元类型
    //参数2指定顶点数组的起始索引
    //参数3指定顶点个数
    glDrawArrays(GL_TRIANGLES, 0, 3);
}

void GLTriangle::resizeGL(int width, int height)
{
    glViewport(0,0,width,height);
}

void GLTriangle::checkShaderError(GLuint id, const QString &type)
{
    int check_flag;
    char check_info[1024];
    if(type != "program"){
        glGetShaderiv(id, GL_COMPILE_STATUS, &check_flag);
        if(!check_flag){
            glGetShaderInfoLog(id, 1024, NULL, check_info);
            qDebug() << type << " error:" << check_info;
        }
    }else{
        glGetShaderiv(id, GL_LINK_STATUS, &check_flag);
        if(!check_flag){
            glGetProgramInfoLog(id, 1024, NULL, check_info);
            qDebug() << type << " error:" << check_info ;
        }
    }
}

Qt 接口示例:

#pragma once
#include <QOpenGLWidget>
#include <QOpenGLFunctions_4_5_Compatibility>
#include <QOpenGLShaderProgram>
#include <QOpenGLVertexArrayObject>
#include <QOpenGLBuffer>

//OpenGL Qt封装的类画三角
//QOpenGLWidget窗口上下文
//QOpenGLFunctions访问OpenGL接口,可以不继承作为成员变量使用
class GLTriangleQt
        : public QOpenGLWidget
        , protected QOpenGLFunctions_4_5_Compatibility
{
public:
    explicit GLTriangleQt(QWidget *parent = nullptr);
    ~GLTriangleQt();

protected:
    //【】继承QOpenGLWidget后重写这三个虚函数
    //设置OpenGL资源和状态。在第一次调用resizeGL或paintGL之前被调用一次
    void initializeGL() override;
    //渲染OpenGL场景,每当需要更新小部件时使用
    void paintGL() override;
    //设置OpenGL视口、投影等,每当尺寸大小改变时调用
    void resizeGL(int width, int height) override;

private:
    //使用Qt提供的便捷类
    QOpenGLShaderProgram shaderProgram;
    QOpenGLVertexArrayObject vao;
    QOpenGLBuffer vbo;
};
#include "GLTriangleQt.h"

GLTriangleQt::GLTriangleQt(QWidget *parent)
    : QOpenGLWidget(parent)
{

}

GLTriangleQt::~GLTriangleQt()
{
    //initializeGL在显示时才调用,释放未初始化的会异常
    if(!isValid())
        return;
    //QOpenGLWidget
    //三个虚函数不需要makeCurrent,对应的操作已由框架完成
    //但是释放时需要设置当前上下文
    makeCurrent();
    vao.destroy();
    vbo.destroy();
    doneCurrent();
}

void GLTriangleQt::initializeGL()
{
    //QOpenGLFunctions
    //为当前上下文初始化opengl函数解析
    initializeOpenGLFunctions();

    //着色器代码
    //in输入,out输出,uniform从cpu向gpu发送
    const char *vertex_str=R"(#version 450
layout (location = 0) in vec3 vertices;
void main() {
gl_Position = vec4(vertices,1.0);
})";
    const char *fragment_str=R"(#version 450
uniform vec3 myColor;
out vec4 fragColor;
void main() {
fragColor = vec4(myColor,1.0);
})";

    //顶点着色器
    //可以直接add着色器代码,也可以借助QOpenGLShader类
    bool success=shaderProgram.addCacheableShaderFromSourceCode(QOpenGLShader::Vertex,vertex_str);
    if(!success){
        qDebug()<<"compiler vertex failed!"<<shaderProgram.log();
    }
    //片段着色器
    success=shaderProgram.addCacheableShaderFromSourceCode(QOpenGLShader::Fragment,fragment_str);
    if(!success){
        qDebug()<<"compiler fragment failed!"<<shaderProgram.log();
    }
    success = shaderProgram.link();
    if(!success){
        qDebug()<<"link shader failed!"<<shaderProgram.log();
    }

    //三角形的三个顶点
    float vertices[] = {
        -0.5f, -0.5f, 0.0f, // left
        0.5f, -0.5f, 0.0f, // right
        0.0f,  0.5f, 0.0f  // top
    };

    vao.create();
    vao.bind();
    vbo=QOpenGLBuffer(QOpenGLBuffer::VertexBuffer);
    vbo.create();
    vbo.bind();
    vbo.allocate(vertices,sizeof(vertices));
    //setAttributeBuffer(int location, GLenum type, int offset, int tupleSize, int stride = 0)
    int attr=0;
    attr=shaderProgram.attributeLocation("vertices");
    //setAttributeBuffer(int location, GLenum type, int offset, int tupleSize, int stride = 0)
    shaderProgram.setAttributeBuffer(attr, GL_FLOAT, 0, 3, sizeof(GLfloat) * 3);
    shaderProgram.enableAttributeArray(attr);
    vbo.release();
    vao.release();
}

void GLTriangleQt::paintGL()
{
    //清屏设置
    glClearColor(0.2f, 0.3f, 0.3f, 1.0f);
    glClear(GL_COLOR_BUFFER_BIT);

    shaderProgram.bind();
    shaderProgram.setUniformValue("myColor",QVector3D(0.0f,0.0f,1.0f));
    {
        QOpenGLVertexArrayObject::Binder vao_bind(&vao); Q_UNUSED(vao_bind);
        //使用当前激活的着色器和顶点属性配置和VBO(通过VAO间接绑定)来绘制图元
        //void glDrawArrays(GLenum mode​, GLint first​, GLsizei count​);
        //参数1为图元类型
        //参数2指定顶点数组的起始索引
        //参数3指定顶点个数
        glDrawArrays(GL_TRIANGLES, 0, 3);
    }
    //只有一个着色器,可以不用release
    shaderProgram.release();
}

void GLTriangleQt::resizeGL(int width, int height)
{
    glViewport(0,0,width,height);
}

 4.参考

Qt文档:https://doc.qt.io/qt-5.12/qopenglwidget.html

博客:https://www.jianshu.com/p/bccc565b5248

  • 5
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

龚建波

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值