Qt OpenGL

引言

OpenGL學習過程中需要配置各種各樣的第三方庫,比如使用GLFW用于創(chuàng)建OpenGL上下文,定義窗口參數以及處理用戶輸入。使用GLAD配置OpenGL接口。這里我們可以使用Qt作為OpenGL的載體,好處:

  1. Qt集成了OpenGL開發(fā)的所有工具,例如上下文創(chuàng)建、接口配置;
  2. Qt對OpenGL進行了面向對象的封裝,即QOpenGL***相關類。使得開發(fā)效率更高。
  3. 強大的跨平臺軟件開發(fā)包,可以直接用于工程開發(fā)。

本文著重介紹如何使用Qt進行OpenGL編碼,對于OpenGL的編碼技術的學習,強烈推薦LearnOpenGL。其中文翻譯見https://learnopengl-cn.github.io/

兩種方式

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

Qt推薦在新的軟件開發(fā)中使用QOpenGLWidget,Qt官方文檔中描述了關于QGLWidget類與QOpenGLWidget類之間的關系:


QOpenGLWidget vs QGLWidget.png

大概意思就是:
QOpenGLxxx類旨在替代QGLxxx類,QOpenGLWidget總是使用幀緩存進行幕后渲染,而QGLWidget則是使用原生窗口和表面進行渲染,當在復雜的用戶界面中使用它時,QGLWidget會導致問題,因為根據平臺的不同,這種本地子部件可能有各種限制,例如堆疊順序。而QOpenGLWidget通過不創(chuàng)建單獨的本機窗口來避免這種情況。正因為QOpenGLWidget使用幀緩沖進行幕后渲染,因此在paintGL()中執(zhí)行的渲染將針對這個幀緩存,以便增量呈現成為可能。有點類似于2D繪圖中的雙緩沖概念。
QOpenGLWidget通過glViewport建立視口時,不會做任何清除動作。
通過QPainter進行繪制時,QGLWidget默認在每次使用QPainter::begin()時都會清空背景調色板顏色。QOpenGLWidget則和其他普通的widget一樣,默認不會清空。但當其用作其他小部件(如QGraphicsView)的視口時,為保證兼容性,則會執(zhí)行清空動作。

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
?著作權歸作者所有,轉載或內容合作請聯系作者
【社區(qū)內容提示】社區(qū)部分內容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

友情鏈接更多精彩內容