写在前面的话
现实生活中,纹理最通常的作用是装饰我们的物体模型,它就像是贴纸一样贴在物体表面,使得物体表面拥有图案。但实际上在OpenGL中,纹理的作用不仅限于此,它可以用来存储大量的数据,一个典型的例子就是利用纹理存储地形信息。
纹理对象和参数
现代OpenGL中,纹理对象和顶点数组对象(VAO)及缓存对象(VBO)一样,需要调用glGenTextures函数生成。
同样的,纹理对象需要进行绑定。OpenGL中纹理可以分为1D,2D和3D纹理,我们在绑定纹理对象的时候需要指定纹理的种类。由于本文将以一张图片为例,因此我们为我们的纹理对象绑定一个GL_TEXTURE_2D的纹理。
纹理坐标
在OpenGL中,我们通常将纹理中的像素将按照纹理坐标进行编址,纹理坐标系是一个空间直角坐标系,横轴为S轴,纵轴为T轴,垂直于屏幕的坐标轴为R轴。在我们的2D纹理中,由于没有R轴,我们也可以将横轴称为U 纵轴称为V轴,也就是我们所说的UV坐标系。但和OpenGL坐标系所不同的是:纹理坐标系的(0,0)点位于纹理的左下角,而(1,1)点位于纹理的右上角。
采样
通过纹理坐标获取像素颜色信息的过程称为采样,而采样的结果会根据纹理参数设置的不同而千差万别。OpenGL中设置纹理参数的API接口为glTextureParameter,我们所有的纹理参数都由这个接口设置,下面我们介绍几种常用的纹理参数的配置。
Wrapping
纹理坐标的范围与OpenGL的屏幕坐标范围一样,是0-1。超出这一范围的坐标将被OpenGL根据GL_TEXTURE_WRAP参数的值进行处理:
GL_REPEAT: 超出纹理范围的坐标整数部分被忽略,形成重复效果。
GL_MIRRORED_REPEAT: 超出纹理范围的坐标整数部分被忽略,但当整数部分为奇数时进行取反,形成镜像效果。
GL_CLAMP_TO_EDGE:超出纹理范围的坐标被截取成0和1,形成纹理边缘延伸的效果。
GL_CLAMP_TO_BORDER: 超出纹理范围的部分被设置为边缘色。
这四种模式所产生的纹理效果如下:
我们可以为纹理坐标系中每条坐标轴设定不同的wrapping模式,例如这里我们将S和T轴的wrapping模式设定为GL_REPEAT:
需要注意的是,如果我们将wrapping模式设置为GL_CLAMP_TO_BORDER,我们需要单独设置另一属性----边界颜色,具体方法也是利用glTextureParameter方法,只不过将属性名称替换为GL_TEXTURE_BORDER_COLOR:
这两行代码执行后,纹理的边界颜色将被设置为红色。
过滤
由于纹理坐标和我们当前的屏幕分辨率是无关的,所以当我们为一个模型贴纹理时,往往会遇到纹理尺寸与模型尺寸不符的情况,这时,纹理会因为缩放而失真。处理这一失真的过程我们称为过滤,在OpenGL中我们有如下几种常用的过滤手段:
GL_NEAREST: 最临近过滤,获得最靠近纹理坐标点的像素。
GL_LINEAR: 线性插值过滤,获取坐标点附近4个像素的加权平均值。
GL_NEAREST_MIPMAP_NEAREST:用于mipmap,下节将详细介绍。
GL_LINEAR_MIPMAP_NEAREST:
GL_NEAREST_MIPMAP_LINEAR:
GL_LINEAR_MIPMAP_LINEAR:
在我们讨论mipmap之前,我们来看看前两种过滤算法在纹理图像面前会产生怎样的过滤效果(原始图片大小和模型大小相差16倍):
从这里我们可以看出,线性插值算法的过滤效果似乎更加平滑,但并不表示这种算法适合所有的场合,如果我们需要对图像进行马赛克处理或模拟一个8位游戏机的游戏画面时,最临近过滤算法是最好的选择。
我们可以单独为纹理缩放指定不同的过滤算法,这两种情况下纹理参数设置分别对应为:GL_TEXTURE_MIN_FILTER和GL_TEXTURE_MAG_FILTER.
Mipmaps
正如前面所提到的,还有一种过滤纹理的方法---mipmaps。如下图所示: Mipmaps 将纹理按照1/2的比例缩小并生成副本,以此类推直到纹理只有1x1大小(这里是纹理坐标,不是真实像素大小)。采样时我们根据当前缩小比率来选择合适大小的纹理,按照线性过滤或最邻近过滤来采样。比如,我们要用做贴图的纹理大小为 64x32,对它做下采样生成32x16,16x8,8x4,4x2,2x1,1x1的纹理,如果要渲染的区域大小为14x6,那么我们要么选16x8的纹理,要么选16x8与8x4的两块纹理做加权平均。
Mipmaps是一个功能强大的纹理技术,它可以提高渲染的性能以及提升场景的视觉质量。它可以用来解决使用一般的纹理贴图会出现的两个常见的问题:
1. 闪烁,当屏幕上被渲染区域与它所应用的纹理图像相比显得非常小时,就会出现闪烁。尤其当视口和物体在移动的时候,这种负面效果更容易被看到。
2. 性能问题。如果我们的贴纹理的区域离我们非常远,远到在屏幕中只有一个像素那么大小时,纹理的所有纹素都集中在这一像素中。这时,我们无论做邻近过滤还是做线性过滤时都不得不将纹理的所有纹素计算在内,这种计算效率将大大影响我们的采样效率,而纹理的数据量越大,对效率的影响就会更大。
使用Mipmaps技术就可以解决上面那两个问题。当加载纹理的同时预处理生成一系列从大到小的纹理,使用时只需选择合适大小的纹理加载就行了。这样虽然会增加一些额外的内存(一个正方形纹理将额外占用约30%的内存),但将大大提高了我们的采样效率和采样质量。
生成mipmaps的过程很简单,只需要在加载纹理后执行下面一行代码:
使用mipmaps也很简单,只需设置过滤参数为以下4种中的任意一种:
GL_NEAREST_MIPMAP_NEAREST:选择最邻近的mip层,并使用最邻近过滤。
GL_NEAREST_MIPMAP_LINEAR:对两个mip层使用最邻近过滤后的采样结果进行加权平均。
GL_LINEAR_MIPMAP_NEAREST:选择最邻近的mip层,使用线性插值算法进行过滤。
GL_LINEAR_MIPMAP_LINEAR:对两个mip层使用线性插值过滤后的采样结果进行加权平均,又称三线性mipmap。
在选择这几种过滤方法时,我们需要考虑的是效率和质量,线性过滤往往更加平滑,但随之而来的是更多的采样次数;而临近过滤减少了采样次数,但最终视觉效果会比较差。
到这里,我们的纹理单元已生成完毕,纹理参数也已配置就绪,在下一篇中,我们将继续讨论如何加载和使用纹理的问题。