前言
这一篇文章的主要内容是用OpenGL去加载纹理,也就是将图片加载到屏幕上。之前几篇文章已经讲解了OpenGL图形绘制的基本图元:点、线、三角形等七种。其实图片加载的过程与基本图元一样,只有一个步骤不同,那就是取色。基本图元绘制时我们给定了颜色值,而加载纹理时是从纹理中取每个像素点的颜色值。要真说区别,那就是颜色值的来源不一样。
然而这篇文章也仅作为纹理加载的入门,该文章用了固定着色器:纹理替换着色器。而在OpenGL ES中我将会自己实现着色器程序,去取纹理中的颜色值,原理一样。
纹理加载流程
这一节实现一个完整的纹理加载过程。
1.纹理坐标
移动端的布局通常以屏幕左上角的点为坐标原点(0,0),而在纹理坐标中,原点是左下角的点,看图1对比一下。 图1-坐标系.png为什么要先说这个,因为纹理加载时,你告诉OpenGL的不是你的图片容器的尺寸,而是容器与纹理坐标的对应关系。改变映射关系就能够实现图片上下左右的颠倒。
2.纹理相关专用名词
a.临近过滤与线性过滤
个人认为与其说是过滤还不如说是取值,下面用两张图片来解释。
过滤方式.png
很明显临近过滤是取对应像素点的颜色值,而线性过滤则是取它周围最近四个点的颜色混合值,这也是为什么有些图片看上去会模糊,与图片大小和容器大小有关,因为图片大小和容器大小差距太大时过滤方式不一样,导致效果不一样。
b.环绕模式
3.纹理相关API
a.绑定纹理
// 用来生成纹理的函数。函数根据纹理参数返回n个纹理索引。纹理名称集合不必是一个连续的整数集合,
//(glGenTextures就是用来产生你要操作的纹理对象的索引的,比如你告诉[OpenGL],我需要5个纹理对象,
// 它会从没有用到的整数里返回5个给你)
// params1:纹理对象个数
// params2:纹理对象指针
glGenTextures(1, &textureID);
// 绑定纹理,GL_TEXTURE_2D:个人理解是OpenGL创建颜色缓冲区时创建的一个标识符
// 将纹理名绑定至当前活动纹理单元目标
// 将GL_TEXTURE_2D与textureID关联,后续需要用到textureID
glBindTexture(GL_TEXTURE_2D, textureID);
b.载入纹理
bool loadTGATexture(const char *fileName) {
GLbyte *pBits;
int nWidth, nHeight, nComponents;
GLenum eFormat;
//1、读纹理位,读取像素
//参数1:纹理文件名称
//参数2:文件宽度地址
//参数3:文件高度地址
//参数4:文件组件地址
//参数5:文件格式地址
//返回值:pBits,指向图像数据的指针
pBits = gltReadTGABits(fileName, &nWidth, &nHeight, &nComponents, &eFormat);
if (pBits == NULL) {
return false;
}
// 设置纹理参数
// 环绕模式,可以试试不设置的效果
// params1:纹理纬度
// params2:OpenGL里面的横纵用s/t表示
// params3:环绕模式,有多种,自行百度,例如:GL_REPEAT
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
// 过滤方式
// 纹理缩小时,用临近过滤
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
// 纹理放大时,用线性过滤
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
// 载入纹理
glTexImage2D(GL_TEXTURE_2D, 0, nComponents, nWidth, nHeight, 0, eFormat, GL_UNSIGNED_BYTE, pBits);
// c,释放
free(pBits);
// 加载mip
glGenerateMipmap(GL_TEXTURE_2D);
return true;
}
c.设置纹理坐标
GLfloat point00[] = {-0.5, -0.5, 0};
GLfloat point10[] = { 0.5, -0.5, 0};
GLfloat point01[] = {-0.5, 0.5, 0};
GLfloat point11[] = { 0.5, 0.5, 0};
GLBatch squareBatch;
// 与之前画图元不一样
/*
参数1:类型
参数2:顶点数
参数3:这个批次中将会应用1个纹理
*/
squareBatch.Begin(GL_TRIANGLE_FAN, 4, 1);
// 设置顶点对应的纹理坐标
// params1:texture,纹理层次,对于使用存储着色器来进行渲染,设置为0
// params2:s: 对应顶点坐标中的x坐标
// params3:t: 对应顶点坐标中的y
squareBatch.MultiTexCoord2f(0, 0, 0);
// 设置(0,0)对应的屏幕上的坐标点
squareBatch.Vertex3fv(point00);
squareBatch.MultiTexCoord2f(0, 1, 0);
squareBatch.Vertex3fv(point10);
squareBatch.MultiTexCoord2f(0, 1, 1);
squareBatch.Vertex3fv(point11);
squareBatch.MultiTexCoord2f(0, 0, 1);
squareBatch.Vertex3fv(point01);
squareBatch.End();
d.着色器程序
当前使用的着色器是固定着色器:纹理替换着色器(GLT_SHADER_TEXTURE_REPLACE)
// 使用纹理替换矩阵
shaderManager.UseStockShader(GLT_SHADER_TEXTURE_REPLACE, transformLine.GetModelViewProjectionMatrix(), 0);
squareBatch.Draw();
e.销毁
// 删除纹理对象
glDeleteTextures(1, &textureID);
以上几个就是载入纹理的主要API。
总结
让我一起来,回顾那些快遗忘的纹理载入步骤。
假设OpenGL的上下文已经初始化完毕,接下来:
1.绑定:glGenTextures-> glBindTexture
2.载入:gltReadTGABits-> glTexParameteri(4次,两次环绕两次过滤)-> glTexImage2D
3.设置纹理坐标:MultiTexCoord2f->Vertex3fv
4.使用着色器:shaderManager.UseStockShader(GLT_SHADER_TEXTURE_REPLACE, transformLine.GetModelViewProjectionMatrix(), 0);
注意:因为这里用的是固定着色器程序,所以需要用到MVP模型视图矩阵,在OpenGL ES中加载纹理时,会手动实现着色器,流程与这篇文章中的流程略有不同。
最后附上demo地址:https://github.com/zhaoguyixia/OpenGL.git
祝各位生活愉快!!
网友评论