个人
OpenGL
专题学习目录,但愿和你们一块儿学习交流进步!编程
- OpenGL学习(一)-- 术语了解
- OpenGL学习(二)-- Xcode 搭建 OpenGL 环境
- OpenGL学习(三)-- OpenGL 基础渲染
- OpenGL学习(四)-- 正面&背面剔除和深度测试
- OpenGL学习(五)-- 裁剪与混合
- OpenGL学习(六)-- 基础纹理
- OpenGL学习(七)-- 基础变化综合练习实践总结
- OpenGL学习(八)-- OpenGL ES 初探(上)
- OpenGL学习(九)-- OpenGL ES 初探(下)GLKit
- OpenGL学习(十)-- 着色语言 GLSL 语法介绍
- OpenGL学习(十一)-- 用 GLSL 实现加载图片
- OpenGL学习(十二)-- OpenGL ES 纹理翻转的策略对比
物理世界中,视域内的颜色会发生快速的变化。你能够看到不少物体表面都会呈现出丰富的颜色,而且在狭小的面积上产生多彩的变化。要捕捉细节如此丰富的色彩变化是很是辛苦和缜密的工做(你须要有效地辨别每一个线性色彩变化区域中的每一个三角形)。若是能找到一张图片,而后把它“粘”到物体表面上,就像贴墙纸同样,那就简单多了。这就是 纹理映射(texture mapping)。 纹理贴图(简称纹理) 是经过拍摄或者绘制的一张图片,OpenGL 支持一维、二维、三维、立方体映射纹理。以及缓存纹理,同时还支持数组纹理。数组
像素包装缓存
图像存储空间 = 图像的高度 * 图像宽度 * 每一个像素的字节数app
咱们可使用下列函数改变或者恢复像素的存储方式:函数
void glPixelStorei(GLenum pname,GLint param);
post
void glPixelStoref(GLenum pname,GLfloat param);
性能
这俩函数用途是同样的,只不过函数名一个是 i 结尾,一个是 f 结尾,区别只是第二个参数的类型,i 的是 GLint,f 的是 GLfloat。 举例来讲,若是咱们想要改为紧密包装像素数据,就这样调用:学习
glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
复制代码
参数 1:GL_UNPACK_ALIGNMENT
,指定 OpenGL 如何从数据缓存 区中解包图像数据。测试
参数 2: 针对 GL_UNPACK_ALIGNMENT
设置的值 相似的,咱们可使用 GL_PACK_ALIGNMENT
来告诉 OpenGL 如何将像素缓冲区中读取并放置到一个用户指定的内存缓冲区的数据进行包装。网站
咱们没法直接将一个像素图绘制到颜色缓冲区中,但可使用下面的函数将将颜色缓存区的内容做为像素图直接读取: 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,指向图形数据的指针
void glReadBuffer(GLenum mode);
—> 指定读取的缓存
void glWriteBuffer(GLenum mode);
—> 指定写⼊的缓存
表 1-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 | 每一个像素包含一个深度值和一个模板值 |
最后 3 个格式 GL_STENCIL_INDEX
、GL_DEPTH_COMPONENT
和 GL_DEPTH_STENCIL
用于对模板缓冲区和深度缓冲区直接进行读写。 参数 type
解释参数 pixels
指向的数据,它告诉 OpenGL 使用缓冲区的什么数据类型来存储颜色份量。以下表1-2。 表1-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 个 OpenGL 函数最常常用来从存储器缓冲区中载入(好比从一个磁盘文件中读取)纹理数据。
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);
复制代码
这三个函数其实是由同一个函数 glTexImage
派生出来的。
参数说明
- 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 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);
复制代码
这俩函数的操做相似于 glTexImage,但在这里 x 和 y 在颜色缓冲区中指定了开始读取纹理数据的位置。源缓冲区是经过 glReadBuffer 函数设置的。请注意,并不存在 glCopyTexImage3D,由于咱们没法从 2D 颜色缓冲区获取体积数据。
替换一个纹理图像经常要比直接使用 glTexImage 从新加载一个新纹理快得多。用于完成这个任务的函数就是 glTexSubImage,它一样有3 个变型。
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);
复制代码
绝大部分参数都与 glTexImage 函数的参数准确地对应。xOffset、yOffset 和 zOffset 参数指定了在原来的纹理贴图中开始替换纹理数据的偏移量。width、height 和 depth 参数指定了“插入”到原来那个纹理中的新纹理的宽度、高度和深度。
下面这组函数容许咱们从颜色缓冲区读取纹理,并插入或替换原来纹理的一部分。下面这组函数都是 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
函数,在一个三维纹理中使用颜色缓冲区的数据来设置它的一个纹理单元平面。
void glGenTextures (GLsizei n, GLuint *textures);
复制代码
在这个函数中,咱们能够指定纹理对象的 数量 n 和一个指针 *textures,这个指针指向一个无符号整型数组(由纹理对象标识符填充)。
咱们能够把他们当作是不一样的可用纹理状态的句柄。为了“绑定”其中一种纹理状态,能够调用下面这个函数。此后,全部的纹理加载和纹理参数设置只影响当前绑定的纹理对象。
void glBindTexture (GLenum target, GLuint texture);
复制代码
- 参数 target: GL_TEXTURE_1D、GL_TEXTURE_2D、GL_TEXTURE_3D
- 参数 texture: 须要绑定的纹理对象
// Load a .TGA file
GLbyte *gltReadTGABits(const char *szFileName, GLint *iWidth, GLint *iHeight, GLint *iComponents, GLenum *eFormat, GLbyte *pData = NULL);
复制代码
- 参数 szFileName: 纹理文件名称
- 参数 iWidth: 文件宽度地址
- 参数 iHeight: 文件高度地址
- 参数 iComponents: 文件组件地址
- 参数 eFormat: 文件格式地址
- 返回值: 指向图像数据的指针
void glDeleteTextures (GLsizei n, const GLuint *textures);
复制代码
GLboolean glIsTexture(GLuint texture) 复制代码
若是这是一个之前已经分配的纹理对象名,则返回 GL_TRUE
,不然返回 GL_FALSE
。
不少参数的应用都会影响渲染的规则和纹理贴图的行为。这些纹理参数都是经过 glTexParameter 函数的变量进行设置的。
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);
复制代码
- 参数1:target,指定这些参数将要应⽤在哪一个纹理模式上,⽐如 GL_TEXTURE_1D、GL_TEXTURE_2D、GL_TEXTURE_3D。
- 参数2:pname,指定了须要设置哪一个纹理参数
- 参数3:param 或 params,用于设置特定的纹理参数的值
根据一个拉伸或收缩的纹理贴图计算颜色片断的过程称为 纹理过滤(Texture Filitering)。使用 OpenGL 的纹理参数函数,能够同时设置放大和缩小过滤器。这两种过滤器的参数名分别是 GL_TEXTURE_MAG_FILTER
和 GL_TEXTURE_MIN_FILTER
。咱们能够为它们从两种基本的纹理过滤器 GL_NEAREST
和 GL_LINEAR
中进行选择,它们分别对应于 邻近过滤 和 线性过滤。
GL_NEAREST
):邻近过滤是把最邻近的纹理单元应用到纹理坐标中。如图1,左上角那个纹理像素的中心距离纹理坐标最近,因此它会被选择为样本颜色:
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);
复制代码
正常状况下,咱们在 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 | 超出的坐标为用户指定的边缘颜色。 |
当纹理坐标超出默认范围时,每一个选项有不一样的效果。以下图:
设置纹理参数:
glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_WRAR_S,GL_CLAMP_TO_EDGE);
复制代码
- 参数1:纹理维度。
GL_TEXTURE_1D、GL_TEXTURE_2D、GL_TEXTURE_3D
- 参数2:为S/T坐标设置模式。
GL_TEXTURE_WRAP_S、GL_TEXTURE_T、GL_TEXTURE_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 范围以外的只使⽤边界纹理单元。边界纹理单元是做为围绕基本图像的额外的行和列,并与基本纹理图像⼀起加载的。
下面是综合前面的函数的一个例子:
// 将TGA文件加载为2D纹理。
bool LoadTGATexture(const char *szFileName, GLenum minFilter, GLenum magFilter, GLenum wrapMode) {
GLbyte *pBits;
int nWidth, nHeight, nComponents;
GLenum eFormat;
//一、读纹理位,读取像素
//参数1:纹理文件名称
//参数2:文件宽度地址
//参数3:文件高度地址
//参数4:文件组件地址
//参数5:文件格式地址
//返回值:pBits,指向图像数据的指针
pBits = gltReadTGABits(szFileName, &nWidth, &nHeight, &nComponents, &eFormat);
if(pBits == NULL)
return false;
//二、设置纹理参数
//参数1:纹理维度
//参数2:为S/T坐标设置模式
//参数3:wrapMode,环绕模式
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, wrapMode);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, wrapMode);
//参数1:纹理维度
//参数2:线性过滤
//参数3:wrapMode,环绕模式
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, minFilter);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, magFilter);
//三、精密包装像素数据
//参数1:GL_UNPACK_ALIGNMENT,指定OpenGL如何从数据缓存区中解包图像数据
//参数2:针对GL_UNPACK_ALIGNMENT 设置的值
glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
//载入纹理
//参数1:纹理维度
//参数2:mip贴图层次
//参数3:纹理单元存储的颜色成分(从读取像素图是得到)
//参数4:加载纹理宽
//参数5:加载纹理高
//参数6:加载纹理的深度
//参数7:像素数据的数据类型(GL_UNSIGNED_BYTE,每一个颜色份量都是一个8位无符号整数)
//参数8:指向纹理图像数据的指针
glTexImage2D(GL_TEXTURE_2D, 0, nComponents, nWidth, nHeight, 0,
eFormat, GL_UNSIGNED_BYTE, pBits);
//使用完毕释放pBits
free(pBits);
//只有minFilter 等于如下四种模式,才能够生成Mip贴图
//GL_NEAREST_MIPMAP_NEAREST具备很是好的性能,而且闪烁现象很是弱
//GL_LINEAR_MIPMAP_NEAREST经常用于对游戏进行加速,它使用了高质量的线性过滤器
//GL_LINEAR_MIPMAP_LINEAR 和GL_NEAREST_MIPMAP_LINEAR 过滤器在Mip层之间执行了一些额外的插值,以消除他们之间的过滤痕迹。
//GL_LINEAR_MIPMAP_LINEAR 三线性Mip贴图。纹理过滤的黄金准则,具备最高的精度。
if(minFilter == GL_LINEAR_MIPMAP_LINEAR ||
minFilter == GL_LINEAR_MIPMAP_NEAREST ||
minFilter == GL_NEAREST_MIPMAP_LINEAR ||
minFilter == GL_NEAREST_MIPMAP_NEAREST)
//加载Mip,纹理生成全部的Mip层
//参数:GL_TEXTURE_1D、GL_TEXTURE_2D、GL_TEXTURE_3D
glGenerateMipmap(GL_TEXTURE_2D);
return true;
}
复制代码
以上的总结参考了并部分摘抄了如下文章,很是感谢如下做者的分享:
一、OpenGl的学习网站 LearnOpenGL-CN 其中的“纹理”一节
二、《OpenGL超级宝典 第5版》
三、《OpenGL编程指南(原书第9版)》
转载请备注原文出处,不得用于商业传播——凡几多