基于OpenGL的3D旋转魔方的实现.docx
《基于OpenGL的3D旋转魔方的实现.docx》由会员分享,可在线阅读,更多相关《基于OpenGL的3D旋转魔方的实现.docx(21页珍藏版)》请在冰点文库上搜索。
基于OpenGL的3D旋转魔方的实现
华中科技大学电子科学与技术系
课程设计报告
(2011--2012年度第2学期)
名称:
软件课程设计
题目:
基于OpenGL的3D旋转魔方实现
院系:
XXXXXX
班级:
XXXXX
学号:
U201014185
学生姓名:
XXX
指导教师:
XXX
设计周数:
XXXX
成绩:
日期:
2012年5月24日
1.课程设计介绍………………………………………………………………1
1.1.内容………………………………………………………………………1
1.2.目的………………………………………………………………………1
1.3.取得的成果………………………………………………………………1
2.程序分析…………………………………………………………………2
2.1.程序原理…………………………………………………………………2
2.1.程序流程…………………………………………………………………3
2.3.数据结构…………………………………………………………………8
2.4.重要函数用法分析………………………………………………………8
3.结果演示与程序分析……………………………………………………9
3.1.成果演示…………………………………………………………………9
3.2.程序分析………………………………………………………………11
4.编程中遇到的问题…………………………………………………………12
5.课程设计小结………………………………………………………………13
参考文献………………………………………………………………………14
基于OpenGL的3D旋转魔方实现
1.课程设计介绍
1.1目的
当今计算机技术流行,引领了各行各业。
而程序是计算机的灵魂,因此编程能力对当今的学生而言至关重要。
虽然我们在前期已经学习了C语言,但是还只对程序有一些简单的认识,说实话,是很浅显的认识。
通过本软件课程设计的学习,可以从整体上对软件工程和项目有全面的认识。
通过此次课程设计,可以锻炼编程能力,激发对编程的兴趣,同时也能培养良好的编程习惯。
这对于个人今后的学习,今后的工作乃至今后的生活都会产生重要的影响。
对于国家而言,极大的推动了计算机普及教育,提高了大学生的计算机使用水平,具有重大的意义。
1.2内容
(1)通过此次项目掌握软件开发模式,模块化结构分析以及程序设计流程
(2)学会使用VC++6.0进行编程
(3)掌握有关程序设计的思想,数据结构的知识,掌握C语言算法,掌握OpenGL编程知识如贴图与键盘控制
(4)掌握win32编程知识,了解windows程序内部运行机制
(5)初步培养需求分析、软件测试、调试的能力
(6)在2X2魔方的基础上,尝试编写3X3的魔方,并实现其旋转
1.3取得的成果
在理解Magic2D例子程序的基础上,借助了Win32平台进行了一系列调试和学习。
在此次项目中,学习了VisualC++6.0软件开发环境,熟练掌握了Win32Application开发流程。
同时也学习了OpenGL的基本知识,掌握了一些OpenGL的重要技术与重要函数的使用,编写了一些简单的OpenGL程序。
参考Magic2D例子流程,我对原程序进行了比较大的修改,并换上了自己的图片,实现了一个立方体贴六张不同的图片,并编写出了自己的2X2魔方程序。
根据相似度分析,成功的编写出了3X3旋转魔方,并自己设计了算法,实现了各个层面的转动,转动效果很完美。
同时,为了增加程序的娱乐效果,我加入了歌曲最炫民族风,虽然很简单,不过感觉非常实用且有趣。
2.程序分析
2.1程序原理
(1)OpenGL
OpenGL是一个开放的三维图形软件包,它独立于窗口系统和操作系统,以它为基础开发的应用程序可以十分方便地在各种平台间移植;OpenGL可以与VisualC++紧密接口,便于实现机械手的有关计算和图形算法,可保证算法的正确性和可靠性;OpenGL使用简便,效率高。
本设计是在VisualC++6.0开发环境下,使用OpenGL(OpenGraphicsLibrary)函数库,绘制魔方并实现魔方的绘制、随机旋转、贴图以及键盘控制等功能。
采用基本图形的绘图函数及定位函数,添加相应纹理来实现魔方模型的绘制。
通过读取载入BMP文件,应用纹理贴图技术来完成对魔方旋转面的处理。
(2)模型的旋转
首先对立方体进行建模。
一个立方体由8个点组成,8个点组成6个面片,对立方体的几何操作本质上就是对这6个平面的操作(绘制、纹理、旋转和平移等)。
点的索引号确定后,每个面片也就确定了,如{0,1,2,3}四个点构成Z向正投影面。
立方体在空间的旋转,归根到底是其顶点的旋转,如空间点(x,y,z)绕Z轴旋转a对应的旋转矩阵:
cosasina
-sinacosa
一个立方体pCube绕Z轴旋转a后对应的坐标变化为:
for(inti=0;i<8;i++)
{
floatx,y;
x=pCube->CubePoint[i].p[0];
y=pCube->CubePoint[i].p[1];
pCube->CubePoint[i].p[0]=x*cosa-y*sina;
pCube->CubePoint[i].p[1]=x*sina+y*cosa;
}
其它轴的旋转类似。
由于魔方体层面的旋转是90°,在某个层面内一个子立方体Ci会被另外一个Cj完全替换,因此旋转后必须同步更新魔方体Cube各层面内包含的子立方的索引号。
为了简化算法,只需查找旋转后Cube[i]在Static_Cube中对应的小立方编号j与i的位置匹配。
2.2程序的流程
通过分析,整个程序大致可以分为6个子功能模块
(1)Win32应用程序框架
WinMain主函数是所有Win32程序的入口点。
在WinMain函数里实现Window是窗体的建立和消息循环,在消息循环中实现键盘、鼠标输入事件处理响应,具体内容可参考VC程序参考手册。
在本课程程序中,不仅要创建Window窗体,而且构建OpenGL设备绘图环境。
Window窗体创建步骤:
●窗体类注册:
RegisterClass
●设置显示分辨率:
ChangeDisplaySettings
●设置窗体大小:
AdjustWindowRectEx
●创建窗体:
CreateWindowEx
OpenGL绘图环境搭建:
●获取设备绘图环境(DC,DeviceContext):
hDC=GetDC(hWnd)
●选择绘图环境像素格式:
ChoosePixelFormat(hDC,&pfd),其中pfd为像素格式描述符,如果设置不对,OpenGL绘图失败,看不到正确的显示结果。
●设置绘图环境像素格式:
SetPixelFormat(hDC,PixelFormat,&pfd)
●获取OpenGL绘图环境:
hRC=wglCreateContext(hDC)
●设置OpenGL绘图环境:
wglMakeCurrent(hDC,hRC)
(2)空间建模得到3阶魔方
显然,要建立3X3的魔方必须首先建立单个魔方的模型,然后通过对单个立方体进行平移从而得到3X3的魔方。
平移单个立方体通过reset_model()函数中的语句实现
for(i=0;i<8;i++)
{
Cube[0].CubePoint[i].p[0]=CubePoint[i].p[0]-2.0f;
Cube[0].CubePoint[i].p[1]=CubePoint[i].p[1]-2.0f;
Cube[0].CubePoint[i].p[2]=CubePoint[i].p[2]+2.0f;
}
上述代码只得到了编号为0的立方体,其他编号的立方体同理可以得到
构成2X2的魔方需要8个立方体,构建3X3的魔方则需要27个立方体。
根据2阶魔方的一些方法,类比到3阶魔方。
则首先对魔方的立方体进行编号,然后通过编号得到魔方各层所包含立方体的编号。
其中编号为
BYTEZP[9]={0,1,2,3,4,5,6,7,8};//z轴方向正向一层
BYTEZZ[9]={9,10,11,12,13,14,15,16,17};//z轴方向中间一层
BYTEZM[9]={18,19,20,21,22,23,24,25,26};//z轴方向负向一层
BYTEYM[9]={0,1,2,11,10,9,18,19,20};//y轴方向负向一层
BYTEYZ[9]={3,4,5,14,13,12,21,22,23};//y轴方向中间一层
BYTEYP[9]={6,7,8,17,16,15,24,25,26};//y轴方向正向一层
BYTEXM[9]={2,3,8,17,12,11,20,21,26};//x轴方向正向一层
BYTEXZ[9]={1,4,7,16,13,10,19,22,25};/x轴方向中间一层
BYTEXP[9]={0,5,6,15,14,9,18,23,24};//x轴方向负向一层
(3)OpenGL纹理贴图
以下是纹理贴图的流程,其中实现了一个立方体贴六张图片
(4)同步更新索引
在整个程序中,同步更新索引所涉及到的算法可以算是最核心的了。
当然,你也可以让每层每个周期转动360度,这样就不需要动态更新索引了,因为每次转动前后,立方体的相对坐标位置并没有改变。
但是这样得到的旋转效果并不好,因为这并没有考虑到魔方旋转的所有情况,每次旋转后魔方都没有被打乱。
由于涉及到动态刷新索引,于是按照常理定义一个静态的魔方与动态的魔方进行比较。
类似动态魔方的编号可以得到静态魔方的编号。
constBYTESZP[9]={0,1,2,3,4,5,6,7,8};//z轴方向正向一层
constBYTESZZ[9]={9,10,11,12,13,14,15,16,17};//z轴方向中间一层
constBYTESZM[9]={18,19,20,21,22,23,24,25,26};//z轴方向负向一层
constBYTESYM[9]={0,1,2,11,10,9,18,19,20};//y轴方向负向一层
constBYTESYZ[9]={3,4,5,14,13,12,21,22,23};//y轴方向中间一层
constBYTESYP[9]={6,7,8,17,16,15,24,25,26};//y轴方向正向一层
constBYTESXM[9]={2,3,8,17,12,11,20,21,26};//x轴方向正向一层
constBYTESXZ[9]={1,4,7,16,13,10,19,22,25};//x轴方向中间一层
constBYTESXP[9]={0,5,6,15,14,9,18,23,24};//x轴方向负向一层
然后编写一个函数intis_equal(stCube*pc1,stCube*pc2)判断两个立方体是否重合,这个函数的算法就是比较立方体所有顶点的坐标是否相同。
最后一步编写函数voidUpdate_Cube_index(),由于魔方转动情况总体上有九种情况,则该函数必须编写九块功能类似的代码,现在只列出X轴负向一层刷新索引的算法。
inti,j,k=0;
k=0;
for(i=0;i<9;i++)
{
for(j=0;j<27;j++)
if(is_equal(&Cube[j],&Static_Cube[SZM[i]]))
{
ZM[k++]=j;
}
}
(5)魔方的旋转以及键盘控制对魔方的平移
魔方层面的旋转已经在前面介绍,此处除了层面旋转还有魔方的整体旋转。
只需调用库函数即可即可。
glRotatef(xrot,1.0,0.0,0.0);//使魔方绕x轴转动xrot度
glRotatef(yrot,0.0,1.0,0.0);//使魔方绕y轴转动yrot度
glRotatef(zrot,0.0,0.0,1.0);//使魔方绕z轴转动zrot度
(6)定时器对魔方层面旋转的控制
为了控制魔方的转动,首先定义了函数voidenable_X_roatate(intdirection),voidenable_Y_roatate(intdirection),voidenable_Z_roatate(intdirection)以确定旋转轴和旋转方向,同时定义了voidRotate_X(intii),voidRotate_Y(intii),voidRotate_Z(intii)来指定是哪一层的旋转。
然后定义两个计时器Time1和Time2,其中Timer1的消息由WM_TIMER接收并处理,以实现立方体的旋转。
Timer2的消息由TimerProc函数处理,用于生成控制量以控制旋转轴和旋转方向。
流程如图:
(7)背景音乐的添加
为了程序有更丰富的效果,我添加了歌曲最炫民族风,方法也很简单。
#include//提供与多媒体有关的接口
#pragmacomment(lib,"WINMM.LIB")//导入winmm.lib库,实现对多媒体编程的支持
编写函数loadsound(),其调用函数PlaySound("sound\\最炫民族风.wav",NULL,SND_LOOP|SND_ASYNC|SND_FILENAME)加载最炫民族风味背景音乐。
(8)窗口文字的添加
为了添加自己的信息,我在程序窗口中加了文字
voiddrawString(constchar*str),实现添加英文字符。
voiddrawCNString(constchar*str)实现添加中文字符
voidselectFont(intsize,intcharset,constchar*face),实现变换字体
2.3数据结构
本程序定义了记录魔方体每个小块编号的数组Cube[i]及相应的定态数组Static_Cube,用来对魔方体变化过程中的相对位置进行索引及重新定位。
定义了立方体记录顶点位置的数组CubePoint[i]及相应静态数组stPointCubePoint[i],用来确定魔方体的旋转,因为旋转归根结底是其顶点位置的旋转。
定义了用来记录魔方体各个面上子块编号的数组ZP/ZZ/ZM,YP/YZ/YM,XP/XZ/XM,及其对应静态数组。
定义了数组TextureImage[i]来接收纹理。
2.4重要函数用法分析
(1)窗口创建
GLvoidresizeScene(GLsizeiwidth,GLsizeiheight)
这个函数的目的是重置OpenGL窗口的大小,具体又包含以下五个函数
glViewport(0,0,width,height);
glViewport是OpenGL中视口变化函数,根据窗口的实时变化重绘窗口,它负责把视景体截取的图像按照怎样的高和宽显示到屏幕上。
glMatrixMode(GL_PROJECTION)、glMatrixMode(GL_MODELVIEW)
这两个函数原型都是glMatrixMode函数。
glMatrixMode这个函数其实就是要指定操作的是哪种矩阵。
如果参数是GL_PROJECTION,这个是投影的意思,如果参数是GL_MODELVIEW,这个是对模型视景的操作,用于对三维场景中坐标系的变换操作。
glLoadIdentity()
glLoadIdentity该函数的功能是将当前的用户坐标系的原点移到了屏幕中心,就像一个复位操作
gluPerspective(45.0f,(GLfloat)width/(GLfloat)height,0.1f,100.0f)
gluPerspective的作用是设置透视投影矩阵,意味着越远的东西看起来越小。
45.0f表示将视角设置为45度,(GLfloat)width/(GLfloat)height表示窗口的纵横比,0.1f表示沿z轴方向的两裁面之间的距离的近处为0.1,100f表示沿z轴方向的两裁面之间的距离的远处为100
(2)初始化操作
glShadeModel(GL_SMOOTH),作用是启用阴影平滑。
glClearColor(0.0f,0.0f,0.0f,0.5f),作用是将背景设置为黑色。
glClearDepth(1.0f),作用是设置深度缓存。
glHint(GL_PERSPECTIVE_CORRECTION_HINT,GL_NICEST),作用是做精细的透视修正。
loadsound(),作用是导入歌曲最炫民族风。
(3)OpenGL贴图
glGenTextures(1,&texture[i]),作用是利用载入的图像生成纹理。
glBindTexture(GL_TEXTURE_2D,texture[i]),作用是选择生成的纹理。
glTexCoord2f(0.0f,0.0f),作用是设置纹理坐标。
glEnable(GL_TEXTURE_2D),作用是启用纹理映射,此点很关键。
(4)平移与旋转
glTranslatef(x,y,z)
平移函数。
表示相对于当前所在的屏幕位置沿着X,Y和Z轴移动x,y和z个单位。
glRotatef(angle,x,y,z)
旋转函数。
与glTranslatef(x,y,z)类似,glRotatef(angle,x,y,z)也是对坐标系进行操作。
旋转轴经过原点,方向为(x,y,z),旋转角度为angle,方向满足右手定则。
(5)添加窗口文字
voiddrawString(constchar*str),实现添加英文字符。
voiddrawCNString(constchar*str)实现添加中文字符
voidselectFont(intsize,intcharset,constchar*face),实现变换字体
3.结果演示与程序分析
3.1成果展示
3.2程序分析
循环调用
3X3魔方有27个小块,对每个小块分别进行编号。
先画出一个子块,经过向相应方向的平移,获得完整的魔方体。
魔方由函数glTranslatef(floatx,floaty,floatz)定位,再由函数glRotate(floatangle,floatx,floaty,floatz)进行旋转,然后由函数glBindTexture(GLenumtarget,GLunittextureName)进行纹理与相应面的绑定(纹理已由glGenTextures()函数来产生),最后再用函数glBegin(GL_QUADS)画出正方面,从而描绘出了魔方的一个小面。
循环调用就可以绘制出的魔方。
循环调用程序的纹理绑定和旋转过程就实现了魔方体的旋转变化。
4.编程中遇到的问题
在对2X2魔方理解的基础上,我经过自己的摸索终于在末期做出了自己的3阶魔方。
其中,遇到了很多问题,不过都被我解决了。
以下是我认为的比较重要的问题
(1)在编写判断两个立方体是否重合的函数时,开始我没有消除累计误差。
说实话,当时我对这个程序并不是很了解。
没有加入这个功能,程序运行结果如下:
刚开始时还比较正常,运行一段时间后,魔方错位了。
想了很久,看了指导书才知道原来还差消除累计误差这个功能。
其实,这也很好解释。
因为我们编写的旋转函数涉及到计算cosa以及sina的值,而计算机计算这些值是不是很准确的。
在一个旋转周期中,要计算180次cosa和sina的值,因此误差会被放大,则消除累计误差成了必须。
只需判断两个数在一定误差范围内相等即可。
(2)魔方循环控制算法的设计中,我遇到了前所未有的问题。
单独编写控制最外面一层或是中间一层旋转的函数确实比较简单,但是如何实现一个实现外层和中间一层同时都可旋转的魔方还是很有难度的,这涉及到如何合理调度的问题。
在多方调试后,我想到了一个很好的算法,就是再引入一个控制变量cs,可是变量应该选择在哪里赋值呢?
刚开始我毫不犹豫的就选择在TimerProc函数里面赋值,可是一下子就出错了。
后来我将Timer2的定时时间设置为1800ms,目的是等于Timer1的180倍,以实现转一个周期cs重新赋值一次,可结果表明我再次错了。
因为转动的延迟,所以转动一个周期所需时间是不定的,但肯定不等于1800ms。
在重新阅读一遍程序后,我发现在voidRotate_ZM()函数中有转动一个周期后的初始化操作,受到启发后,我于是将cs的赋值移到了这个函数中,运行了观察很久后都没有出错,果然成功了。
(3)编程过程中还有一些小问题,不过只要是因为C语言编程的一些知识都忘记了,于是我又重新复习了一下C语言的有关知识,果然编起来得心应手多了。
5.课程设计小结
编程是一件急需细心和耐心的事,刚开始调试过程中遇到了很多弄不清楚原因的报错现象,但是经过不断地修改,那些问题也都没有了。
一开始写3X3程序的时候,为了避免犯错误,我是没有加旋转的,只是建了一个魔方的模型并将其绘制出。
加了旋转函数后果然遇到了一些问题,旋转过程中有时候会出现折叠和重叠现象,经过仔细的检查修改,发现原因是在调用旋转函数时,有部分子块的运动没有写入函数,于是问题得到了部分解决。
但是随后编写循环控制算法遇到了很大问题,刚开始都弄烦了,但是休息一段时间后,沉下心来仔细思考,我想到了非常好的控制算法,终于问题全部解决了。
这次课程设计,我确实学到了很多。
不仅有提升编程能力,而且也学习到了很多做人的道理。
刚开始入手这个项目,感觉压力非常大,因为我之前在3D设计方面还是零基础,OpenGL让我感觉是那么的复杂。
随后,我掌握了OpenGL贴图技术,并充分利用OpenGL的特性实现了一个立方体贴六张不同的图片。
在我以为实现了一个旋转立方体后,实现2阶魔方就难度不大时,我再次因为我自己的无知而停下了脚步。
阅读完老师的代码,总感觉思维很混乱。
于是此后很久都没有进展。
一段时间过后,当我再次仔细阅读这些算法时,我终于将它们完美的串接了起来,实现了2阶魔方。
由2阶魔方到3阶魔方是类似的,但是循环控制算法要难一些。
好在我再次编写出来了很好的控制算法,感觉效果很好
总的来说总的来说,本次软件课程设计,对我来说收获不小。
不仅复习了C语言知识,也对编程及3D设计产生了一定的兴趣,分析问题和解决问题的能力也得到了不小的提高。
而且,我也学到了很多人生的道理。
在做一件看似很难的事时,我们要有一个总体的框架,不要有畏难心理。
静下心来,沉着的分析问题,问题总会迎刃而解的。
因此,成功的关键