007-基础纹理上篇
[toc]
image.png前言
首先,我们通过对点,线,三角形的进行渲染,也可以通过计算机颜色对它们进行着色,也可以进行插值操作来模拟灯光的效果,这一切都非常不错,但是为了更加真实的效果,还有一种非常棒的途径,那就是纹理贴图(texture mapping
).
纹理
是一种能应用到场景中的三角形图形数据,它通过经过过滤的纹理单元(telex,想当于基于纹理的像素
)填充到实心区域.
原始图像数据
像素的包装
图像存储空间 = 图像的⾼度 * 图像宽度 * 每个像素的字节数
- 图像数据在内存中很少以紧密包装的形式存在
- 一幅图像的每一行都应该从一个特定的字节对齐地址开始,大部分的编译器会自动把变量和缓冲区放置一个针对该架构对齐优化的地址上.
- OpenGL采用4个字节的对齐方式.
通过下面函数改变或恢复像素的存储方式
//改变像素存储⽅方式
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);
glPixeIStore参数
GLenum pname
像素图
像素图
在内存布局上和位图非常相似,但是每个像素需要一个以上
的存储位来表示.
每个像素
的附加为运行存储强度(intensity
,有时被称为亮度即Iuminance
)或颜色分量值
.
OpenGL中无法将一个像素图绘制到颜色缓冲区中
,可以使用下面函数将颜色缓冲区的内容作为像素图直接读取.
参数1:
x
,矩形左下⻆角的窗⼝口坐标
参数2: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);
OpenGL像素格式
GLenum format
像素数据的数据类型
GLenum type
glReadPixels
的读取操作在双缓存区渲染环境下在后台缓冲区进行
glReadPixels
从图形硬件中复制数据,通常通过总线传输到系统内存.在这种情况下应用程序将被阻塞,知道内存传输完成.
此外,如果我们指定一个与图形硬件的本地排序不同
的像素布局,那么在数据进行重定格式时将产生额外的性能开销
.
包装的像素格式
包装的像素格式将颜色数据压缩到了尽可能少的存储位中,每个颜色通道的位数显示在常量中.
格式的第一个分量提供3位存储位,第一个也是,第三个提供2位.
这些数据从高位(MSB)到低位(LSB)进行排列.
image.png单缓冲区渲染环境下则在前台缓冲区进行读取操作,使用函数如下
glReadBuffer(mode)
模式参数使用
:
写入缓存
glWriteBuffer(mode)
读取像素
Traga图像格式是一种方便而且容易使用的图像格式.
- 从磁盘中载入Targa文件的函数
读纹理位,读取像素
参数1
:纹理文件名称
参数2
:文件宽度地址
参数3
:文件高度地址
参数4
:文件组件地址
参数5
:文件格式地址
返回值
:pBits
,指向图像数据的指针
GLbyte *gltReadTGABits(const char *szFileName, GLint *iWidth, GLint *iHeight, GLint *iComponents, GLenum *eFormat, GLbyte *pData = NULL);
载入纹理
在几何图形中应用纹理贴图,第一个必要的步骤就是将纹理载入内存.一旦被载入就会成为当前纹理状态的一部分.
OpenGL
有3
函数经常用来从存储器缓冲区中裁入纹理数据.
-
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
函数对于的参数相同
- glTexImage1D
void glTexImage1D(GLenum target,GLint level,GLint
internalformat,GLsizei width,GLint border,GLenum
format,GLenum type,void *data);
- glTexImage2D
void glTexImage2D(GLenum target,GLint level,GLint
internalformat,GLsizei width,GLsizei height,GLint
border,GLenum format,GLenum type,void * data);
- glTexImage3D
void glTexImage3D(GLenum target,GLint level,GLint internalformat,GLSizei width,GLsizei height,GLsizei depth,GLint border,GLenum format,GLenum type,void *data);
它们都是由
glTextImage
派生而来,OpenGL
支持一维,二维,三维
纹理贴图.
常见纹理内部格式
GLint nternalformat
使用颜色缓冲区
一维和二维纹理
也可以从颜色缓冲区加载数据,也可以从颜色缓冲区读取一幅画像.
x,y 在颜⾊色缓存区中指定了了开始读取纹理理数据的位置; 缓存区⾥里里的数据,是源缓存区通过glReadBuffer
设置的。
注意:不不存在
glCopyTextImage3D
,因为我们无法从2D
颜⾊缓存区中获取体积数据
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);
更新纹理
替换一个纹理比重新加载一个新的纹理快得多.
常见函数:
大部分参数和
glTextImag
e函数使用的参数准确对应.
xOffset yOffset zOffset
参数指定在原来的纹理贴图中开始替换纹理参数的偏移量.
width height depth
指定插入到原来那个纹理中的新的纹理的宽度,高度和深度.
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);
- glCopyTexSubImage1D插入替换纹理
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,
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);
纹理对象
纹理
直接进行切换或者加载不同的纹理图像可能会是开销很大的操作.
纹理对象
允许我们加载一个以上的纹理状态(包括纹理图像
),以及他们之间快速切换.纹理状态是由当前绑定的纹理对象维护的,而纹理对象是由一个无符号整数表示的.
- 使⽤用函数分配纹理理对象
指定纹理理对象的数量量 和 指针(指针指向⼀一个⽆无符号整形数组,由纹理理对象标识符填充)。
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);
纹理应用
纹理坐标
纹理坐标是作为0.0
到1.0
范围内的浮点值指定的.
纹理坐标被命名为s,f,r
和q
.
q
对应几何左边中的w
.
一维,二维,三维纹理坐标方式和纹理单元排列的纹理.
image.png- 一个纹理坐标会在每个顶点上应用一个纹理,然后OpenGL根据需要对纹理进行放大或缩小,将纹理贴图到几个图形上.
image.png四边形中应用一个二维纹理
image.png三角形应用纹理贴图中的一部分
纹理参数
纹理参数通过glTexParameter
进行设置的.
参数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);
基本过滤
纹理图像中的纹理单元和屏幕上的像素几乎从来不会形成一对一的关系.
根据几何图形的方向,一个特定的纹理
甚至有可能会贴到一些物体表面的同时就开始进行拉伸
和收缩
.
根据一个拉伸或收缩的纹理贴图计算颜色片段的过程称为纹理过滤
(texture fililtering
).
- 最邻近过滤
纹理坐标
总是根据纹理图像的纹理单元进行
求值和
绘图的.不管纹理坐标位于哪个纹理单元,
这个纹理单元的颜色就作为这个片段的
纹理颜色`.最邻近过滤最显著的特征就是当纹理被拉伸到特别大时所出现的大片斑驳庄像素.
- GL_TEXTURE_MAG_FILTER 放大
- GL_TEXTURE_MIN_FILTER 缩小
使用函数
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);
比较
image.png- 线性过滤色块比之前更柔和没有最邻近过滤那么显得突兀.
纹理环绕
在正常情况下,我们在0.0
到1.0
范围内知道纹理坐标
,使它与纹理贴图中的纹理单元形成映射关系,如果纹理坐落在这个范围之外,OpenGL
根据当前的纹理环绕模式(Wrapping Mode)处理这个问题.
使用函数
-
参数1
:GL_TEXTURE_1D、GL_TEXTURE_2D、GL_TEXTURE_3D -
参数2
:GL_TEXTURE_WRAP_S、GL_TEXTURE_T、GL_TEXTURE_R,针对s,t,r坐标 -
参数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)
image.png
案例
image.png综合应用
- 做一个完整的金字塔
1. 加载纹理stone.tga
//1.分配纹理对象
glGenTextures(1, &textureID);
//2.绑定纹理状态
glBindTexture(GL_TEXTURE_2D, textureID);
//3.将TGA文件加载到2D纹理
LoadTGATexture("stone.tga", GL_LINEAR_MIPMAP_NEAREST, GL_LINEAR, GL_CLAMP_TO_EDGE);
2.LoadTGATexture函数实现加载纹理和设置纹理状态
// 将TGA文件加载为2D纹理。
bool LoadTGATexture(const char *szFileName, GLenum minFilter, GLenum magFilter, GLenum wrapMode)
{
GLbyte *pBits;
int nWidth,nHeight,nComponents;
GLenum eFormat;
//读取像素
pBits = gltReadTGABits(szFileName, &nWidth, &nHeight, &nComponents, &eFormat);
if(!pBits){
return false;
}
//s设置纹理参数
//1 纹理环绕
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, wrapMode);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, wrapMode);
//2 过滤方式
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, minFilter);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, magFilter);
// 3.载入纹理
glTexImage2D(GL_TEXTURE_2D, 0, nComponents, nWidth, nHeight, 0, eFormat, GL_UNSIGNED_BYTE, pBits);
free(pBits);
if(minFilter == GL_LINEAR_MIPMAP_LINEAR ||
minFilter == GL_LINEAR_MIPMAP_NEAREST ||
minFilter == GL_NEAREST_MIPMAP_LINEAR ||
minFilter == GL_NEAREST_MIPMAP_NEAREST)
glGenerateMipmap(GL_TEXTURE_2D);
return true;
}
3. 指定纹理坐标 通过批次类构建金字塔
image.png金字塔图形解析
-
MultiTexCoord2f
添加纹理坐标Vertex3fv
添加顶点位置
-
先确定顶点位置,以金字塔为中心
-1 1
-
确定每一个点对应的纹理坐标 以
vbackLeft
为纹理坐标原点
void MakePyramid(GLBatch& pyramidBatch)
{
/***前情导入
2)设置纹理坐标
void MultiTexCoord2f(GLuint texture, GLclampf s, GLclampf t);
参数1:texture,纹理层次,对于使用存储着色器来进行渲染,设置为0
参数2:s:对应顶点坐标中的x坐标
参数3:t:对应顶点坐标中的y
(s,t,r,q对应顶点坐标的x,y,z,w)
pyramidBatch.MultiTexCoord2f(0,s,t);
3)void Vertex3f(GLfloat x, GLfloat y, GLfloat z);
void Vertex3fv(M3DVector3f vVertex);
向三角形批次类添加顶点数据(x,y,z);
pyramidBatch.Vertex3f(-1.0f, -1.0f, -1.0f);
*/
pyramidBatch.Begin(GL_TRIANGLES, 18, 1);
// 点位置
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 };
//vBackLeft
pyramidBatch.MultiTexCoord2f(0, 0.0f, 0.0f);
pyramidBatch.Vertex3fv(vBackLeft);
//vBackRight
pyramidBatch.MultiTexCoord2f(0, 1.0f, 0.0f);
pyramidBatch.Vertex3fv(vBackRight);
//vFrontRight
pyramidBatch.MultiTexCoord2f(0, 1.0f, 1.0f);
pyramidBatch.Vertex3fv(vFrontRight);
//三角形Y =(vFrontLeft,vBackLeft,vFrontRight)
//vFrontLeft
pyramidBatch.MultiTexCoord2f(0, 0.0f, 1.0f);
pyramidBatch.Vertex3fv(vFrontLeft);
//vBackLeft
pyramidBatch.MultiTexCoord2f(0, 0.0f, 0.0f);
pyramidBatch.Vertex3fv(vBackLeft);
//vFrontRight
pyramidBatch.MultiTexCoord2f(0, 1.0f, 1.0f);
pyramidBatch.Vertex3fv(vFrontRight);
// 金字塔前面
//三角形:(Apex,vFrontLeft,vFrontRight)
pyramidBatch.MultiTexCoord2f(0, 0.5f, 1.0f);
pyramidBatch.Vertex3fv(vApex);
pyramidBatch.MultiTexCoord2f(0, 0.0f, 0.0f);
pyramidBatch.Vertex3fv(vFrontLeft);
pyramidBatch.MultiTexCoord2f(0, 1.0f, 0.0f);
pyramidBatch.Vertex3fv(vFrontRight);
//金字塔左边
//三角形:(vApex, vBackLeft, vFrontLeft)
pyramidBatch.MultiTexCoord2f(0, 0.5f, 1.0f);
pyramidBatch.Vertex3fv(vApex);
pyramidBatch.MultiTexCoord2f(0, 1.0f, 0.0f);
pyramidBatch.Vertex3fv(vBackLeft);
pyramidBatch.MultiTexCoord2f(0, 0.0f, 0.0f);
pyramidBatch.Vertex3fv(vFrontLeft);
//金字塔右边
//三角形:(vApex, vFrontRight, vBackRight)
pyramidBatch.MultiTexCoord2f(0, 0.5f, 1.0f);
pyramidBatch.Vertex3fv(vApex);
pyramidBatch.MultiTexCoord2f(0, 1.0f, 0.0f);
pyramidBatch.Vertex3fv(vFrontRight);
pyramidBatch.MultiTexCoord2f(0, 0.0f, 0.0f);
pyramidBatch.Vertex3fv(vBackRight);
//金字塔后边
//三角形:(vApex, vBackRight, vBackLeft)
pyramidBatch.MultiTexCoord2f(0, 0.5f, 1.0f);
pyramidBatch.Vertex3fv(vApex);
pyramidBatch.MultiTexCoord2f(0, 0.0f, 0.0f);
pyramidBatch.Vertex3fv(vBackRight);
pyramidBatch.MultiTexCoord2f(0, 1.0f, 0.0f);
pyramidBatch.Vertex3fv(vBackLeft);
//结束批次设置
pyramidBatch.End();
}
4. 使用纹理替换矩阵着色器渲染
void RenderScene(void)
{
//1.灯泡 颜色值
static GLfloat vLightPos [] = {1.0f, 1.0f, 0.0f};
static GLfloat vWhite [] = {1.0f, 1.0f, 1.0f, 1.0f};
//2.清除缓冲区
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
//压榨出栈
modelViewMatrix.PushMatrix();
M3DMatrix44f mCamera;
cameraFrame.GetCameraMatrix(mCamera);
modelViewMatrix.MultMatrix(mCamera);
M3DMatrix44f mObjectFrame;
objectFrame.GetMatrix(mObjectFrame);
modelViewMatrix.MultMatrix(mObjectFrame);
//绑定纹理
glBindTexture(GL_TEXTURE_2D, textureID);
//纹理替换矩阵着色器
shaderManager.UseStockShader(GLT_SHADER_TEXTURE_REPLACE, transformPipeline.GetModelViewProjectionMatrix(), 0);
pyramidBatch.Draw();
modelViewMatrix.PopMatrix();
glutSwapBuffers();
}
严格来说,对纹理进行绑定并不是必须的,因为项目中就一个纹理,在加载纹理的时候就已经进行了绑定.
效果图
image.pngDemo地址