纹理
纹理的意义
实际上,如果我们规定死每个顶点的颜色属性,那么其实也可以呈现出丰富的画面,但是往往一个面上的像素点有成千上万个,尚且不说这么做会消耗大量的内存,光是将这些点渲染出来就会耗费大量的时间。所以如果我们可以将一张图片和一个面上的点一一映射起来,这样不就可以把图片“粘”在物体的面上了吗,因此这也便是纹理(Texture)的由来。
纹理映射
和之前的三角颜色板一样,我们只要规定好顶点的颜色(坐标),其余中间的点变会根据顶点自动进行差值运算。所以我们只需要规定好每个顶点的纹理坐标。
- 纹理坐标:以2D纹理为例,纹理坐标有x、y两个轴(或者说是s、t轴),范围是在(0,1)之间。确定好顶点的纹理坐标后,中间点的纹理直接可以通过插值来得出。
- 纹理过滤:如果此时纹理图片非常大图形很小又或者是图形很大而纹理图案很小将会怎么样呢?openGL本身会自适应大小,即是讲纹理放缩到和图形同样大小。在这个过程中会出现这样一个现象:纹理坐标和像素没有关系,它只是由片段着色器插值出来的一个(0,1)的坐标,由坐标映射到图片上的时候,往往不是一一对应到每个像素点,也就是说它很可能落在几个像素块之间,这时候要怎么办?于是便引申出来了两种近似方法:邻近过滤和线性过滤。
邻近过滤顾名思义是讲离纹理坐标最近的像素快中心作为这一点的像素颜色,而线性过滤则是根据纹理坐标周围的像素块进行差值,(通常是四块,图形学里叫做双线性差值),然后将结果作为改纹理坐标点颜色。两种方式的结果也显而易见,由于都是通过周围点的信息来代替该点的信息,因此该点综合周围点的信息越多,图片效果也会越清晰,或者说越平滑。
- 多级渐远纹理(MipMap):在一个复杂场景中,物体的远近会影响物体视觉上的大小,如果物体很小,甚至只有几百个像素点时,那么原来的纹理就会显得很大,选取点就会十分困难,而此时如果一个纹理可以分成像素不同的多份,然后根据物体的远近自动切换相应像素点纹理,这样不就可以解决了,这也便是MipMap的功能。但是这里还有一个问题,就是如果刚好是处在两个级别之间应该如果处理呢?其实还是可以借用之前处理纹理过滤的方法,即是NEAREST和LINEAR,为此OpenGL为MipMap提供了四种不同的过滤方式:GL_NEAREST_MIPMAP_NEAREST、GL_LINEAR_MIPMAP_NEAREST、GL_NEAREST_MIPMAP_LINEAR和GL_LINEAR_MIPMAP_LINEAR。
- 纹理环绕:纹理坐标是(0,1),但是如果我们把纹理坐标放大到(0,1)之外呢?openGL默认会对纹理进行重复,即复制一份然后接上去,当然还有其他的复制样式:GL_REPEAT(直接进行重复)、GL_MIRRORED_REPEAT(镜像复制)、GL_CLAMP_TO_EDGE(超出部分会重复纹理的边缘,呈现出一种拉升的效果)、GL_CLAMP_TO_BORDER(超出部分会变成指定颜色,呈现出一种边框的效果)。
纹理设置
和之前的VAO、VBO和EBO一样texture也需要先申明Texture,然后再将数据绑定到上面去,这也体现了OpenGL状态机的特性。
- 创建纹理:也是用
glGenXXX
开头的语句,glGenTextures
来创建。 - 设置属性:和VBO一样,纹理也有很多属性,但是在设置这些属性之前,还应当先绑定
glBindTexture
,然后可以设置纹理的复制方式,也就是纹理环绕,glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_WRAP_S,GL_REPEAT)
和glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_WRAP_T,GL_REPEAT)
因为S和T坐标都要设置,当然如果是三维纹理那么还有R坐标。之后便是纹理过滤,即是NEAREST还是LINEAR,glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR)
,注意到属性的设置都是glTexParameter
开头,这也是一个重构函数,后面也可以跟i、f等这样的参数类型后缀。 - 绑定数据:这里通过
stb_image
头文件将png的数据导入stbi_load("/path/to/image",&width,&height,&nrChannels,0)
,其中width、height和nrChannels分别表示图片的长、宽还有颜色通道数。讲图片数据导入后,便要和纹理进行绑定glTexImage2D(GL_TEXTURE_2D,0,GL_RGB,width,height,0,GL_RGB,GL_UNSIGNED_BYTE,data)
,其中data
就是图片的数据,以二进制的形式导入。
着色器应用
纹理最后如何参与着色器工作?这是在片段着色器中实现的,在片段着色器中声明一个sampler2D
类型的变量,这个类型的变量可以拿到纹理的数据,之后用纹理和纹理坐标进行融合,边可以得到像素最后的颜色texture(myTexture,myTexcoord)
。这个变量还可以赋值,值是表示第几个纹理,一般openGL可以允许同时存在16个纹理,分别是TEXTURE0
~TEXTURE15
,而赋值则用之前提到过的glUniformXXX
即可。
最后全部代码可以在此查看。