OpenGL(9)——纹理

585 阅读9分钟

1.认识函数

1.1 像素存储方式

当我们在向OpenGL提交图像数据或者从OpenGL获取数据图像时,OpenGL需要知道我们想要在内存中对数据进行怎样的包装或解包操作。可以使用下面的函数改变和恢复像素存储方式:

void glPixelStorei(GLenum pname,GLint param);
void glPixelStoref(GLenum pname,GLfloat param);

//例: glPixelStorei(GL_UNPACK_ALIGNMENT,1);
//参数1:GL_UNPACK_ALIGNMENT 指定OpenGL 如何从数据缓存区中解包图像数据
//参数2:表示参数GL_UNPACK_ALIGNMENT 设置的值为1

1.2 从颜色缓冲区读取像素图

我们无法将一个像素图绘制到颜色缓冲区中,但是可以使用下面的函数将颜色缓冲区的内容做为像素图直接读取。

void glReadPixels(GLint x, GLint y, 
                  GLSizei width,GLSizei height, 
                  GLenum format, GLenum type,
                  const void * pixels);
                  
//参数1:x,矩形左下角的窗⼝坐标
//参数2:y,矩形左下角的窗口坐标
//参数3:width,矩形的宽,以像素为单位     
//参数4:height,矩形的⾼,以像素为单位   
//参数5:format,OpenGL 的像素格式 详见下表1.1
//参数6:type,解释参数pixels指向的数据,告诉OpenGL 使用缓存区中的什么 数据类型来存储颜色分量,像素数据的数据类型 详见下表1.2
//参数7:pixels,指向图形数据的指针

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 每个像素包含了一个整数形式的红⾊、蓝⾊、绿⾊、Alpah分量
GL_BGR_INTEGER 每个像素包含了一个整数形式的蓝⾊、绿⾊、红色分量
GL_BGRA_INTEGER 每个像素包含了一个整数形式的蓝⾊、绿⾊、红色、Alpah分量
GL_STENCIL_INDEX 每个像素只包含了一个模板值
GL_DEPTH_COMPONENT 每个像素只包含一个深度值
GL_DEPTH_STENCIL 每个像素包含一个深度值和一个模板值

像素数据的数据类型

常量 说明
GL_UNSIGNED_BYTE 每种颜色分量都是一个8位无符号整数
GL_BYTE 8位有符号整数
GL_UNSIGNED_SHORT 16位无符号整数
GL_SHORT 16位有符号整数
CL_UNSIGNED_INT 32位无符号整数
GL_INT 32位有符号整数
GL_FLOAT 单精度浮点数
GL_HALF_FLOAT 半精度浮点数
GL_UNSIGNED_BYTE_3_2_3 包装的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 包装的RGB值
GL_UNSIGNED_SHORT_4_4_4_4_REV 包装的RGB值
GL_UNSIGNED_SHORT_5_5_5_1 包装的RGB值
GL_UNSIGNED_SHORT_1_5_5_5_REV 包装的RGB值
GL_UNSIGNED_INT_8_8_8_8 包装的RGB值
GL_UNSIGNED_INT_8_8_8_8_REV 包装的RGB值
GL_UNSIGNED_INT_10_10_10_2 包装的RGB值
GL_UNSIGNED_INT_2_10_10_10_REV 包装的RGB值
GL_UNSIGNED_INT_24_8 包装的RGB值
GL_UNSIGNED_INT_10F_11F_REV 包装的RGB值
GL_FLOAT_24_UNSIGNED_INT_24_8_REV 包装的RGB值

1.3 载入纹理

void glTexImage1D(GLenum target,GLint level,GLint
     internalformat,GLsizei width,GLint border,GLenum
     format,GLenum type,void *data);
void glTexImage2D(GLenum target,GLint level,GLint
     internalformat,GLsizei width,GLsizei height,GLint
     border,GLenum format,GLenum type,void * data);
