Qt OpenGL

作者: 爬上墙头deng红杏 | 来源:发表于2020-06-08 22:46 被阅读0次

    引言

    OpenGL学习过程中需要配置各种各样的第三方库,比如使用GLFW用于创建OpenGL上下文,定义窗口参数以及处理用户输入。使用GLAD配置OpenGL接口。这里我们可以使用Qt作为OpenGL的载体,好处:

    1. Qt集成了OpenGL开发的所有工具,例如上下文创建、接口配置;
    2. Qt对OpenGL进行了面向对象的封装,即QOpenGL***相关类。使得开发效率更高。
    3. 强大的跨平台软件开发包,可以直接用于工程开发。

    本文着重介绍如何使用Qt进行OpenGL编码,对于OpenGL的编码技术的学习,强烈推荐LearnOpenGL。其中文翻译见https://learnopengl-cn.github.io/

    两种方式

    • 使用QGLWidget等QGLxxx类
    • 使用QOpenGLWidget等QOpenGLxxx类

    Qt推荐在新的软件开发中使用QOpenGLWidget,Qt官方文档中描述了关于QGLWidget类与QOpenGLWidget类之间的关系:


    QOpenGLWidget vs QGLWidget.png

    大概意思就是:
    QOpenGLxxx类旨在替代QGLxxx类,QOpenGLWidget总是使用帧缓存进行幕后渲染,而QGLWidget则是使用原生窗口和表面进行渲染,当在复杂的用户界面中使用它时,QGLWidget会导致问题,因为根据平台的不同,这种本地子部件可能有各种限制,例如堆叠顺序。而QOpenGLWidget通过不创建单独的本机窗口来避免这种情况。正因为QOpenGLWidget使用帧缓冲进行幕后渲染,因此在paintGL()中执行的渲染将针对这个帧缓存,以便增量呈现成为可能。有点类似于2D绘图中的双缓冲概念。
    QOpenGLWidget通过glViewport建立视口时,不会做任何清除动作。
    通过QPainter进行绘制时,QGLWidget默认在每次使用QPainter::begin()时都会清空背景调色板颜色。QOpenGLWidget则和其他普通的widget一样,默认不会清空。但当其用作其他小部件(如QGraphicsView)的视口时,为保证兼容性,则会执行清空动作。

    QOpenGLWidget

    在Qt中使用OpenGL渲染绘制,只需子类化QOpenGLWidget,重写initializeGL、resizeGL和paintGL即可。QOpenGLWidget的基本使用方法:
    头文件内容

    // 继承自QOpenGLFunctions,免去每次调用OpenGL的接口时,都必须获取当前上下文对应的接口封装
    class OpenGLWidget : public QOpenGLWidget, protected QOpenGLFunctions
    {
        Q_OBJECT
    
    public:
        OpenGLWidget(QWidget *parent = nullptr);
        ~OpenGLWidget();
    
    protected:
        // 初始化步骤,部件show时调用
        void initializeGL() override;
        // 部件尺寸修改时调用
        void resizeGL(int w, int h) override;
        // 部件绘制时调用
        void paintGL() override;
    
    };
    

    源文件内容

    OpenGLWidget::OpenGLWidget(QWidget *parent)
        : QOpenGLWidget(parent)
    {
        resize(800, 600);
    }
    
    OpenGLWidget::~OpenGLWidget()
    {
    }
    
    void OpenGLWidget::initializeGL()
    {
        // 初始化OpenGL函数接口
        initializeOpenGLFunctions();
    
        // 设置OpenGL上下文属性,如擦除颜色、深度测试等
        glClearColor(0.2f, 0.3f, 0.3f, 1.0f);
    }
    
    void OpenGLWidget::resizeGL(int w, int h)
    {
        // 设置OpenGL渲染视口
        glViewport(0, 0, w, h);
    }
    
    void OpenGLWidget::paintGL()
    {
        // 具体渲染操作
        glClear(GL_COLOR_BUFFER_BIT);
    }
    
    

    QOpenGLWidget扩展

    接下来,使用QOpenGLWidget绘制一个带纹理贴图的盒子,盒子绕Y轴不停旋转。

    #pragma once
    #include <QMatrix4x4>
    #include <QOpenGLWidget>
    #include <QOpenGLFunctions>
    #include <QOpenGLVertexArrayObject>
    
    
    class QOpenGLBuffer;
    class QOpenGLTexture;
    class QOpenGLShaderProgram;
    class OpenGLWidget : public QOpenGLWidget, protected QOpenGLFunctions
    {
        Q_OBJECT
    
    public:
        OpenGLWidget(QWidget *parent = nullptr);
        ~OpenGLWidget();
    
    protected:
        // 初始化步骤,部件show时调用
        void initializeGL() override;
        // 部件尺寸修改时调用
        void resizeGL(int w, int h) override;
        // 部件绘制时调用
        void paintGL() override;
    
    private:
        void makeObject();
        void makeShader(const QString& vertexSourcePath, const QString& fragmentSourcePath);
    
    private:
        QOpenGLShaderProgram* m_shader;
        QOpenGLBuffer* m_vbo;
        QOpenGLTexture* m_texture;
        QOpenGLVertexArrayObject m_vao;
        QMatrix4x4 m_model, m_view, m_projection;
    };
    
    
    #include "OpenGLWidget.h"
    #include <QOpenGLShader>
    #include <QOpenGLBuffer>
    #include <QOpenGLTexture>
    #include <QOpenGLShaderProgram>
    
    OpenGLWidget::OpenGLWidget(QWidget *parent)
        : QOpenGLWidget(parent), m_shader(nullptr), m_vbo(nullptr), m_texture(nullptr)
    {
        // 设置视图矩阵
        m_view.lookAt(QVector3D(0.0f, 0.0f, 3.0f), QVector3D(0.0f, 0.0f, 0.0f), QVector3D(0.0f, 1.0f, 0.0f));
    
        resize(800, 600);
    }
    
    OpenGLWidget::~OpenGLWidget()
    {
        makeCurrent();
        if (m_vbo != nullptr)
            delete m_vbo;
        if (m_texture != nullptr)
            delete m_texture;
        doneCurrent();
    }
    
    void OpenGLWidget::initializeGL()
    {
        // 初始化OpenGL函数接口
        initializeOpenGLFunctions();
    
        // 新建着色器程序
        makeShader("shaders/container.vert", "shaders/container.frag");
        // 新建渲染对象
        makeObject();
    
        // 设置OpenGL上下文属性,如擦除颜色、深度测试等
        glClearColor(0.2f, 0.3f, 0.3f, 1.0f);
    }
    
    void OpenGLWidget::resizeGL(int w, int h)
    {
        // 设置OpenGL渲染视口
        glViewport(0, 0, w, h);
        // 设置透视矩阵
        m_projection.setToIdentity();
        m_projection.perspective(35.0f, (float)w / (float)h, 0.1f, 100.0f);
        // 传递给着色器程序
        m_shader->setUniformValue("view", m_view);
        m_shader->setUniformValue("projection", m_projection);
    }
    
    void OpenGLWidget::paintGL()
    {
        glEnable(GL_DEPTH_TEST);
        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    
        // 绑定着色器程序
        m_shader->bind();
    
        // 设置模型矩阵
        m_model.rotate(1.5f, 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);
    
        update();
    }
    
    void OpenGLWidget::makeObject()
    {
        float vertices[] = {
            // positions          // texture coords
            -0.5f, -0.5f, -0.5f,  0.0f,  0.0f,
             0.5f, -0.5f, -0.5f,  1.0f,  0.0f,
             0.5f,  0.5f, -0.5f,  1.0f,  1.0f,
             0.5f,  0.5f, -0.5f,  1.0f,  1.0f,
            -0.5f,  0.5f, -0.5f,  0.0f,  1.0f,
            -0.5f, -0.5f, -0.5f,  0.0f,  0.0f,
    
            -0.5f, -0.5f,  0.5f,  0.0f,  0.0f,
             0.5f, -0.5f,  0.5f,  1.0f,  0.0f,
             0.5f,  0.5f,  0.5f,  1.0f,  1.0f,
             0.5f,  0.5f,  0.5f,  1.0f,  1.0f,
            -0.5f,  0.5f,  0.5f,  0.0f,  1.0f,
            -0.5f, -0.5f,  0.5f,  0.0f,  0.0f,
    
            -0.5f,  0.5f,  0.5f,  1.0f,  0.0f,
            -0.5f,  0.5f, -0.5f,  1.0f,  1.0f,
            -0.5f, -0.5f, -0.5f,  0.0f,  1.0f,
            -0.5f, -0.5f, -0.5f,  0.0f,  1.0f,
            -0.5f, -0.5f,  0.5f,  0.0f,  0.0f,
            -0.5f,  0.5f,  0.5f,  1.0f,  0.0f,
    
             0.5f,  0.5f,  0.5f,  1.0f,  0.0f,
             0.5f,  0.5f, -0.5f,  1.0f,  1.0f,
             0.5f, -0.5f, -0.5f,  0.0f,  1.0f,
             0.5f, -0.5f, -0.5f,  0.0f,  1.0f,
             0.5f, -0.5f,  0.5f,  0.0f,  0.0f,
             0.5f,  0.5f,  0.5f,  1.0f,  0.0f,
    
            -0.5f, -0.5f, -0.5f,  0.0f,  1.0f,
             0.5f, -0.5f, -0.5f,  1.0f,  1.0f,
             0.5f, -0.5f,  0.5f,  1.0f,  0.0f,
             0.5f, -0.5f,  0.5f,  1.0f,  0.0f,
            -0.5f, -0.5f,  0.5f,  0.0f,  0.0f,
            -0.5f, -0.5f, -0.5f,  0.0f,  1.0f,
    
            -0.5f,  0.5f, -0.5f,  0.0f,  1.0f,
             0.5f,  0.5f, -0.5f,  1.0f,  1.0f,
             0.5f,  0.5f,  0.5f,  1.0f,  0.0f,
             0.5f,  0.5f,  0.5f,  1.0f,  0.0f,
            -0.5f,  0.5f,  0.5f,  0.0f,  0.0f,
            -0.5f,  0.5f, -0.5f,  0.0f,  1.0f
        };
            
        if (m_vao.create())
        {
            m_vao.bind();
            m_vbo = new QOpenGLBuffer(QOpenGLBuffer::VertexBuffer);
    
            m_vbo->create();
            m_vbo->bind();
            m_vbo->allocate(vertices, sizeof(vertices));
            
            m_shader->enableAttributeArray("aPos");
            m_shader->setAttributeBuffer("aPos", GL_FLOAT, 0, 3, 5 * sizeof(float));
            m_shader->enableAttributeArray("aTexCoord");
            m_shader->setAttributeBuffer("aTexCoord", GL_FLOAT, 3 * sizeof(float), 2, 5 * sizeof(float));
        }
    
        m_texture = new QOpenGLTexture(QImage("images/container2.png").mirrored());
        m_texture->setMinificationFilter(QOpenGLTexture::Nearest);
        m_texture->setMagnificationFilter(QOpenGLTexture::Linear);
        m_texture->setWrapMode(QOpenGLTexture::Repeat);
    
        m_shader->bind();
        m_shader->setUniformValue("tex", 0);
    }
    
    void OpenGLWidget::makeShader(const QString& vertexPath, const QString& fragmentPath)
    {
        QOpenGLShader vertexShader(QOpenGLShader::Vertex);
        bool success = vertexShader.compileSourceFile(vertexPath);
        if (!success)
        {
            qDebug() << "ERROR::SHADER::VERTEX::COMPILATION_FAILED" << endl;
            qDebug() << vertexShader.log() << endl;
        }
        QOpenGLShader fragmentShader(QOpenGLShader::Fragment);
        success = fragmentShader.compileSourceFile(fragmentPath);
        if (!success)
        {
            qDebug() << "ERROR::SHADER::FRAGMENT::COMPILATION_FAILED" << endl;
            qDebug() << fragmentShader.log() << endl;
        }
    
        m_shader = new QOpenGLShaderProgram(this);
        m_shader->addShader(&vertexShader);
        m_shader->addShader(&fragmentShader);
        success = m_shader->link();
        if (!success)
        {
            qDebug() << "ERROR::SHADER::PROGRAM::LINKING_FAILED" << endl;
            qDebug() << m_shader->log() << endl;
        }
    }
    

    顶点着色器代码

    #version 330 core
    
    layout(location = 0) in vec3 aPos;
    layout(location = 1) in vec2 aTexCoord;
    
    out vec2 TexCoords;
    
    uniform mat4 model;
    uniform mat4 view;
    uniform mat4 projection;
    
    void main()
    {
        TexCoords = aTexCoord;
        
        gl_Position = projection * view * model * vec4(aPos, 1.0);
    }
    

    片段着色器代码

    #version 330 core
    
    in vec2 TexCoords;
    out vec4 FragColor;
    
    uniform sampler2D tex;
    
    void main()
    {
        FragColor = texture(tex, TexCoords);
    }
    

    运行效果图


    container0.png
    container.png

    相关文章

      网友评论

          本文标题:Qt OpenGL

          本文链接:https://www.haomeiwen.com/subject/rlertktx.html