引言
OpenGL学习过程中需要配置各种各样的第三方库,比如使用GLFW用于创建OpenGL上下文,定义窗口参数以及处理用户输入。使用GLAD配置OpenGL接口。这里我们可以使用Qt作为OpenGL的载体,好处:
- Qt集成了OpenGL开发的所有工具,例如上下文创建、接口配置;
- Qt对OpenGL进行了面向对象的封装,即QOpenGL***相关类。使得开发效率更高。
- 强大的跨平台软件开发包,可以直接用于工程开发。
本文着重介绍如何使用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
网友评论