void glTexImage3D(GLenum target,GLint level,GLint 
     internalformat,GLSizei width,GLsizei height,GLsizei depth,
     GLint border,GLenum format,GLenum type,void *data);
  • target:GL_TEXTURE_1DGL_TEXTURE_2DGL_TEXTURE_3D
  • Level:指定所加载的mip贴图层次。一般我们都把这个参数设置为0。
  • internalformat:每个纹理单元中存储多少颜⾊成分。详见下表1.3
  • width、height、depth参数:指加载纹理理的宽度、高度、深度。注意!这些值必须是2的整数次方。(这是因为OpenGL 旧版本上的遗留下的一个要求。当然现在已经可以支持不是 2的整次方。但是开发者们还是习惯使用以2的整数次方去设置这些参数。)
  • border参数:允许为纹理贴图指定一个边界宽度。
  • format、type、data参数:与我们在讲glReadPixels 函数对应的参数相同

常见的纹理内部格式表1.3

常量 含义
GL_ALPHA 按照alpha值存储纹理单元
GL_LUMINANCE 按照亮度值值存储纹理单元
GL_LUMINANCE_ALPHA 按照亮度值和alpha值存储纹理单元
GL_RGB 按照红绿蓝成分存储纹理单元
GL_RGBA 按照红绿蓝成分和alpha值存储纹理单元

1.4 替换纹理

如果我们不在需要一个已加载的纹理,它可以被全部替换,也可以被替换一部分。替换一个纹理图像比直接重新加载一个新纹理快得多。

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);

绝大部分参数都与前面的glTexIamge相同,xOffset yOffsetzOffset参数指定了在原来的纹理贴图中开始替换纹理数据的偏移量。width height depth参数指定了插入到原来那个纹理中的新纹理的宽度高度和深度。

1.5 插入替换纹理

下面的函数允许我们从颜色缓冲区读取纹理,并插入或替换原来纹理的一部分。

void glCopyTexSubImage1D(GLenum target,GLint level,
                    GLint xoffset,GLint x,
                    GLint y,GLsize width);
void glCopyTexSubImage2D(GLenum target,GLint level,
                    GLint xoffset,GLint yOffset,
                    GLint x, GLinty, GLsizei width,
                    GLsizei height);
void glCopyTexSubImage3D(GLenum target,GLint level,
                    GLint xoffset,GLint yOffset,
                    GLint zOffset,GLint x,GLint y,
                    GLsizei width,GLsizei height);

1.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);

x,y 在颜色缓存区中指定了开始读取纹理数据的位置;缓存区里的数据,是源缓存区通过glReadBuffer设置的。
注意:不存在glCopyTextImage3D ,因为我们无法从2D 颜色缓存区中获取体积数据。

2.纹理对象

纹理图像本身就是所谓的纹理状态的一部分。纹理状态包含了纹理图像本身和一组纹理参数,这些参数控制过滤和纹理坐标的行为。

2.1 分配纹理对象

纹理状态是由当前绑定的纹理对象维护的,而纹理对象是由一个无符号整数标识的。

void glGenTextures(GLsizei n,GLuint * textTures);
//指定纹理对象的数量 和 指针(指针指向一个⽆符号整形数组,由纹理对象标识符填充)。

2.2 绑定纹理对象

target参数必须是GL_TEXTURE_1D、GL_TEXTURE_2D、GL_TEXTURE_3D,texture是需要绑定的纹理对象。

void glBindTexture(GLenum target,GLunit texture);

2.3 删除纹理对象

纹理理对象 以及 纹理对象针(指针指向一个无符号整形数组,由纹理对象标识符填充)。

void glDeleteTextures(GLsizei n,GLuint *textures);

2.4 测试纹理对象有效

如果texture是⼀个已经分配空间的纹理对象,那么这个函数会返回GL_TRUE,否则会返回GL_FALSE

GLboolean glIsTexture(GLuint texture);

3.纹理坐标

纹理坐标是作为0.0到1.0范围内的浮点值指定的。纹理坐标命名为s、t、r和q,与顶点坐标x、y、y和相对应。 一个纹理坐标会在每个顶点上应用一个纹理,然后OpenGL根据需要对纹理进行放大或缩小,将纹理贴在几何图像上。如下图所示

