教程已转移到b站,请到b站查看
https://www.bilibili.com/read/cv10421402
终于到了实际操作的环节了,这一章我们要做的是:
- 创建OpenGL窗口
- 创建并编写着色器程序
- 为着色器程序装配顶点数据
- 启动渲染管线进行绘图
第一步:创建新项目
首先,我们使用Qt创建一个常规的QWidget项目,建议不要勾选ui设计器(个人习惯,笔者不太习惯于使用ui来设计)
第二步:创建QOpenGLWidget并实现三个虚函数
QOpenGLWidget
Qt5.4之前,Qt可以通过一个QGLWidget类来创建OpenGL窗口,但由于opengl的发展,在5.4版本的时候,Qt提供了一个QGLWidget的现代替代品——QOpenGLWidget。因此,这个教程使用的是QOpenGLWidget来创建和管理窗口。
创建opengl窗口只需新建类继承于QOpenGLWidegt,再实现QOpenGL提供的三个虚函数,就可以完成opengl窗口的创建。
- initializeGL()—建立OpenGL的资源和状态。在第一次调用resizeGL()或paintGL()之前调用一次
- resizeGL()—设置OpenGL视口,投影等。每当调整Widget的大小时(第一次显示窗口Widget时会调用它,因为所有新创建Widget都会自动获得调整大小的事件)。
- paintGL()—渲染OpenGL场景,需要更新Widget时就会调用。
QOpenGLExtraFunctions
QOpenGLExtraFunctions类继承于QOpenGLFunctions,相较于QOpenGLFunctions,额外提供了对OpenGL ES 3.0、3.1和3.2 API的跨平台访问,如果我们需要在类中使用opengl函数,只需要使类继承于QOpenGLExtraFunctions,就能在内部通过this指针访问到OpenGL函数
注:如果你的Qt版本比较低,可能需要在pro文件中添加Qt += opengl
视频节点
QOpenGL入门教程
操作说明
- glClearColor(0,0.5,0.9,1.0); 设置清屏颜色
- glClear(GL_COLOR_BUFFER_BIT); 清空颜色缓冲区
第三步:搭建渲染管线
想想我们应该画什么呢?画个三角形吗?太简单了,整点难的,我们画个两个,拼成一个四边形。
因此我们需要的是:
- 两个三角形的顶点数据
- 着色器程序(顶点着色器+片段着色器)
如果你还记得OpenGL的标准设备坐标的话,很容易给出顶点数据,这里我们使用的数据是
GLfloat vertices[]={
0.5f, 0.5f, 0.0f, // 右上角
0.5f, -0.5f, 0.0f, // 右下角
-0.5f, 0.5f, 0.0f, // 左上角
-0.5f, -0.5f, 0.0f, // 左下角
};
你是不是会感到疑惑?——明明我们要绘制两个三角形,不应该是六个顶点,怎么只有四个呢?
因为我们会绘制的两个三角形要拼成一个四边形,而一个四边形其实只需要四个顶点就可以了,因此我们借助图元GL_TRIANGLE_STRIP来绘制两个三角形。为什么要这么做是因为这些顶点数据是需要从CPU传送到GPU中,这个传输过程非常非常非常缓慢,所以我们要尽可能的缩减传输数据的数量,GPU绘图能绘制出非常复杂的图形就是因为这些细节方面可以优化的很好。
还有就是为什么不使用GL_QUADS图元,是因为这个图元在部分版本的OpenGL中无法使用,为了兼容,我们最好不要去使用这个图元。
接下来还需要一个着色器程序,着色器使用着色器语言GLSL(OpenGL Shading Language)编写代码,编译着色器之后,再链接着色器程序,我们就可以在程序中使用它了
顶点着色器
顶点着色器(Vertex Shader)是几个可编程着色器中的一个。如果我们打算做渲染的话,现代OpenGL需要我们至少设置一个顶点和一个片段着色器。我们会简要介绍一下着色器以及配置两个非常简单的着色器来绘制我们第一个三角形,下面你会看到一个非常基础的GLSL顶点着色器的源代码:
#version 330 core
in vec3 aPos;
void main()
{
gl_Position = vec4(aPos,1.0);
}
可以看到,GLSL看起来很像C语言。每个着色器都起始于一个版本声明。OpenGL 3.3以及和更高版本中,GLSL版本号和OpenGL的版本是匹配的(比如说GLSL 420版本对应于OpenGL 4.2)。我们同样明确表示我们会使用核心模式。
下一步,使用in关键字,在顶点着色器中声明所有的输入顶点属性(Input Vertex Attribute)。现在我们只关心位置(Position)数据,所以我们只需要一个顶点属性。GLSL有一个向量数据类型,它包含1到4个float分量,包含的数量可以从它的后缀数字看出来。由于每个顶点都有一个3D坐标,我们就创建一个vec3输入变量aPos。
向量(Vector)
在图形编程中我们经常会使用向量这个数学概念,因为它简明地表达了任意空间中的位置和方向,并且它有非常有用的数学属性。在GLSL中一个向量有最多4个分量,每个分量值都代表空间中的一个坐标,它们可以通过vec.x、vec.y、vec.z和vec.w来获取。注意vec.w分量不是用作表达空间中的位置的(我们处理的是3D不是4D),而是用在所谓透视除法(Perspective Division)上。我们会在后面的教程中更详细地讨论向量。
为了设置顶点着色器的输出,我们必须把位置数据赋值给预定义的gl_Position变量,它在幕后是vec4类型的。在main函数的最后,我们将gl_Position设置的值会成为该顶点着色器的输出。由于我们的输入是一个3分量的向量,我们必须把它转换为4分量的。我们可以把vec3的数据作为vec4构造器的参数,同时把w分量设置为1.0f(我们会在后面解释为什么要这样设置)。
当前这个顶点着色器可能是我们能想到的最简单的顶点着色器了,因为我们对输入数据什么都没有处理就把它传到着色器的输出了。在真实的程序里输入数据通常都不是标准化设备坐标,所以我们首先必须先把它们转换至OpenGL的可视区域内。
片段着色器(Fragment Shader)
片段着色器是第二个也是最后一个我们打算创建的用于渲染三角形的着色器。片段着色器所做的是计算像素最后的颜色输出。为了让事情更简单,我们的片段着色器将会一直输出橘黄色。
在计算机图形中颜色被表示为有4个元素的数组:红色、绿色、蓝色和alpha(透明度)分量,通常缩写为RGBA。当在OpenGL或GLSL中定义一个颜色的时候,我们把颜色每个分量的强度设置在0.0到1.0之间。比如说我们设置红为1.0f,绿为1.0f,我们会得到两个颜色的混合色,即黄色。这三种颜色分量的不同调配可以生成超过1600万种不同的颜色!
#version 330 core
void main(void)
{
gl_FragColor = vec4(1.0,0.0,1.0,1.0);
}
片段着色器只需要一个输出变量,这个变量是一个4分量向量,它表示的是最终的输出颜色,我们应该自己将其计算出来。我下面,我们将一个alpha值为1.0(1.0代表完全不透明)的紫色(vec4)赋值给颜色输出。
着色器程序(Shader Program)
之前我们把图形渲染管线比作是一个工厂流水线,着色器就相当于流水线中的加工设备,那么着色器程序是什么?着色器程序是一系列着色器的组合,打个比方:假如一条流水线是用来生产饮料的,现在我们想把它改装成生成牛奶,只需要把流水线中的加工设备给换成加工牛奶的就行,生产饮料的那一堆加工设备,我们叫做一个“加工程序”,而生产牛奶的,则是另一个“加工程序”,而着色器程序存在的目的就是为了把绘制某种特定图形的着色器集成到一起,这样当我们需要绘制其他图形时,只需要切换着色器程序即可。
因此,我们还需要创建一个着色器程序(QOpenGLShaderProgram),把之前的两个着色器附加进去,编译之后再进行链接,之后我们就能进行使用了。
注:看完上述的描述,你可能对着色器的认识并不全面,不要担心,之后我会单独用一个章节来全面地讲解着色器的知识。
视频节点
QOpenGL入门教程
操作说明
- 首先我们创建了一个qrc文件,利用qrc文件可以直接在项目中引用着色器文件
- 之后我们创建着色器文件和着色器程序,并将着色器文件附加(附加时会进行编译)到着色器程序中并进行链接
第四步:万事俱备,只欠东风,开始绘制图形
绘制图形一般需要进行以下步骤:
- 绑定着色器程序
- 设置顶点数据的数据来源(一般是调用QOpenGLShaderProgram::setAttribute开头一些方法,有setAttributeValue(装载一个数据),setAttributeArray(装载一组数据),setAttributeBuffer(从缓存区装载一组数据))
- 调用draw函数(glDraw开头的一些函数,常用的有glDrawArrays(通过数值绘制),glDrawElements(通过索引进行绘制)等)
初学我们采用的方法是QOpenGLShaderProgram::setAttributeArray + glDrawArrays来进行绘制,或许你可能已经了解过VAO,VBO,EBO等一下缓存对象,但这初学者来说可能有些难以理解,并且容易产生误区,之后我们会有一个单独的章节来熟悉这些缓存对象。
视频节点
QOpenGL入门教程
操作说明
- shaderProgram.enableAttributeArray("aPos") 是开启着色器程序的 “aPos” 属性,因为可能存在某些 需要关闭的 应用环境,因此OpenGL默认这个操作需要进行手动设置
- shaderProgram.setAttributeArray("aPos",vertices,3) 设置顶点数据的数据来源:从vertices数组中读取,且每三个数据作为一个顶点数据(被解析为vec3)
QSurfaceFormat——关于opengl窗口的一些配置
格式包括颜色缓冲区的大小(红色,绿色和蓝色)。alpha缓冲区的大小;深度和模板缓冲区的大小;以及用于多采样的每个像素的采样数。此外,该格式包含表面配置参数,例如OpenGL配置文件和用于渲染的版本,是否启用立体声缓冲区以及交换行为。
注意:对上下文或窗口格式问题进行故障排除时,启用日志记录类别可能会有所帮助qt.qpa.gl
。根据平台的不同,当涉及到OpenGL初始化以及QSurfaceFormat映射到的本机视觉或帧缓冲区配置时,这可能会打印有用的调试信息。
实例,在构造函数中修改format
Widget::Widget(QWidget *parent)
: QOpenGLWidget(parent)
{
QSurfaceFormat format;
format.setAlphaBufferSize(24); //设置alpha缓冲大小
format.setVersion(3,3); //设置版本号
format.setSamples(10); //设置重采样次数,用于反走样
//...
this->setFormat(format);
}