OpenGL 七 - OpenGL 纹理基础与案例演示
纹理基初知识
一、纹理
1)无论是 tga 文件还是 png/jpg 文件,最终图片文件都是要归结到位图文件去处理的。
纹理文件 --> TGA文件 --> OpenGL --> 位图
iOS开发中 --> OpenGL ES --> png/jpg --> 位图
2)原始图像数据:
图像存储空间 = 图像高度 * 图像宽度 * 每个像素的字节数
二、相关函数
1)
// 改变像素存储⽅式
void glPixelStorei(GLenum pname,GLint param);
// 恢复像素存储⽅式
void glPixelStoref(GLenum pname,GLfloat param);
// 举例 :
// 参数1: GL_UNPACK_ALIGNMENT 指定 OpenGL 如何从数据缓存区中解包图像数据
// 参数2: 表示参数 GL_UNPACK_ALIGNMENT 设置的值
// GL_UNPACK_ALIGNMENT 指内存中每个像素⾏起点的排列请求,
// 允许设置为1:byte排列列、2:排列为偶数byte的行、4:字word排列、8:行从双字节边界开始
glPixelStorei(GL_UNPACK_ALIGNMENT,1);
2)从颜色缓存区 内容作为像素图 直接读取
// 参数1:x, 矩形左下角的窗⼝坐标
// 参数2:y, 矩形左下角的窗口坐标
// 参数3:width, 矩形的宽,以像素为单位
// 参数4:height, 矩形的高,以像素为单位
// 参数5:format, OpenGL 的像素格式,参考下面表格: 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);// 指定写入的缓存
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_1D`、`GL_TEXTURE_2D`、`GL_TEXTURE_3D`
* Level:指定所加载的 mip 贴图层次。一般我们都把这个参数设置为0
* internalformat:每个纹理单元中存储多少颜色成分
* width、height、depth 参数:指加载纹理的宽度、⾼度、深度。注意: 这些值必须是 2的整数次⽅ --> 这是因为 OpenGL 旧版本上的遗留下的⼀个要求,当然现在已经可以支持不是 2 的整数次方,但是开发者们还是习惯使⽤以2的整数次⽅去设置这些参数。
* border 参数:允许为纹理贴图指定一个边界宽度
* format、type、data 参数:与我们在讲 glDrawPixels 函数对应的参数相同*/
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);
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, 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);
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 颜⾊缓存区中获取体积数据。
7)纹理对象
// 使⽤函数分配纹理对象
// 指定纹理对象的数量 和 指针 (指针指向⼀个⽆符号整形数组,由纹理对象标识符填充)
void glGenTextures(GLsizei n,GLuint * textTures);
// 绑定纹理状态
// 参数target: GL_TEXTURE_1D、GL_TEXTURE_2D、GL_TEXTURE_3D
// 参数texture: 需要绑定的纹理对象
void glBindTexture(GLenum target,GLunit texture);
// 删除绑定纹理对象
// 纹理对象 以及 纹理对象指针 —> 指针指向⼀个⽆符号整形数组,由纹理对象标识符填充
void glDeleteTextures(GLsizei n,GLuint *textures);
// 测试纹理对象是否有效
// 如果 texture 是⼀个已经分配空间的纹理对象,那么这个函数会返回 GL_TRUE, 否则会返回 GL_FALSE。
GLboolean glIsTexture(GLuint texture);
8)设置纹理参数
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,设定特定的纹理参数的值*/
2种纹理过滤方式的比较:纹理放大缩小时,设置是使⽤线性过滤还是临近过滤
临近过滤-图像锯齿;线性过滤-平滑柔焦 --> 此效果是指图像放大 or 缩小到一定程度的效果,正常图片的显示不会有问题的。
纹理参数设置函数
glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR); // 纹理放大时,使⽤线性过滤
glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR); // 纹理缩小时,使⽤线性过滤
glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_NEAREST);// 纹理放大时,使⽤临近过滤
glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_NEAREST); // 纹理缩小时,使⽤临近过滤
8.2)设置环绕方式
设置环绕方式 API
/*
参数1:GL_TEXTURE_1D、GL_TEXTURE_2D、GL_TEXTURE_3D
参数2:GL_TEXTURE_WRAP_S、GL_TEXTURE_T、GL_TEXTURE_R,针对 s、t、r 坐标 -- s -> x
参数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范围之外的 只使⽤边界纹理单元。边界纹理单元是作为围绕基本图像的 额外的行和列,并与基本纹理图像⼀起加载的。
*/
glTextParameteri(GL_TEXTURE_2D,GL_TEXTURE_WRAR_S,GL_CLAMP_TO_EDGE);
glTextParameteri(GL_TEXTURE_2D,GL_TEXTURE_WRAR_T,GL_CLAMP_TO_EDGE);
二、相关表格
1)OpenGL像素表格:
2)像素数据的像素类型:
UNSIGNED_BYTE_3_3_2
UNSIGNED_BYTE_2_3_3_REV
指定分量 RGBA 的排列顺序根据 format 参数确定。分量按照分量高位到低位排列。
三、纹理坐标
顶点坐标:x、y、z
纹理坐标:s、t、r --> 其实就是对应的 x、y、z
纹理坐标范围是 0~1 之间,更多地是描述一种映射关系。纹理坐标默认左下角为原点 (0,0)。图示:
立方体:
四、案例
效果:
1)三角锥底部(2个三角形)的坐标纹理对应关系:
其他剩余三角形面同理。
主要代码:
1 /// 绘制金字塔 2 void MakePyramid(GLBatch& pyramidBatch) 3 { 4 /*1、通过pyramidBatch组建三角形批次 5 参数1:类型 6 参数2:顶点数 7 参数3:这个批次中将会应用1个纹理 8 注意:如果不写这个参数,默认为0。 9 */ 10 pyramidBatch.Begin(GL_TRIANGLES, 18, 1); 11 12 /***前情导入 13 14 2)设置纹理坐标 15 void MultiTexCoord2f(GLuint texture, GLclampf s, GLclampf t); 16 参数1:texture,纹理层次,对于使用存储着色器来进行渲染,设置为0 17 参数2:s:对应顶点坐标中的x坐标 18 参数3:t:对应顶点坐标中的y 19 (s,t,r,q对应顶点坐标的x,y,z,w) 20 21 pyramidBatch.MultiTexCoord2f(0,s,t); 22 23 3)void Vertex3f(GLfloat x, GLfloat y, GLfloat z); 24 void Vertex3fv(M3DVector3f vVertex); 25 向三角形批次类添加顶点数据(x,y,z); 26 pyramidBatch.Vertex3f(-1.0f, -1.0f, -1.0f); 27 28 */ 29 30 //塔顶 31 M3DVector3f vApex = { 0.0f, 1.0f, 0.0f }; 32 M3DVector3f vFrontLeft = { -1.0f, -1.0f, 1.0f }; 33 M3DVector3f vFrontRight = { 1.0f, -1.0f, 1.0f }; 34 M3DVector3f vBackLeft = { -1.0f, -1.0f, -1.0f }; 35 M3DVector3f vBackRight = { 1.0f, -1.0f, -1.0f }; 36 37 //金字塔底部 38 // 底部的四边形 = 三角形X + 三角形Y 39 // 三角形X = (vBackLeft,vBackRight,vFrontRight) 40 // vBackLeft 41 pyramidBatch.MultiTexCoord2f(0, 0.0f, 0.0f); 42 pyramidBatch.Vertex3fv(vBackLeft); 43 44 // vBackRight 45 pyramidBatch.MultiTexCoord2f(0, 1.0f, 0.0f); 46 pyramidBatch.Vertex3fv(vBackRight); 47 48 // vFrontRight 49 pyramidBatch.MultiTexCoord2f(0, 1.0f, 1.0f); 50 pyramidBatch.Vertex3fv(vFrontRight); 51 52 53 // 三角形Y =(vFrontLeft,vBackLeft,vFrontRight) 54 // vFrontLeft 55 pyramidBatch.MultiTexCoord2f(0, 0.0f, 1.0f); 56 pyramidBatch.Vertex3fv(vFrontLeft); 57 58 //vBackLeft 59 pyramidBatch.MultiTexCoord2f(0, 0.0f, 0.0f); 60 pyramidBatch.Vertex3fv(vBackLeft); 61 62 //vFrontRight 63 pyramidBatch.MultiTexCoord2f(0, 1.0f, 1.0f); 64 pyramidBatch.Vertex3fv(vFrontRight); 65 66 67 // 金字塔前面 68 //三角形:(Apex,vFrontLeft,vFrontRight) 69 pyramidBatch.MultiTexCoord2f(0, 0.5f, 1.0f); 70 pyramidBatch.Vertex3fv(vApex); 71 72 pyramidBatch.MultiTexCoord2f(0, 0.0f, 0.0f); 73 pyramidBatch.Vertex3fv(vFrontLeft); 74 75 pyramidBatch.MultiTexCoord2f(0, 1.0f, 0.0f); 76 pyramidBatch.Vertex3fv(vFrontRight); 77 78 //金字塔左边 79 //三角形:(vApex, vBackLeft, vFrontLeft) 80 pyramidBatch.MultiTexCoord2f(0, 0.5f, 1.0f); 81 pyramidBatch.Vertex3fv(vApex); 82 83 pyramidBatch.MultiTexCoord2f(0, 1.0f, 0.0f); 84 pyramidBatch.Vertex3fv(vBackLeft); 85 86 pyramidBatch.MultiTexCoord2f(0, 0.0f, 0.0f); 87 pyramidBatch.Vertex3fv(vFrontLeft); 88 89 //金字塔右边 90 //三角形:(vApex, vFrontRight, vBackRight) 91 pyramidBatch.MultiTexCoord2f(0, 0.5f, 1.0f); 92 pyramidBatch.Vertex3fv(vApex); 93 94 pyramidBatch.MultiTexCoord2f(0, 1.0f, 0.0f); 95 pyramidBatch.Vertex3fv(vFrontRight); 96 97 pyramidBatch.MultiTexCoord2f(0, 0.0f, 0.0f); 98 pyramidBatch.Vertex3fv(vBackRight); 99 100 //金字塔后边 101 //三角形:(vApex, vBackRight, vBackLeft) 102 pyramidBatch.MultiTexCoord2f(0, 0.5f, 1.0f); 103 pyramidBatch.Vertex3fv(vApex); 104 105 pyramidBatch.MultiTexCoord2f(0, 0.0f, 0.0f); 106 pyramidBatch.Vertex3fv(vBackRight); 107 108 pyramidBatch.MultiTexCoord2f(0, 1.0f, 0.0f); 109 pyramidBatch.Vertex3fv(vBackLeft); 110 111 //结束批次设置 112 pyramidBatch.End(); 113 } 114 115 /// 将TGA文件加载为2D纹理。 116 //参数1:纹理文件名称 117 //参数2&参数3:需要缩小&放大的过滤器模式设置 118 //参数4:纹理坐标 环绕模式 119 bool LoadTGATexture(const char *szFileName, GLenum minFilter, GLenum magFilter, GLenum wrapMode) 120 { 121 GLbyte *pBits; 122 int nWidth, nHeight, nComponents; 123 GLenum eFormat; 124 125 //1、读纹理位,读取像素 --> 将图片读取为 位图 126 //参数1:纹理文件名称 127 //参数2:文件宽度地址 128 //参数3:文件高度地址 129 //参数4:文件组件地址 130 //参数5:文件格式地址 131 //返回值:pBits,指向图像数据的指针 132 pBits = gltReadTGABits(szFileName, &nWidth, &nHeight, &nComponents, &eFormat); 133 134 if(pBits == NULL)// 读取图片数据失败直接返回 false 135 return false; 136 137 //2、设置纹理参数 138 //参数1:纹理维度 139 //参数2:为 S/T 坐标设置模式 140 //参数3:wrapMode,环绕模式 141 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, wrapMode); 142 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, wrapMode); 143 144 // z设置过滤方式:纹理放大缩小时怎么显示 145 //参数1:纹理维度 146 //参数2:过滤的放大缩小 147 //参数3: 缩小/放大过滤方式. 148 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, minFilter); 149 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, magFilter); 150 151 152 //3.载入纹理 153 //参数1:纹理维度 154 //参数2:mip贴图层次 155 //参数3:纹理单元存储的颜色成分(从读取像素图是获得) 156 //参数4:加载纹理宽 157 //参数5:加载纹理高 158 //参数6:border 159 //参数7、8:像素数据的数据类型(GL_UNSIGNED_BYTE,每个颜色分量都是一个8位无符号整数) 160 //参数9:指向纹理图像数据的指针 161 glTexImage2D(GL_TEXTURE_2D, 0, nComponents, nWidth, nHeight, 0, 162 eFormat, GL_UNSIGNED_BYTE, pBits); 163 164 //使用完毕释放pBits 165 free(pBits); 166 167 //只有minFilter 等于以下四种模式,才可以生成Mip贴图 168 //GL_NEAREST_MIPMAP_NEAREST具有非常好的性能,并且闪烁现象非常弱 169 //GL_LINEAR_MIPMAP_NEAREST常常用于对游戏进行加速,它使用了高质量的线性过滤器 170 //GL_LINEAR_MIPMAP_LINEAR 和GL_NEAREST_MIPMAP_LINEAR 过滤器在Mip层之间执行了一些额外的插值,以消除他们之间的过滤痕迹。 171 //GL_LINEAR_MIPMAP_LINEAR 三线性Mip贴图。纹理过滤的黄金准则,具有最高的精度。 172 if(minFilter == GL_LINEAR_MIPMAP_LINEAR || 173 minFilter == GL_LINEAR_MIPMAP_NEAREST || 174 minFilter == GL_NEAREST_MIPMAP_LINEAR || 175 minFilter == GL_NEAREST_MIPMAP_NEAREST) 176 //4.纹理生成所有的Mip层 177 //参数:GL_TEXTURE_1D、GL_TEXTURE_2D、GL_TEXTURE_3D 178 glGenerateMipmap(GL_TEXTURE_2D); 179 180 return true; 181 } 182 183 // 初始化 184 void SetupRC() 185 { 186 //1. 187 glClearColor(0.7f, 0.7f, 0.7f, 1.0f ); 188 shaderManager.InitializeStockShaders(); 189 190 //2. 191 glEnable(GL_DEPTH_TEST); 192 193 //3. 194 //分配纹理对象 参数1:纹理对象个数,参数2:纹理对象指针 195 glGenTextures(1, &textureID); 196 197 //绑定纹理状态 参数1:纹理状态2D 参数2:纹理对象 198 glBindTexture(GL_TEXTURE_2D, textureID); 199 200 //将TGA文件加载为2D纹理 --> LoadTGATexture:自定义方法 201 //参数1:纹理文件名称 202 //参数2&参数3:需要缩小&放大的过滤器 203 //参数4:纹理坐标环绕模式 204 LoadTGATexture("stone.tga", GL_LINEAR_MIPMAP_NEAREST, GL_LINEAR, GL_CLAMP_TO_EDGE); 205 206 //4.创造金字塔 pyramidBatch --> MakePyramid:自定义方法 207 MakePyramid(pyramidBatch); 208 209 //5. 210 /**相机frame MoveForward(平移) 211 参数1:Z,深度(屏幕到图形的Z轴距离) 212 */ 213 cameraFrame.MoveForward(-10); 214 } 215 216 // 渲染绘制 217 void RenderScene(void) 218 { 219 //1.颜色值&光源位置 220 static GLfloat vLightPos [] = { 1.0f, 1.0f, 0.0f }; 221 static GLfloat vWhite [] = { 1.0f, 1.0f, 1.0f, 1.0f }; 222 223 //2.清理缓存区 224 glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT); 225 226 //3.当前模型视频压栈 227 modelViewMatrix.PushMatrix(); 228 229 //添加照相机矩阵 230 M3DMatrix44f mCamera; 231 //从camraFrame中获取一个4*4的矩阵 232 cameraFrame.GetCameraMatrix(mCamera); 233 //矩阵乘以矩阵堆栈顶部矩阵,相乘结果存储到堆栈的顶部 将照相机矩阵 与 当前模型矩阵相乘 压入栈顶 234 modelViewMatrix.MultMatrix(mCamera); 235 236 //创建mObjectFrame矩阵 237 M3DMatrix44f mObjectFrame; 238 //从objectFrame中获取矩阵,objectFrame保存的是特殊键位的变换矩阵 239 objectFrame.GetMatrix(mObjectFrame); 240 //矩阵乘以矩阵堆栈顶部矩阵,相乘结果存储到堆栈的顶部 将世界变换矩阵 与 当前模型矩阵相乘 压入栈顶 241 modelViewMatrix.MultMatrix(mObjectFrame); 242 243 //4.绑定纹理,因为我们的项目中只有一个纹理。如果有多个纹理。绑定纹理很重要 244 glBindTexture(GL_TEXTURE_2D, textureID); 245 246 //5.纹理替换矩阵着色器 247 /* 248 参数1:GLT_SHADER_TEXTURE_REPLACE(着色器标签) 249 参数2:模型视图投影矩阵 250 参数3:纹理层 251 */ shaderManager.UseStockShader(GLT_SHADER_TEXTURE_REPLACE, transformPipeline.GetModelViewProjectionMatrix(), 0); 252 253 //pyramidBatch 绘制 254 pyramidBatch.Draw(); 255 256 //模型视图出栈,恢复矩阵(push一次就要pop一次) 257 modelViewMatrix.PopMatrix(); 258 259 //6.交换缓存区 260 glutSwapBuffers(); 261 }
2)Mip 贴图
当不同大小图的绘制时,缩放规则:每次缩放 1/2 直至最小的图为 1:1 的纹理单元为止,会过滤(比例缩放)成多少份取决于图片的大小 --> 提高渲染性能和显示质量
2.1)设置 Mip 贴图
// 设置Mip贴图最基层
glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_BASE_LEVEL,0);
// 设置Mip贴图最大层
glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_BASE_LEVEL,0);
--> 参数0: 第一层是0,第二层 1 ... ... 以此类推。
2.2)mip 贴图的过滤方式
上面代码 204 行:LoadTGATexture("stone.tga", GL_LINEAR_MIPMAP_NEAREST, GL_LINEAR, GL_CLAMP_TO_EDGE);// GL_LINEAR_MIPMAP_NEAREST --> 选择最邻近mip层,并进行线性过滤。
表格中后 4 种方式方式才可生成 mip 贴图。代码示例:上面的‘主要代码’167行处。