.-
《计算机图形学》课程设计
(2015----2016学年第二学期)
学 院
专业班级
学 号
学生姓名
老 师
编写日期:2016年xx月xx日
.-
目 录
真实感游戏场景绘制 3
一 实验目的 3
二 实验内容 3
三 实验分工 3
四 理论基础 4
1 雾化模型 4
2 颜色模型 5
3 光照模型 6
4 纹理模型 6
五 系统描述 13
1 墙壁、地面、箱子 13
2 石柱、雪人 14
3 玻璃球 15
4 天空 17
六 心得体会 19
七 附录:程序源代码 19
.-
真实感游戏场景绘制
【摘要】本次课程设计绘制了一个真实感的三维场景,并实现场景漫游。主要绘制了墙壁与地面、天空、石柱、箱子、玻璃球、雪人、雾等对象。以Visual Studio2012为平台用OpenGL基础知识实现此真实感场景的绘制。
一 实验目的
1.熟悉OpenGL基础函数,并了解其用法。
2.通过程序模拟真实感游戏场景,掌握图形综合展示效果,基于专业背景,结合实验课内容与课程设计要求,使用OpenGL绘制简单的3D真实感游戏场景,包括光栅化算法、多边形裁剪计算以及消隐算法在场景绘制中的应用。
二 实验内容和效果
光栅化算法、多边形裁剪计算以及消隐算法在场景绘制中的应用,其中真实感场景绘制包括颜色模型、纹理模型、雾化模型、运动模型以及环境光、漫反射、镜面反射等光照模型设置。
图1 游戏场景整体效果
三 实验分工
本次课程设计实验,小组成员齐心协力。首先从自己尝试编写没有成功到后来的各种搜集资料,寻找3D游戏场景的绘制代码,到最后小组成员分工解析代码并注释,做PPT,演讲PPT等,大家合作都很用心。这次的实验中我们小组每个成员在每一个环节都参与任务,因代码过多,也都参与到代码解析中。具体安排如下:
姓名
任务
xxx
搜集资料,代码解析注释,做PPT,PPT演讲
xxx
搜集资料,代码解析并注释,做PPT,PPT演讲
xxx
搜集资料,代码解析注释,做PPT,PPT演讲
xxx
搜集资料,代码解析注释,做PPT,PPT演讲
四 理论基础
1 雾化模型
OpenGL中提供了完整的雾化接口,我们只需要选择合适的雾气的混合因子、密度、颜色、起始位置等。在OpenGL中,雾的工作模式有两种:线性模式和指数模式。这两种模式是根据雾的浓度变化来区分的。
在线性模式下,只需要提供一个距离视点的开始位置和结束位置。从开始位置到结束位置之间,雾的浓度越来越高,浓度的变化和距离成正比。
在指数模式下,雾的浓度随着距离的增加呈指数增长。这种模式通常用来用于烟雾、烟幕等效果。
glFogf(GL_FOG_START, 1.0f)确定了雾的开始初离屏幕有多近。glFogf(GL_FOG_END, 5.0),它告诉OpenGL雾能离开屏幕有多远
glHint(GL_FOG_HINT, GL_DONT_CARE)确定了雾的渲染方式,使用GL_DONT_CARE是因为并不关心建议值。这个项的不同值之间的区别:
GK_DONT_CARE:让OPENGL自己来确定雾的渲染方式,每顶点或是每像素。
GL_NICEST:对每一像素进行雾的渲染,它看起来是极棒的。
GL_FASTEST:对每一顶点进行雾的渲染,它速度较快,但是不够美丽
我们的雾气设置如下:
修改函数里面的参数改变雾的颜色和浓度:
图2 改变颜色(蓝色)和浓度的雾气
2 颜色模型
OpenGL支持两种颜色模式:一种是RGBA,一种是颜色索引模式。不同的是,RGBA模式中,数据直接就代表了颜色;而颜色索引模式中,数据代表的是一个索引,要得到真正的颜色,还必须去查索引表。
RGBA颜色
RGBA模式中,每一个像素会保存以下数据:R值(红色分量)、G值(绿色分量)、B值(蓝色分量)和A值(alpha分量)。其中红、绿、蓝三种颜色相组合,就可以得到我们所需要的各种颜色,而alpha不直接影响颜色。
3 光照模型
光照模型包括许多因素,如物体的类型,物体相对光源与其他物体的位置以及场景中所设置的光源属性,物体的透明度,物体的表面光亮程度,甚至物体的各种表面纹理等。光照到物体表面时,物体对光会发生反射、透射、吸收、衍射、折射和干涉。通常观察不透明、不发光的物体,人眼所观察到的是从物体表面的得到的反射光,它是由场景中的光源和其他物体表面的反射光共同作用产生的。
简单光照明模型模拟物体表面对直接光照的反射作用,包括镜面反射和漫反射,而物体间的光反射作用没有被充分考虑到,仅仅用一个与物体周围和视点、光源位置都无关的环境光常量来近似表示。可以用如下表达式表示:
入射光=环境光+漫反射+镜面反射光
4 纹理模型
(因为我们的场景大量使用了纹理模型,且我主要也负责纹理模型,因此处会详细解释。)
我们都知道物体表面通常并不是具有简单颜色的平滑面,而是有着花纹图案等丰富细节的。
计算机三维图形通过给面贴纹理来表现表面细节。OpenGL默认设置是关闭贴纹理的,所以必须先用命令打开纹理计算。
glEnable(GL_TEXTURE_2D); // 启用二维纹理
glDisable(GL_TEXTURE_2D); // 禁用二维纹理
1、启用纹理和载入纹理
如同我们曾经学习过的OpenGL光照、混合等功能一样。在使用纹理前,必须启用它。OpenGL支持一维纹理、二维纹理、三维纹理和四维纹理。 一般情况下使用二维纹理即可。
使用纹理前,必须载入纹理。利用glTexImage2D函数可以载入一个二维的纹理,该函数有多达九个参数,
glTexImage2D(GL_TEXTURE_2D,Glint level,Glint components, Glsizei width,Glsizei Height,Glint order,Glenum ype,const Glvoid *pixels)
详细说明如下:第一个参数为指定的目标,在我们的入门教材中,这个参数将始终使用GL_TEXTURE_2D。
第二个参数为“多重细节层次”,现在我们并不考虑多重纹理细节,因此这个参数设置为零。
第三个参数有两种用法。在OpenGL最初的版本中,使用整数来表示颜色分量数目,例如:像素数据用RGB颜色表示,总共有红、绿、蓝三个值,因此参数设置为3,而如果像素数据是用RGBA颜色表示,总共有红、绿、蓝、alpha四个值,因此参数设置为4。而在后来的版本中,可以直接使用GL_RGB或GL_RGBA来表示以上情况,显得更直观。注意:虽然我们使用Windows的BMP文件作为纹理时,一般是蓝色的像素在最前,其真实的格式为GL_BGR而不是GL_RGB,在数据的顺序上有所不同,但因为同样是红、绿、蓝三种颜色,因此这里仍然使用GL_RGB。如果使用GL_BGR,OpenGL将无法识别这个参数,造成错误。
第四、五个参数是二维纹理像素的宽度和高度。在使用纹理时要特别注意其大小,必须使用大小为2的整数次方的纹理。在很长一段时间内,很多图形程序都喜欢使用256*256大小的纹理,不仅因为256是2的整数次方,也因为某些硬件可以使用8位的整数来表示纹理坐标,2的8次方正好是256,这一巧妙的组合为处理纹理坐标时的硬件优化创造了一些不错的条件。
第六个参数是纹理边框的大小。
最后三个参数分别是纹理的图像数据格式,数据类型和纹理图像的像素数据的存储地址。
举个例子,如果有一幅大小为width*height,格式为Windows系统中使用最普遍的24位BGR,保存在pixels中的像素图像。则把这样一幅图像载入为纹理可使用以下代码:
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, width, height, 0, GL_BGR_EXT, GL_UNSIGNED_BYTE, pixels);
下面一段为我们的游戏场景中的载入纹理的代码:
//对于指定的多个纹理,要根据自己的需要映射到不同的面上,需要对位图创建一个数组,
//用来存储位图的名称,然后在初始化OpenGL的时候,可以读取这些位图,然后生成多个纹理存储到一个纹理数组中,接着就可以指定绘制的某个面,对该指定的面进行纹理映射
//加载位图
AUX_RGBImageRec *LoadBMP(char *Filename)// 根据位图文件的名称进行加载。 AUX_RGBImageRec定义纹理数据的格式
{
FILE *File=NULL; // 文件指针
if (!Filename)//确保文件名已提供
{
return NULL;//如果没有提供,返回null
}
File=fopen(Filename,"r"); // 根据指定的位图文件名称,打开该位图文件
if (File)// 如果位图文件存在
{
fclose(File);// 关闭句柄。 因为只是需要判断问题是否存在,而不需要对位图文件进行写操作,所以关闭位图文件
return auxDIBImageLoad(Filename); //载入位图并返回指针(其实,只需要一个真正存在的位图文件的名称,实现加载位图文件,并返回)
}
return NULL;
}
首先,AUX_RGBImageRec类型是一个RGB图像结构类型。该结构定义了三个成员:
sizeX —— 图像的宽度;
sizeY —— 图像的高度;
data; —— 图形所包含的数据,其实也就是该图形在内存中的像素数据的一个指针。
AUX_RGBImageRec类型的变量描述了一幅图像的特征。
上述函数中,调用了glaux.h库文件中的auxDIBImageLoad函数,其实它是一个宏,函数原型为auxRGBImageLoadW(LPCWSTR)或者auxRGBImageLoadA(LPCSTR),可以在该库文件中找到它的定义,如下所示:
/* AUX_RGBImageRec * APIENTRY auxRGBImageLoad(LPCTSTR); */
#ifdef UNICODE
#define auxRGBImageLoad auxRGBImageLoadW
#else
#define auxRGBImageLoad auxRGBImageLoadA
#endif
AUX_RGBImageRec * APIENTRY auxRGBImageLoadA(LPCSTR);
AUX_RGBImageRec * APIENTRY auxRGBImageLoadW(LPCWSTR);
#ifdef UNICODE
#define auxDIBImageLoad auxDIBImageLoadW
#else
#define auxDIBImageLoad auxDIBImageLoadA
#endif
AUX_RGBImageRec * APIENTRY auxDIBImageLoadA(LPCSTR);
AUX_RGBImageRec * APIENTRY auxDIBImageLoadW(LPCWSTR);
宏auxDIBImageLoad实现的功能就是:根据指定的位图名称,将该位图的信息加载到内存中,以便用来创建成为纹理。
//加载纹理
int LoadGLTextures()//用于创建并加载纹理的函数为LoadGLTextures
{
int Status=FALSE;//很多函数的返回类型都是Status,这里Status是用typedef定义的intl类型 即:typedef int Status;
AUX_RGBImageRec *TextureImage[10]; //创建一个纹理图像数组,这里指定数组大小为10
memset(TextureImage,0,sizeof(void *)*10); // 创建一个纹理图像数组,这里指定数组大小为10
//创建的位图名称数组,对应10幅位图
if((TextureImage[0]=LoadBMP("Data/Floor.bmp"))&&(TextureImage[1]=LoadBMP("Data/Wall.bmp"))&&(TextureImage[2]=LoadBMP("Data/Door.bmp"))&&(TextureImage[3]=LoadBMP("Data/Box.bmp"))&&(TextureImage[4]=LoadBMP("Data/Hat.bmp"))&&(TextureImage[5]=LoadBMP("Data/Cylinder.bmp"))&&(TextureImage[6]=LoadBMP("Data/Star.bmp"))&&(TextureImage[7]=LoadBMP("Data/Mask.bmp"))&&(TextureImage[8]=LoadBMP("Data/Sky.bmp"))&&(TextureImage[9]=LoadBMP("Data/Lightning.bmp")))
{
Status=TRUE;//加载位图成功,修改状态标志变量Status为TRUE
glGenTextures(10, &texture[0]);//纹理标识
for(int loop=0;loop<10;loop++)//遍历位图名称数组,根据位图名称分别生成
{
glBindTexture(GL_TEXTURE_2D,texture[loop]);//进行绑定纹理。
glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR);//设置滤波模式,功能就是实现线形滤波的功能,当纹理映射到图形表面以后,
glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR);//如果因为其它条件的设置导致纹理不能更好地显示的时候,进行过滤,按照指定的方式进行显示,可能会过滤掉显示不正常的纹理像素。
//纹理的定义,此处用到的是二维纹理
glTexImage2D(GL_TEXTURE_2D, 0, 3, TextureImage[loop]->sizeX, TextureImage[loop]->sizeY, 0, GL_RGB, GL_UNSIGNED_BYTE, TextureImage[loop]->data);//生成纹理
}
for (int loop=0; loop<10; loop++) // 释放位图数组占用的内存空间
{
if (TextureImage[loop]!=NULL)
{
if (TextureImage[loop]->data!=NULL)
{
free(TextureImage[loop]->data);
}
free(TextureImage[loop]);
}
}
}
return Status; // 创建纹理并加载,返回成功或者失败的标志Status
}
2、纹理控制
OpenGL的纹理控制实质上就是定义纹理如何包裹物体的表面的,因为纹理的外形并不总是与物体一致,比如纹理通常是矩形的,但会被映射到一个多边形或曲面上,在被变换到屏幕坐标后,纹理的单个纹素很难与屏幕上的像素对应。根据所使用的变换和所用的纹理的映射方式,屏幕上的单个像素可能对应纹理单元中单个纹素的一部分,即放大滤波或对应于多个纹素的,即缩小滤波。而控制缩小和放大滤波的是采用glTexParameter函数来实现的。
3、纹理坐标
纹理的使用只要指定每一个顶点在纹理图像中所对应的像素位置,OpenGL就会自动计算顶点以外的其它点在纹理图像中所对应的像素位置。例如:在绘制一条线段时,我们设置其中一个端点为红色,另一个端点为绿色,则OpenGL会自动计算线段中其它各像素的颜色,如果是使用glShadeMode(GL_SMOOTH);,则最终会形成一种渐变的效果(例如线段中点,就是红色和绿色的中间色)。类似的,在绘制一条线段时,我们设置其中一个端点使用“纹理图像中最左下角的颜色”作为它的颜色,另一个端点使用“纹理图像中最右上角的颜色”作为它的颜色,则OpenGL会自动在纹理图像中选择合适位置的颜色,填充到线段的各个像素(例如线段中点,可能就是选择纹理图像中央的那个像素的颜色)。
使用glTexCoord*系列函数来指定纹理坐标。这些函数的用法与使用glVertex*系列函数来指定顶点坐标十分相似。例如:glTexCoord2f(0.0f, 0.0f);指定使用(0, 0)纹理坐标。
通常,每个顶点使用不同的纹理,于是下面这样形式的代码是比较常见的。
glBegin( /* ... */ );
glTexCoord2f( /* ... */ ); glVertex3f( /* ... */ );
glTexCoord2f( /* ... */ ); glVertex3f( /* ... */ );
/* ... */
glEnd();
我们的代码中使用的纹理坐标:
五 系统描述
本实验绘制的游戏场景可以使用按键↑、↓、←、→或W、S、A、D控制运动方向,PgDn和PgUp可以改变观察者的高度,鼠标控制转向,按键‘F’可以打开和关闭“雾气”,Esc退出程序。
主要绘制了墙壁与地面、天空、石柱、箱子、玻璃球、雪人、雾等对象下面将依次分析它们的设计思路,这里将使用相同绘制技术的对象放到一起说明。
1 墙壁、地面、箱子
它们的基本操作对四边形的纹理映射,将一幅纹理图的四个顶点坐标分别映射到四边形的四个顶点上即可。OpenGL就自动通过插值填充多边形内部的纹理。由于在OpenGL中纹理坐标是一个点的属性,故需要在绘制点之前指定该点的纹理坐标。另外在绘制四边形时要保证绘制顺序的一致,这里统一采用逆时针顺序绘制。纹理坐标范围在[0,1]之间,坐标超过1则采用重复的方式(OpenGL默认处理方式)。这样可以在映射时按四边形的比例设置纹理坐标,从而防止纹理图过度拉伸造成的变形。一个四边形的纹理映射步骤为:
glBindTexture(GL_TEXTURE_2D,texture[0]);
glBegin(GL_QUADS);
glTexCoord2f(0.0f, 0.0f); glVertex3f(30.0f,0.0f, -170.0f);
glTexCoord2f(1.0f, 0.0f); glVertex3f(30.0f,0.0f, -150.0f);
glTexCoord2f(1.0f, 1.0f); glVertex3f(30.0f,20.0f,-150.0f);
glTexCoord2f(0.0f, 1.0f); glVertex3f(30.0f,20.0f,-170.0f);
glEnd();
实验效果图如下:
图3 墙壁 地面 箱子
2 石柱、雪人
石柱和雪人都属于二次几何体的绘制和纹理映射,其中雪人由圆柱、球体、圆盘、圆锥构成。OpenGL提供了这些基本几何体的绘制函数,而且在纹理映射时我们采用自动生成纹理坐标的方法,所以这一部分工作量其实很少。另外,为了给雪人的身体、鼻子等部位设置颜色时,我们需要关闭纹理映射、并更改材质的属性(物体的颜色是由光源和材质共同作用的结果)。对于石柱,我们加上了绕自身局部坐标Y轴的匀速旋转。我们使用Glut库提供的如下函数绘制二次几何体:
gluCylinder:绘制圆柱和圆柱
gluSphere:绘制球体
gluDisk:绘制圆盘
使用gluQuadricTexture设置自动计算纹理坐标。
实验效果如下:
图4 雪人和石柱
3 玻璃球
在绘制玻璃球时,为了在球面上反射出周围的场景,我们使用了环境映射。由于我们能够在三维场景中漫游,这就需要球面上反射出的场景是随着视点移动而变化的,这一需求可以利用“渲染到纹理”(RTT)技术实现。其基本思想是:首先绘制出环境中的所有物体(除了该玻璃球),接着将绘制得到的场景图制作成纹理,在绘制该玻璃球时将此纹理图贴在球面上即可。
该场景使用的核心函数是glCopyTexImage2D,该函数可以将帧缓存中的颜色值复制到纹理缓存中。实验发现该函数性能较低,会拖慢整个程序的速度,为了平衡绘制质量和速度,可以只拷贝一部分像素值。实验效果如下:
图5 玻璃球
4、 雾气
OpenGL中提供了完整的雾化接口,我们只需要选择合适的雾气的混合因子、密度、颜色、起始位置等。该场景的雾气设置如下:
GLfloat fogColor[4]={0.5f, 0.5f, 0.5f, 0.5f};
glFogi(GL_FOG_MODE,GL_EXP2);//模式
glFogfv(GL_FOG_COLOR,fogColor);//颜色
glFogf(GL_FOG_DENSITY, 0.004f);//密度
glFogf(GL_FOG_START, 5.0f);//开始距离
glFogf(GL_FOG_END, 300.0f);//结束距离
glHint(GL_FOG_HINT,GL_NICEST);//雾化效果
实验效果如下:
图6 雾气
4 天空
天空包含云、星星、闪电三个对象。我们要实现的目标是:云在空中飘动、且能够遮盖住空中的星星,每间隔一段时间都会有闪电和雷声。
天空的绘制主要使用了颜色混合技术。首先将星星的纹理图映射到天空,为了实现云朵覆盖住星星,我们使用了如下技巧:制作一个关于云图(图6)的二值图像(图7),其中有云的部分为黑色、无云的部分为白色。将该二值图像作为“掩模”与星星图进行混合,并设置混合因子glBlendFunc (GL_DST_COLOR,GL_ZERO),该设置可以将星星图中对应“掩模”黑色的部分置为黑色、对应“掩模”白色的部分保留原色。接着再将云图映射至天空,并更改混合因子glBlendFunc(GL_ONE, GL_ONE),该设置实现源色与目的色相加。至此已经实现云对星星的遮盖,另外,在进行颜色混合时需要打开OpenGL的颜色混合功能,且要关闭深度测试功能。
为了实现云的飘动,我们在设置云图纹理坐标时添加一个变量,该变量从0递增至1,超过1时做减1操作,这样就可以模拟云的移动。如下的roll即为该变量:
glBegin(GL_QUADS);
glTexCoord2f(0.0f, 3.0f-roll);glVertex3f(-50.0f, 100.0f, -300.0f);
glTexCoord2f(1.0f, 3.0f-roll);glVertex3f( 50.0f, 100.0f, -300.0f);
glTexCoord2f(1.0f, 0.0f-roll);glVertex3f( 50.0f, 100.0f, 300.0f);
glTexCoord2f(0.0f, 0.0f-roll);glVertex3f(-50.0f, 100.0f, 300.0f);
glEnd();
图7 云图和云的二值图
图8 天空
六 心得体会
这次的课程设计不仅考察了书上所学知识点,将所学融合在一起。过程中虽然刚开始感觉有些难,也遇到很多困难,比如电脑配置问题等等,但遇到困难不可怕,重要的是解决困难的过程,我们的游戏场景的绘制,因工程量很大,且代码很多,在实验过程中,我不断查资料,看书去理解各种模型代码,从来不敢相信自己能将看完并理解这么多代码。但是这次我做到了,虽然我重点讲的是纹理模型,其他模型也都去认真学习了一遍。在这个过程中真的学到很多,而大学锻炼的也就是这种自学能力。
七 附录:程序源代码
#include
#include
#include
#include
#include
#pragma comment( lib, "opengl32.lib")
#pragma comment( lib, "glu32.lib")
#pragma comment( lib, "glaux.lib")
#define KEY_DOWN(vk_code)((GetAsyncKeyState(vk_code) & 0x8000) ? 1 : 0)
HDC hDC=NULL;
HGLRC hRC=NULL;
HWND hWnd=NULL;
HINSTANCE hInstance;
bool keys[256];
bool active=TRUE;
bool fullscreen=TRUE;
int SCREEN_WIDTH =800;//屏幕宽
int SCREEN_HEIGHT =600;//屏幕高
GLfloat theta = 0.0f; //左右旋转角度
GLfloat viewUp = 0.0f;//向上和向下程度
GLfloat speed = 0.23f;//运动速度
GLfloat dis=10;//碰撞检测保留距离
GLfloat viewAtPosition[3];//观察目标位置
GLfloat eyePosition[3] = {0.0f, 40.0f, 0.0f}; //视点初始位置
GLfloat Matblack[]= {0.0f, 0.0f, 0.0f, 1.0f};
GLfloat Matwhite[]= {0.7f, 0.7f, 0.7f, 1.0f};
GLfloat Matred[]= {0.6f, 0.0f, 0.0f, 1.0f};
GLfloat LightAmbient[]= {0.8f, 0.8f, 0.8f, 1.0f};
GLfloat LightDiffuse[]= {1.0f, 1.0f, 1.0f, 1.0f};
GLfloat LightPosition[]={1.0f, 1.0f, 1.0f, 0.0f};
GLfloat mat_specular[] ={1.0f, 1.0f, 1.0f, 1.0f};
GLfloat mat_shininess = 100; //高光度
GLfloat fogColor[4]= {0.5f, 0.5f, 0.5f, 0.5f};//雾气颜色
GLuint texture[10];//纹理标识
GLUquadricObj *quadratic=gluNewQuadric();;//二次几何体
GLfloat rotate=0.0f;//石柱旋转角度
GLfloat roll=0;//云层移动量
GLfloat lightning=0;
bool thunder=true;//是否打雷
GLboolean fog=false;//是否开启雾化
bool fp;//F键是否按下
GLuint EnvTexture;//环境纹理
//*********************************************************************//
//接口集
void CollDetec(GLfloat &x,GLfloat &z);
void SetViewByMouse();
void Camera();
AUX_RGBImageRec *LoadBMP(char *Filename);
int LoadGLTextures();
GLuint EmptyTexture();
int InitGL(GLvoid);
void DrawCave();
void DrawBox();
void DrawSnowMan();
void Drawcylinder();
void DrawGlassBall();
void DrawSky();
int DrawGLScene(GLvoid);
GLvoid ReSizeGLScene(GLsizei width, GLsizei height);
GLvoid KillGLWindow(GLvoid);
BOOL CreateGLWindow(char* title, int width, int height, int bits, bool fullscreenflag);
LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);
//*********************************************************************//
//改变宽口大小
GLvoid ReSizeGLScene(GLsizei width, GLsizei height)
{ if (height==0)
{
height=1;
}
//下面函数是定义视图窗口在画布上的大小和位置
glViewport(0,0,width,height);
//设置为投影矩阵
glMatrixMode(GL_PROJECTION);
//当前矩阵设置为单位矩阵
glLoadIdentity();
////角度视,景体的宽高比,沿z轴方向的两裁面之间的距离的近处,//沿z轴方向的两裁面之间的距离的远处
gluPerspective(45.0f,(GLfloat)width/(GLfloat)height,0.1f,1000.0f);
//模型视图应用这个参数后,表示接下来的矩阵操作都是针对模型视景矩阵堆栈 ,
//直到下一次调用这个函数并更改参数为止。
glMatrixMode(GL_MODELVIEW);
//当前矩阵设置为单位矩阵
glLoadIdentity();
}
//碰撞检测,防止视点穿过物体,是极为简单的最小外包矩形进行碰撞检测
void CollDetec(GLfloat &x,GLfloat &z)
{
if (x<-(50-dis))x= -(50-dis);//房间
if (x> (50-dis))x= 50-dis;
if (z<-(300-dis))z= -(300-dis);
if (z> (300-dis))z= 300-dis;
if (z>=-170&&z<=-150&&x>(30-dis)&&eyePosition[1]<=60)x=30-dis;//箱子与玻璃球
if (x>=30&&z>=-170&&z<-(150-dis)&&eyePosition[1]<=60)z=-(150-dis);
if (x>=30&&z<=-150&&z>-(170+dis)&&eyePosition[1]<=60)z=-(170+dis);
if (z>=150&&z<=170&&x<-(30-dis)&&eyePosition[1]<=55) x=-(30-dis);//雪人
if (x<=-30&&z<=170&&z>(150-dis)&&eyePosition[1]<=55)z=(150-dis);
if (x<=-30&&z>=150&&z<(170+dis)&&eyePosition[1]<=55)z=(170+dis);
if (z<=-280&&x>(30-dis))x=30-dis;//石柱
if (z<=-280&&x<-(30-dis))x=-(30-dis);
if (x>=30&&z<-(280-dis))z=-(280-dis);
if (x<=-30&&z<-(280-dis))z=-(280-dis);
}
//利用鼠标控制转向
void SetViewByMouse()
{
POINT mousePos; //鼠标位置
POINT middlePos; //屏幕中心位置
middlePos.x =SCREEN_WIDTH/2;
middlePos.y =SCREEN_HEIGHT/2;
GetCursorPos(&mousePos); //得到鼠标当前位置
if(mousePos.x==middlePos.x&&mousePos.y==middlePos.y) //如果鼠标没有动,返回。
return ;
SetCursorPos(middlePos.x, middlePos.y); //如果鼠标动了,则恢复到屏幕中心
//通过鼠标在x轴上的移动分量,计算旋转改变量
theta += GLfloat(-middlePos.x +mousePos.x)/500; //旋转改变量
//通过鼠标在Y轴上的移动分量,计算旋转改变量
viewUp +=GLfloat( middlePos.y - mousePos.y)/500; //上下改变量
return ;
}
//视点改变
void Camera()
{
SetViewByMouse();//通过鼠标获得
if (KEY_DOWN(VK_LEFT)||KEY_DOWN(A)) // 向左
{
eyePosition[0]+=(viewAtPosition[2]-eyePosition[2])*speed; //利用相似三角形计算
eyePosition[2]+= -(viewAtPosition[0]-eyePosition[0])*speed;
}
if (KEY_DOWN(VK_RIGHT)||KEY_DOWN(D)) //向右
{
eyePosition[0]-=(viewAtPosition[2]-eyePosition[2])*speed;
eyePosition[2]-= -(viewAtPosition[0]-eyePosition[0])*speed;
}
if(theta>360) theta=0.0f;
if (viewUp>0.6f)viewUp = 0.6f;
if (viewUp<-0.6f)viewUp =-0.6f;
if (KEY_DOWN(VK_UP)||KEY_DOWN(W)) //前进
{
eyePosition[0]+= (viewAtPosition[0]-eyePosition[0])*speed;
eyePosition[2]+= (vie
展开阅读全文