音视频开发:OpenGL + OpenGL ES + Metal 系列文章汇总
纹理可以对物体进行包装,以显示不同图案,本文开始认识纹理,并掌握如何设置纹理
主要内容:
1、纹理认识
2、纹理的设置
3、纹理坐标
4、金字塔案例
1、纹理认识
- 纹理是一种图形数据,主要用于在屏幕上包装物体,就像新房装修,需要贴不同的墙纸,此时的墙纸就是我们所说的纹理。
- 纹理其实就是图片,只是在OpenGL中专业术语中称其为纹理。
- 图片在屏幕上的显示,最终都是解码成位图,然后进行显示的。
- 在OpenGL中纹理一般是TGA文件,在iOS开发中我们使用的OpenGL ES是可以直接使用png、jpg压缩图片作为纹理数据,并最终都会在底层被解码成位图,作为纹理来进行处理。
- 一个图形在帧缓存区中的存储空间的计算公式:存储空间=图像高度* 宽度 *一个像素的字节数
2、设置纹理
主要就是介绍一些纹理设置的API,这里只是先认识一下,具体的使用会在金字塔案例中详细说明。以后有需要用到直接在这里查找API即可。
2.1 纹理的使用(重点)
2.1.1 读取文件
API:
//参数1:x,矩形左下⻆的窗⼝x坐标
//参数2:y,矩形左下⻆的窗⼝y坐标
//参数3:width,矩形的宽,以像素为单位
//参数4:height,矩形的⾼,以像素为单位
//参数5:format,OpenGL 的像素格式
//参数6:type,解释参数pixels指向的数据,告诉OpenGL 使⽤缓存区中的什么 数据类型来存储颜⾊分量,像素数据的数据类型
//参数7:pixels,指向图形数据的指针,也就是返回的图形数据的值
void glReadPixels(GLint x,GLint y,GLSizei width,GLSizei height, GLenum format, GLenum type,const void * pixels);
glReadBuffer(mode);—> 指定读取的缓存
glWriteBuffer(mode);—> 指定写⼊入的缓存
参数5:format表示像素格式,下面是所有的像素格式,一般使用的也就是GL_RGB、GL_RGBA
参数6表示像素数据的数据类型,下面是所有的数据类型,常用的是无符号整型GL_UNSIGNED_BYTE
2.1.2 载入纹理
//target:指定纹理应用的纹理模式,一般为 GL_TEXTURE_2D
//`GL_TEXTURE_1D`
//`GL_TEXTURE_2D`
//`GL_TEXTURE_3D`
//Level:指定所加载的mip贴图层次。⼀般我们都把这个参数设置为0。
//internalformat:每个纹理单元中存储多少颜色成分。
//width、height、depth参数:指加载纹理的宽度、⾼度、深度。==注意!==这些值必须是 2的整次⽅。(这是因为OpenGL 旧版本上的遗留下的⼀个要求。当然现在已经可以支持不是 2的整数次方。但是开发者们还是习惯使⽤以2的整数次⽅去设置这些参数。)
//border参数:允许为纹理贴图指定⼀个边界宽度,如果不指定,可以直接写0。
//format参数:OpenGL 的像素格式
//type参数:解释参数pixels指向的数据,告诉OpenGL 使⽤缓存区中的什么 数据类型来存储颜⾊分量,像素数据的数据类型
//pixels参数:指向图形数据的指针
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);
我们平常用的就只是glTexImage2D,参数中的像素格式以及像素数据的数据格式不再赘言
2.1.3 设置纹理参数
API
区别仅在于最后参数的类型,以后用第一个就可以
//参数1:target,指定这些参数将要应⽤用在那个纹理理模式上,⽐比如GL_TEXTURE_1D、GL_TEXTURE_2D、GL_TEXTURE_3D。
//参数2:pname,指定需要设置那个纹理理参数,总共四个
//参数3:param,设定特定的纹理理参数的值
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);
target一般是GL_TEXTURE_2D
1、纹理的缩小/放大的过滤方式
过滤场景:
有两种,一种是放大(GL_TEXTURE_MAG_FILTER),一种是缩小(GL_TEXTURE_MIN_FILTER)
过滤类型:
主要使用的过滤类型有两种,邻近过滤(GL_NEAREST)和线性过滤(GL_LINEAR)
邻近过滤: 选择离当前位置最近的颜色
线性过滤: 所有颜色综合后的颜色,进行线性计算,类似于颜色混合
过滤方式对比:
其他过滤方式:
可以看出过滤方式中有MIP贴图
具体使用:
一般来说建议纹理缩小时使用邻近过滤,纹理放大时,使用线性过滤
//参数1:纹理维度
//参数2:过滤场景
//参数3:过滤方式的参数
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
2、x/y轴上的环绕方式
环绕方式
环绕方式是指当纹理坐标超出默认范围时,边缘的显示形式,环绕方式的设置主要是针对x/y轴设置的,在纹理中的描述不适用xy,而是使用s,t。
环绕方向:
有两种:GL_TEXTURE_WRAP_S,GL_TEXTURE_WRAP_T,分别代表x、y轴。
具体使用:
//参数1: 纹理应用的维度,一般设置的都是 GL_TEXTURE_2D
// GL_TEXTURE_1D:一维
// GL_TEXTURE_2D: 二维
// GL_TEXTURE_3D: 三维
//参数2: 纹理坐标,一般设置s,t即可
// GL_TEXTURE_WRAP_S: 对应坐标系中的x轴
// GL_TEXTURE_T: 对应坐标系中的y轴
// GL_TEXTURE_R: 对应坐标系中的z轴
//参数3:纹理环绕方式
// GL_REPEAT:OpenGL 在纹理坐标超过1.0的⽅方向上对纹理进行重复;
// GL_CLAMP:所需的纹理单元取⾃纹理边界或TEXTURE_BORDER_COLOR.
// GL_CLAMP_TO_EDGE环绕模式强制对范围之外的纹理坐标沿着合法的纹理单元的最后⼀行或者最后⼀列来进⾏采样。
// GL_CLAMP_TO_BORDER:在纹理理坐标在0.0到1.0范围之外的只使⽤边界纹理单元。边界纹理单元是作为围绕基本图像的额外的行和列,并与基本纹理图像⼀一起加载的。
glTextParameteri(GL_TEXTURE_2D,GL_TEXTURE_WRAR_S,GL_CLAMP_TO_EDGE);
glTextParameteri(GL_TEXTURE_2D,GL_TEXTURE_WRAR_T,GL_CLAMP_TO_EDGE);
2.1.4 绑定纹理状态
分配纹理标识符glGenTextures:
/*
指定纹理理对象的数量和指针(指针指向⼀一个⽆无符号整形数组,由纹理理对象标识符填充)。
GLsizei表示数量
GLuint*表示纹理对象数组指针
*/
void glGenTextures(GLsizei n,GLuint * textTures);
绑定纹理状态glBindTexture:
//参数target:GL_TEXTURE_1D、GL_TEXTURE_2D、GL_TEXTURE_3D
//参数texture:需要绑定的纹理理对象
void glBindTexture(GLenum target,GLunit texture);
删除绑定纹理glDeleteTextures:
//纹理对象个数 及 纹理对象指针(指针指向⼀一个⽆无符号整形数组,由纹理理对象标识符填充)。
void glDeleteTextures(GLsizei n,GLuint *textures);
测试绑定纹理是否有效
//如果texture是⼀个已经分配空间的纹理对象,那么这个函数会返回GL_TRUE,否则会返回GL_FALSE。
GLboolean glIsTexture(GLuint texture);
2.2 载入纹理其他API
更新纹理:
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);
插入替换纹理:
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,GLint y,GLsizei width,GLsizei height);
void glCopyTexSubImage3D(GLenum target,GLint level,GLint xoffset,GLint yOffset,GLint zOffset,GLint x,GLint y,GLsizei width,GLsizei height);
使用颜色缓冲区加载数据,形成新的纹理
函数中的x、y是在颜色缓存区中指定的开始读取纹理数据的位置。
缓冲区中的数据是通过源缓冲区glReadBuffer设置的。
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);
3、纹理坐标
纹理坐标是一种抽象概念,并不是真实的坐标。
1、比如物体在世界坐标系中的坐标是真实的,如果进行变换,坐标是会发生改变的。但是此时纹理坐标并不会发生改变。
2、纹理坐标无视物体的大小和方位。
3、纹理坐标可以代表一种映射关系,纹理贴到物体上时的一个投射,
如图:
特点:
1、范围是在0-1之间
2、坐标方位是这样的,左下角是(0,0),左上角是(0,1),右上角是(1,1),右下角是(1,0)
注:纹理坐标在贴到物体上时,可以以任意角度贴,但是坐标顺序不可以乱,这样会导致纹理图片交叉。
1、一个坐标的对角坐标要保持一致,比如(0,0)的对角线上纹理坐标不可以为(1,0)或(0,1),必须是(1,1)
2、一个坐标的相邻坐标可以相反,比如(0,0)的左上角可以为(1,0),也可以为(0,1).
4、金字塔案例
使用金字塔案例熟悉使用纹理API
案例地址:金字塔案例
效果:
4.1 简单介绍
代码主要实现:
给一个金字塔设置纹理
- 实现金字塔
- 使用法线让金字塔显示光照效果
- 给金字塔增加纹理
重点需要掌握内容:
-
纹理的设置过程
- 分配纹理
- 绑定纹理状态
- 加载纹理对象
- 解析纹理
- 设置纹理参数
- 载入纹理
纹理坐标的解析
法线的简单理解
4.2 纹理的设置
分配纹理对象和绑定纹理
//纹理变量,一般使用无符号整型
GLuint textureID;
//分配纹理对象 参数1:纹理对象个数,参数2:纹理对象指针
glGenTextures(1, &textureID);
//绑定纹理状态 参数1:纹理状态2D 参数2:纹理对象
glBindTexture(GL_TEXTURE_2D, textureID);
将纹理TAG文件载入到内存中
- 解析纹理文件获取纹理数据
- 设置纹理参数
- 载入纹理到内存中
//定义变量
GLbyte *pBits;//纹理数据
int nWidth, nHeight, nComponents;
GLenum eFormat;//像素格式
//1、解析纹理文件读取到纹理数据、宽高像素、组件、格式
//参数1:纹理文件名称
//参数2:文件宽度地址
//参数3:文件高度地址
//参数4:文件组件地址
//参数5:文件格式地址
//返回值:pBits,指向图像数据的指针
pBits = gltReadTGABits(szFileName, &nWidth, &nHeight, &nComponents, &eFormat);
if(pBits == NULL)
return false;
//2、设置纹理参数
//参数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);
/*
3、载入纹理
*/
//参数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);
4.3 纹理坐标的认识
其实在实际工作中无需我们处理,会有设计师帮我们设计好
因此此处仅以底部两个三角形为例简单说明
其顶点坐标如图:
按照处在金字塔中心的坐标系来确定每个顶点的坐标
按照纹理坐标对顶点坐标的映射,可以看到纹理坐标如下:
当然这里的映射可以不是这样,因为纹理坐标是一种映射关系,其实选任何方位都可以
法线坐标: 我们知道法线就是垂直与一个面的一条线,按照图上所示,底部的三角形的法线自然就是Y轴的线
按照上面划分的顶点坐标和纹理坐标和法线坐标就可以写下面的代码了。
//三角形X
pyramidBatch.Normal3f(0.0f, -1.0f, 0.0f);//这里发现就是与Y轴一致,所以就直接这么写了
pyramidBatch.MultiTexCoord2f(0, 0.0f, 0.0f);//纹理的坐标就是一个平面的坐标,这里的左上角就是(0,0)
//vBlackLeft点
pyramidBatch.Vertex3f(-1.0f, -1.0f, -1.0f);
pyramidBatch.Normal3f(0.0f, -1.0f, 0.0f);
pyramidBatch.MultiTexCoord2f(0, 1.0f, 0.0f);
//vBlackRight点
pyramidBatch.Vertex3f(1.0f, -1.0f, -1.0f);
pyramidBatch.Normal3f(0.0f, -1.0f, 0.0f);
pyramidBatch.MultiTexCoord2f(0, 1.0f, 1.0f);
//vFrontRight点
pyramidBatch.Vertex3f(1.0f, -1.0f, 1.0f);
//三角形Y
pyramidBatch.Normal3f(0.0f, -1.0f, 0.0f);
pyramidBatch.MultiTexCoord2f(0, 0.0f, 1.0f);
pyramidBatch.Vertex3f(-1.0f, -1.0f, 1.0f);
pyramidBatch.Normal3f(0.0f, -1.0f, 0.0f);
pyramidBatch.MultiTexCoord2f(0, 0.0f, 0.0f);
pyramidBatch.Vertex3f(-1.0f, -1.0f, -1.0f);
pyramidBatch.Normal3f(0.0f, -1.0f, 0.0f);
pyramidBatch.MultiTexCoord2f(0, 1.0f, 1.0f);
pyramidBatch.Vertex3f(1.0f, -1.0f, 1.0f);