1. 前言
理解纹理之前,需要理解两个概念:
- Fragment;
- Fragment 的片段插值;
官方描述如下:


总结:
- 一个 Fragment 对应一个像素,只不过 Fragment 是一个数据模型,其中的数据提供给 Fragment Shader 最终渲染出这个 Pixel 的 RGBA;
- 片段插值对每个属性都其作用,不仅仅是颜色。这个功能可以帮助开发者节省大量的工作,比如减少输入的顶点数量、减少输入的颜色等;
- 纹理坐标的个数最开始只和顶点数量一致,但是片段插值之后,每个 Fragment 都对应着有一个纹理坐标,从而可以在纹理中取出对应的像素色值(后文会讲);
2. 纹理的概念
我们已经了解到,我们可以为每个顶点添加颜色来增加图形的细节,从而创建出有趣的图像。但是,如果想让图形看起来更真实,我们就必须有足够多的顶点,从而指定足够多的颜色。虽然片段着色器会根据顶点和顶点输入的属性进行片段插值的操作,但是仍然满足不了很多场景。

此时,纹理的概念就出现了。
纹理是一个2D图片(甚至也有1D和3D的纹理),它可以用来添加物体的细节,纹理类似于一张贴纸一样,可以直接贴在图元上。因为我们可以在一张图片上插入非常多的细节,这样就可以让物体非常精细而不用指定额外的顶点。

除了图像以外,纹理也可以被用来储存大量的数据,这些数据可以发送到着色器上,但是这不是我们现在的主题。
3. 纹理坐标
纹理坐标是针对纹理而言的,对于 2D 纹理而言,纹理坐标在 x 和 y 轴上,范围为 0 到 1 之间,纹理坐标起始于(0, 0),也就是纹理图片的左下角,终于(1, 1),即纹理图片的右上角。其坐标系如下:

为了能够把纹理映射(Map)到三角形上(顶点/图元),我们需要指定三角形的每个顶点各自对应纹理的哪个部分。这样每个顶点就会关联着一个纹理坐标(Texture Coordinate),用来标明该从纹理图像的哪个部分采集片段颜色(采样,后文会将)。
三个顶点对应的纹理坐标确定之后,因为经过光栅化阶段之后会产生很多片段(Fragment)供片段着色器来渲染像素点最终的颜色。此时,片段着色器会根据三个顶点关联的纹理坐标进行采样。同时,还会根据三个顶点关联的纹理坐标来对每个 Fragment 的纹理坐标属性进行片段插值(Fragment Interpolation)。也就是为每个 Fragment 都计算并关联一个纹理坐标,再根据这个纹理坐标去纹理中根据一定的规则获取颜色(采样),最终计算出这个 Fragment 对应的 pixel 的 RGBA;
这个步骤的理解很重要,关乎到能否理解后面的线性采样和邻近采样为什么会产生不同的效果。
4. 环绕方式
环绕方式和我们平常接触到的平铺方式关联性很大。
纹理坐标的范围通常是从(0, 0)到(1, 1),那如果我们把纹理坐标设置在范围之外会发生什么?OpenGL 默认的行为是重复这个纹理图像(,但OpenGL提供了更多的选择:
GL_REPEAT
:对纹理的默认行为。重复纹理图像;GL_MIRRORED_REPEAT
:和GL_REPEAT
一样,但每次重复图片是镜像放置的;GL_CLAMP_TO_EDGE
:纹理坐标会被约束在 0 到 1 之间,超出的部分会重复纹理坐标的边缘,产生一种边缘被拉伸的效果;GL_CLAMP_TO_BORDER
:超出的坐标为用户指定的边缘颜色;
不同行为的效果如下:

这是方式如下:
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_MIRRORED_REPEAT);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_MIRRORED_REPEAT);
- 第一个参数:纹理目标
我们使用2D纹理,所以是GL_TEXTURE_2D - 第二个参数:纹理轴
- 第三个参数:该轴上的环绕方式
如果我们选择 GL_CLAMP_TO_BORDER
选项,我们还需要指定一个边缘的颜色。这需要使用 glTexParameter
函数的 fv
后缀形式,用 GL_TEXTURE_BORDER_COLOR
作为它的选项,并且传递一个float数组作为边缘的颜色值:
float borderColor[] = { 1.0f, 1.0f, 0.0f, 1.0f };
glTexParameterfv(GL_TEXTURE_2D, GL_TEXTURE_BORDER_COLOR, borderColor);
5. 纹理过滤
纹理坐标相当于台球桌上框球的三脚架。当纹理坐标确定之后,这张图片上哪些像素点需要被映射到 OpenGL(或者说Buffer)上就确定了;
此时考虑,从选取像素数量的多少大概可以分为三种情况:
- 顶点坐标选取了很多像素点,也就是图片缩放比例很小:

- 顶点坐标选取了较多像素点,图片被稍微放大,展现部分更偏向细节:

- 顶点坐标选取了很少的像素点,图片被放的很大,展现部分更加粒子化: