1. 程序说明
1.1 图形项目说明:
即将毕业,以“夏日狂欢”与同学们告别。“夏日狂欢”绘制了一个三维场景,主要包括
四个部分,一张简易的桌子,一瓶红葡萄酒,五个杯子,一个电风扇,十分简陋,一是因为我
所掌握的技术不够,二是因为,这就是我们的生活,它模拟的,是前年宿舍五人共渡中秋佳节
的情景,只是当时凉快,而现在已经是炎炎夏日了,天气热时就用右键菜单打开我们的小电风
扇吧;看不见背面的场景,就用上下左右键旋转视图吧。
1.2 术语定义
旋转矩阵:指 glRotatef()产生的矩阵
纹理映射:指应用内存中的图像覆盖模型表面
1.3 参考资料
《OpenGL 三维图形系统开发与实用技术(基础编程篇)》 清华大学出版社
《Computer Graphics with OpenGL,Third Edition》
D. Hearn, M.P Baker 电子工业出版社
2 应用软件的总体设计
2.1 需求规定 :
输入:键盘的“←”、“↑”、“→”和“↓”;鼠标。
输出:显示屏。
基本图形建模:
工具库:立方体,球。
实用库:二次曲面、NURBS 曲面。
使用旋转、平移、放大缩小等方法变换坐标矩阵,组成相应物体。使用纹理和光照模型,
完善场景外观。
其它处理:
工具库:创建子窗口,使用定时器,创建右键菜单,键盘响应函数。
核心库:光栅位图。
Windows 相关:读 BMP 位图作为纹理数据。
2.2 运行环境
具体说明自己程序的开发和运行环境(软,硬件).
开发环境:Visual C++ 6.0 Console
运行环境:
硬件:
项目
CPU
时钟频率
内存
硬盘
软件:
OpenGL
Pentinum 或 Pentinum Pro
90MHz
16/32/64MB
512MB
本人运行环境
PM
1.5GHz
512MB
40GB
Windows 2000 或 Windows XP
头文件:gl.h, glu.h, glut.h, math.h, windows.h, stdib.h, stdio.h
动态链接库:glu.dll, glu32.dll, glut32.dll 和 opengl32.dll
静态链接库:glu32.lib, glut32.lib, opengl32.lib
1
2.3 基本设计概念和流程图(要求较为详细)
软件系统创意的简要说明:本软件系统的基本设计方案
数据定义
纹理数据
文字位图
酒瓶列表
桌子列表
电扇列表
杯子列表
子窗口
主窗口
右键菜单
定时器
键盘响应
绘图循环
消息循环
初
始
化
绘
图
系统的初始化(如,背景色的设定,单/双缓存的说明)
1 使用双缓冲、RGB 模式、深度缓冲
2 主窗口设置
window=glutCreateWindow ("夏日狂欢");
glutReshapeFunc(main_reshape);
glutDisplayFunc(main_display);
glutSpecialFunc(screen_special);
glutCreateMenu(menu);
主窗口的背景色设置为蓝色,在其 display 函数中定义。
3 子窗口设置
screen = glutCreateSubWindow(window, 50, 50, 600, 600);
init ( );
glutDisplayFunc (screen_display);
glutSpecialFunc(screen_special);
glutReshapeFunc(screen_reshape);
glutTimerFunc(3, timf, 0); // 设置定时器
glutCreateMenu(menu);
其中主窗口和子窗口的键盘响应函数和右键菜单设置均相同,这是防止当鼠标不
在子窗口范围内时出现不响应的情况;初始化函数 init( )载入纹理、显示列表等。
主窗口和子窗口的 Reshape 函数均设置为视口不随窗口变化而变化。
主窗口投影矩阵设置为 gluOrtho2D;子窗口投影矩阵设置为直角投影,在子窗口
的 display 函数中。
4 子窗口初始化
2
子窗口为主要的绘图窗口,初始化背景为黑色,开启光照、二维纹理设置、深度
测试及归一化处理。
glClearColor (0.0, 0.0, 0.0, 0.0);
glFrontFace(GL_CCW);
glEnable(GL_LIGHTING);
glEnable(GL_LIGHT0);
glEnable(GL_AUTO_NORMAL);
glEnable(GL_NORMALIZE);
glEnable(GL_DEPTH_TEST);
子窗口初始化函数中载入纹理、光照模型、显示列表,定义像素存储属性,定义
二次曲面及 NURSE 曲面属性。
loadlight();
qobj = gluNewQuadric();
theNurb = gluNewNurbsRenderer();
gluNurbsProperty(theNurb, GLU_SAMPLING_TOLERANCE, 25.0);
gluNurbsProperty(theNurb, GLU_DISPLAY_MODE, GLU_FILL);
glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
loadtexture();
loadlist();
光照为:
void loadlight()
{
GLfloat ambient[] = {0.5, 0.5, 0.5, 1.0};
GLfloat diffuse[] = {1, 1, 1.0, 1.0};
GLfloat specular[] = {1.0, 1.0, 1.0, 1.0};
GLfloat position[] = {40.0, 40.0, 40.0, 0.0};
GLfloat lmodel_ambient[] = {0.2, 0.2, 0.2, 1.0};
GLfloat local_view[] = {0.0};
glLightfv(GL_LIGHT0, GL_AMBIENT, ambient);
glLightfv(GL_LIGHT0, GL_DIFFUSE, diffuse);
glLightfv(GL_LIGHT0, GL_POSITION, position);
glLightModelfv(GL_LIGHT_MODEL_AMBIENT, lmodel_ambient);
glLightModelfv(GL_LIGHT_MODEL_LOCAL_VIEWER, local_view);
}
loadtexture( )和 loadlist( )在稍后详解。
对象模型的建立
1 桌子
桌子的绘制较为简单,分别用 glScalef 将 glutSolidCube 绘出的正方体变形为扁扁
的桌面和长长的桌脚,然后用 glTranslate 平移至相应的位置,最后贴上纹理。贴纹理
时先自动计算纹理坐标满贴整个桌子,此时效果并不理想,尤其是桌面,所以将两面
桌面重新贴上手动计算的纹理。
void table()
{
tablelist=glGenLists(1);
3
glNewList(tablelist,GL_COMPILE);
glEnable(GL_TEXTURE_GEN_S);
glEnable(GL_TEXTURE_GEN_T);
glTexGeni(GL_S, GL_TEXTURE_GEN_MODE, GL_SPHERE_MAP);
glTexGeni(GL_T, GL_TEXTURE_GEN_MODE, GL_SPHERE_MAP);
glEnable(GL_TEXTURE_2D);
自动计算二维纹理
glTexEnvf(GL_TEXTURE_ENV,GL_TEXTURE_ENV_MODE, GL_REPLACE);
glBindTexture(GL_TEXTURE_2D, texName);
使用桌子的木制纹理
glScalef(1.6,0.05,1);
glutSolidCube(50);
glPopMatrix();
glPushMatrix();
glTranslatef(35,-15,20);
glScalef(0.1,1,0.1);
glutSolidCube(30);
………
坐标变换使下面要重置矩阵
桌面
重置模型矩阵
桌脚
其余三个桌脚
glDisable(GL_TEXTURE_GEN_S);
glDisable(GL_TEXTURE_GEN_T);
关闭自动计算二维纹理
glPopMatrix();
glPushMatrix();
glBegin(GL_QUADS);
手动指定纹理坐标贴桌面
glTexCoord2f(0.0, 0.0); glVertex3f(-40, 1.5, -25);
glTexCoord2f(1.0, 0.0); glVertex3f(-40,1.5,25);
glTexCoord2f(1.0, 1); glVertex3f(40,1.5,25);
glTexCoord2f(0.0, 1); glVertex3f(40,1.5,-25);
glTexCoord2f(0.0, 0.0); glVertex3f(-40, -1.5, -25);
glTexCoord2f(1.0, 0.0); glVertex3f(-40,-1.5,25);
glTexCoord2f(1.0, 1); glVertex3f(40,-1.5,25);
glTexCoord2f(0.0, 1); glVertex3f(40,-1.5,-25);
glEnd();
glDisable(GL_TEXTURE_2D);
glEndList();
}
2 酒瓶
酒瓶是本场景中最难画的一个物体,它由四部份组成,一是瓶身下半部份,二是
酒瓶中的酒,以上两部分用二次曲面的 gluCylinder 完成;第三是瓶身的上半部分,用
两个相同的 NURSE 曲面模拟;以上三部份均需开启融合,即 GL_BLEND。最后一部
分是酒瓶上的标签,由 NURSE 曲面构建并贴图,疑惑的是,此部分的贴图始终没有
贴好,也没有找到原因,可能是坐标计算不当。
void bottle()
{
4
bottlelist=glGenLists(1);
glNewList(bottlelist,GL_COMPILE);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
以半透明方式与背景融合
开启融合
设置 Alpha 为 0.5
绘制酒瓶下半部份
绘制红葡萄酒
glEnable(GL_BLEND);
material(0.7,0.1,0.1,0.5,0.7,0.1,0.1,0.5,0.0,0.0,0.0);
glPopMatrix();
glPushMatrix();
glTranslatef(0,17,0);
glRotatef(90,1,0,0);
gluCylinder(qobj,3.8,3.8,17,50,20);
glPopMatrix();
glPushMatrix();
material(1,0.9,0.9,0.5,0.9,0.9,0.9,0.5,0.0,0.0,0.0);
glTranslatef(0,20,0);
glRotatef(90,1,0,0);
gluCylinder(qobj,4.5,4.5,20,50,20);
glPopMatrix();
glPushMatrix();
glTranslatef(0,20,0);
glScalef (2,2,2.5);
glRotatef(-90.0, 1.0,0.0,0.0);
gluBeginSurface(theNurb);
gluNurbsSurface(theNurb,
绘制酒瓶上半部分的一半
8, knots, 8, knots,
4 * 3, 3, &ctlpoints[0][0][0],
4, 4, GL_MAP2_VERTEX_3);
gluEndSurface(theNurb);
glRotatef(180.0, 0.0,0.0,1.0);
gluBeginSurface(theNurb);
gluNurbsSurface(theNurb,
将上半部分旋转 180 度绘制另一半
8, knots, 8, knots,
4 * 3, 3, &ctlpoints[0][0][0],
4, 4, GL_MAP2_VERTEX_3);
gluEndSurface(theNurb);
glDisable(GL_BLEND);
//-----------------------------------------
glEnable(GL_TEXTURE_2D);
glEnable(GL_TEXTURE_2D);
关闭融合
glTexEnvf(GL_TEXTURE_ENV,GL_TEXTURE_ENV_MODE, GL_REPLACE);
glBindTexture(GL_TEXTURE_2D, texName+1);
glPopMatrix();
glPushMatrix();
glTranslatef(0,3,0);
贴标签
5
glScalef (2.1,2.1,2.7);
glRotatef(-90.0, 1.0,0.0,0.0);
gluBeginSurface(theNurb);
gluNurbsSurface(theNurb,
8, knots, 8, knots,
4 * 3, 3, &ctlpoints2[0][0][0],
4, 4, GL_MAP2_TEXTURE_COORD_2); 纹理坐标
gluNurbsSurface(theNurb,
8, knots, 8, knots,
4 * 3, 3, &ctlpoints2[0][0][0],
4, 4, GL_MAP2_VERTEX_3);
对应点坐标
gluEndSurface(theNurb);
glDisable(GL_TEXTURE_2D);
glEndList();
}
NURSE 曲面的控制点为:
GLfloat knots[8] = {0.0, 0.0, 0.0, 0.0, 1.0, 1.0, 1.0, 1.0};
GLfloat ctlpoints[4][4][3]={
{{-0.55,0,10},{-0.55*XP,0.55*XP,10.0},{0.55*XP,0.55*XP,10.0},{0.55,0,10.0}},
{{-0.55,0,6},{-0.55*XP,0.55*XP,6.0},{0.55*XP,0.55*XP,6.0},{0.55,0,6.0}},
{{-1.0,0,4},{-1.0*XP,1.0*XP,4.0},{1.0*XP,1.0*XP,4.0},{1.0,0,4.0}},
{{-2.25,0,0},{-2.25*XP,2.25*XP,0.0},{2.25*XP,2.25*XP,0.0},{2.25,0,0.0}}};
GLfloat ctlpoints2[4][4][3]={
{{-2.25,0,6},{-2.25*XP,2.25*XP,6.0},{2.25*XP,2.25*XP,6.0},{2.25,0,6.0}},
{{-2.25,0,3},{-2.25*XP,2.25*XP,3.0},{2.25*XP,2.25*XP,3.0},{2.25,0,3.0}},
{{-2.25,0,2},{-2.25*XP,2.25*XP,2.0},{2.25*XP,2.25*XP,2.0},{2.25,0,2.0}},
{{-2.25,0,1},{-2.25*XP,2.25*XP,1.0},{2.25*XP,2.25*XP,1.0},{2.25,0,1.0}}};
3 酒杯
酒杯由三部分构成,一是杯身,由二次曲面构建;二是杯底,是由一个球体压扁
后平移至相应的位置;三是杯中的酒,也是由二次曲面构建。上述三个部分均为透明。
void cap()
{
caplist=glGenLists(1);
glNewList(caplist,GL_COMPILE);
开启融合模式
杯中酒
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
glEnable(GL_BLEND);
glPopMatrix();
glPushMatrix();
material(0.7,0.1,0.1,0.5,0.7,0.1,0.1,0.5,0.0,0.0,0.0);
glTranslatef(0,5,0);
glRotatef(90,1,0,0);
gluCylinder(qobj,2,1.8,3,20,20);
material(0.7,0.7,0.5,0.7,0.7,0.7,0.5,0.7,0.0,0.0,0.0);
glPopMatrix();
杯底
6
glPushMatrix();
glTranslatef(0,2,0);
glScalef(1,0.2,1);
gluSphere(qobj,2,10,10);
glPopMatrix();
glPushMatrix();
glTranslatef(0,10,0);
glRotatef(90,1,0,0);
gluCylinder(qobj,2.5,1.8,10,50,20);
glDisable(GL_TEXTURE_2D);
glDisable(GL_BLEND);
杯身
关闭融合模式
glEndList();
}
由于场景中由多个酒杯,其调用模式如下:
glPopMatrix();
glPushMatrix();
glTranslatef(X,Y,Z); 表示酒杯的位置
glPushMatrix();
glCallList(caplist);
glPopMatrix();
4 电扇
电扇模拟的是小微风扇,由两个部分组成,一是其中心的圆,由球压扁而成;二
是三片叶子,为使其看起来有一点厚度,由三个圆台的曲面压扁并旋转平移到相应位
置。电扇的难点在于对旋转矩阵的控制。经分析矩阵相乘的属性和多次尝试,采用
Translate/Scale/Rotate 的顺序。
void fan()
{
fanlist=glGenLists(1);
glNewList(fanlist,GL_COMPILE);
material(0.7,0.1,0.1,0.5,0.7,0.1,0.1,0.5,0.0,0.0,0.0);
glPopMatrix();
glPushMatrix();
glTranslatef(0,40,0);
glScalef(1,0.3,1);
gluSphere(qobj,2,20,20);
material(0.7,0.7,0.7,1.0,0.7,0.7,0.7,1.0,0.0,0.0,0.0);
glPopMatrix();
glPushMatrix();
glTranslatef(0,40,0);
glScalef(1,0.05,1);
glRotatef(120,0,1,0);
gluCylinder(qobj,1,3,10,20,20);
glPopMatrix();
glPushMatrix();
7
中心小红圆
周围的三片白叶子
旋转 120 度的叶子
不旋转的叶子
glTranslatef(0,40,0);
glScalef(1,0.05,1);
gluCylinder(qobj,1,3,10,20,20);
glPopMatrix();
glPushMatrix();
glTranslatef(0,40,0);
glScalef(1,0.05,1);
glRotatef(240,0,1,0);
gluCylinder(qobj,1,3,10,20,20);
glEndList();
}
由于电扇要旋转,调用模式为:
glPopMatrix();
glPushMatrix();
glTranslatef(20,-5,20);
if(TurnOn==GL_TRUE)
{
fantheta+=10;
if(fantheta==360) fantheta=0;
}
glRotatef(fantheta,0,1,0);
glPushMatrix();
glCallList(fanlist);
glPopMatrix();
TurnOn 由右键菜单控制,并由定时函数控制旋转速度。
旋转 240 度叶子
控制电扇位置
每次旋转 10 度
旋转矩阵
表项为“关电扇”
重绘子窗口
表项为“天热了,开电扇吧”
void menu(int choice)
{
switch (choice)
{
case 2:
TurnOn=GL_FALSE;
redisplay_all();
break;
case 1:
TurnOn=GL_TRUE;
redisplay_all();
break;
}
}
void timf(int value)
{
redisplay_all();
glutTimerFunc(3, timf, 0);
}
8