一、认识纹理
现在我们能够通过计算颜色值对它们的表面进行着色,以及在它们之间进行插值操作来模拟光照效果。但是为了达到更加真实的效果,还有一种非常棒的途径,这就是纹理贴图(texture mapping)。纹理只是一种能够应用到场景中的三角形上的图像数据,它通过经过过滤的纹理单元(textel,相当于基于纹理的像素)填充到实心区域。图片(也可以称之为纹理)内容可以是任何东西,但是他们一般都是一些比如砖,叶子,地面等的图案,纹理贴图增加了场景的真实性。例如,对比下面的两幅图片。
二、纹理API
像素包装
图像存储空间 = 图像的高度 * 图像宽度 * 每个像素的字节数
1. 改变、恢复像素存储⽅方式
参数说明
- pname:
GL_UNPACK_ALIGNMENT
指定OpenGL
如何从数据缓存区中解包图像- param:表示参数
GL_UNPACK_ALIGNMENT
设置的值,允许设置为
1: byte排列
2: 排列为偶数byte的⾏
4: 字word排列
8: 行从双字节边界开始
void glPixelStorei(GLenum pname,GLint param);
void glPixelStoref(GLenum pname,GLfloat param);
这俩函数用途是一样的,只不过函数名一个是 i 结尾,一个是 f 结尾,区别只是第二个参数的类型,i 的是 GLint
,f 的是 GLfloat
。
2. 从颜色缓存区内容作为像素图直接读取
参数说明
- x:矩形左下角的窗⼝坐标
- y:矩形左下⻆的窗口坐标
- width:矩形的宽,以像素为单位
- height:矩形的⾼,以像素为单位
- format:OpenGL 的像素格式,参考 表2-1
- type:解释参数
pixels
指向的数据,告诉OpenGL
使⽤用缓存区中的什么,数据类型来存储颜色分量,像素数据的数据类型 。参考 表2-2- pixels:指向图形数据的指针
void glReadPixels(GLint x,GLint y,GLSizei width,GLSizei
height, GLenum format, GLenum type,const void * pixels);
表 2-1 OpenGL 像素格式
常量 | 描述 |
---|---|
GL_RGB | 描述红、绿、蓝顺序排列的颜色 |
GL_RGBA | 按照红、绿、蓝、Alpha顺序排列的颜色 |
GL_BGR | 按照蓝、绿、红顺序排列颜色 |
GL_BGRA | 按照蓝、绿、红、Alpha顺序排列颜色 |
GL_RED | 每个像素只包含了一个红色分量 |
GL_GREEN | 每个像素只包含了一个绿色分量 |
GL_BLUE | 每个像素只包含了一个蓝色分量 |
GL_RG | 每个像素依次包含了一个红色和绿色的分量 |
GL_RED_INTEGER | 每个像素包含了一个整数形式的红色分量 |
GL_GREEN_INTEGER | 每个像素包含了一个整数形式的绿色分量 |
GL_BLUE_INTEGER | 每个像素包含了一个整数形式的蓝色分量 |
GL_RG_INTEGER | 每个像素依次包含了一个整数形式的红色、绿色分量 |
GL_RGB_INTEGER | 每个像素包含了一个整数形式的红色、蓝色、绿色分量 |
GL_RGBA_INTEGER | 每个像素包含了一个整数形式的红色、蓝色、绿色、Alpha分量 |
GL_BGR_INTEGER | 每个像素包含了一个整数形式的蓝色、绿色、红色分量 |
GL_BGRA_INTEGER | 每个像素包含了一个整数形式的蓝色、绿色、红色、Aplha分量 |
GL_STENCIL_INDEX | 每个像素只包含一个模板值 |
GL_DEPTH_COMPONENT | 每个像素只包含一个深度值 |
GL_DEPTH_STENCIL | 每个像素包含一个深度值和一个模板值 |
表2-2 像素数据的数据类型
常量 | 描述 |
---|---|
GL_UNSIGNED_BYTE | 每种颜色分量都是一个 8 位无符号整数 |
GL_BYTE | 8 位有符号整数 |
GL_UNSIGNED_SHORT | 16 位无符号整数 |
GL_SHORT | 16 位有符号整数 |
GL_UNSIGNED_INT | 32 位无符号整数 |
GL_INT | 32 位有符号整数 |
GL_FLOAT | 单精度浮点数 |
GL_HALF_FLOAT | 半精度浮点数 |
GL_UNSIGNED_BYTE_3_2_2 | 包装的 RGB 值 |
GL_UNSIGNED_BYTE_2_3_3_REV | 包装的 RGB 值 |
GL_UNSIGNED_SHORT_5_6_5 | 包装的 RGB 值 |
GL_UNSIGNED_SHORT_5_6_5_REV | 包装的 RGB 值 |
GL_UNSIGNED_SHORT_4_4_4_4 | 包装的 RGBA 值 |
GL_UNSIGNED_SHORT_4_4_4_4_REV | 包装的 RGBA 值 |
GL_UNSIGNED_SHORT_5_5_5_1 | 包装的 RGBA 值 |
GL_UNSIGNED_SHORT_5_5_5_1_REV | 包装的 RGBA 值 |
GL_UNSIGNED_INT_8_8_8_8 | 包装的 RGBA 值 |
GL_UNSIGNED_INT_8_8_8_8_REV | 包装的 RGBA 值 |
GL_UNSIGNED_INT_10_10_10_2 | 包装的 RGBA 值 |
GL_UNSIGNED_INT_2_10_10_10_REV | 包装的 RGBA 值 |
GL_UNSIGNED_INT_24_8 | 包装的 RGBA 值 |
GL_UNSIGNED_INT_10F_11F_11F_REV | 包装的 RGBA 值 |
GL_FLOAT_32_UNSIGNED_INT_24_8_REV | 包装的 RGBA 值 |
3. 载⼊纹理
参数说明
- target: 纹理维度
GL_TEXTURE_1D
、GL_TEXTURE_2D
、GL_TEXTURE_3D
。- Level: 指定所加载的mip贴图层次。⼀一般我们都把这个参数设置为 0。
- internalformat: 每个纹理单元中存储多少颜色成分。(从读取像素图时获得)
- width、height、depth 参数: 指加载纹理的宽度、⾼度、深度。注意! 这些值必须是 2 的整数次方。(这是因为
OpenGL
旧版本上的遗留下的⼀个要求。当然现在已经可以⽀持不是 2 的整数次方。但是开发者们还是习惯使⽤用以 2 的整数次方去设置这些参数。)- border 参数: 允许为纹理理贴图指定⼀一个边界宽度。
- format 参数: 像素数据的数据类型(
GL_UNSIGNED_BYTE
,每个颜色分量都是一个 8 位无符号整数)- type 参数:
- data 参数: 指向纹理图像数据的指针
void glTexImage1D (GLenum target, GLint level, GLint internalformat, GLsizei width, GLint border, GLenum format, GLenum type, const GLvoid *pixels);
void glTexImage2D (GLenum target, GLint level, GLint internalformat, GLsizei width, GLsizei height, GLint border, GLenum format, GLenum type, const GLvoid *pixels);
void glTexImage3D (GLenum target, GLint level, GLint internalformat, GLsizei width, GLsizei height, GLsizei depth, GLint border, GLenum format, GLenum type, const GLvoid *pixels);
4. 更新纹理
更新一个纹理图像常常要比直接使用 glTexImage
重新加载一个新纹理快得多。用于完成这个任务的函数就是 glTexSubImage
,它同样有3 个变型。
参数说明
绝大部分参数都与glTexImage
函数的参数准确地对应。xOffset、yOffset 和 zOffset 参数指定了在原来的纹理贴图中开始替换纹理数据的偏移量。width、height 和 depth 参数指定了“插入”到原来那个纹理中的新纹理的宽度、高度和深度。
void glTexSubImage1D(GLenum target,GLint level,GLint xOffset,GLsizei width,GLenum
format,GLenum type,const GLvoid *data);
void glTexSubImage2D(GLenum target,GLint level,GLint xOffset,GLint yOffset,GLsizei
width,GLsizei height,GLenum format,GLenum type,const GLvoid *data);
void glTexSubImage3D(GLenum target,GLint level,GLint xOffset,GLint yOffset,GLint
zOffset,GLsizei width,GLsizei height,GLsizei depth,Glenum type,const GLvoid * data);
5. 插入替换纹理
下面这组函数允许我们从颜色缓冲区读取纹理,并插入或替换原来纹理的一部分。下面这组函数都是 glCopyTexSubImage
函数的变型。
void glTexSubImage1D(GLenum target,GLint level,GLint xOffset,GLsizei width,GLenum
format,GLenum type,const GLvoid *data);
void glTexSubImage2D(GLenum target,GLint level,GLint xOffset,GLint yOffset,GLsizei
width,GLsizei height,GLenum format,GLenum type,const GLvoid *data);
void glTexSubImage3D(GLenum target,GLint level,GLint xOffset,GLint yOffset,GLint
zOffset,GLsizei width,GLsizei height,GLsizei depth,Glenum type,const GLvoid * data);
注意:
这里并没有glCopyTexImage
函数。这是因为颜色缓冲区是 2D 的,不存在一种对应的方法来将一副 2D 彩色图像作为一个 3D 纹理的来源。但是,我们可以使用glCopyTexSubImage3D
函数,在一个三维纹理中使用颜色缓冲区的数据来设置它的一个纹理单元平面。
6. 使⽤颜⾊缓存区加载数据,形成新的纹理使用
一维和二维纹理也可以从颜色缓冲区加载数据。我们可以从颜色缓冲区读取一幅图像,并通过下面这两个函数将它作为一个新的纹理使用
void glCopyTexImage1D(GLenum target,GLint level,GLenum
internalformt,GLint x,GLint y,GLsizei width,GLint border);
void glCopyTexImage2D(GLenum target,GLint level,GLenum
internalformt,GLint x,GLint y,GLsizei width,GLsizei
height,GLint border);
7、纹理对象
1)分配纹理对象
参数说明
在这个函数中,我们可以指定纹理对象的 数量 n 和一个指针 *textures,这个指针指向一个无符号整型数组(由纹理对象标识符填充)。
void glGenTextures (GLsizei n, GLuint *textures);
2)绑定纹理状态
我们可以把他们看成是不同的可用纹理状态的句柄。为了“绑定”其中一种纹理状态,可以调用下面这个函数。此后,所有的纹理加载和纹理参数设置只影响当前绑定的纹理对象。
参数说明
- target:
GL_TEXTURE_1D
、GL_TEXTURE_2D
、GL_TEXTURE_3D
- texture: 需要绑定的纹理对象
void glBindTexture (GLenum target, GLuint texture);
3)读纹理位,读取像素
参数说明
- szFileName: 纹理文件名称
- iWidth: 文件宽度地址
- iHeight: 文件高度地址
- iComponents: 文件组件地址
- eFormat: 文件格式地址
- pData:返回值,指向图像数据的指针
// Load a .TGA file
GLbyte *gltReadTGABits(const char *szFileName, GLint *iWidth, GLint *iHeight, GLint *iComponents, GLenum *eFormat, GLbyte *pData = NULL);
4)删除绑定的纹理对象
参数说明
- n: 纹理理对象
- textures: 纹理理对象指针(指针指向⼀一个⽆无符号整形数组,由纹理理对象标识符填充)。
void glDeleteTextures (GLsizei n, const GLuint *textures);
5)测试纹理对象是否有效
如果texture是一个已经分配空间的纹理对象,那么这个函数会返回
GL_TRUE
,否则会返回GL_FALSE
。
GLboolean glIsTexture(GLuint texture)
8. 设置纹理参数
很多参数的应用都会影响渲染的规则和纹理贴图的行为。这些纹理参数都是通过 glTexParameter 函数的变量进行设置的。
参数说明
- target,指定这些参数将要应⽤在哪个纹理模式上,⽐如
GL_TEXTURE_1D
、GL_TEXTURE_2D
、GL_TEXTURE_3D
。- pname,指定了需要设置哪个纹理参数
- param 或 params,用于设置特定的纹理参数的值
void glTexParameterf (GLenum target, GLenum pname, GLfloat param);
void glTexParameterfv (GLenum target, GLenum pname, const GLfloat *params);
void glTexParameteri (GLenum target, GLenum pname, GLint param);
void glTexParameteriv (GLenum target, GLenum pname, const GLint *params);
8.1、设置过滤方式
根据一个拉伸或收缩的纹理贴图计算颜色片段的过程称为纹理过滤(Texture Filitering)。使用OpenGL
的纹理参数函数,可以同时设置放大和缩小过滤器。这两种过滤器的参数名分别是GL_TEXTURE_MAG_FILTER
和 GL_TEXTURE_MIN_FILTER
。我们可以为它们从两种基本的纹理过滤器 GL_NEAREST
和 GL_LINEAR
中进行选择,它们分别对应于 邻近过滤 和 线性过滤。
1)邻近过滤(GL_NEAREST):
邻近过滤是把最邻近的纹理单元应用到纹理坐标中。如图1,左上角那个纹理像素的中心距离纹理坐标最近,所以它会被选择为样本颜色:
2)线性过滤(GL_LINEAR):
如图2,线性过滤会把这个纹理坐标周围的纹理单元的加权平均值应用到这个纹理坐标上(线性插值),一个纹理像素的中心距离纹理坐标越近,那么这个纹理像素的颜色对最终的样本颜色的贡献越大。下图中你可以看到返回的颜色是邻近像素的混合色:
两种纹理过滤方式的视觉效果,当在一个很大的物体上应用一张低分辨率的纹理时(纹理被放大了,每个纹理像素都能看到):
我们可以使用下面这个函数进行过滤:
void glTexParameteri (GLenum target, GLenum pname, GLint param);
四种组合方式的过滤:
glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_HEAREST);
glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_HEAREST);
glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR);
8.2、设置环绕方式
正常情况下,我们在 0.0 到 1.0 的范围内指定纹理坐标,使它与纹理贴图中的纹理单元形成映射关系。如果纹理坐标落在这个范围之外,OpenGL
则根据当前纹理环绕模式(Wrapping Mode)处理这个问题。
环绕方式(Wrapping) | 描述 |
---|---|
GL_REPEAT | 对纹理的默认行为。重复纹理图像。 |
GL_MIRRORED_REPEAT | 和GL_REPEAT一样,但每次重复图片是镜像放置的。 |
GL_CLAMP_TO_EDGE | 纹理坐标会被约束在0到1之间,超出的部分会重复纹理坐标的边缘,产生一种边缘被拉伸的效果。 |
GL_CLAMP_TO_BORDER | 超出的坐标为用户指定的边缘颜色。 |
当纹理坐标超出默认范围时,每个选项有不同的效果。如下图:
设置纹理参数:
参数说明
- 参数1:纹理维度。
GL_TEXTURE_1D
、GL_TEXTURE_2D
、GL_TEXTURE_3D
- 参数2:为S/T坐标设置模式。
GL_TEXTURE_WRAP_S
、GL_TEXTURE_WRAP_T
、GL_TEXTURE_WRAP_R
,针对 s,t,r 坐标- 参数3:wrapMode,环绕模式:
GL_REPEAT
、GL_CLAMP
、GL_CLAMP_TO_EDGE
、GL_CLAMP_TO_BORDER
(1)GL_REPEAT
:OpenGL
在纹理坐标超过 1.0 的⽅向上对纹理理进⾏重复;
(2)GL_CLAMP
: 所需的纹理单元取自纹理边界或TEXTURE_BORDER_COLOR
.
(3)GL_CLAMP_TO_EDGE
: 环绕模式强制对范围之外的纹理坐标沿着合法的纹理单元的最后一⾏或者最后一
列来进行采样。
(4)GL_CLAMP_TO_BORDER
: 在纹理坐标在 0.0 到 1.0 范围之外的只使⽤边界纹理单元。边界纹理单元是作为围绕基本图像的额外的行和列,并与基本纹理图像⼀起加载的。
glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_WRAR_S,GL_CLAMP_TO_EDGE);