NeHe_OpenGL_第九课 3D空间中移动图像

来源:互联网 发布:杜应流 知乎 编辑:程序博客网 时间:2024/05/22 12:29

http://52coding.com/nehe-3d-space-picture-moving

我将上下左右改成键盘上wsad的控制,好多天没学习OpenGL,忘记了specialKeyboard如何使用了。按下t键开启闪烁效果(我就感觉亮了一些)

代码下载 (没注意修改下积分,这个需要1积分)

#include "stdafx.h"#define GLUT_DISABLE_ATEXIT_HACK#include "glaux.h" // GLaux 库的头文件#include "glut.h"// 下列这几行新加的。twinkle和 tp是布尔变量,  表示它们只能设为 TRUE 或 FALSE 。 twinkle用来跟踪/*闪烁 效果是否启用。 tp用来检查 'T'键有没有被按下或松开. ( 按下时 tp=TRUE,  松开时 tp=FALSE).*/BOOL    twinkle;//  闪烁的星星BOOL    tp;// 'T' 按下了么? // num 跟踪屏幕上所绘制的星星数。这个数字被定义为一个常量。这意味着无法在以后的代码中对其// 进行修改。这么做的原因是因为您无法重新定义一个数组。因此,如果我们定义一个50颗星星的数// 组,然后又将num 增加到51的话,就会出错『CKER :数组越界』。不过您还是可以( 也只可以) 在这/*一行上随意修改这个数字。但是以后请您别再改动 num  的值了,除非您想看见灾难发生。*/const int  num=50; //  绘制的星星数/*现在我们来创建一个结构。 结构这词听起来有点可怕,但实际上并非如此。 一个结构使用一组简单类型的数据 ( 以及变量等) 来表达较大的具有相似性的数据组合。 我们知道我们在保持对星星的跟踪。 您可以看到下面的第七行就是 stars;并且每个星星有三个整型的色彩值。第三行 int r,g,b 设置了三个整数.  一个红色 (r),  一个绿色 (g),  以及一个蓝色 (b).  此外,每个星星离屏幕中心的距离不同,而且可以是以屏幕中心为原点的任意360度中的一个角度。如果你看下面第四行的话,  会发现我们使用了一个叫做 dist 的浮点数来保持对距离 的跟踪.  第五行则用一个叫做 angle 的浮点数保持对星星角度值的跟踪。因此我们使用了一组数据来描述屏幕上星星的色彩,  距离,  和角度。 不幸的是我们不止对一个星星进行跟踪。但是无需创建 50  个红色值、50个绿色值、50个蓝色值、50个距离值 以及 50个角度值,而只需创建一个数组star 。star 数组的每个元素都是stars 类型的,里面存放 了描述星星的所有数据。star 数组在下面的第八行创建。 第八行的样子是这样的: stars star[num] 。数组类型是 stars结构.  所数组 能存放所有stars 结构的信息。 数组名字是 star. 数组大小是 [num] 。 数组中存放着 stars结构的元素.  跟踪结构元素会比跟踪各自分开的变量容易的多.  不过这样也很笨,  因为我们竟然不能改变常量 num来增减星星 数量。*/typedef struct         //  为星星创建一个结构{int r, g, b;       //  星星的颜色GLfloat dist;      //  星星距离中心的距离GLfloat angle;   //  当前星星所处的角度}stars;                 //  结构命名为starsstars star[num];       //  使用 'stars' 结构生成一个包含 'num'个元素的 'star'数组/*接下来我们设置几个跟踪变量:星星离观察者的距离变量(zoom) ,我们所见到的星星所处的角度(tilt) ,以及使闪烁的星星绕Z轴自转的变量spin 。loop 变量用来绘制50颗星星。texture[1] 用来存放一个黑白纹理。如果您需要更多的纹理的话,您应该增加texture 数组的大小至您决定采用的纹理个数。*/GLfloat zoom = -15.0f;  //  星星离观察者的距离GLfloat tilt = 90.0f;   //  星星的倾角GLfloat spin;           //  闪烁星星的自转GLuint  loop;           //  全局 Loop  变量GLuint  texture[1];     //  存放一个纹理/*紧接着上面的代码就是我们用来载入纹理的代码。我不打算再详细的解释这段代码。这跟我们在第六、七、八课中所用的代码是一模一样的。这一次载入的位图叫做star.bmp 。这里我们使用glGenTextures(1, &texture[0]),来生成一个纹理。纹理采用线性滤波方式。*/AUX_RGBImageRec *LoadBMP(char *Filename)  //  载入位图文件{FILE *File=NULL;                      //  文件句柄if (!Filename)                        //  确认已给出文件名{printf("can't open!\n");return NULL;                      //  若无返回 NULL}File=fopen(Filename,"r");             //  检查文件是否存在if (File)                             //  文件存在么?{fclose(File);                     //  关闭文件句柄return auxDIBImageLoad(Filename); //  载入位图并返回指针}return NULL;                          //  如果载入失败返回 NULL}/*下面的代码( 调用上面的代码) 载入位图,并转换成纹理。变量用来跟踪纹理是否已载入并创建好了。*/int LoadGLTextures()                               //  载入位图并转换成纹理{int Status=FALSE;                              //  状态指示器AUX_RGBImageRec *TextureImage=new AUX_RGBImageRec;//  为纹理分配存储空间memset(TextureImage,0,sizeof(void *)*1);       //  将指针设为 NULL//  载入位图,查错,如果未找到位图文件则退出if (TextureImage=LoadBMP("Particle.bmp")){Status=TRUE;                               //  将 Status  设为TRUEglGenTextures(1, &texture[0]);             //  创建一个纹理//  创建一个线性滤波纹理glBindTexture(GL_TEXTURE_2D, texture[0]);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->sizeX, TextureImage->sizeY, 0, GL_RGB, GL_UNSIGNED_BYTE, TextureImage->data);}else{printf("failed texture\n");return FALSE;}if (TextureImage)                //  如果纹理存在{if (TextureImage->data)      //  如果纹理图像存在{free(TextureImage->data);//  释放纹理图像所占的内存}free(TextureImage);          //  释放图像结构}return Status;                      //  返回 Status的值}/*现在设置OpenGL的渲染方式。这里不打算使用深度测试,如果您使用第一课的代码的话,请确认是否已经去掉了 glDepthFunc(GL_LEQUAL);  和 glEnable(GL_DEPTH_TEST); 两行。否则,您所见到的效果将会一团糟。这里我们使用了纹理映射,因此请您确认您已经加上了这些第一课中所没有的代码。您会注意到我们通过混色来启用了纹理映射。   [为啥不用深度测试啊]*/int InitGL(GLvoid)                                              //  此处开始对OpenGL 进行所有设置{if (!LoadGLTextures())                                  //  调用纹理载入子例程{return FALSE;                                   //  如果未能载入,返回FALSE}glEnable(GL_TEXTURE_2D);                                //  启用纹理映射glShadeModel(GL_SMOOTH);                                //  启用阴影平滑glClearColor(0.0f, 0.0f, 0.0f, 0.5f);                   //  黑色背景glClearDepth(1.0f);                                     //  设置深度缓存glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST);       //  真正精细的透视修正glBlendFunc(GL_SRC_ALPHA,GL_ONE);                        //  设置混色函数取得半透明效果glEnable(GL_BLEND);                                     //  启用混色/*以下是新增的代码。设置了每颗星星的起始角度、距离、和颜色。您会注意到修改结构的属性有多容易。全部50颗星星都会被循环设置。要改变star[1] 的角度我们所要做的只是star[1].angle={某个数值} ;就这么简单!*/for (loop=0; loop<num; loop++)                          //  创建循环设置全部星星{star[loop].angle=0.0f;                          //  所有星星都从零角度开始/*第loop 颗星星离中心的距离是将loop 的值除以星星的总颗数,然后乘上5.0f 。基本上这样使得后一颗星星比前一颗星星离中心更远一点。这样当loop 为50时( 最后一颗星星) ,loop 除以 num正好是1.0f 。之所以要乘以5.0f 是因为1.0f*5.0f  就是 5.0f 。『CKER :废话,废话!这老外怎么跟孔乙己似的!:) 』5.0f 已经很接近屏幕边缘。我不想星星飞出屏幕,5.0f 是最好的选择了。当然如果如果您将场景设置的更深入屏幕里面的话,也许可以使用大于5.0f 的数值,但星星看起来就更小一些( 都是透视的缘故) 。您还会注意到每颗星星的颜色都是从0~255之间的一个随机数。也许您会奇怪为何这里的颜色得取值范围不是OpenGL通常的0.0f ~1.0f 之间。这里我们使用的颜色设置函数是glColor4ub ,而不是以前的glColor4f 。ub意味着参数是Unsigned Byte型的。一个byte 的取值范围是0~255。这里使用byte 值取随机整数似乎要比取一个浮点的随机数更容易一些。*/star[loop].dist=(float(loop)/num)*5.0f;          //  计算星星离中心的距离star[loop].r=rand()%256;                        //  为star[loop]设置随机红色分量star[loop].g=rand()%256;                        //  为star[loop]设置随机红色分量star[loop].b=rand()%256;                        //  为star[loop]设置随机红色分量} return TRUE;                                            //  初始化一切OK}/*Resize 的代码也是一样的,现在我们转入绘图代码。如果您使用第一课的代码,删除旧的DrawGLScene 代码,只需将下面的代码复制过去就行了。实际上,第一课的代码只有两行,所以没太多东西要删掉的。*/void DrawGLScene(GLvoid)                                         //  此过程中包括所有的绘制代码{glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);      //  清除屏幕及深度缓存glBindTexture(GL_TEXTURE_2D, texture[0]);                //  选择纹理for (loop=0; loop<num; loop++)                          //  循环设置所有的星星{glLoadIdentity();                               //  绘制每颗星星之前,重置模型观察矩阵glTranslatef(0.0f,0.0f,zoom);                    //  深入屏幕里面glRotatef(tilt,1.0f,0.0f,0.0f);                  //  倾斜视角/*现在我们来移动星星。星星开始时位于屏幕的中心。我们要做的第一件事是把场景沿Y 轴旋转。如果我们旋转90度的话,X 轴不再是自左至右的了,他将由里向外穿出屏幕。为了让大家更清楚些,举个例子。假想您站在房子中间。再设想您左侧的墙上写着-x ,前面的墙上写着-z ,右面墙上就是+x咯,您身后的墙上则是+z。加入整个房子向右转90度,但您没有动,那么前面的墙上将是-x 而不再是-z 了。所有其他的墙也都跟着移动。-z 出现在右侧,+z出现在左侧,+x出现在您背后。神经错乱了吧?通过旋转场景,我们改变了x和z 平面的方向。第二行代码沿x轴移动一个正值。通常x轴上的正值代表移向了屏幕的右侧( 也就是通常的x轴的正向) ,但这里由于我们绕y轴旋转了坐标系,x轴的正向可以是任意方向。如果我们转180度的话,屏幕的左右侧就镜像反向了。因此,当我们沿 x 轴正向移动时,可能向左,向右,向前或向后。*/glRotatef(star[loop].angle,0.0f,1.0f,0.0f);      //  旋转至当前所画星星的角度glTranslatef(star[loop].dist,0.0f,0.0f);         //  沿X 轴正向移动/*接着的代码带点小技巧。星星实际上是一个平面的纹理。现在您在屏幕中心画了个平面的四边形然后贴上纹理,这看起来很不错。一切都如您所想的那样。但是当您当您沿着y轴转上个90度的话,纹理在屏幕上就只剩右侧和左侧的两条边朝着您。看起来就是一条细线。这不是我们所想要的。我们希望星星永远正面朝着我们,而不管屏幕如何旋转或倾斜。我们通过在绘制星星之前,抵消对星星所作的任何旋转来实现这个愿望。您可以采用逆序来抵消旋转。当我们倾斜屏幕时,我们实际上以当前角度旋转了星星。通过逆序,我们又以当前角度" 反旋转" 星星。也就是以当前角度的负值来旋转星星。就是说,如果我们将星星旋转了10度的话,又将其旋转-10度来使星星在那个轴上重新面对屏幕。下面的第一行抵消了沿y轴的旋转。然后,我们还需要抵消掉沿x轴的屏幕倾斜。要做到这一点,我们只需要将屏幕再旋转-tilt 倾角。在抵消掉x和y轴的旋转后,星星又完全面对着我们了。*/glRotatef(-star[loop].angle,0.0f,1.0f,0.0f);     //  取消当前星星的角度glRotatef(-tilt,1.0f,0.0f,0.0f);                 //  取消屏幕倾斜/*如果 twinkle  为 TRUE ,我们在屏幕上先画一次不旋转的星星:将星星总数(num)  减去当前的星星数(loop) 再减去1,来提取每颗星星的不同颜色( 这么做是因为循环范围从0到num-1)。举例来说,结果为10的时候,我们就使用10号星星的颜色。这样相邻星星的颜色总是不同的。这不是个好法子,但很有效。最后一个值是alpha通道分量。这个值越小,这颗星星就越暗。由于启用了twinkle,每颗星星最后会被绘制两遍。程序运行起来会慢一些,这要看您的机器性能如何了。但两遍绘制的星星颜色相互融合,会产生很棒的效果。同时由于第一遍的星星没有旋转,启用twinkle后的星星看起来有一种动画效果。( 如果您这里看不懂得话,就自己去看程序的运行效果吧。)值得注意的是给纹理上色是件很容易的事。尽管纹理本身是黑白的,纹理将变成我们在绘制它之前选定的任意颜色。此外,同样值得注意的是我们在这里使用的颜色值是byte 型的,而不是通常的浮点数。甚至alpha通道分量也是如此。*/if (twinkle)                                    //  启用闪烁效果{//  使用byte型数值指定一个颜色glColor4ub(star[(num-loop)-1].r,star[(num-loop)-1].g,star[(num-loop)-1].b,255);glBegin(GL_QUADS);                      //  开始绘制纹理映射过的四边形glTexCoord2f(0.0f, 0.0f); glVertex3f(-1.0f,-1.0f, 0.0f);glTexCoord2f(1.0f, 0.0f); glVertex3f( 1.0f,-1.0f, 0.0f);glTexCoord2f(1.0f, 1.0f); glVertex3f( 1.0f, 1.0f, 0.0f);glTexCoord2f(0.0f, 1.0f); glVertex3f(-1.0f, 1.0f, 0.0f);glEnd();                                //  四边形绘制结束}//现在绘制第二遍的星星。唯一和前面的代码不同的是这一遍的星星肯定会被绘制,并且这次的星星绕着z 轴旋转。glRotatef(spin,0.0f,0.0f,1.0f);                  //  绕z 轴旋转星星//  使用byte型数值指定一个颜色glColor4ub(star[loop].r,star[loop].g,star[loop].b,255);glBegin(GL_QUADS);                              //  开始绘制纹理映射过的四边形glTexCoord2f(0.0f, 0.0f); glVertex3f(-1.0f,-1.0f, 0.0f);glTexCoord2f(1.0f, 0.0f); glVertex3f( 1.0f,-1.0f, 0.0f);glTexCoord2f(1.0f, 1.0f); glVertex3f( 1.0f, 1.0f, 0.0f);glTexCoord2f(0.0f, 1.0f); glVertex3f(-1.0f, 1.0f, 0.0f);glEnd();       /*以下的代码代表星星的运动。我们增加spin 的值来旋转所有的星星( 公转) 。然后,将每颗星星的自转角度增加loop/num 。这使离中心更远的星星转的更快。最后减少每颗星星离屏幕中心的距离。这样看起来,星星们好像被不断地吸入屏幕的中心。*/spin+=0.01f;                                    //  星星的公转star[loop].angle+=float(loop)/num;               //  改变星星的自转角度star[loop].dist-=0.01f;                         //  改变星星离中心的距离/*接着几行检查星星是否已经碰到了屏幕中心。当星星碰到屏幕中心时,我们为它赋一个新颜色,然后往外移5个单位,这颗星星将踏上它回归屏幕中心的旅程。*/if (star[loop].dist<0.0f)                       //  星星到达中心了么{star[loop].dist+=5.0f;                  //  往外移5 个单位star[loop].r=rand()%256;                //  赋一个新红色分量star[loop].g=rand()%256;                //  赋一个新绿色分量star[loop].b=rand()%256;                //  赋一个新蓝色分量}}glFlush();//必须加这句话printf(".");}void ReSizeFunc(int width,int height){glViewport(0,0,width,height);glMatrixMode(GL_PROJECTION);glLoadIdentity();gluPerspective(45,width/height,0.1,100);glMatrixMode(GL_MODELVIEW);glLoadIdentity();}void KeyBoardFunc(unsigned char key, int x, int y){printf("%x\n",key);if(VK_ESCAPE == key){exit(0);}/*现在我们添加监视键盘的代码。下移到WinMain()。找到SwapBuffers(hDC) 一行。我们就在这一行后面增加键盘监视代码。代码将检查T 键是否已按下。如果T 键按下过,并且又放开了,if 块内的代码将被执行。如果twinkle为FALSE,他将变为TRUE 。反之亦然。只要T 键按下, tp就变为TRUE 。这样处理可以防止如果您一直按着T 键的话,块内的代码被反复执行。*/if ((('T'==key) || ('t'==key)) && !tp)                           //  是否T 键已按下并且 tp 值为 FALSE{tp=TRUE;                                //  若是,将tp设为TRUEtwinkle=!twinkle;                       //  翻转 twinkle 的值}// 下面的代码检查是否松开了T 键。若是,使 tp=FALSE 。除非tp 的值为FALSE,否则按着T 键时什么/*也不会发生。所以这行代码很重要。*/if ( ('T'!=key) && ('t'!=key))                                 // T 键已松开了么?{tp=FALSE;                               //  若是  ,tp为 FALSE}/*余下的代码检查上、下方向键,向上翻页键或向下翻页键是否按下。*/if ( 'w' == key )                                //  上方向键按下了么?{tilt-=0.5f;                             //  屏幕向上倾斜}if ( 's' == key )                              //  下方向键按下了么?{tilt+=0.5f;                             //  屏幕向下倾斜}if ( 'a' == key )                             //  向上翻页键按下了么{zoom-=0.2f;                             //  缩小}if ( 'd' == key )                              //  向下翻页键按下了么?{zoom+=0.2f;                             //  放大}DrawGLScene();}int main(int argc, char *argv[]){glutInit(&argc, argv);glutInitDisplayMode(GLUT_RGB | GLUT_SINGLE);glutInitWindowPosition(100, 100);glutInitWindowSize(500, 500);glutCreateWindow("第九个OpenGL程序");glutReshapeFunc(ReSizeFunc);glutDisplayFunc(DrawGLScene);glutKeyboardFunc(KeyBoardFunc);InitGL();glutMainLoop();return 0;}


没有开启闪烁效果:

开启闪烁效果后:

 

原创粉丝点击