一. 了解纹理
现实生活中,纹理最通常的作用是装饰我们的物体模型,它就像是贴纸一样贴在物体表面,使得物体表面拥有图案。纹理可以简单理解为图片,在使用 OpenGL 绘图时为了使得场景更加逼真开发者在渲染图形时需要在其编码上填充图片,而这里所使用的图片就是常说的纹理,但在 OpenGL 中我们习惯称之为纹理而不是图片。实际上在 OpenGL 中,纹理的作用不仅限于此,它可以用来存储大量的数据,一个典型的例子就是利用纹理存储地形信息。
图像存储空间 = 图像的高度 * 图像的宽度 * 每个像素所占的字节大小
二. 使用纹理
2.1 读取文件
-
从颜色缓冲区内容作为像素图读取纹理
void glReadPixel(GLint x, GLint y, GLSizei width, GLSizei height, GLenum format, GLenum type, const void * pixels); 参数 1:x,矩形左晓娇的窗口坐标 x 值 参数 2:y,矩形左晓娇的窗口坐标 y 值 参数 3:width,矩形的宽,以像素为单位 参数 4:height,矩形的高,以像素为单位 参数 5:format,OpenGL 的像素格式 参数 6:type,像素数据的数据类型,解释参数 pixels 指向的数据,告诉 OpenGL使用缓冲区中的什么数据类型来存储颜色分量 参数 7:pixels,指向图形数据的指针 -
从 tga 文件读取纹理
GLbyte* gltReadTGABits(const char *szFileName, GLint *iWidth, GLint *iHeight, GLint *iComponents, GLenum *eFormat, GLbyte *pData = NULL); 参数 1:tga 纹理文件名称 参数 2:文件宽度地址 参数 3:文件高度地址 参数 4:文件组件地址 参数 5:文件格式地址 返回值:pData, 指向图像数据的指针
2.2 载入纹理
void glTexImage2D(GLenum target, GLint level, GLint internalformat, GLSizei width, GLSizei height, GLint border, GLenum format, GLenum type, void * data);
参数1:纹理维度,GL_TEXTURE_2D
参数2:mip贴图层次
参数3:纹理单元存储的颜色成分(从读取像素图中获得)
参数4:加载纹理宽度
参数5:加载纹理的高度
参数6:加载纹理的深度
参数7:像素数据的数据类型,GL_UNSIGNED_BYTE无符号整型
参数8:指向纹理图像数据的指针
2.3 生成纹理对象
-
生成纹理对象
void glGenTextures(GLSizei n, GLuint * textures); 参数1:纹理对象的数量 参数2:纹理对象标识数组/单个纹理 id 的地址使用示例: // 生成单个纹理 GLuint textureID; glGenTextures(1, &textureID); // 生成多个纹理 #define TEXTURE_COUNT 3 GLuint textures[TEXTURE_COUNT]; glGenTextures(TEXTURE_COUNT, textures); -
绑定文理状态
void glBindTexture(GLenum target, GLuint texture); 参数1:纹理模式,GL_TEXTURE_1D,GL_TEXTURE_2D,GL_TEXTURE_3D, 一般我们开发中用 GL_TEXTURE_2D 参数2:需要绑定的纹理对象 // 使用示例: glBindTexture(GL_TEXTURE_2D, textureID); -
删除绑定的纹理对象
void glDeleteTextures(GLSizei n, GLuint * textures); 参数1:纹理对象的数量 参数2:纹理对象标识数组/单个纹理 id 的地址 // 使用示例: glDeleteTextures(1, &textureID); -
测试纹理对象是否有效
GLBoolean glIsTexture(GLuint texture) 参数 1:纹理 id
OpenGL 默认的纹理坐标左下角(0,0),右下角(1,0),右上角(1,1),左上角(0,1)
三. 设置纹理参数
void GLAPIENTRY glTexParameterf (GLenum target, GLenum pname, GLfloat param);
void GLAPIENTRY glTexParameterfv (GLenum target, GLenum pname, const GLfloat *params);
void GLAPIENTRY glTexParameteri (GLenum target, GLenum pname, GLint param);
void GLAPIENTRY glTexParameteriv (GLenum target, GLenum pname, const GLint *params);
参数 1:target,指定这些参数将要应用在哪个纹理模式上面,比如 GL_TEXTURE_2D
参数 2:pname,指定需要设置哪个纹理参数
参数 3:param,设定特定的纹理参数的值
3.1 设置放大/缩小过滤方式
//GL_TEXTURE_MAG_FILTER(放大过滤器,GL_LINEAR(线性过滤)
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
//GL_TEXTURE_MIN_FILTER(缩小过滤器),GL_NEAREST(邻近过滤)
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
一般推荐放大过滤设置为线性过滤,缩小过滤设置为临近过滤
3.2 设置环绕方式
环绕模式强制对范围之外的纹理坐标沿着合法的纹理单元的最后一行或一列进行采样。
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE);
参数 1:GL_TEXTURE_1D,GL_TEXTURE_2D,GL_TEXTURE_3D
参数 2:GL_TEXTURE_WRAP_S,GL_TEXTURE_WRAP_T,GL_TEXTURE_WRAP_R,其中 S, T, R 分别对应3D笛卡尔坐标系的x,y,z。
参数 3:GL_REPEAT, GL_MIRRORED_REPEAT, GL_CLAMP_TO_EDGE,
| 环绕方式 | 描述 |
|---|---|
| GL_REPEAT | 对纹理的默认行为。重复纹理图像 |
| GL_MIRRORED_REPEAT | 和GL_REPEAT一样,但每次重复图片是镜像放置的 |
| GL_CLAMP_TO_EDGE | 纹理坐标会被约束在0到1之间,超出的部分会重复纹理坐标的边缘,产生一种边缘被拉伸的效果 |
| GL_CLAMP_TO_BORDER | 超出的坐标为用户指定的边缘颜色 |
四. 设置 Mipmap
Mipmap 是一个功能强大的纹理技术,它可以提高渲染的性能以及提升场景的视觉质量。它可以用来解决使用一般的纹理贴图会出现的两个常见的问题:
- 闪烁,当屏幕上被渲染物体的表面与它所应用的纹理图像相比显得非常小时,就会出现闪烁。尤其当相机和物体在移动的时候,这种负面效果更容易被看到。
- 性能问题。加载了大量的纹理数据之后,还要对其进行过滤处理(缩小),在屏幕上显示的只是一小部分。纹理越大,所造成的性能影响就越大。
Mipmap 就可以解决上面那两个问题。当加载纹理的时候,不单单是加载一个纹理,而是加载一系列从大到小的纹理当 mipmapped 纹理状态中。然后 OpenGL 会根据给定的几何图像的大小选择最合适的纹理。Mipmap 是把纹理按照 2 的倍数进行缩放,直到图像为1x1的大小,然后把这些图都存储起来,当要使用的就选择一个合适的图像。这会增加一些额外的内存。在正方形的纹理贴图中使用 mipmap 技术,大概要比原先多出三分之一的内存空间。但是随着硬件的提升,牺牲一点内存空间来换取更好的显示效果还是值得的!
mipmap 有多少个层级是有 glTexImage 的第二个参数 level 决定的。层级从0开始,0,1,2,3这样递增。如果没有使用 mipmap 技术,只有第0层的纹理会被加载。在默认情况下,为了使用mipmap,所有层级都会被加载。但我们可以通过纹理参数来控制要加载的层级范围,使用glTexParameteri(), 第二个参数为 GL_TEXTURE_BASE_LEVEL 来指定最低层级的 level,第二个参数为 GL_TEXTURE_MAX_LEVEL 指定最高层级的 level。例如我只需要加载0到4层级的纹理:
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_BASE_LEVEL, 0);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, 4);
除此之外,我们还可通过GL_TEXTURE_MIN_LOD和GL_TEXTURE_MAX_LOD来限制纹理的使用范围(最底层和最高层)。
只有在缩小模式下面才可以使用 mip 贴图。
if (minFilter == GL_LINEAR_MIPMAP_NEAREST ||
minFilter == GL_LINEAR_MIPMAP_LINEAR ||
minFilter == GL_NEAREST_MIPMAP_LINEAR ||
minFilter == GL_NEAREST_MIPMAP_NEAREST) {
// 只有在缩小模式下面才可以使用 mip 贴图
glGenerateMipmap(GL_TEXTURE_2D);
}
4.1 Mipmap 过滤
Mipmap的纹理过滤模式如下表:
| 常量 | 描述 |
|---|---|
| GL_NEAREST | 在mip基层上使用最邻近过滤 |
| GL_LINEAR | 在mip基层上使用线性过滤 |
| GL_NEAREST_MIPMAP_NEAREST | 选择最邻近的mip层,并使用最邻近过滤 |
| GL_NEAREST_MIPMAP_LINEAR | 在mip层之间使用线性插值和最邻近过滤 |
| GL_LINEAR_MIPMAP_NEAREST | 选择最邻近的mip层,使用线性过滤 |
| GL_LINEAR_MIPMAP_LINEAR | 在mip层之间使用线性插值和使用线性过滤,又称三线性mipmap |
如果纹理过滤选择为GL_NEAREST或GL_LINEAR模式,那么只有基层的纹理会被加载,其他的纹理将会被忽略。我们必须指定其中一个mipmap过滤器,这样才能使用所有已加载的纹理。这个mipmap过滤器的常量是GL_FILTER_MIPMAP_SELECTOR的形式。其中FILTER指定了过滤模式,SELECTOR指定了如何选择mipmap层。例如GL_NEAREST_MIPMAP_LINEAR模式,它的SELECTOR是GL_LINEAR,它会在两个最邻近的mip层中执行线性插值,然后得出的结果又由被选择的过滤器GL_NEAREST进行过滤。
其中GL_NEAREST_MIPMAP_NEAAREST具有很好的性能,也能够解决闪烁的问题,但在视觉效果上会比较差。其中GL_LINEAR_MIPMAP_NEAREST常用于游戏加速,使用了质量较高的线性过滤,和快速的选择的方式(最邻近方式)。
使用最邻近的方式作为mipmap选择器的效果依然不能令人满意。从某一个角度去看,常常可以看到物体表面从一个mip层到另一个mip层的转变。GL_LINEAR_MIPMAP_LINEAR和GL_NEAREST_MIPMAP_LINEAR过滤器在mip层之间执行一些额外的线性插值,以消除不同层之间的变换痕迹,但也需要一些额外的性能开销。GL_LINEAR_MIPMAP_LINEAR具有最高的精度。