原始图像数据与内存包装
图像的存储空间= 图像的宽度 * 图像的高度 * 每个像素的字节数(系统决定)
内存对齐:
字长32位的计算机上,如果数据在内存中按照32位的边界对齐(地址为4字节的倍数),那么硬件提取数据的速度就会快得多,同样在64位计算机上,如数据地址按照8字节对齐,他对数据存取效率会非常高。
在许多硬件平台上,考虑到性能的原因位图和像素图的每一行的数据会从特殊的字节对齐地址开始。绝大多数编译器会自动把变量和缓冲区放置在当前计算机架构优化的对齐地址上。OpenGL默认是4字节对齐的,可以通过glPixelStorei来设置像素的存储方式,通过glPixelStoref来恢复像素的存储方式:
glPixelStorei(GL_UNPACK_ALIGNMENT,1);
参数如下图:

举个例子理解一下:
- 如果一个图像每行有99个像素,每个像素有RGB 3个颜色通道,那么该图像每行需要的存储空间为:99*3 = 297个字节。按照默认的4字节对齐,可以算出,每行实际分配的存储空间为 300个字节,虽然这会浪费存储空间,但会提升CPU抓取数据的效率。
读取纹理
/** 图形硬件中复制数据,通常通过总线传输到系统内存
* <#GLint x#> <#GLint y#>: 坐标
* <#GLsizei width#> <#GLsizei height#> : 读取的宽度、高度(以像素为单位)
* <#GLenum format#> : 像素格式(见下图)
* <#GLenum type#> : 告诉OpenGL使用缓冲区中的什么数据类型来存储颜色分量(见下图)
* <#GLvoid *pixels#> :指向图形数据的指针
*/
glReadPixels(<#GLint x#>, <#GLint y#>, <#GLsizei width#>, <#GLsizei height#>, <#GLenum format#>, <#GLenum type#>, <#GLvoid *pixels#>)

/** 从磁盘中载入Targa文件
* <#const char *szFileName#>: 文件名称
* <#GLint *iWidth#> <#GLint *iHeight#>: 读取文件的宽度地址、高度地址
* <#GLint *iComponents#> :文件组件地址
* <#GLenum *eFormat#> :文件格式地址
* 返回值:pBits,指向图像数据的指针
*/
gltReadTGABits(<#const char *szFileName#>, <#GLint *iWidth#>, <#GLint *iHeight#>, <#GLint *iComponents#>, <#GLenum *eFormat#>)
载入纹理
/** 载入纹理
<#GLenum target#> :纹理纬度,一般都是 GL_TEXTURE_2D
<#GLint level#> : mip贴图层次
<#GLint internalformat#> :纹理单元存储的颜色成分(读取时获得)
<#GLsizei width#> <#GLsizei height#>: 纹理的宽高 (读取时获得)
<#GLint border#> : 为纹理指定一个边界宽度,一般传0
<#GLenum format#> :指定纹理数据的格式 :GL_RGB,GL_RGBA等(见上图)。
<#GLenum type#> :指定纹理数据的数据类型 : GL_UNSIGNED_BYTE (见上图)
<#const GLvoid *pixels#> :指向纹理图像数据的指针
*/
glTexImage2D(<#GLenum target#>, <#GLint level#>, <#GLint internalformat#>, <#GLsizei width#>, <#GLsizei height#>, <#GLint border#>, <#GLenum format#>, <#GLenum type#>, <#const GLvoid *pixels#>)
//更新纹理
glTexSubImage2D(<#GLenum target#>, <#GLint level#>, <#GLint xoffset#>, <#GLint yoffset#>, <#GLsizei width#>, <#GLsizei height#>, <#GLenum format#>, <#GLenum type#>, <#const GLvoid *pixels#>)
//插入替换纹理
glCopyTexSubImage2D(<#GLenum target#>, <#GLint level#>, <#GLint xoffset#>, <#GLint yoffset#>, <#GLint x#>, <#GLint y#>, <#GLsizei width#>, <#GLsizei height#>)
设置纹理参数
// 参数一:纹理纬度 参数二:参数名称 参数三:参数的值
glTexParameteri(<#GLenum target#>, <#GLenum pname#>, <#GLint param#>)
glTexParameterf(<#GLenum target#>, <#GLenum pname#>, <#GLfloat param#>)
过滤方式
常用的过滤方式有两种:
- 临近过滤:是我们能够选择的最简单、最快速的过滤方法,其最显著的特征就是当纹理被拉伸到特别大的时候所出现的大片斑驳状像素。
-
线性过滤:更接近真实,没有人工操作的痕迹
image.png
image.png
设置方式:
//纹理放大时,使用临近过滤
glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_NEAREST);
//纹理缩小时,使用临近过滤(推荐)
glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_NEAREST);
//纹理放大时,使用线性过滤 (推荐)
glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR);
//纹理缩小时,使用线性过滤
glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR);
环绕方式

//纹理坐标里的 s t r 分别代表 x y z轴
//指定横轴的环绕方式为GL_CLAMP_TO_EDGE
glTextParameteri(GL_TEXTURE_2D,GL_TEXTURE_WRAR_S,GL_CLAMP_TO_EDGE);
//指定纵轴的环绕方式为GL_CLAMP_TO_EDGE
glTextParameteri(GL_TEXTURE_2D,GL_TEXTURE_WRAR_T,GL_CLAMP_TO_EDGE);
不同的环绕方式效果如下:

纹理坐标
纹理坐标系以纹理左下角为坐标原点,向右为x正轴方向,向上为y轴正轴方向。他的总长度是1。即纹理图片的四个角的坐标分别是:(0,0)、(1,0)、(0,1)、(1,1),分别对应左下、右下、左上、右上四个顶点。二维纹理常用(s, t)坐标表示:

//设置纹理坐标
//注意这里的参数1:texture,纹理层次,对于使用存储着色器来进行渲染,设置为0
//后面两个参数对应 x y
void MultiTexCoord2f(GLuint texture, GLclampf s, GLclampf t);
如何把纹理坐标应用到三角形上:其纹理坐标就是:
GLfloat texCoords[] = {
0.0f, 0.0f, // 左下角
1.0f, 0.0f, // 右下角
0.5f, 1.0f // 顶部位置
};

接下来我们来实践一下,给OpenGL固定管线着色器这篇文章里的金字塔加上纹理:其实渲染的流程基本一致,主要是要给每个顶点添加纹理坐标,并读取纹理图像进行渲染:
我们来分析下金字塔的底面纹理坐标:
底面其实是两个三角形:
纹理坐标如下:

前面/背面/两个侧面都是单独的三角形,纹理坐标如下:

具体代码实现如下:(Normal3f是为了光照效果设置的法线,不影响金字塔的纹理实现,可以自行去掉看看效果)
//塔顶
M3DVector3f vApex = { 0.0f, 1.0f, 0.0f };
M3DVector3f vFrontLeft = { -1.0f, -1.0f, 1.0f };
M3DVector3f vFrontRight = { 1.0f, -1.0f, 1.0f };
M3DVector3f vBackLeft = { -1.0f, -1.0f, -1.0f };
M3DVector3f vBackRight = { 1.0f, -1.0f, -1.0f };
M3DVector3f n;
//金字塔底部
//底部的四边形 = 三角形X + 三角形Y
//三角形X = (vBackLeft,vBackRight,vFrontRight)
//1.找到三角形X 法线
m3dFindNormal(n, vBackLeft, vBackRight, vFrontRight);
//vBackLeft
pyramidBatch.Normal3fv(n);
// pyramidBatch.MultiTexCoord2f(0, 0.0f, 0.0f);
pyramidBatch.MultiTexCoord2f(0, 0.0f, 1.0f);
pyramidBatch.Vertex3fv(vBackLeft);
//vBackRight
pyramidBatch.Normal3fv(n);
// pyramidBatch.MultiTexCoord2f(0, 1.0f, 0.0f);
pyramidBatch.MultiTexCoord2f(0, 1.0f, 1.0f);
pyramidBatch.Vertex3fv(vBackRight);
//vFrontRight
pyramidBatch.Normal3fv(n);
// pyramidBatch.MultiTexCoord2f(0, 1.0f, 1.0f);
pyramidBatch.MultiTexCoord2f(0, 1.0f, 0.0f);
pyramidBatch.Vertex3fv(vFrontRight);
//三角形Y =(vFrontLeft,vBackLeft,vFrontRight)
//1.找到三角形X 法线
m3dFindNormal(n, vFrontLeft, vBackLeft, vFrontRight);
//vFrontLeft
pyramidBatch.Normal3fv(n);
// pyramidBatch.MultiTexCoord2f(0, 0.0f, 1.0f);
pyramidBatch.MultiTexCoord2f(0, 0.0f, 0.0f);
pyramidBatch.Vertex3fv(vFrontLeft);
//vBackLeft
pyramidBatch.Normal3fv(n);
// pyramidBatch.MultiTexCoord2f(0, 0.0f, 0.0f);
pyramidBatch.MultiTexCoord2f(0, 0.0f, 1.0f);
pyramidBatch.Vertex3fv(vBackLeft);
//vFrontRight
pyramidBatch.Normal3fv(n);
// pyramidBatch.MultiTexCoord2f(0, 1.0f, 1.0f);
pyramidBatch.MultiTexCoord2f(0, 1.0f, 0.0f);
pyramidBatch.Vertex3fv(vFrontRight);
// 金字塔前面
//三角形:(Apex,vFrontLeft,vFrontRight)
m3dFindNormal(n, vApex, vFrontLeft, vFrontRight);
pyramidBatch.Normal3fv(n);
pyramidBatch.MultiTexCoord2f(0, 0.5f, 1.0f);
pyramidBatch.Vertex3fv(vApex);
pyramidBatch.Normal3fv(n);
pyramidBatch.MultiTexCoord2f(0, 0.0f, 0.0f);
pyramidBatch.Vertex3fv(vFrontLeft);
pyramidBatch.Normal3fv(n);
pyramidBatch.MultiTexCoord2f(0, 1.0f, 0.0f);
pyramidBatch.Vertex3fv(vFrontRight);
//金字塔左边
//三角形:(vApex, vBackLeft, vFrontLeft)
m3dFindNormal(n, vApex, vBackLeft, vFrontLeft);
pyramidBatch.Normal3fv(n);
pyramidBatch.MultiTexCoord2f(0, 0.5f, 1.0f);
pyramidBatch.Vertex3fv(vApex);
pyramidBatch.Normal3fv(n);
pyramidBatch.MultiTexCoord2f(0, 1.0f, 0.0f);
pyramidBatch.Vertex3fv(vBackLeft);
pyramidBatch.Normal3fv(n);
pyramidBatch.MultiTexCoord2f(0, 0.0f, 0.0f);
pyramidBatch.Vertex3fv(vFrontLeft);
//金字塔右边
//三角形:(vApex, vFrontRight, vBackRight)
m3dFindNormal(n, vApex, vFrontRight, vBackRight);
pyramidBatch.Normal3fv(n);
pyramidBatch.MultiTexCoord2f(0, 0.5f, 1.0f);
pyramidBatch.Vertex3fv(vApex);
pyramidBatch.Normal3fv(n);
pyramidBatch.MultiTexCoord2f(0, 1.0f, 0.0f);
pyramidBatch.Vertex3fv(vFrontRight);
pyramidBatch.Normal3fv(n);
pyramidBatch.MultiTexCoord2f(0, 0.0f, 0.0f);
pyramidBatch.Vertex3fv(vBackRight);
//金字塔后边
//三角形:(vApex, vBackRight, vBackLeft)
m3dFindNormal(n, vApex, vBackRight, vBackLeft);
pyramidBatch.Normal3fv(n);
pyramidBatch.MultiTexCoord2f(0, 0.5f, 1.0f);
pyramidBatch.Vertex3fv(vApex);
pyramidBatch.Normal3fv(n);
pyramidBatch.MultiTexCoord2f(0, 0.0f, 0.0f);
pyramidBatch.Vertex3fv(vBackRight);
pyramidBatch.Normal3fv(n);
pyramidBatch.MultiTexCoord2f(0, 1.0f, 0.0f);
pyramidBatch.Vertex3fv(vBackLeft);
顶点和纹理坐标设置好之后,开始去读取纹理图像:
使用流程:(4、5顺序不是严格的,也可以放在前面两步)
- 读取
- 载入
- 设置纹理属性参数(过滤方式和环绕方式)
- 分配纹理对象
- 绑定纹理对象
- 清除纹理对象
- 判断纹理对象是否清除
//分配纹理对象 参数1:纹理对象个数,参数2:纹理对象指针
glGenTextures(1, &textureID);
//绑定纹理状态 参数1:纹理状态2D 参数2:纹理对象
glBindTexture(GL_TEXTURE_2D, textureID);
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:纹理维度
//参数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);
//加载Mip,纹理生成所有的Mip层
//参数:GL_TEXTURE_1D、GL_TEXTURE_2D、GL_TEXTURE_3D
glGenerateMipmap(GL_TEXTURE_2D);
网友评论