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; // 结构命名为stars stars 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 设为TRUE glGenTextures(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设为TRUE twinkle=!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; }
没有开启闪烁效果:
开启闪烁效果后: