在Qt中使用OpenGL(一)

前言

之前一段时间在研究Kinect的深度摄像头,发现了深度摄像头存在一个“点云”模式。
也就是可以将摄像头识别到的深度信息在3D空间以一个一个点的形式给表现出来。
于是想要自己试着渲染一个点云出来。
既然是在3D空间,那么显然需要使用3D相关的技术了。
在Qt中……那就用Qt提供的OpenGL吧。
恰好之前(挺早之前了)我也算是接触过OpenGL的,然而当我查找了一下相关的文档才发现……新的OpenGL已经和我之前了解的OpenGL是两个东西了……
详见LearnOpenGL CN:https://learnopengl-cn.github.io/
新的OpenGL竟然在最简单的例子中要求写shader?
好家伙,直接给我整不会了。
不过没办法,既然需要写shader那就写吧……
于是最终的成品就是如下所示:
距离摄像机越近,点的颜色越红,越远越蓝,适中就是绿色。

Qt中的OpenGL

要怎么说呢,Qt中对OpenGL的使用是进行了很多层的包装的,我尽可能的挑选了Qt原生的对OpenGL的使用方法,也就是继承QOpenGLWidgetQOpenGLExtraFunctions的方案:
在Qt中使用OpenGL(一)_第1张图片在Qt中使用OpenGL(一)_第2张图片
大致上,你的用于显示OpenGL渲染结果的窗口,应该是这么一个类,即拥有以下特性:

  • 继承QOpenGLWidgetQOpenGLExtraFunctions
  • 重载void initializeGL(),void paintGL()还有void resizeGL(int w, int h)三个函数
  • 提供一些函数,用于设置你的摄像机的位置与旋转
  • 提供一些函数,用于设置你需要显示的内容具体数据(在本例子中,就是提供点云的具体数据。在其他例子中,可能就是提供模型数据了。比如加载模型,卸载模型等)
  • 提供一些函数,用于设置你要显示内容的缩放,旋转与位移(在本例子中,就是点云。在其它例子中,可能就是设置加载的模型的缩放,位置,旋转了)

注意:当你需要显示复杂的内容时,就需要额外的编写逻辑,例如模型管理,贴图管理等等。本例子中因为只显示一个点云数据,所以并没有什么复杂的资源管理逻辑,只需要一个顶点缓存用来保存点云数据,然后在有一些矩阵数据保存各种变化矩阵即可。

initializeGL

void initializeGL()函数中,我们需要对OpenGL进行一定的初始化。
具体来说,就是:

  1. 初始化函数指针以便可以使用OpenGL的各种函数。
    Qt帮我们提供了这么一个函数:initializeOpenGLFunctions()只要调用就可以了。如果不调用这个函数,则我们将无法使用OpenGL提供的函数。
  2. 启用一些flag
    例如glEnable(GL_DEPTH_TEST)启用深度测试等。深度测试可以让我们在绘制3D图形的时候,总是看到“近处的物体可以挡住远处的物体”这个效果。
  3. 进行一些缓存的创建。
    OpenGL使用的时候需要一些缓存对象,顶点队列对象(VAO),顶点缓存对象(VBO)之类的,都可以在初始化的时候进行创建。
    至于什么是VAO,VBO我们之后再说。目前只需要知道OpenGL在3D绘制的时候,其数据的来源,就是来自于这些缓存之中。
  4. 初始化shader程序并链接:
    Qt提供了一个类QOpenGLShaderProgram,我们用它就可以了。
    new出来这个类,然后用addShaderFromSourceCodeaddShaderFromSourceFile加载你的shader,最后用link函数连接即可。
    至于shader是干什么用的我们之后再说。目前只需要知道shader可以让OpenGL在3D绘制的时候,对数据来源进行一定程度的修改。例如你感受到的3D空间的位置的变化或旋转实际上都是通过shader对要绘制的模型的顶点进行了额外处理模拟出来的,并不是真的存在一个摄像机,而是模拟出来了一个摄像机。
  5. 根据需要,如果你要显示的内容是静态的(例如一个静态的模型),那么就可以在初始化的时候加载,否则就需要实时的加载了。我们的点云数据是动态变化的,所以不能初始化加载,而是动态加载。

paintGL

