本篇文章的复现对应于LearnOpenGL教程的入门你好,窗口,你好,三角形,着色器三篇教程。
在QT框架中使用OpenGL
首先新建一个Qt Widgets Application工程,注意把创建界面点掉,以便我们用代码来创建界面。
接着添加一个继承QOpenGLWidget的类来使用OpenGL,这里命名为MyOpenGLWidget,类的具体声明和定义如下:
MyOpenGLWidget.h
#ifndef MYOPENGLWIDGET_H
#define MYOPENGLWIDGET_H
#include <QOpenGLWidget>
#include <QOpenGLFunctions>
class MyOpenGLWidget : public QOpenGLWidget
{
Q_OBJECT
public:
MyOpenGLWidget(QWidget *partent);
~MyOpenGLWidget();
protected:
void initializeGL();//初始化函数,在Widget刚加载时被调用
void paintGL();//绘图函数,每一次绘图请求产生,都会进行一次绘图
void resizeGL(int w, int h);//用于处理窗口大小变化的情况
};
#endif // MYOPENGLWIDGET_H
MyOpenGLWidget.cpp
#include <MyOpenGLWidget.h>
MyOpenGLWidget::MyOpenGLWidget(QWidget* parent):
QOpenGLWidget (parent)
{
}
MyOpenGLWidget::~MyOpenGLWidget()
{
}
void MyOpenGLWidget::initializeGL()
{
}
void MyOpenGLWidget::paintGL()
{
QOpenGLFunctions *f = this->context()->functions();
f->glClearColor(0.0f, 0.2f, 0.0f, 1.0f);//设置清空屏幕所用的颜色
f->glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT);//清空屏幕的颜色缓冲
}
void MyOpenGLWidget::resizeGL(int w, int h)
{
Q_UNUSED(w);
Q_UNUSED(h);
}
这样就完成了一个最简单的OpenGL的类的定义,在main.cpp中直接用下面的语句调用即可:
MyOpenGLWidget w(nullptr);
w.resize(800, 600);
w.show();
如果只是想将OpenGL的窗体嵌入到QT的窗体内,则修改mainwindow类如下:
mainwindow.h
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include <QMainWindow>
#include <QVBoxLayout>
#include <MyOpenGLWidget.h>
class MainWindow : public QWidget
{
Q_OBJECT
public:
MainWindow(QWidget *parent = nullptr);
~MainWindow();
private:
QVBoxLayout *m_layout;
MyOpenGLWidget *m_painting;
};
#endif // MAINWINDOW_H
mainwindow.cpp
#include "mainwindow.h"
#include <MyOpenGLWidget.h>
MainWindow::MainWindow(QWidget *parent)
: QWidget(parent)
{
m_layout = new QVBoxLayout(this);
setLayout(m_layout);
m_painting = new MyOpenGLWidget(this);
m_layout->addWidget(m_painting);
}
MainWindow::~MainWindow()
{
}
顶点
完成了上面的操作,我们得到的是一个什么都没有的OpenGL显示窗口,那么怎么才能在上面显示东西呢?
在OpenGL中任何对象都是由点组成,无论你看到的对象最后呈现出来的效果多么精细,其背后都是由点组成的一个个图元加上贴图和光影调整等操作后展现出来的,这些点一般被称之为顶点。
本人在学习教程的时候被这里的各种缓存搞得很懵圈,因此在此稍作梳理。
一般我们可以这样定义顶点数组:
GLfloat vertices[] = {
-0.5f, -0.5f, 0.0f,
0.5f, -0.5f, 0.0f,
0.0f, 0.5f, 0.0f
};
在程序运行时,它存放在内存中。如果要使用它,需要将这些顶点送到顶点缓冲对象(Vertex Buffer Objects, VBO)中。假设我们有n个顶点缓冲对象,需要经常切换,那么弄一个专门来管理这些顶点缓冲对象的东西是非常有必要的,它是顶点数组对象(Vertex Array Object, VAO)。当绑定顶点数组对象然后进行顶点缓冲对象绑定之后,顶点数组对象就自动成为这个顶点缓冲对象的对外通道。只要我们在需要的地方绑定下这个通道,就能直接访问这个顶点缓冲对象。
是不是被上面那个逻辑绕懵逼了,本人一开始也被这些东西搞得很头大。但其实它的并不复杂,可以把顶点缓冲对象比喻为一个房子,而顶点数组对象比喻为这个房子通向外部的道路。当我们建了个房子过后,也需要把它通向外部的道路给给建起来。返问这些顶点缓冲对象就相当于从外部道路开车去返问其中一间房子,我们选择了哪一条路就最终决定了终点的房子是哪一个。
最终顶点数组的绑定代码就变成了下面这个样子:
QOpenGLVertexArrayObject *m_vao;
QOpenGLBuffer *m_vbo;
m_vao = new QOpenGLVertexArrayObject();
m_vbo = new QOpenGLBuffer(QOpenGLBuffer::Type::VertexBuffer);
m_vao->create();
m_vao->bind();
m_vbo->create();
m_vbo->bind();
GLfloat vertices[] = {
-0.5f, -0.5f, 0.0f,
0.5f, -0.5f, 0.0f,
0.0f, 0.5f, 0.0f
};
m_vbo->allocate(vertices, 3 * 3 * sizeof(GLfloat));
QOpenGLFunctions *f = this->context()->functions();
f->glEnableVertexAttribArray(0);
f->glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE,3*sizeof(GLfloat), 0);
m_vbo->release();
m_vao->release();
着色器
着色器是OpenGL的核心,最基础的OpenGL程序只需要两种着色器。
顶点着色器
主要目的是把3D坐标转为另一种3D坐标,同时也允许我们对顶点属性进行一些基本处理。
片元着色器
主要目的是计算一个像素的最终颜色。
具体的解释请参考LearnOpenGL教程,由于QT为着色器提供了专门的处理类,因此我们不再需要自己实现着色器代码的文件读取。具体的,可以通过添加GLSL的Vertex Shader和Fragment Sharder文件进行着色器代码的编写,之后通过addShaderFromSourceFile函数将编写的着色器添加进来即可。具体的:
Vertex.vert
#version 330
layout(location = 0) in vec3 posVertex;
void main()
{
gl_Position = vec4(posVertex, 1.0f);
}
Fragment.frag
#version 330
out vec4 fragColor;
void main()
{
fragColor = vec4(1.0f, 0.0f, 0.0f, 1.0f);
}
m_shader->addShaderFromSourceFile(QOpenGLShader::Vertex, "../Demo1/Vertex.vert");
m_shader->addShaderFromSourceFile(QOpenGLShader::Fragment, "../Demo1/Fragment.frag");
到此我们就把顶点和着色器复现完成了,如有不清楚的地方可以参考下面给出的整份代码:
MyOpenGLWidget.h
#ifndef MYOPENGLWIDGET_H
#define MYOPENGLWIDGET_H
#include <QOpenGLWidget>
#include <QOpenGLFunctions>
#include <QOpenGLBuffer>
#include <QOpenGLVertexArrayObject>
#include <QOpenGLShaderProgram>
class MyOpenGLWidget : public QOpenGLWidget
{
Q_OBJECT
public:
MyOpenGLWidget(QWidget *partent);
~MyOpenGLWidget();
protected:
void initializeGL();//初始化函数,在Widget刚加载时被调用
void paintGL();//绘图函数,每一次绘图请求产生,都会进行一次绘图
void resizeGL(int w, int h);//用于处理窗口大小变化的情况
private:
QOpenGLBuffer *m_vbo;
QOpenGLVertexArrayObject *m_vao;
QOpenGLShaderProgram *m_shader; // 渲染器程序对象
};
#endif // MYOPENGLWIDGET_H
MyOpenGLWidget.cpp
#include <MyOpenGLWidget.h>
MyOpenGLWidget::MyOpenGLWidget(QWidget* parent):
QOpenGLWidget (parent)
{
}
MyOpenGLWidget::~MyOpenGLWidget()
{
}
void MyOpenGLWidget::initializeGL()
{
m_shader = new QOpenGLShaderProgram();
m_shader->addShaderFromSourceFile(QOpenGLShader::Vertex, "../Demo1/Vertex.vert");
m_shader->addShaderFromSourceFile(QOpenGLShader::Fragment, "../Demo1/Fragment.frag");
if (m_shader->link()) {
qDebug("Shaders link success.");
} else {
qDebug("Shaders link failed!");
}
m_vao = new QOpenGLVertexArrayObject();
m_vbo = new QOpenGLBuffer(QOpenGLBuffer::Type::VertexBuffer);
m_vao->create();
m_vao->bind();
m_vbo->create();
m_vbo->bind();
static const GLfloat vertices[] = {
-0.5f, -0.5f, 0.0f,
0.5f, -0.5f, 0.0f,
0.0f, 0.5f, 0.0f
};
m_vbo->allocate(vertices, 3 * 3 * sizeof(GLfloat));
QOpenGLFunctions *f = this->context()->functions();
f->glEnableVertexAttribArray(0);
f->glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE,3*sizeof(GLfloat), 0);
m_vbo->release();
m_vao->release();
}
void MyOpenGLWidget::paintGL()
{
QOpenGLFunctions *f = this->context()->functions();
f->glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT);
f->glClearColor(0.0f, 0.2f, 0.0f, 1.0f);
m_vao->bind();
m_shader->bind();
f->glDrawArrays(GL_TRIANGLES, 0, 3);
m_shader->release();
m_vao->release();
}
void MyOpenGLWidget::resizeGL(int w, int h)
{
Q_UNUSED(w);
Q_UNUSED(h);
}
mainwindow.h
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include <QMainWindow>
#include <QVBoxLayout>
#include <MyOpenGLWidget.h>
class MainWindow : public QWidget
{
Q_OBJECT
public:
MainWindow(QWidget *parent = nullptr);
~MainWindow();
private:
QVBoxLayout *m_layout;
MyOpenGLWidget *m_painting;
};
#endif // MAINWINDOW_H
mainwindow.cpp
#include "mainwindow.h"
#include <MyOpenGLWidget.h>
MainWindow::MainWindow(QWidget *parent)
: QWidget(parent)
{
m_layout = new QVBoxLayout(this);
setLayout(m_layout);
m_painting = new MyOpenGLWidget(this);
m_layout->addWidget(m_painting);
}
MainWindow::~MainWindow()
{
}