0%

Qt+OpenGL 1.基本绘制框架搭建

每次建立新的OpenGL工程,都要翻翻笔记,做一些复制粘贴的工作,非常朴实无华且枯燥。于是我便产生了搭建一个比较通用的OpenGL模板的想法。最近又在学习Qt和设计模式,顺便将OpenGL与Qt相结合,并亲手实践一些设计模式,权当练手罢。

工程目录

我首先新建立了一个Qt的工程,并且按下图建立工程目录。其中mainwindow就是应用的主窗口,glwidget.cpp文件定义了GLWidget类,openglmodel文件中定义了被绘制的模型类。我还新建了一个资源文件目录,用来存放各种着色器文件。

GLWidget类

GLWidget类是opengl的绘制对象,负责所有模型、着色器、用户交互。继承QOpenGLWidget类,并重写其中的initializeGL(),paintGL(),resizeGL()虚函数,其分别对应初始化、绘制、窗体大小改变时会触发的回调函数。重写后的initializeGL主要负责一些opengl绘制环境初始化、着色器初始化、模型初始化的工作,而paintGL则相当于原生opengl中的绘制循环。

注意点:

  • opengl初始化工作要在initializeGL()中进行,而不是构造函数中,并且记得调用initializeOpenGLFunctions。
  • 我通过public继承了QOpenGLFunctions_4_3_Core类(可以换成其他版本)来调用opengl的函数,有些教程,包括官方文档上使用的是protected继承,但由于我要从模型类调用GLWidget类的opengl函数,所以改成了public继承,可能会导致一些封装性上的问题。
  • 我将着色器初始化工具函数initShader()放在了命名空间GLWidgetTools类中,因为该函数仅仅为了方便操作,与类本身的关系并不大。为了加强类的封装,并减小类的职责,将该函数放在外部的命名空间内更合适。

宁以non-member, non-friend函数替换member函数——《effective c++, item 23》

glwidget.h
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
#ifndef GLWIDGET_H
#define GLWIDGET_H
#include <QOpenGLWidget>
#include <QOpenGLFunctions_4_3_Core>
#include <QOpenGLShaderProgram>
#include <QWidget>
#include <memory>
class GLModel;
using std::shared_ptr;
class GLWidget : public QOpenGLWidget, public QOpenGLFunctions_4_3_Core
{
Q_OBJECT
public:
explicit GLWidget(QWidget *parent = nullptr);

signals:

private:
void initializeGL() override;
void paintGL() override;
void resizeGL(int w,int h) override;
void init();
private:
QWidget* father;
//智能指针是个好习惯!
shared_ptr<QOpenGLShaderProgram> pShader;
shared_ptr<GLModel> model;
};

#endif // GLWIDGET_H
glwidget.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
#include "glwidget.h"
#include <QDebug>
#include "openglmodel.h"
#include <vector>
namespace GLWidgetTools {
//加载shader文件
void initShader(QOpenGLShaderProgram *shader, QString vs, QString fs, QString gs="")
{
if(!shader->addShaderFromSourceFile(QOpenGLShader::Vertex, vs)){
qDebug()<<shader->log();
return;
}
if(gs!=""&&!shader->addShaderFromSourceFile(QOpenGLShader::Geometry, gs)){
qDebug()<<shader->log();
return;
}
if(!shader->addShaderFromSourceFile(QOpenGLShader::Fragment, fs)){
qDebug()<<shader->log();
return;
}

if(!shader->link()){
qDebug()<<shader->log();
return;
}
if(!shader->bind()){
qDebug()<<shader->log();
return;
}
shader->release();
}
}
GLWidget::GLWidget(QWidget *parent) : QOpenGLWidget(parent)
{
father = parent;
init();
}

void GLWidget::initializeGL()
{
initializeOpenGLFunctions();
if(father) setGeometry(0,0,father->width(),father->height());
qDebug()<< (const char*)glGetString(GL_VERSION)<<endl; //获取显卡opengl版本信息
//glEnable(GL_DEPTH_TEST);
glClearColor(82.0/255, 87.0/255, 110.0/255,0.0); //设置背景色
glClear(GL_DEPTH_BUFFER_BIT|GL_COLOR_BUFFER_BIT);

pShader.reset(new QOpenGLShaderProgram);
//shader文件用资源文件储存
GLWidgetTools::initShader(pShader.get(),":/shader/shader.vs",":/shader/shader.fs");
//模型初始化,一个三角形
std::vector<Vertex> v(3);
v[0].Position=QVector3D(-1,-1,0);
v[1].Position=QVector3D(1,-1,0);
v[2].Position=QVector3D(0,1,0);
std::vector<unsigned int> i{0,1,2};
model.reset(new GLModel(this,v,i));
model->setupModel();
}

void GLWidget::paintGL()
{
//初始化工作
glClearColor(82.0/255, 87.0/255, 110.0/255,0.00f);
glClear(GL_DEPTH_BUFFER_BIT|GL_COLOR_BUFFER_BIT);

model->Draw(pShader.get());
qDebug()<<glGetError();
update();
}

void GLWidget::resizeGL(int w, int h)
{
//改变窗体大小
setGeometry(0,0,w,h);
//改变视口
glViewport(0,0,w,h);
}

void GLWidget::init()
{
}

openglmodel类

我主要想实现一个重用性较强的模型基类,说是模型类,其实其功能可以包含光照、摄像机矩阵等与着色器之间的交互。
实践了一下设计方法中的Template Method,将Draw()函数视为一个绘制的模板,在其中处理一些绘制前后的通用步骤,而核心的绘制过程则作为虚函数交给派生类重写。

