资源描述
实验五 纹理映射实验
实验项目性质:设计性实验
所属课程名称:3D游戏图形学
实验计划学时:3学时
一、 实验目的和要求
掌握纹理映射的基本原理,利用VC++ OpenGL实现纹理映射技术。
二、 实验原理
纹理映射是真实感图形制作的一个重要部分,运用纹理映射可以方面地制作真实感图形,而不必花更多的时间去考虑物体的表面纹理。如一张木制桌子其表面的木纹是不规范的,看上去又是那么自然,如果在图形制作中不用纹理映射,那么只是这张桌面纹理的设计,就要花费很大精力,而且设计结果也未必能像现实中那么自然。如果运用纹理映射就非常方便,可以用扫描仪将这样的一张桌子扫成一个位图。然后的具体的操作中,只需把桌面形状用多边形画出来,把桌面纹理贴上去就可以了。
另外,纹理映射能够在多边形进行变换时仍保证纹理的图案与多边形保持一致性。例如,以透视投影方式观察墙面时,远端的砖会变小,而近处的砖就会大一些。
此外,纹理映射也可以用于其他方面。例如,使用一大片植被的图像映射到一些连续的多边形上,以模拟地貌,或者以大理石、木纹等自然物质的图像作为纹理映射到相应的多边形上,作为物体的真实表面。
在OpenGL中提供了一系列完整的纹理操作函数,用户可以用它们构造理想的物体表面,可以对光照物体进行处理,使其映射出所处环境的景象,可以用不同方式应用到曲面上,而且可以随几何物体的几何属性变换而变化,从而使制作的三维场景和三维物体更真实更自然。
在OpenGL中要实现纹理映射,需要经历创建纹理、指定纹理应用方式、启用纹理映射、使用纹理坐标和几何坐标绘制场景几个过程。
用于指定一维、二维和三维纹理的函数分别为:
Void glTexImage1D(GLenum target, Glint level, Glint components, GLsizei width, Glint border, GLenum format, GLenum type, const GLvoid *texels);
Void glTexImage2D(GLenum target, Glint level, Glint components, GLsizei width, GLsizei height, GLint border, GLenum format, GLenum type, const GLvoid *texels);
Void glTexImage3D(GLenum target, Glint level, Glint components, GLsizei width, GLsizei height, GLsizei depth, Glint border, GLenum format, GLenum type, const GLvoid *texels);
其中,参数target取值一般为GL_TEXTURE_1D, GL_TEXTURE_2D和GL_TEXTURE_3D,分别与一维、二维和三维的纹理相对应。参数Level表示纹理多分辨率层数,通常取值为0,表示只有一种分辨率。参数components的可能取值为1~4的整数以及多种符号常量(如GL_RGBA),表示纹理元素中存储的哪些分量(RGBA颜色、深度等)在纹理映射中被使用,1表示使用R颜色分量,2表示使用R和A颜色分量,3表示使用RGB颜色分量,4表示使用RGBA颜色分量。参数width,height,depth分别指定纹理的宽度、高度、深度。参数format和type表示给出的图像数据的数据格式和数据类型,这两个参数的取值都是符号常量(比如format指定为GL_RGBA,type指定为GL_UNSIGNED_BYTE,参数texels指向内存中指定的纹理图像数据。
在定义了纹理之后,需要启用纹理的函数:
glEnable(GL_TEXTURE_1D);
glEnable(GL_TEXTURE_2D);
glEnable(GL_TEXTURE_3D);
在启用纹理之后,需要建立物体表面上点与纹理空间的对应关系,即在绘制基本图元时,在glVertex函数调用之前调用glTexCoord函数,明确指定当前顶点所对应的纹理坐标,例如:
glBegin(GL_TRIANGLES);
glTexCoord2f(0.0, 0.0); glVertex2f(0.0, 0.0);
glTexCoord2f(1.0, 1.0); glVertex2f(15.0, 15.0);
glTexCoord2f(1.0, 0.0); glVertex2f(30.0, 0.0);
glEnd();
其图元内部点的纹理坐标利用顶点处的纹理坐标采用线性插值的方法计算出来。
在OpenGL中,纹理坐标的范围被指定在[0,1]之间,而在使用映射函数进行纹理坐标计算时,有可能得到不在[0,1]之间的坐标。此时OpenGL有两种处理方式,一种是截断,另一种是重复,它们被称为环绕模式。在截断模式(GL_CLAMP)中,将大于1.0的纹理坐标设置为1.0,将小于0.0的纹理坐标设置为0.0。在重复模式(GL_REPEAT)中,如果纹理坐标不在[0,1]之间,则将纹理坐标值的整数部分舍弃,只使用小数部分,这样使纹理图像在物体表面重复出现。例如,使用下面的函数:
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP);
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
分别指定二维纹理中s坐标采用截断或重复处理方式。
另外,在变换和纹理映射后,屏幕上的一个像素可能对应纹理元素的一小部分(放大),也可能对应大量的处理元素(缩小)。在OpenGL中,允许指定多种方式来决定如何完成像素与纹理元素对应的计算方法(滤波)。比如,下面的函数可以指定放大和缩小的滤波方法:
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
其中,glTexParameteri函数的第一个参数指定使用的是一维、二维或三维纹理;第二个参数为GL_TEXTURE_MAG_FILTER或GL_TEXTURE_MIN_FILTER,指出要指定缩小还是放大滤波算法;最后一个参数指定滤波的方法。
补充:透视投影函数
void gluPerspective(GLdouble fovy, GLdouble aspect, GLdouble zNear, GLdouble zFar);
它也创建一个对称透视视景体,但它的参数定义于前面的不同。其操作是创建一个对称的透视投影矩阵,并且用这个矩阵乘以当前矩阵。参数fovy定义视野在X-Z平面的角度,范围是[0.0,180.0];参数aspect是投影平面宽度与高度的比率;参数zNear和Far分别是远近裁剪面沿Z负轴到视点的距离,它们总为正值。
三、 实验内容
在OpenGL中纹理映射所使用的纹理数据,既可以是程序生成的一组数据,也可以从外部文件中直接读取,参考示范代码完成以下两项内容:
1. 利用直接创建纹理的方法生成二维纹理并映射到四边形上。
参考代码:
void makeImage(void)
{
int i, j, r,g,b;
for (i = 0; i < ImageWidth; i++)
{
for (j = 0; j < ImageHeight; j++)
{
r=(i*j)%255;
g=(4*i)%255;
b=(4*j)%255;
Image[i][j][0] = (GLubyte) r;
Image[i][j][1] = (GLubyte) g;
Image[i][j][2] = (GLubyte) b;
}
}
}
void myinit(void)
{
glClearColor (0.0, 0.0, 0.0, 0.0);
glEnable(GL_DEPTH_TEST);
glDepthFunc(GL_LESS);
// 生成纹理数据
makeImage();
// 设置像素存储模式
glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
// 定义二维纹理映射
glTexImage2D(…… );
// 定义纹理映射参数
glTexParameterf(……);
glTexParameterf(……);
glTexParameterf(……);
glTexParameterf(……);
// 启用二维纹理
glEnable(GL_TEXTURE_2D);
glShadeModel(GL_FLAT);
}
void display(void)
{
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
//绘制四边形,并完成纹理映射
……
glFlush();
}
void myReshape(GLsizei w, GLsizei h)
{
……
}
void main(int argc, char* argv[])
{
……
}
2. 从外部文件中直接读取纹理实现正方体每个面的纹理映射,并使正方体转动。整个过程需要三个步骤:创建纹理、启用纹理映射和使用纹理坐标和几何坐标绘制,下面我们对三个过程进行阐述,并给出参考代码。
1)创建纹理对象并绑定纹理
纹理创建即在内存中创建保存纹理数据的数组,一般是先读入一个图像文件,将图像文件的RGBA信息存入我们创建的纹理空间中,当然图像的位图不同,创建的纹理空间结构也会有所不同。为了更加简单易懂地实现这个过程,我们使用未压缩的纹理。
代码:
GLuinttexture[1]; //创建一个纹理空间
AUX_RGBImageRec *LoadBMP(CHAR *Filename) //载入位图图像
{
FILE *File=NULL; //文件句柄
if(!Filename) //确保文件名已提供
{
return NULL;
}
File=fopen(Filename, "r"); //尝试打开文件
if(File)
{
fclose(File); //关闭文件
return auxDIBImageLoadA(Filename); //载入位图并返回指针
}
return NULL; //如果载入失败,返回NULL
}
int LoadGLTextures() //载入位图并转换成纹理
{
int Status=FALSE; //状态指示器
AUX_RGBImageRec *TextureImage[1]; //创建纹理的存储空间
memset(TextureImage, 0, sizeof(void *)*1);//初始化
//载入位图,检查有无错误,如果位图没找到则退出
if(TextureImage[0]=LoadBMP("data.bmp"))
{
Status=TRUE;
glGenTextures(1,&texture[0]); //创建纹理
//使用来自位图数据生成的典型纹理
glBindTexture(GL_TEXTURE_2D, texture[0]); //生成2D纹理
glTexImage2D(GL_TEXTURE_2D,0,3,TextureImage[0]->sizeX,TextureImage[0]->sizeY, 0, GL_RGB, GL_UNSIGNED_BYTE, TextureImage[0]->data);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
}
if(TextureImage[0]) //纹理是否存在
{
if(TextureImage[0]->data) //纹理图像是否存在
{
free(TextureImage[0]->data); //释放纹理图像占用的内存
}
free(TextureImage[0]); //释放图像结构
}
return Status; //返回Status
}
2)启用纹理映射操作,初始化相关参数
在OpenGL中使用纹理映射之前,必须打开纹理映射。
int InitGL(GLvoid)
{
if(!LoadGLTextures()) //调用纹理载入子例程
{
return FALSE;
}
glEnable(GL_TEXTURE_2D); //启用纹理映射
glShadeModel(GL_SMOOTH); //启用阴影平滑
glClearColor(0.0f, 0.0f, 0.0f, 0.5f); //黑色背景
glClearDepth(1.0f); //设置深度缓存
glEnable(GL_DEPTH_TEST); //启用深度测试
return TRUE;
}
3)使用纹理坐标和几何坐标绘制
void DrawGLScene(void)
{
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);
// 选择纹理
glBindTexture(GL_TEXTURE_2D,texture[0]);
//绘制一个正方体,给每个面贴上纹理,并使之转动
glBegin(GL_QUADS);
……
glEnd();
xrot+=0.3f;
yrot+=0.2f;
zrot+=0.4f;
}
展开阅读全文