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