注意点:

  • 在头文件中使用类声明,而不是文件包含,可以减小文件之间的编译耦合
  • 对于一些数据量较大的模型,顶点位置数据应该交给一个模型类来存储,而其他模型类则共享该VBO
openglmodel.h
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
#ifndef OPENGLMODEL_H
#define OPENGLMODEL_H
#include <QOpenGLShaderProgram>
#include <vector>
#include <QVector>
//在头文件中声明GLWidget类,在源文件中引入glwidget.h,减少编译耦合
class GLWidget;
class Camera;
//纹理类
struct Texture{
unsigned int id;
std::string type;
};
//顶点类
//结构体中内存连续存储,因此可以安心使用
struct Vertex{
QVector3D Position;
QVector3D Normal=QVector3D();
QVector2D TexCoords=QVector2D();
};
using std::vector;
class GLModel
{
private:
public:
GLModel(GLWidget* f);
GLModel(GLWidget* f,vector<Vertex> v,vector<unsigned int> i=vector<unsigned int>(),vector<Texture> t=vector<Texture>());
//当有多个对象使用同一个顶点数据时,由一个对象创建VBO,其他对象共享该VBO;
GLModel(GLWidget* f,unsigned int vbo,vector<unsigned int> i=vector<unsigned int>(),vector<Texture> t=vector<Texture>());
//基类在绘制流程前后添加了一些通用步骤,子类重写绘制核心过程
void setCamera(Camera* c);
void Draw(QOpenGLShaderProgram* shader);
void setupModel();
protected:
//子类可以重写绘制的核心步骤
virtual void mainDraw(QOpenGLShaderProgram* shader);
GLWidget* father;
vector<Vertex> vertices;
vector<unsigned int> indices;
//如果要使用纹理的话,在着色器uniform中要给纹理一个统一的编号规则
vector<Texture> textures;
unsigned int VAO,VBO=0,EBO;
//目前来说空余的Camera对象
Camera* myCamera;
};
#endif // OPENGLMODEL_H
openglmodel.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
#include "openglmodel.h"
#include "glwidget.h"

GLModel::GLModel(GLWidget *f):father(f){}


GLModel::GLModel(GLWidget* f,vector<Vertex> v, vector<unsigned int> i, vector<Texture> t)
:father(f),vertices(v),indices(i),textures(t){}

GLModel::GLModel(GLWidget *f, unsigned int vbo, vector<unsigned int> i, vector<Texture> t)
:VBO(vbo),indices(i),textures(t){}

void GLModel::setCamera(Camera *c)
{
myCamera=c;
}

void GLModel::setupModel()
{
father->glGenVertexArrays(1, &VAO);
father->glBindVertexArray(VAO);
//如果没有从其他对象共享VBO,则创建VBO
if(VBO) father->glBindBuffer(GL_ARRAY_BUFFER,VBO);
else{
father->glGenBuffers(1, &VBO);
father->glBindBuffer(GL_ARRAY_BUFFER, VBO);
father->glBufferData(GL_ARRAY_BUFFER, vertices.size() * sizeof(Vertex), &vertices[0], GL_STATIC_DRAW);
father->glEnableVertexAttribArray(0);
father->glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, sizeof(Vertex), (void*)0);
// 顶点法线
father->glEnableVertexAttribArray(1);
//offsetof可以方便地设置结构体的偏移量
father->glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, sizeof(Vertex), (void*)offsetof(Vertex, Normal));
// 顶点纹理坐标
father->glEnableVertexAttribArray(2);
father->glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, sizeof(Vertex), (void*)offsetof(Vertex, TexCoords));
}
father->glGenBuffers(1, &EBO);

father->glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO);
father->glBufferData(GL_ELEMENT_ARRAY_BUFFER, indices.size() * sizeof(unsigned int),
&indices[0], GL_STATIC_DRAW);

father->glBindVertexArray(0);
}

void GLModel::mainDraw(QOpenGLShaderProgram *shader)
{
father->glDrawElements(GL_TRIANGLES, indices.size(), GL_UNSIGNED_INT, 0);
}

void GLModel::Draw(QOpenGLShaderProgram* shader){
shader->bind();
unsigned int diffuseNr = 1;
unsigned int specularNr = 1;
using std::string;
//如果没有纹理,跳过这一部分
for(unsigned int i = 0; i < textures.size(); i++)
{
father->glActiveTexture(GL_TEXTURE0 + i); // 在绑定之前激活相应的纹理单元
// 获取纹理序号(diffuse_textureN 中的 N)
string number;
string name = textures[i].type;
if(name == "texture_diffuse")
number = std::to_string(diffuseNr++);
else if(name == "texture_specular")
number = std::to_string(specularNr++);

shader->setUniformValue(("material." + name + number).c_str(), i);
father->glBindTexture(GL_TEXTURE_2D, textures[i].id);
}
father->glActiveTexture(GL_TEXTURE0);
// 绘制模型
father->glBindVertexArray(VAO);
mainDraw(shader);
father->glBindVertexArray(0);
shader->release();
}

着色器

最基础的shader,没啥好讲的。

shader.vs
1
2
3
4
5
6
#version 430 core
layout (location = 0) in vec3 aPos;
void main()
{
gl_Position = vec4(aPos,1.0f);
}
shader.fs
1
2
3
4
5
6
#version 430 core
out vec4 FragColor;
void main()
{
FragColor = vec4(1.0f,1.0f,1.0f,1.0f);
}

主窗口

mainwindow.ui中添加OpenGL Widget控件

右键OpenGL Widget控件,提升为

在提升的类名称中填入GLWidget,头文件则为glwidget.h,点击提升,该控件就继承了我们之前编写的GLWidget类

运行

当当当当~一个朴实无华且枯燥的三角形