OpenGL可以把纹理映射到指定的图形的表面上。简单一点的,就是给平面映射纹理,比如一个四边形,一个长方体的6个面,都可以指定位图作为纹理映射到各个面上。
关于将一个位图作为纹理映射到某个或者多个面上,可以学习Jeff Molofee的OpenGL系列教程。
对于指定的多个纹理,要根据自己的需要映射到不同的面上,需要对位图创建一个数组,用来存储位图的名称,然后在初始化OpenGL的时候,可以读取这些位图,然后生成多个纹理存储到一个纹理数组中,接着就可以指定绘制的某个面,对该指定的面进行纹理映射。
下面,在的Jeff Molofee教程的第六课的基础上,实现对6个面分别进行不同的纹理映射。
准备工作就是制作6幅不同的位图,如图所示:
关键代码及其说明如下。
创建全局纹理数组
GLuint texture[6]; // 创建一个全局的纹理数组,用来存储将位图转换之后得到的纹理,对应于立方体的6个面
加载位图文件
加载位图,也就是把位图读取到内存空间,实现纹理的创建,加载位图的函数说明一下:
AUX_RGBImageRec *LoadBMP(char *Filename) // 根据位图文件的名称进行加载
{
FILE *File=NULL; // 文件指针
if (!Filename) // 如果没有指定位图文件名称就返回NULL
{
return NULL;
}
File=fopen(Filename,"r"); // 根据指定的位图文件名称,打开该位图文件
if (File) // 如果位图文件存在
{
fclose(File); // 因为只是需要判断问题是否存在,而不需要对位图文件进行写操作,所以关闭位图文件
return auxDIBImageLoad(Filename); // 其实,只需要一个真正存在的位图文件的名称,实现加载位图文件,并返回一个指针
}
return NULL; // 位图文件加载失败就返回NULL
}
上面实现加载位图的函数中,AUX_RGBImageRec是glaux.h中定义的类型,该类型的定义如下所示:
/*
** RGB Image Structure
*/
typedef struct _AUX_RGBImageRec {
GLint sizeX, sizeY;
unsigned char *data;
} AUX_RGBImageRec;
首先,AUX_RGBImageRec类型是一个RGB图像结构类型。该结构定义了三个成员:
sizeX —— 图像的宽度;
sizeY —— 图像的高度;
data; —— 图形所包含的数据,其实也就是该图形在内存中的像素数据的一个指针。
AUX_RGBImageRec类型的变量描述了一幅图像的特征。
上述函数中,调用了glaux.h库文件中的auxDIBImageLoad函数,其实它是一个宏,函数原型为auxRGBImageLoadW(LPCWSTR)或者auxRGBImageLoadA(LPCSTR),可以在该库文件中找到它的定义,如下所示:
/* AUX_RGBImageRec * APIENTRY auxRGBImageLoad(LPCTSTR); */
#ifdef UNICODE
#define auxRGBImageLoad auxRGBImageLoadW
#else
#define auxRGBImageLoad auxRGBImageLoadA
#endif
AUX_RGBImageRec * APIENTRY auxRGBImageLoadA(LPCSTR);
AUX_RGBImageRec * APIENTRY auxRGBImageLoadW(LPCWSTR);
#ifdef UNICODE
#define auxDIBImageLoad auxDIBImageLoadW
#else
#define auxDIBImageLoad auxDIBImageLoadA
#endif
AUX_RGBImageRec * APIENTRY auxDIBImageLoadA(LPCSTR);
AUX_RGBImageRec * APIENTRY auxDIBImageLoadW(LPCWSTR);
宏auxDIBImageLoad实现的功能就是:根据指定的位图名称,将该位图的信息加载到内存中,以便用来创建成为纹理。
创建纹理并加载纹理
用于创建并加载纹理的函数为LoadGLTextures,实现如下所示:
int LoadGLTextures() // 根据加载的位图创建纹理
{
int Status=FALSE; // 指示纹理创建是否成功的标志
AUX_RGBImageRec *TextureImage[6]; // 创建一个纹理图像数组,这里指定数组大小为6
memset(TextureImage,0,sizeof(void *)*6); // 初始化纹理图像数组,为其分配内存
char *pictures[] = {// 创建一个位图名称数组,对应6幅位图
"Data/No1.bmp",
"Data/No2.bmp",
"Data/No3.bmp",
"Data/No4.bmp",
"Data/No5.bmp",
"Data/No6.bmp"
};
for(int i=0; i<6; i++)// 遍历位图名称数组,根据位图名称分别生成
{
if (TextureImage[i]=LoadBMP(pictures[i]))// 加载位图i成功,修改状态标志变量Status为TRUE
{
Status=TRUE;
glGenTextures(1, &texture[i]); // 为第i个位图创建纹理
glBindTexture(GL_TEXTURE_2D, texture[i]);// 将生成的纹理的名称绑定到指定的纹理上
glTexImage2D(GL_TEXTURE_2D, 0, 3, TextureImage[i]->sizeX, TextureImage[i]->sizeY, 0, GL_RGB, GL_UNSIGNED_BYTE, TextureImage[i]->data);
glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR);
}
if (TextureImage[i]) // 释放位图数组占用的内存空间
{
if (TextureImage[i]->data)
{
free(TextureImage[i]->data);
}
free(TextureImage[i]);
}
}
return Status; // 创建纹理并加载,返回成功或者失败的标志Status
}
上述函数是创建和加载纹理的核心实现。
1、glGenTextures函数
其中,调用了glGenTextures函数,查看MSDN可以看到,声明如下所示:
void glGenTextures(
GLsizein,
GLuint *textures
);
函数参数的含义:
n—— 生成的纹理的名称的个数;
textures—— 生成的纹理名称所存储位置的指针,也就是一个纹理数组的内存地址,或者说是数组首元素的内存地址。
函数被调用,会生成一系列纹理的名字,并存储到指定的数组中。
2、glBindTexture函数
glBindTexture函数实现了将调用glGenTextures函数生成的纹理的名字绑定到对应的目标纹理上。该函数的声明如下所示:
void glBindTexture(
GLenumtarget,
GLuinttexture
);
函数参数的含义:
target—— 纹理被绑定的目标,它只能取值GL_TEXTURE_1D或者GL_TEXTURE_2D;
texture—— 纹理的名称,并且,该纹理的名称在当前的应用中不能被再次使用。
3、glTexImage2D函数
调用glTexImage2D函数,用来指定二维纹理图像。该函数的声明如下所示:
void glTexImage2D(
GLenumtarget,
GLintlevel,
GLintcomponents,
GLsizeiwidth,
GLsizeiheight,
GLintborder,
GLenumformat,
GLenumtype,
const GLvoid*pixels
);
函数参数的含义:
target—— 指定目标纹理,必须为GL_TEXTURE_2D;
level—— 指定图像级别的编号,0表示基本图像,其它可以参考MSDN;
components—— 纹理中颜色组件的编号,可是是1或2或3或4;
width—— 纹理图像的宽度;
height—— 纹理图像的高度;
border—— 纹理图像的边框宽度,必须是0或1;
format—— 指定像素数据的格式,一共有9个取值:GL_COLOR_INDEX、GL_RED、GL_GREEN、GL_BLUE、GL_ALPHA、GL_RGB、GL_RGBA、GL_BGR_EXT、GL_BGRA_EXT、GL_LUMINANCE、GL_LUMINANCE_ALPHA ,具体含义可以参考MSDN;
type—— 像素数据的数据类型,取值可以为GL_UNSIGNED_BYTE, GL_BYTE, GL_BITMAP, GL_UNSIGNED_SHORT, GL_SHORT, GL_UNSIGNED_INT, GL_INT, and GL_FLOAT;
pixels—— 内存中像素数据的指针。
4、glTexParameteri函数
glTexParameteri函数或者glTexParameterf函数用来设置纹理参数,声明如下所示:
void glTexParameterf(
GLenumtarget,
GLenumpname,
GLfloatparam
);
void glTexParameteri(
GLenumtarget,
GLenumpname,
GLintparam
);
函数参数的含义:
target—— 目标纹理,必须为GL_TEXTURE_1D或GL_TEXTURE_2D;
pname—— 用来设置纹理映射过程中像素映射的问题等,取值可以为:GL_TEXTURE_MIN_FILTER、GL_TEXTURE_MAG_FILTER、GL_TEXTURE_WRAP_S、GL_TEXTURE_WRAP_T,详细含义可以查看MSDN;
param—— 实际上就是pname的值,可以参考MSDN。
另外,该类函数还有两个:
void glTexParameterfv(
GLenumtarget,
GLenumpname,
const GLfloat*params
);
void glTexParameteriv(
GLenumtarget,
GLenumpname,
const GLint*params
);
上述程序中调用如下:
glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR);
功能就是实现线形滤波的功能,当纹理映射到图形表面以后,如果因为其它条件的设置导致纹理不能更好地显示的时候,进行过滤,按照指定的方式进行显示,可能会过滤掉显示不正常的纹理像素。
纹理映射过程
纹理映射的过程是在DrawGLScene函数中实现的,也就是在绘制图形的过程中,直接进行我呢里映射,或者称为,为指定的平面贴纹理,DrawGLScene函数实现如下所示:
int DrawGLScene(GLvoid)
{
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glLoadIdentity();
glTranslatef(0.0f,0.0f,-5.0f);
glRotatef(xrot,1.0f,0.0f,0.0f);
glRotatef(yrot,0.0f,1.0f,0.0f);
glRotatef(zrot,0.0f,0.0f,1.0f);
// Front Face
glBindTexture(GL_TEXTURE_2D, texture[0]);// 选择第一个纹理texture[0],进行贴纹理
glBegin(GL_QUADS);
glTexCoord2f(0.0f, 0.0f); glVertex3f(-1.0f, -1.0f, 1.0f);
glTexCoord2f(1.0f, 0.0f); glVertex3f( 1.0f, -1.0f, 1.0f);
glTexCoord2f(1.0f, 1.0f); glVertex3f( 1.0f, 1.0f, 1.0f);
glTexCoord2f(0.0f, 1.0f); glVertex3f(-1.0f, 1.0f, 1.0f);
glEnd();
// Back Face
glBindTexture(GL_TEXTURE_2D, texture[1]);// 选择第二个纹理texture[1],进行贴纹理
glBegin(GL_QUADS);
glTexCoord2f(1.0f, 0.0f); glVertex3f(-1.0f, -1.0f, -1.0f);
glTexCoord2f(1.0f, 1.0f); glVertex3f(-1.0f, 1.0f, -1.0f);
glTexCoord2f(0.0f, 1.0f); glVertex3f( 1.0f, 1.0f, -1.0f);
glTexCoord2f(0.0f, 0.0f); glVertex3f( 1.0f, -1.0f, -1.0f);
glEnd();
// Top Face
glBindTexture(GL_TEXTURE_2D, texture[2]);// 选择第三个纹理texture[2],进行贴纹理
glBegin(GL_QUADS);
glTexCoord2f(0.0f, 1.0f); glVertex3f(-1.0f, 1.0f, -1.0f);
glTexCoord2f(0.0f, 0.0f); glVertex3f(-1.0f, 1.0f, 1.0f);
glTexCoord2f(1.0f, 0.0f); glVertex3f( 1.0f, 1.0f, 1.0f);
glTexCoord2f(1.0f, 1.0f); glVertex3f( 1.0f, 1.0f, -1.0f);
glEnd();
// Bottom Face
glBindTexture(GL_TEXTURE_2D, texture[3]);// 选择第四个纹理texture[3],进行贴纹理
glBegin(GL_QUADS);
glTexCoord2f(1.0f, 1.0f); glVertex3f(-1.0f, -1.0f, -1.0f);
glTexCoord2f(0.0f, 1.0f); glVertex3f( 1.0f, -1.0f, -1.0f);
glTexCoord2f(0.0f, 0.0f); glVertex3f( 1.0f, -1.0f, 1.0f);
glTexCoord2f(1.0f, 0.0f); glVertex3f(-1.0f, -1.0f, 1.0f);
glEnd();
// Right face
glBindTexture(GL_TEXTURE_2D, texture[4]);// 选择第五个纹理texture[4],进行贴纹理
glBegin(GL_QUADS);
glTexCoord2f(1.0f, 0.0f); glVertex3f( 1.0f, -1.0f, -1.0f);
glTexCoord2f(1.0f, 1.0f); glVertex3f( 1.0f, 1.0f, -1.0f);
glTexCoord2f(0.0f, 1.0f); glVertex3f( 1.0f, 1.0f, 1.0f);
glTexCoord2f(0.0f, 0.0f); glVertex3f( 1.0f, -1.0f, 1.0f);
glEnd();
// Left Face
glBindTexture(GL_TEXTURE_2D, texture[5]);// 选择第六个纹理texture[5],进行贴纹理
glBegin(GL_QUADS);
glTexCoord2f(0.0f, 0.0f); glVertex3f(-1.0f, -1.0f, -1.0f);
glTexCoord2f(1.0f, 0.0f); glVertex3f(-1.0f, -1.0f, 1.0f);
glTexCoord2f(1.0f, 1.0f); glVertex3f(-1.0f, 1.0f, 1.0f);
glTexCoord2f(0.0f, 1.0f); glVertex3f(-1.0f, 1.0f, -1.0f);
glEnd();
xrot+=0.3f;
yrot+=0.2f;
zrot+=0.4f;
return TRUE;
}
因为,通过前面的过程,已经将位图加载并创建和加载纹理成功,纹理数组已经存在于内存之中,调用上述函数实现纹理映射,即,从内存中取出指定的纹理,将其映射到立方体的指定的面上。
上述函数中调用了glTexCoord2f函数,设置纹理坐标,该函数的声明如下所示:
void glTexCoord2f(
GLfloats,
GLfloatt
);
glTexCoord2f 的第一个参数是X坐标,当s=0.0f 时是纹理的左侧,s=0.5f 时是纹理的中点,s=1.0f 时是纹理的右侧。 glTexCoord2f 的第二个参数是Y坐标,t=0.0f 是纹理的底部,t=0.5f 是纹理的中点, t=1.0f 是纹理的顶部。
上述函数在为前面那个面映射纹理的时候调用如下:
// Front Face
glBindTexture(GL_TEXTURE_2D, texture[0]);
glBegin(GL_QUADS);
glTexCoord2f(0.0f, 0.0f); glVertex3f(-1.0f, -1.0f, 1.0f);
glTexCoord2f(1.0f, 0.0f); glVertex3f( 1.0f, -1.0f, 1.0f);
glTexCoord2f(1.0f, 1.0f); glVertex3f( 1.0f, 1.0f, 1.0f);
glTexCoord2f(0.0f, 1.0f); glVertex3f(-1.0f, 1.0f, 1.0f);
glEnd();
其中:
glTexCoord2f(0.0f, 0.0f);表示将纹理texture[0]的左下角坐标(0.0f, 0.0f)映射到立方体前面那个面的顶点(-1.0f, -1.0f, 1.0)上;
glTexCoord2f(1.0f, 0.0f);表示将纹理texture[0]的右下角坐标(1.0f, 0.0f)映射到立方体前面那个面的顶点(1.0f, -1.0f, 1.0f)上;
glTexCoord2f(1.0f, 1.0f);表示将纹理texture[0]的右上角坐标(1.0f, 1.0f)映射到立方体前面那个面的顶点(1.0f, 1.0f, 1.0f)上;
glTexCoord2f(0.0f, 1.0f);表示将纹理texture[0]的左上角坐标(0.0f, 1.0f)映射到立方体前面那个面的顶点(-1.0f, 1.0f, 1.0f)上。
这样,纹理texture[0]就被映射到了立方体前面那个面上。
纹理映射结果
为了使立方体能够运动起来,对立方体进行累的旋转变换,而且,定义了三个全局变量:
GLfloat xrot; // 沿着x旋转的变量
GLfloat yrot; // 沿着y旋转的变量
GLfloat zrot; // 沿着z旋转的变量
初始化都为0,然后,在每次调用DrawGLScene函数的时候,改变x、y、z的分量值,在DrawGLScene函数中如下所示:
xrot+=0.3f;
yrot+=0.2f;
zrot+=0.4f;
在DrawGLScene函数中还要执行旋转变换:
glRotatef(xrot,1.0f,0.0f,0.0f);
glRotatef(yrot,0.0f,1.0f,0.0f);
glRotatef(zrot,0.0f,0.0f,1.0f);
每次重绘都在改变旋转轴,所以我们绘制的立方体能够动起来,看到各个进行纹理映射的面的效果,如图所示