void paintGL()函数中,我们就可以进行主要的3D绘制逻辑了。
我们主要需要进行以下行为:

  1. 清除之前绘制的内容,包含颜色与深度信息
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    
  2. 启用shader,调用bind函数
    m_program->bind();
    
  3. 设置shader中的全局数据,例如摄像机矩阵,投影矩阵,每个模型自身的变换矩阵等
	// 设置视图矩阵
	m_view.setToIdentity();
	QVector3D _lookDir{ 0, 0, 1 };
	QVector3D _up{ 0, -1, 0 };
	_lookDir = _lookDir * m_cameraRotate;
	_up = _up * m_cameraRotate;
	m_view.lookAt(m_cameraPos, m_cameraPos + _lookDir, _up);
	m_program->setUniformValue("view", m_view);
	// 设置投影矩阵
	m_program->setUniformValue("projection", m_projection);
	// 针对本模型, 设置模型矩阵
	m_model.setToIdentity();
	m_model *= m_scale;
	m_model *= m_rotate;
	m_model *= m_translate;
	m_program->setUniformValue("model", m_model);
  1. 启用顶点队列对象(VAO)
	m_vao.bind();
  1. 启用并设置顶点缓存(VBO),即告诉OpenGL当前要绘制的顶点信息和其它信息
	// 绑定本模型需要的顶点缓冲
	m_vbo.bind();
	m_vbo.setUsagePattern(QOpenGLBuffer::StaticDraw);
	m_vbo.allocate(m_vertices, m_vertecesCount * 3 * sizeof(float));
  1. 将shader与顶点缓存进行对应,即告知OpenGL要如何使用将顶点缓存中的数据转化为shader中的顶点数据
		// 针对本模型, 设置如何渲染
		m_program->setAttributeBuffer(0, GL_FLOAT, 0, 3, 0);
		m_program->enableAttributeArray(0);
  1. 调用OpenGL的绘制函数,绘制顶点缓存中的内容
		// 绘制模型
		glDrawArrays(GL_POINTS, 0, m_vertecesCount);
  1. 释放顶点队列对象(VAO)
	m_vao.release();
  1. 释放shader
	m_program->release();

其中,第5步和第6步就是动态加载数据的步骤。如果你要显示的内容是静态的(例如一个静态模型),你实际上应该在初始化的时候进行这个操作。注意,初始化的时候也要先启用shader和VAO后在操作,并在最后释放VAO和shader。

总结

说了这么多,实际上我们只是对整个OpenGL的使用流程做了一个最初步的介绍,如果你是第一次接触肯定会依旧感到莫名其妙。
但是不要紧,因为我们现在其实并不需要真的理解,面对复杂的内容,我个人的习惯是首先了解整个流程,然后再找到一个最小样的例来体验这个流程。
目前我们已经了解了整个流程,即初始化然后绘制。
简单来说,就是:

初始阶段:

  1. 初始化OpenGL函数
  2. 初始化各种flag
  3. 创建各种缓存对象
  4. 创建并链接shader
  5. 启用shader
  6. 启用缓存
  7. 绑定缓存(加载不变的数据)
  8. 绑定shader的缓存数据(告诉OpenGL如何从缓存中读取数据到shader中)
  9. 释放缓存
  10. 释放shader

绘制阶段:

  1. 清屏
  2. 启用shader
  3. 绑定shader的全局数据(例如摄像机变换矩阵,投影矩阵等)
  4. 启用缓存
  5. 绑定缓存(加载变化的数据)
  6. 绑定shader的缓存数据(告诉OpenGL如何从缓存中读取数据到shader中)
  7. 绘制(使用shader读取缓存中的内容,转换为顶点,然后按照命令基于顶点绘制3D数据)
  8. 释放缓存
  9. 释放shader

根据我们的需求(数据变化与否),我们会在初始化阶段绑定缓存(数据不会变化)或者在绘制阶段绑定缓存(数据会变化)。

注意:可以存在多个缓存。每个缓存可以保存不同的内容(例如不同的模型),根据需要,我们可以在绘制阶段重复4,5,6,7,8步骤以达到绘制不同模型的目的。

下一篇:
在Qt中使用OpenGL(二)

你可能感兴趣的:(OpenGL,qt,opengl)