Qt中使用OpenGL(三)
纹理
简单理解,纹理就是将 “一层皮” 套在了三维世界中的物体身上,物体身上的每个顶点都对应到了这层皮上的一个位置。
以下通过简单的例子解释纹理,展示一张风景画。
绘制矩形与设定纹理映射坐标
首先要绘制一个矩形。OpenGL 提供了多种绘制多个三角形的方法,其中有一个方法,使得我们只需要 逆时针 定义 4 个顶点,就可以绘制出可以组成矩形的两个三角形。
在屏幕坐标中,左上是 (-1, 1),左下是 (-1, -1),右下是 (1, -1),右上是 (1, 1)。按照这个顺序就可以定义四个顶点了。
1 | |
至此,画两个三角形的准备工作就完成了。然后需要纹理映射坐标,就是将每一个顶点,都对应到纹理上的一个位置。
简单来讲就是用(x,y)这个二维坐标来表示纹理上的一个位置,其中,x 与 y 的值域是 [0, 1]。
其中 [0, 0]表示左下角 ,[1, 1]表示右上角。了解了这些,就可以给顶点附加纹理坐标了,方法很简单,只需要在每个顶点坐标后面,附加两个代表纹理坐标的 float 值就行。
1 | |
此时,每个顶点就有 5 个 float 值了。
使用包含纹理的顶点缓存
很显然,之前使用了 setAttributeBuffer() 这个函数告诉了 OpenGL 每个顶点有 3 个值,现在同样需要使用 setAttributeBuffer() 这个函数告诉 OpenGL,现在每个顶点有 5 个值,其中前三个是顶点坐标,后两个是纹理坐标。
所以,首先要修改shader。
修改 shader
之前的 shader 里面,定义了 1 个 vec3 类型的输入,所以要再加一个 vec2 类型的输入
1 | |
在 Vertex shader 中,在获取坐标的同时也要获取纹理坐标,然后将纹理坐标发送给其他shader。
这里添加了一个输入变量 vTexture,还定义了一个输出变量 oTexture,因为 shader 是一种链式调用的代码,Vertex shader 会自动从顶点缓存中获取数据,可是 Fargment shader 不会,所以需要从顶点缓存中获取数据,然后将获取到的数据传给 Fragment shader。
1 | |
在 Fragment shader 中,要定义一个名字和 Vertex shader 中定义输出变量名字一致的输入变量 oTexture,Vertex shader 中的输出变量就会自动的传递到 Fragment shader 中的输入变量中。然后使用 OpenGL 内置的函数 texture() 来进行纹理映射了。
这里除了 in 和 out 之外还有另一个指示器 uniform,它和 in 一样,也表示一种输入,但是,和 in 不一样的是,它的来源可以是任何地方。而 in 的来源只能是顶点缓存或者其他 shader 的输出。可以在不修改顶点缓存的情况下,在任何合理地方输入 uniform 的值。例如可以根据需要,动态的改变纹理。sampler2D 这个类型表示二维纹理。
然后告诉 OpenGL 如何使用顶点缓存。
绑定坐标信息
1 | |
setAttributeBuffer():
- 第一个参数:表示 shader 中变量的名字
- 第二个参数:表示数据类型
- 第三个参数:表示在一个顶点中从哪个位置开始是需要的数据,坐标是 0 * sizeof(float),纹理是 3 * sizeof(float)
- 第四个参数:表示有几个数据,坐标有 3 个数据,纹理坐标有 2 个数据
- 第五个参数:表示顶点数据中多少数据表示一个顶点,在这里是 5 个 float 值表示一个顶点
绘制带纹理的矩形
加载纹理
在 Qt 中,OpenGL 的纹理使用 QOpenGLTexture 表示。
1 | |
只要在类的头文件中定义了 QOpenGLTexture *m_texture = nullptr; ,初始化的时候就可以创建纹理了。
在加载图片的时候,要使用 mirrored() 方法,它默认会将图片沿 y 轴进行镜像翻转。因为我们用 (0, 0)表示左下角,所以坐标和一般的图像坐标发生了镜像翻转,所以加载纹理的时候也要镜像翻转一下。
绑定纹理
加载好纹理后,要绑定一下,告诉 OpenGL 有纹理才可以使用。
在绘制前 bind 一下,绘制完毕 release 就可以了。
1 | |
绘制矩形的参数不是之前绘制三角形时的 GL_TRIANGLES,0,3, 而是 GL_TRIANGLE_FAN,0,4。其中 GL_TRIANGLE_FAN 就是另一种绘制三角形的方法,它允许我们逆时针定义四个顶点,然后绘制出来两个三角形组成一个矩形。
此时,虽然定义了 uniform sampler2D uTexture; 用来表示纹理,但是始终没有给这个输入变量赋值,但是 OpenGL 又是如何将加载的纹理传到 shader 中?
因为 OpenGL 的纹理系统有个默认设定,那就是使用纹理的时候(调用 bind 函数的时候),会被自动对应到当前激活的纹理,默认激活的纹理就是纹理 0,而我们第一个定义的纹理变量,就会被当作纹理 0。这样,使用的纹理就自动的和 shader 中定义的纹理对应上了。