引言
OpenGL學(xué)習(xí)過程中需要配置各種各樣的第三方庫弯予,比如使用GLFW用于創(chuàng)建OpenGL上下文,定義窗口參數(shù)以及處理用戶輸入装获。使用GLAD配置OpenGL接口篙梢。這里我們可以使用Qt作為OpenGL的載體,好處:
- Qt集成了OpenGL開發(fā)的所有工具逆航,例如上下文創(chuàng)建鼎文、接口配置;
- Qt對(duì)OpenGL進(jìn)行了面向?qū)ο蟮姆庋b因俐,即QOpenGL***相關(guān)類拇惋。使得開發(fā)效率更高周偎。
- 強(qiáng)大的跨平臺(tái)軟件開發(fā)包,可以直接用于工程開發(fā)撑帖。
本文著重介紹如何使用Qt進(jìn)行OpenGL編碼蓉坎,對(duì)于OpenGL的編碼技術(shù)的學(xué)習(xí),強(qiáng)烈推薦LearnOpenGL胡嘿。其中文翻譯見https://learnopengl-cn.github.io/
兩種方式
- 使用QGLWidget等QGLxxx類
- 使用QOpenGLWidget等QOpenGLxxx類
Qt推薦在新的軟件開發(fā)中使用QOpenGLWidget蛉艾,Qt官方文檔中描述了關(guān)于QGLWidget類與QOpenGLWidget類之間的關(guān)系:
大概意思就是:
QOpenGLxxx類旨在替代QGLxxx類,QOpenGLWidget總是使用幀緩存進(jìn)行幕后渲染衷敌,而QGLWidget則是使用原生窗口和表面進(jìn)行渲染勿侯,當(dāng)在復(fù)雜的用戶界面中使用它時(shí),QGLWidget會(huì)導(dǎo)致問題缴罗,因?yàn)楦鶕?jù)平臺(tái)的不同助琐,這種本地子部件可能有各種限制,例如堆疊順序面氓。而QOpenGLWidget通過不創(chuàng)建單獨(dú)的本機(jī)窗口來避免這種情況兵钮。正因?yàn)镼OpenGLWidget使用幀緩沖進(jìn)行幕后渲染,因此在paintGL()中執(zhí)行的渲染將針對(duì)這個(gè)幀緩存舌界,以便增量呈現(xiàn)成為可能矢空。有點(diǎn)類似于2D繪圖中的雙緩沖概念。
QOpenGLWidget通過glViewport建立視口時(shí)禀横,不會(huì)做任何清除動(dòng)作屁药。
通過QPainter進(jìn)行繪制時(shí),QGLWidget默認(rèn)在每次使用QPainter::begin()時(shí)都會(huì)清空背景調(diào)色板顏色柏锄。QOpenGLWidget則和其他普通的widget一樣酿箭,默認(rèn)不會(huì)清空。但當(dāng)其用作其他小部件(如QGraphicsView)的視口時(shí)趾娃,為保證兼容性缭嫡,則會(huì)執(zhí)行清空動(dòng)作。
QOpenGLWidget
在Qt中使用OpenGL渲染繪制抬闷,只需子類化QOpenGLWidget妇蛀,重寫initializeGL、resizeGL和paintGL即可笤成。QOpenGLWidget的基本使用方法:
頭文件內(nèi)容
// 繼承自QOpenGLFunctions评架,免去每次調(diào)用OpenGL的接口時(shí),都必須獲取當(dāng)前上下文對(duì)應(yīng)的接口封裝
class OpenGLWidget : public QOpenGLWidget, protected QOpenGLFunctions
{
Q_OBJECT
public:
OpenGLWidget(QWidget *parent = nullptr);
~OpenGLWidget();
protected:
// 初始化步驟炕泳,部件show時(shí)調(diào)用
void initializeGL() override;
// 部件尺寸修改時(shí)調(diào)用
void resizeGL(int w, int h) override;
// 部件繪制時(shí)調(diào)用
void paintGL() override;
};
源文件內(nèi)容
OpenGLWidget::OpenGLWidget(QWidget *parent)
: QOpenGLWidget(parent)
{
resize(800, 600);
}
OpenGLWidget::~OpenGLWidget()
{
}
void OpenGLWidget::initializeGL()
{
// 初始化OpenGL函數(shù)接口
initializeOpenGLFunctions();
// 設(shè)置OpenGL上下文屬性,如擦除顏色纵诞、深度測(cè)試等
glClearColor(0.2f, 0.3f, 0.3f, 1.0f);
}
void OpenGLWidget::resizeGL(int w, int h)
{
// 設(shè)置OpenGL渲染視口
glViewport(0, 0, w, h);
}
void OpenGLWidget::paintGL()
{
// 具體渲染操作
glClear(GL_COLOR_BUFFER_BIT);
}
QOpenGLWidget擴(kuò)展
接下來,使用QOpenGLWidget繪制一個(gè)帶紋理貼圖的盒子培遵,盒子繞Y軸不停旋轉(zhuǎn)浙芙。
#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時(shí)調(diào)用
void initializeGL() override;
// 部件尺寸修改時(shí)調(diào)用
void resizeGL(int w, int h) override;
// 部件繪制時(shí)調(diào)用
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)
{
// 設(shè)置視圖矩陣
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函數(shù)接口
initializeOpenGLFunctions();
// 新建著色器程序
makeShader("shaders/container.vert", "shaders/container.frag");
// 新建渲染對(duì)象
makeObject();
// 設(shè)置OpenGL上下文屬性,如擦除顏色、深度測(cè)試等
glClearColor(0.2f, 0.3f, 0.3f, 1.0f);
}
void OpenGLWidget::resizeGL(int w, int h)
{
// 設(shè)置OpenGL渲染視口
glViewport(0, 0, w, h);
// 設(shè)置透視矩陣
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();
// 設(shè)置模型矩陣
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;
}
}
頂點(diǎn)著色器代碼
#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);
}
運(yùn)行效果圖