二维纹理映射一、实验目的和要求掌握纹理映射的基本原理,利用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中纹理映射所使用的纹理数据,既可以是程序生成的一组数据,也可以从外部文件中直接读取,参考示范代码完成以下两项内容:源码(// cgtest.cpp : Defines the entry point for the console application.//// test.cpp : 定义控制台应用程序的入口点。
//#include <stdio.h>#include "glut.h"#include <math.h>#include <Windows.h>//这是一个点的类,用于存储其中点的坐标class Point{public:int x, y;void setxy(int _x, int _y) {x = _x;y = _y;}};//点的数量static int POINTSNUM = 0;//用于存储点的集合,因为绘制的都是4个点的贝塞尔曲线,所以数组大小为4 static Point points[4];//初始化函数void init(void){glClearColor(1.0, 1.0, 1.0, 0); //设定背景为黑色glColor3f(0.0, 0.0, 0.0); //绘图颜色为白色glPointSize(2.0); //设定点的大小为2*2像素的glMatrixMode(GL_PROJECTION); // 设定合适的矩阵glLoadIdentity(); // 是一个无参的无值函数,其功能是用一个4×4的单位矩阵来替换当前矩阵,实际上就是对当前矩阵进行初始化。
也就是说,无论以前进行了多少次矩阵变换,在该命令执行后,当前矩阵均恢复成一个单位矩阵,即相当于没有进行任何矩阵变换状态gluOrtho2D(0.0, 600.0, 0.0, 480.0); //平行投影,四个参数分别是x,y范围}//绘制点void setPoint(Point p) {glBegin(GL_POINTS);glVertex2f(p.x, p.y);glEnd();glFlush();}// 绘制直线void setline(Point p1, Point p2) {glBegin(GL_LINES);glVertex2f(p1.x, p1.y);glVertex2f(p2.x, p2.y);glEnd();glFlush();}// 绘制贝塞尔曲线Point setBezier(Point p1, Point p2, Point p3, Point p4, double t) {Point p;double a1 = pow((1 - t), 3);double a2 = pow((1 - t), 2) * 3 * t;double a3 = 3 * t*t*(1 - t);double a4 = t*t*t;p.x = a1*p1.x + a2*p2.x + a3*p3.x + a4*p4.x;p.y = a1*p1.y + a2*p2.y + a3*p3.y + a4*p4.y;return p;}//display函数void display(){glClear(GL_COLOR_BUFFER_BIT);glFlush();}// 鼠标事件void mymouseFunction(int button, int state, int x, int y) {if (state == GLUT_DOWN) // 如果鼠标按下,不区分左右键的{points[POINTSNUM].setxy(x, 480 - y); // 这里求鼠标点的坐标的时候// 设置点的颜色,绘制点glColor3f(1.0, 0.0, 0.0);setPoint(points[POINTSNUM]);// 设置线的颜色,绘制线glColor3f(1.0, 0.0, 0.0);if (POINTSNUM > 0) setline(points[POINTSNUM - 1], points[POINTSNUM]);//如果达到了4个绘制贝塞尔曲线,并在之后给计数器清零if (POINTSNUM == 3) {//绘制贝塞尔曲线glColor3f(0.0, 0.0, 1.0); // 设定贝塞尔曲线的颜色Point p_current = points[0]; //设为起点for (double t = 0.0; t <= 1.0; t += 0.05){Point P = setBezier(points[0], points[1], points[2], points[3], t);setline(p_current, P);p_current = P;}POINTSNUM = 0;}else {POINTSNUM++;}}}int main(int argc, char *argv[]){glutInit(&argc, argv); //固定格式glutInitDisplayMode(GLUT_RGB | GLUT_SINGLE); //缓存模式glutInitWindowSize(600, 480); //显示框的大小glutInitWindowPosition(100, 100); //确定显示框左上角的位置glutCreateWindow("");init(); // 初始化glutMouseFunc(mymouseFunction); // 添加鼠标事件glutDisplayFunc(display); // 执行显示glutMainLoop(); //进人GLUT事件处理循环return 0;}五、运行截图。