还有一种比较常见的情形,比如一个正方形纹理贴到一个三角形上,需要设置好相对的纹理坐标。

4. 纹理参数

纹理参数的设置会影响渲染的规则和纹理贴图的行为。这些纹理参数都是通过glTexParameteri函数的变量来设置的。

glTexParameterf(GLenum target,GLenum pname,GLFloat param);
glTexParameteri(GLenum target,GLenum pname,GLint param);
glTexParameterfv(GLenum target,GLenum pname,GLFloat *param);
glTexParameteriv(GLenum target,GLenum pname,GLint *param);

参数1:target,指定这些参数将要应用在那个纹理模式上,
      比如GL_TEXTURE_1D、GL_TEXTURE_2D、GL_TEXTURE_3D。
参数2:pname,指定需要设置哪个纹理参数
参数3:param,设置指定的纹理参数的值

4.1 设置过滤方式

纹理图像中的纹理的单元和屏幕上的像素几乎不会形成一一对应的关系。因此,当纹理应用于几何图形的表面时,纹理对象不是被拉伸就是被收缩。
当一个拉伸或收缩的纹理贴图计算颜色片段的过程成为纹理过滤(Texture Fililtering)
使用glTexParameteri纹理参数函数可以设置放大和缩小过滤器。我们还可以为这两个过滤器选择过滤方式。从邻近过滤(GL_NEAREST)线性过滤(GL_LINEAR) 中选择一中过滤方式。 代码示例:

glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_NEAREST);

glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR);

临近过滤中,纹理坐标位于哪个纹理单元,这个纹理单元的颜色就作为这个片段的纹理颜色。而线性过滤会把这个纹理坐标周围的纹理单元的加权平均值应用到这个纹理坐标上(现行插值)。

两种过滤方式放大时的效果。 临近过滤方式在拉伸的特别大时,会出现斑驳的像素块。线性过滤在拉伸的特别大时,会出现模糊效果。显然线性过滤效果更好一些,但将造成一些额外的开销。在当今高性能的硬件上,这些额外开销可以忽略不计。

4.2 设置环绕方式

正常情况下, 我们在0-1氛围内指定纹理坐标,使它与纹理贴图中的纹理单元形成映射关系。如果纹理坐标坐落于这个范围之外,OpenGL根据当前设置的环绕模式来解决这个问题。我们可以调'glTexParameteri函数来设置环绕模式.

glTextParameteri(GL_TEXTURE_2D,GL_TEXTURE_WRAR_S,GL_CLAMP_TO_EDGE);
glTextParameteri(GL_TEXTURE_2D,GL_TEXTURE_WRAR_T,GL_CLAMP_TO_EDGE);
glTextParameteri(GL_TEXTURE_2D,GL_TEXTURE_WRAR_R,GL_CLAMP_TO_EDGE);

参数1:GL_TEXTURE_1D、GL_TEXTURE_2D、GL_TEXTURE_3D
参数2:GL_TEXTURE_WRAP_S、GL_TEXTURE_T、GL_TEXTURE_R,针对s,t,r坐标
参数3:GL_REPEAT、GL_CLAMP、GL_CLAMP_TO_EDGE、GL_CLAMP_TO_BORDER
  GL_REPEAT:OpenGL 在纹理理坐标超过1.0的方向上对纹理进行重复;
  GL_CLAMP:所需的纹理单元取⾃纹理理边界或TEXTURE_BORDER_COLOR.
  GL_CLAMP_TO_EDGE:强制对范围之外的纹理坐标沿着合法的纹理单元的最后一⾏行或者最后一列来进行采样。
  GL_CLAMP_TO_BORDER:在纹理坐标在0.0到1.0范围之外的只使⽤边界纹理单元。边界纹理单元是作为围绕基本图像的额外的⾏和列,并与基本纹理图像一起加载的。