OpenGL纹理API

一、认识纹理

现在我们能够通过计算颜色值对它们的表面进行着色,以及在它们之间进行插值操作来模拟光照效果。但是为了达到更加真实的效果,还有一种非常棒的途径,这就是纹理贴图(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 的是 GLintf 的是 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_1DGL_TEXTURE_2DGL_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 函数的参数准确地对应。xOffsetyOffsetzOffset 参数指定了在原来的纹理贴图中开始替换纹理数据的偏移量。widthheightdepth 参数指定了“插入”到原来那个纹理中的新纹理的宽度、高度和深度。

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_1DGL_TEXTURE_2DGL_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_1DGL_TEXTURE_2DGL_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_FILTERGL_TEXTURE_MIN_FILTER。我们可以为它们从两种基本的纹理过滤器 GL_NEARESTGL_LINEAR 中进行选择,它们分别对应于 邻近过滤线性过滤
1)邻近过滤(GL_NEAREST):
邻近过滤是把最邻近的纹理单元应用到纹理坐标中。如图1,左上角那个纹理像素的中心距离纹理坐标最近,所以它会被选择为样本颜色:

临近过滤.png

2)线性过滤(GL_LINEAR):
如图2,线性过滤会把这个纹理坐标周围的纹理单元的加权平均值应用到这个纹理坐标上(线性插值),一个纹理像素的中心距离纹理坐标越近,那么这个纹理像素的颜色对最终的样本颜色的贡献越大。下图中你可以看到返回的颜色是邻近像素的混合色:
线性过滤.png

两种纹理过滤方式的视觉效果,当在一个很大的物体上应用一张低分辨率的纹理时(纹理被放大了,每个纹理像素都能看到):
GL_NEAREST、GL_LINEAR

我们可以使用下面这个函数进行过滤:

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.01.0 的范围内指定纹理坐标,使它与纹理贴图中的纹理单元形成映射关系。如果纹理坐标落在这个范围之外,OpenGL 则根据当前纹理环绕模式(Wrapping Mode)处理这个问题。

环绕方式(Wrapping) 描述
GL_REPEAT 对纹理的默认行为。重复纹理图像。
GL_MIRRORED_REPEAT 和GL_REPEAT一样,但每次重复图片是镜像放置的。
GL_CLAMP_TO_EDGE 纹理坐标会被约束在0到1之间,超出的部分会重复纹理坐标的边缘,产生一种边缘被拉伸的效果。
GL_CLAMP_TO_BORDER 超出的坐标为用户指定的边缘颜色。

当纹理坐标超出默认范围时,每个选项有不同的效果。如下图:

纹理环绕方式.png

设置纹理参数:

参数说明

  • 参数1:纹理维度。 GL_TEXTURE_1DGL_TEXTURE_2DGL_TEXTURE_3D
  • 参数2:为S/T坐标设置模式。 GL_TEXTURE_WRAP_SGL_TEXTURE_WRAP_TGL_TEXTURE_WRAP_R,针对 s,t,r 坐标
  • 参数3:wrapMode,环绕模式:GL_REPEATGL_CLAMPGL_CLAMP_TO_EDGEGL_CLAMP_TO_BORDER
    (1) GL_REPEATOpenGL 在纹理坐标超过 1.0 的⽅向上对纹理理进⾏重复;
    (2) GL_CLAMP: 所需的纹理单元取自纹理边界或 TEXTURE_BORDER_COLOR.
    (3) GL_CLAMP_TO_EDGE: 环绕模式强制对范围之外的纹理坐标沿着合法的纹理单元的最后一⾏或者最后一
    列来进行采样。
    (4) GL_CLAMP_TO_BORDER: 在纹理坐标在 0.01.0 范围之外的只使⽤边界纹理单元。边界纹理单元是作为围绕基本图像的额外的行和列,并与基本纹理图像⼀起加载的。
glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_WRAR_S,GL_CLAMP_TO_EDGE);
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 205,033评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,725评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,473评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,846评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,848评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,691评论 1 282
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,053评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,700评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 42,856评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,676评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,787评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,430评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,034评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,990评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,218评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,174评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,526评论 2 343