
计算机科学与技术学院
班 级: 0 9 1 1 班
学 号: U200915XXX
姓 名: XXXX
指导教师:徐海银
完成日期: 2011/12/06
目 录
实验一
实验目的与要求------------------------------------1
实验内容与分析------------------------------------1
实验结果显示---------------------------------------3
实验体会---------------------------------------------8
源代码------------------------------------------------8
实验二
实验目的与要求------------------------------------18
实验内容与分析------------------------------------18
实验结果显示---------------------------------------19
实验体会---------------------------------------------19
源代码-----------------------------------------------20
实验一(基本图元绘制)
实验目的与要求
(1)理解glut程序框架; (2)理解窗口到视区的变换 ;
(3)理解OpenGL实现动画的原理; (4)添加代码实现中点Bresenham算法画直线;
(5)添加代码实现改进Bresenham算法画直线;
(6)添加代码实现圆的绘制(可以适当对框架坐标系进行修改);
(7)适当修改代码实现具有宽度的图形(线刷子或方刷子)。
实验内容与分析
①中点Bresenham 算法画直线思想:仅考虑0≤k≤1,由于最大位移方向为x,因此,每次x方向上加1,而y方向上或加1或加0。判别式初值d= dx-2*dy,若d<0,则(x,y)更新为(x+1,y+1),d更新为d+2*dx-2*dy;否则(x,y)更新为(x+1,y),d更新为d -2*dy。
改进Bresenham算法画直线思想:判别式初值e= -dx,e每次加2*dy,判断e的符号,若e>0,则(x,y)更新为(x+1,y+1),同时将e更新为e-2*dx;否则(x,y)更新为(x+1,y)。
Bresenham 算法绘制圆的算法思想:若考虑第一象限内x∈「0,R/」的1/8圆弧,此时最大位移方向为x,因此,每次x方向上走一步,而y方向上或减1或减0。判别式初值为d= 1-R,若d<0,则先将d更新为d+2*x+3 ,再将(x,y)更新为(x+1,y);否则先将d更新为d+2*(x-y)+5,再将(x,y)更新为(x+1,y-1)。但是,当圆心不在原点时,不妨设圆心为(x0,y0),则此时判别式初值为d= 1-R,若d<0,则先将d更新为d+2*(x-x0)+3;否则先将d更新为d+2*((x-x0)-(y-y0))+5。而且此时8个对称点分别为(x,y)、(x,2*y0-y) 、(y-y0+x0,y0+x0-x)、(x0+y0-y,y0+x0-x)、(2*x0-x,2*y0-y)、(2*x0-x,y)、(x0+y0-y,y0+x-x0)、(x0+y-y0,y0+x-x0)。即在画1/8圆弧上任一点时,需要同时画出另外7个点,最后即可得到整个圆。
②由于要实现动画,所以需要创建一个循环,在每次调用显示回调函数之前给当前像素点着色,使其看起来像是在直线上连续的画出一个个像素点。为了不断的调用显示回调函数,需要利用函数glutTimerFunc(unsigned int msecs,(*func) (int value),int value),指定一个定时器回调函数,即经过msecs毫秒后由GLUT调用指定的函数,并将value值传递给他。被定时器调用的函数原型为void TimerFunction(int value),注意,该函数与其他的回调函数不一样的地方在于该函数只会被激发一次。所以为了实现连续的动画,必须在定时器函数中再次重新设置定时器回调函数。
③线刷子。当用线刷子处理线宽时,若为竖直刷子,除了需要画出(x,y)像素点之外,还需要画出像素点(x,y+1)和(x,y-1);若为水平刷子,除了需要画出(x,y)像素点之外,还需要画出像素点(x-1,y)和(x+1,y)。本实验中仅以带竖直刷子的中点Bresenham 算法画直线为例。
④程序操作。程序运行后,会生成一个25X25的网格,每一个网格即代表一个像素点。共有5种绘制模式:1、DDA算法画直线(起点(0,0),终点(20,15));2 、中点Bresenham算法画直线(起点(0,0),终点(20,15));3 、改进Bresenham算法画直线(起点(0,0),终点(20,15));4 、八分法绘制圆(圆点(12,12),半径10);5 、带线刷子的中点Bresenham算法画直线(起点(0,0),终点(20,15))。分别通过按键盘上的数字键1~5来调用控制。在画每一个像素点时,同时会显示当前各点坐标和判别式的值。
实验结果显示
(图1.1,DDA画线算法,各点坐标、以及最终画出的直线)
(图1.2,中点Bresenham算法画线,各点坐标、以及最终画出的直线)
(图1.3,改进的Bresenham算法画线,各点坐标、以及最终画出的直线)
(图1.4,中点Bresenham算法画圆,各点坐标、以及最终画出的图形)
(图1.5,带竖直线刷子的中点Bresenham算法,各点坐标、以及最终画出的直线)
实验体会
本次实验是我第一次用OpenGL编程,很不习惯它的库函数,因为库函数名太长了,而且由于本人英语不太好,所以显得更难记住函数名。不过感觉OpenGL功能还是很强大,图形显示效果很不错。通过本次实验,让我进一步理解了几种基本的图形生成算法,包括DDA画线,中点Bresenhama画线,改进的Bresenhama画线,Bresenhama画圆,以及如何使用线刷子处理线宽。本来如果让我写出完整的代码,会比较困难,所以很感谢助教老师,给我们展示了实现动画的部分代码,我们只需要稍加修改并添加一些代码即可。
源代码(完整代码及注释见附件)
#include #include #include "stdio.h" int m_PointNumber = 0; //动画时绘制点的数目 /* 不同的m_DrawMode值表示不同的绘制模式。 1 DDA算法画直线;2 中点Bresenham算法画直线;3 改进Bresenham算法画直线;4 八分法绘制圆;5 带线刷子的中点Bresenham算法画直线。初始时默认为1*/ int m_DrawMode = 1; void DrawCordinateLine(void) //绘制坐标线 { int i = 0 ; glColor3f(0.0f, 0.0f ,0.0f); //坐标线为黑色 glBegin(GL_LINES); for (i=10;i<=250;i=i+10) { glVertex2f((float)(i), 0.0f); glVertex2f((float)(i), 250.0f); glVertex2f(0.0f, (float)(i)); glVertex2f(250.0f, (float)(i)); } glEnd(); } void putpixel(GLsizei x, GLsizei y) //绘制一个像素点,这里用一个正方形表示一个点 { glRectf(10*x,10*y,10*x+10,10*y+10); } /*DDA画线算法。参数说明:起点坐标(x0,y0) , 终点坐标(x1,y1) , num 扫描转换时从起点开始输出的点的数目,用于动画*/ void DDACreateLine(GLsizei x0, GLsizei y0, GLsizei x1, GLsizei y1, GLsizei num) { glColor3f(1.0f,0.0f,0.0f); //设置颜色 if(num == 1) //对画线动画进行控制 printf("DDA画线算法:各点坐标\\n"); else if(num==0) return; //以下为画线算法的实现 GLsizei dx,dy,epsl,k; GLfloat x,y,xIncre,yIncre; dx = x1-x0; dy = y1-y0; x = x0; y = y0; if(abs(dx) > abs(dy)) epsl = abs(dx); else epsl = abs(dy); xIncre = (float)dx / epsl ; yIncre = (float)dy / epsl ; for(k = 0; k<=epsl; k++) { putpixel((int)(x+0.5), (int)(y+0.5)); if (k>=num-1) { printf("x=%f,y=%f,取整后 x=%d,y=%d,num=%d\\n", x, y, (int)(x+0.5),(int)(y+0.5),num); break; } x += xIncre; y += yIncre; i(x >= 25 || y >= 25) break; } } /*中点Bresenham算法画直线(0<=k<=1)。参数说明:起点坐标(x0,y0) ,终点坐标(x1,y1) ,num扫描转换时从起点开始输出的点的数目,用于动画*/ void BresenhamLine1(GLsizei x0, GLsizei y0, GLsizei x1, GLsizei y1, GLsizei num) { glColor3f(1.0f,0.0f,0.0f); if(num == 1) printf("中点Bresenham算法画直线:各点坐标及判别式的值\\n"); else if(num==0) return; //以下为画线算法的实现 GLsizei dx,dy,d,UpIncre,DownIncre,x,y; if(x0>x1) { x=x1;x1=x0;x0=x; y=y1;y1=y0;y0=y; } x=x0;y=y0; dx=x1-x0;dy=y1-y0; d=dx-2*dy; UpIncre=2*dx-2*dy;DownIncre=-2*dy; while(x<=x1) { putpixel(x,y); if(x-x0>=num-1) { printf("x=%d,y=%d,d=%d\\n", x, y,d); break; } x++; if(d<0) { y++; d+=UpIncre; } else d+=DownIncre; if(x >= 25 || y >= 25) break; } } /*带线刷子的中点Bresenham算法画直线(0<=k<=1)。参数说明:x0,y0 起点坐标,x1,y1 终点坐标,num扫描转换时从起点开始输出的点的数目,用于动画*/ void BresenhamLine2(GLsizei x0, GLsizei y0, GLsizei x1, GLsizei y1, GLsizei num) { glColor3f(1.0f,0.0f,0.0f); if(num == 1) printf("带线刷子的中点Bresenham算法画直线:各点坐标及判别式的值\\n"); else if(num==0) return; //以下为画线算法的实现 GLsizei dx,dy,d,UpIncre,DownIncre,x,y; if(x0>x1) { x=x1;x1=x0;x0=x; y=y1;y1=y0;y0=y; } x=x0;y=y0; dx=x1-x0;dy=y1-y0; d=dx-2*dy; UpIncre=2*dx-2*dy;DownIncre=-2*dy; while(x<=x1) { putpixel(x,y+1); putpixel(x,y); putpixel(x,y-1); if(x-x0>=num-1) { printf("x=%d,y=%d,d=%d\\n", x, y,d); break; } x++; if(d<0) { y++; d+=UpIncre; } else d+=DownIncre; if(x >= 25 || y >= 25) break; } } /*改进的Bresenham算法画直线(0<=k<=1),参数说明:起点坐标(x0,y0),终点坐标(x1,y1), num扫描转换时从起点开始输出的点的数目,用于动画 */ void Bresenham2Line(GLsizei x0, GLsizei y0, GLsizei x1, GLsizei y1, GLsizei num) { glColor3f(1.0f,0.0f,0.0f); if(num == 1) printf("改进的Bresenham算法画直线:各点坐标及判别式的值\\n"); else if(num==0) return; GLsizei x,y,dx,dy,e; dx=x1-x0;dy=y1-y0; e=-dx;x=x0;y=y0; while(x<=x1) { putpixel(x,y); if(x-x0>=num-1) { printf("x=%d,y=%d,e=%d\\n",x,y,e); break; } x++; e=e+2*dy; if(e>0) { y++; e=e-2*dx; } } } //画出以(x0,y0)为圆心,包括点(x,y)在内的8个对称点 void circlePoint(GLsizei x0,GLsizei y0,GLsizei x,GLsizei y) { putpixel(x,y); putpixel(x,2*y0-y); putpixel(y-y0+x0,y0+x0-x); putpixel(x0+y0-y,y0+x0-x); putpixel(2*x0-x,2*y0-y); putpixel(2*x0-x,y); putpixel(x0+y0-y,y0+x-x0); putpixel(x0+y-y0,y0+x-x0); } /*Bresenham算法画圆。参数说明:圆心坐标(x0,y0),圆半径r, num扫描转换时从起点开始输出的点的数目,用于动画 */ void BresenhamCircle(GLsizei x0, GLsizei y0, GLsizei r, GLsizei num) { glColor3f(1.0f,0.0f,0.0f); if(num == 1) printf("Bresenham算法画圆:各点坐标及判别式的值\\n"); else if(num==0) return; GLsizei x,y,d; x=x0;y=y0+r;d=1-r; while(x-x0<=y-y0) { circlePoint(x0,y0,x,y); if(x-x0>=num-1) { printf("x=%d,y=%d,d=%d\\n",x,y,d); break; } if(d<0) d+=2*(x-x0)+3; else { d+=2*((x-x0)-(y-y0))+5; y--; } x++; } } //初始化窗口,设置窗口颜色为蓝色 void Initial(void) { glClearColor(1.0f, 1.0f, 1.0f, 1.0f); } // 窗口大小改变时调用的登记函数 void ChangeSize(GLsizei w, GLsizei h) { if(h == 0) h = 1; glViewport(0, 0, w, h); // 设置视区尺寸 glMatrixMode(GL_PROJECTION); glLoadIdentity();// 重置坐标系统 if (w <= h) // 建立修剪空间的范围 glOrtho (0.0f, 250.0f, 0.0f, 250.0f*h/w, 1.0, -1.0); else glOrtho (0.0f, 250.0f*w/h, 0.0f, 250.0f, 1.0, -1.0); } // 在窗口中绘制图形 void ReDraw(void) { glClear(GL_COLOR_BUFFER_BIT); //用当前背景色填充窗口 DrawCordinateLine();//画出坐标线 switch(m_DrawMode) { case 1: DDACreateLine(0,0,20,15,m_PointNumber); break; //DDA算法画线 case 2: BresenhamLine1(0,0,20,15,m_PointNumber); break; //中点Bresenham算法画线 case 3: Bresenham2Line(0,0,20,15,m_PointNumber); break; //改进的中点Bresenham算法画线 case 4: BresenhamCircle(12,12,10,m_PointNumber); break; //Bresenham算法画圆 case 5: BresenhamLine2(0,0,20,15,m_PointNumber); break; //带竖直线刷子的中点Bresenham算法画线 default: break; } glFlush(); } //设置时间回调函数 void TimerFunc(int value) { if(m_PointNumber == 0) value = 1; m_PointNumber = value; glutPostRedisplay(); glutTimerFunc(500, TimerFunc, value+1); } //设置键盘回调函数,按键盘1~5,调用不同的画线模式 void Keyboard(unsigned char key, int x, int y) { if (key == '1') m_DrawMode = 1; if (key == '2') m_DrawMode = 2; if (key == '3') m_DrawMode = 3; if (key == '4') m_DrawMode = 4; if (key == '5') m_DrawMode = 5; m_PointNumber = 0; glutPostRedisplay(); } //主函数 int main(int argc, char* argv[]) { glutInit(&argc, argv); //初始化GLUT库OpenGL窗口的显示模式 glutInitDisplayMode(GLUT_SINGLE | GLUT_RGB); glutInitWindowSize(600,600); glutInitWindowPosition(100,100); glutCreateWindow("基本图元绘制程序--0911班刘荡"); glutDisplayFunc(ReDraw); glutReshapeFunc(ChangeSize); glutKeyboardFunc(Keyboard);//键盘响应回调函数 glutTimerFunc(500, TimerFunc, 1); Initial();// 窗口初始化 glutMainLoop(); //启动主GLUT事件处理循环 return 0; } 实验二 (日地月模型) 实验目的与要求 (1)理解OpenGL中的变换过程; (2)理解透视投影与平行投影的不同; (3)添加代码实现太阳、地球和月亮的运动模型; (4)了解深度测试 ; (5)通过变换调整观察的位置与方向; (6)加入光照模型。 实验内容与分析 ①首先,我们认定这三个天体都是标准的球形,建立以下坐标系:太阳的中心为原点,地球绕太阳旋转的平面与X轴与Z轴决定的平面平行,即glRotatef(increment1, 0.0f, 1.0f, 0.0f);月亮绕地球旋转的平面与X轴与Y轴决定的平面平行,即glRotatef(increment2, 0.0f, 0.0f, 1.0f)。且每年第一天,地球在X轴正方向上,月亮在地球的正X轴方向。而且根据地球绕太阳转、月亮绕地球转的关系,画图时,可以依次画太阳、地球、月亮,这样可以不必使用连续使用glPushMatrix()、glPopMatrix()来保存当前的模型视图矩阵,同样可以保证地球绕太阳转、月亮绕地球转。 ②由于月亮绕地球旋转的速度是地球绕太阳旋转速度的12倍,为便于计算角度,可设地球绕太阳旋转角度为increment1, 月亮绕地球旋转角度为increment2,初值均为0,为产生旋转的动画,还应增加旋转步长, increment1每次增加2度,而increment1每次增加24度。 ③为了得到透视效果,我们使用gluPerspective来设置可视空间。假定可视角为45度,最近可视距离为1.0,最远可视距离为500。 ④当地球、月亮处于太阳的不同位置时,应该考虑遮挡效果。比如,地球处在太阳背面时,地球不可见。为增强真实感的立体感, 应激活光照和深度检测。为以示区别,可假定除太阳外,地球和月亮本身也可发光,就通过设置材质Emission成分使物体看起来有发光效果。为产生光照的视觉效果,可使用glEnable(GL_LIGHTING)、glEnable(GL_LIGHT1)启用光照系统和点光源,且点光源就在太阳中心所在的位置。在环境中增加漫反射,相应的,使用glMaterialfv()给太阳、地球,月亮设置对漫反射光的反射率的RGBA值 ,即可达到效果。 ⑤程序操作。直接运行程序即可显示运行效果。 实验结果 (图2.1,地球在太阳背面,完全被遮挡时) (图2.2,地球旋转到太阳左侧时) (图2.3,地球旋转到太阳前方时) (图2.4,地球旋转到太阳右侧时) 实验体会 通过本次实验,让我又学会了OpenGL编程中的一些比较高级的运用。其中包括坐标轴变换(glLoadIdentity(),glTranslatef()),旋转glRotatef(),透视投影gluPerspective,使用glutSolidSphere ()、glLightfv()画球体,使用glEnable(GL_LIGHTING)启用光照系统,使用glEnable(GL_LIGHT1)启用点光源,使用glMaterialfv()设置材质对漫反射光的反射率,以及在窗口中使用双缓存、RGB颜色和深度测试。尤其是光照模型,花了我很长时间,找了一些关于OpenGL编程的资料后,再经过不断的调试和修改,才基本上实现了光照的效果。做完之后,回想整个实验过程,觉得挺有意思的。 源代码(完整代码记住时间附件) #include #include #include #include void Initial() { glClearColor(1.0f, 1.0f, 1.0f, 1.0f ); //设置背景为白色 GLfloat diffuse[]={1.0f,1.0f,1.0f,1.0f};//漫反射分量,三个参数分别为红、绿、蓝光线成分,以及透明度 glLightfv(GL_LIGHT1, GL_DIFFUSE, diffuse);//指定漫反射分量 GLfloat position[]={0.0f,0.0f,-250.0f,1.0f}; glLightfv(GL_LIGHT1, GL_POSITION, position);//指定点光源的坐标位置 glEnable(GL_LIGHTING); //启用光照系统 glEnable(GL_LIGHT1); //启用点光源(GL_LIGHT1) // 启用深度测试,即深度(z值)会影响图形的遮挡关系(外边的会挡住里边的),不启动的话就是后画的挡住先画的(z值不起作用) glEnable(GL_DEPTH_TEST); } void ChangeSize(int w, int h) { if(h == 0) h = 1; glViewport(0, 0, w, h);// 设置视区尺寸 glMatrixMode(GL_PROJECTION); glLoadIdentity(); GLfloat fAspect; fAspect = (float)w/(float)h; gluPerspective(45.0, fAspect, 1.0, 500.0); // 设置修剪空间 glMatrixMode(GL_MODELVIEW); glLoadIdentity(); } void myDisplay(void) { static float increment1 = 0.0; static float increment2 = 0.0; // 地球绕太阳旋转角度、月亮绕地球旋转角度分别为increment1、increment2,从0到360变化 GLfloat position[]={0.0f,0.0f,1.0f,2.0f}; //清除颜色和深度缓冲区 glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); glMatrixMode(GL_MODELVIEW); glLoadIdentity(); //将坐标原点定位到屏幕中心 glTranslatef(0.0f, 0.0f, -250.0f);//将坐标沿Z负轴平移250,即与点光源的位置相同 // 以下绘制红色的“太阳” GLfloat s_diffuse1[]={0.2f,0.8f,1.0f,1.0f};//设置材质对漫反射光的反射率的RGBA glMaterialfv(GL_FRONT, GL_DIFFUSE, s_diffuse1);//设置材质对漫反射光的反射率 GLfloat s_emission1[]={0.9,0.2,0.2,1.0}; //设置材质发光颜色的RGBA值 //设置材质对各种光的反光率 glMaterialfv(GL_FRONT, GL_EMISSION, s_emission1); glutSolidSphere(40, 40, 50); glEnable(GL_LIGHTING); // 以下绘制蓝色的“地球” GLfloat e_diffuse2[]={0.8f,0.8f,0.8f,1.0f}; glMaterialfv(GL_FRONT, GL_DIFFUSE, e_diffuse2); GLfloat e_emission2[]={0.1,0.1,1.0,1.0}; glMaterialfv(GL_FRONT, GL_EMISSION, e_emission2); glRotatef(increment1, 0.0f, 1.0f, 0.0f);//地球绕太阳旋转(Y轴) glTranslatef(80, 0.0f, 0.0f); glutSolidSphere(12.0f, 40, 50); //以下 绘制灰色的“月亮” GLfloat m_diffuse3[]={0.4f,0.4f,0.4f,1.0f}; glMaterialfv(GL_FRONT, GL_DIFFUSE, m_diffuse3); GLfloat m_emission3[]={0.25,0.25,0.05,1.0}; glMaterialfv(GL_FRONT, GL_EMISSION, m_emission3); glRotatef(increment2, 0.0f, 0.0f, 1.0f);//月亮绕地球旋转(Z轴) glTranslatef(20. 0f, 0.0f, 0.0f); glutSolidSphere(5.0f, 40, 50); // 增加旋转步长 increment1 += 2.0f; if(increment1 > 360.0f) increment1 = 2.0f; increment2 += 24.0f; if(increment1 > 360.0f) increment1 = 24.0f; //交换缓冲区。由于使用了双缓冲技术,使得所有的绘制都是绘制到一个后台的缓冲区里面,如果不交换缓冲区,就看不到绘制的图。 glutSwapBuffers(); } void TimerFunc(int value) { glutPostRedisplay(); glutTimerFunc(100, TimerFunc, 1); } int main(int argc, char* argv[]) { glutInit(&argc, argv); //窗口使用双缓存、RGB颜色、深度测试 glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGB | GLUT_DEPTH); glutCreateWindow("日地月模型--0911班刘荡"); glutReshapeFunc(ChangeSize); glutDisplayFunc(myDisplay); glutTimerFunc(50, TimerFunc, 1);//50毫秒后调用定时器回调函数 Initial(); glutMainLoop(); //若漏了此条语句,将不会显示图形 return 0; }
