引言
OpenGL學習過程中需要配置各種各樣的第三方庫,比如使用GLFW用于創(chuàng)建OpenGL上下文,定義窗口參數以及處理用戶輸入。使用GLAD配置OpenGL接口。這里我們可以使用Qt作為OpenGL的載體,好處:
- Qt集成了OpenGL開發(fā)的所有工具,例如上下文創(chuàng)建、接口配置;
- Qt對OpenGL進行了面向對象的封裝,即QOpenGL***相關類。使得開發(fā)效率更高。
- 強大的跨平臺軟件開發(fā)包,可以直接用于工程開發(fā)。
本文著重介紹如何使用Qt進行OpenGL編碼,對于OpenGL的編碼技術的學習,強烈推薦LearnOpenGL。其中文翻譯見https://learnopengl-cn.github.io/
兩種方式
- 使用QGLWidget等QGLxxx類
- 使用QOpenGLWidget等QOpenGLxxx類
Qt推薦在新的軟件開發(fā)中使用QOpenGLWidget,Qt官方文檔中描述了關于QGLWidget類與QOpenGLWidget類之間的關系:

大概意思就是:
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);
}
運行效果圖

