在这节课中,我将教你如何使用三种不同的纹理过滤器。我将教你如何使用键盘来移动物体,如何在OpenGL场景中使用简单的光照。这一课包含了很多内容,如果你对前面的课程有疑问的话,先回头复习一下。在你学习下面的知识之前,对基础知识的熟练掌握很重要。
我们再次地修改第一节课中的代码。通常,如果某一部分代码做了重要的改动,我会把这部分代码全部重写。我们首先给程序添加一些新的变量。
#include <windows.h> // Windows头文件
#include <stdio.h> // 标准输入/输出头文件 ( ADD )
#include <gl\gl.h> // OpenGL32库头文件
#include <gl\glu.h> // GLu32库头文件
#include <gl\glaux.h> // GLaux库头文件
HGLRC hRC=NULL;
// 永久的渲染上下文( Rendering Context)
HDC
hDC=NULL;
// 私有的GDI设备上下文( GDI Device Context)
HWND
hWnd=NULL;
// 获得我们窗口的句柄
HINSTANCE
hInstance;
// 获得应用程序的实例
bool
keys[256];
// 用于键盘行为的数组
bool
active=TRUE;
// 窗口活动标记,默认设置为TRUE
bool
fullscreen=TRUE;
// 全屏标记,默认设置为全屏
BOOL light; // 光照打开/关闭
BOOL lp; // L键是否按下
BOOL fp; // F键是否按下
接下来,我们定义五个变量,xrot用来控制在x轴上的转动角度,yrot用来控制y轴上的转动角度,xspeed用来控制条板箱在x轴上的转动速度,yspeed用来控制条板箱在y轴上的转动速度。z变量用来控制条板箱在屏幕里的深度。
GLfloat xrot; // x轴上的转动角度
GLfloat yrot; // y轴上的转动角度
GLfloat xspeed; // x轴上的转动速度
GLfloat yspeed; // y轴上的转动速度
GLfloat z=-5.0f; //在屏幕里的深度
然后,我们设置用来产生光线的数组。我们将使用两种不同类型的光。第一种类型的光叫做环绕光。环绕光是没有特定方向的光线。场景中的任何物体都会被环绕光照亮。第二种类型的光叫做发散光。发散光是由你设置的光源产生的,它能被场景中的物体表面反射。直接被光线照射的物体表面会很亮,而光线少的那一面会很暗。这会使条板箱的侧面产生一个不错的阴影效果。光线的生成方式和颜色的生成方式是一样的。如果第一个参数是1.0f,接下来的两个参数都是0.0f,将产生一个明亮的红光。如果第三个参数是1.0f,而前两个参数是0.0f,将产生一个明亮的蓝光。最后一个参数是alpha值,我们暂且把它设置为1.0f。
所以,下面的代码生成了一个半强度的白光。因为所有的参数都是0.5f,我们将产生一个灰白的光。(Because all the numbers are 0.5f, we will end up with a light that's halfway between off (black) and full brightness (white). )红、绿和蓝以相同的值混合,将形成一个从黑到白的色调。如果没有环绕光,没有被散射光找到的部分将会非常暗。
GLfloat LightAmbient[]= { 0.5f, 0.5f, 0.5f, 1.0f }; // 环绕光( NEW )
下面的代码,我们将生成一个非常明亮的,强度最大的白光。所有的参数都为1.0f。意味着这是我们能够产生的最亮的光。散射光源放置在条板箱的正前方。
GLfloat LightDiffuse[]= { 1.0f, 1.0f, 1.0f, 1.0f }; // 散射光 ( NEW )
最后,我们保存光源的位置。前三个参数和glTranslate函数的前三个参数的用处是相同的。第一个参数用来在x屏幕左右移动,第二个参数用来在y轴上上下移动,第三个参数用来在z轴上前后移动。因为我们想让我们的光源直接照射在条板箱的正面,我们不需要左右移动,所以第一个参数为0.0f,我们也不想上下移动,所以第二个参数也为0.0f。至于第三个参数,因为我们想让光源总是在条板箱的正前方。所以我们把光源移出屏幕。假设照相机的镜头在z平面的0.0f处。那么我们把光源的位置放在z平面的2.0f处。如果你可以实际地看到光源,你会发现它就浮在照相机镜头的正前方。这么做之后,光线唯一能照到条板箱后面的方法是,把条板箱也移到照相机镜头的前面。当然,如果条板想已经不在照相机镜头的后面了,我们就观察不到条板箱了,所以,光源在哪里并不重要。能感觉到吗?(这里可能不太好理解,我稍微解释以下。现在照相机镜头在z平面的位置为0.0f,光源在z平面的位置为2.0f,条板箱在z屏幕的位置为负数(还没确定,但肯定是负数)。因为2.0f > 0.0f,所以光源在照相机镜头的前面;因为0.0f > 负数,所以照相机镜头在条板箱的前面。总之,谁大谁在前面,谁小谁在后面。之后物体在照相机镜头的后面,才可以看见物体。)
没有更简单的方法来解释这三个参数了。你应该知道-2.0f比-5.0f和-100.0f在屏幕中的位置离你更近。一旦你到了0.0f,物体将大到充满整个屏幕。一旦z值大于0.0f,你将不会再看到物体了,因为它已经远离屏幕了。我说的远离屏幕的意思是,物体依然存在,只是你再也看不见它了。
最后一个参数为1.0f。它通知OpenGL我们指定的位置是光源的位置。我们将在以后的课程中做解释。
GLfloat LightPosition[]= { 0.0f, 0.0f, 2.0f, 1.0f }; //光源位置 ( NEW )
下面第一行代码的变量用来监视使用了哪一种纹理。第一种纹理(texture 0)使用了gl_nearest 参数(没有平滑效果)。第二种纹理(texture 1)使用了gl_linear参数,它带有一点的平滑效果。第三种纹理(texture 2)使用了mipmapped纹理,可以产生完美的视觉效果。filter变量将依据我们使用的纹理取值0,1,2。我们首先使用第一种纹理。
GLuint texture[3]用来生成三种不同纹理的存储空间。纹理将被保存在texture[0], texture[1] 和 texture[2]中。
GLuint filter; // 使用哪种纹理
GLuint texture[3]; // 存储三种纹理
LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM); // 声明WndProc函数
现在,我们加载一张bitmap图像,然后用它生成三种不同的纹理。教程中使用glaux函数库来加载bitmap图像,所以在编译代码之前,确认你已经引入了glaux函数库。我知道Delphi和 Visual C++ 都有glaux函数库。我不确定其它的语言有没有。我将解释新的代码的作用,如果你发现哪些代码我没有注释,而你又想知道它的用途,回头看第六课的教程。它很详细地解释了如何加载bitmap图像,并用它来生成纹理。
紧接着上面的代码,在ReSizeGLScene()函数之前,我们要加入下面的代码。下面的代码和第六课教程中用来加载bitmap图像的代码完全一样。没有做任何修改。如果你对下面的代码还有什么疑问,回头看第六课教程,它解释的很详细。
AUX_RGBImageRec *LoadBMP(char *Filename) // 加载一幅Bitmap图像
{
FILE *File=NULL; // 文件句柄
if (!Filename) //
检查是否已经有了一个要加载文件的文件名
{
return NULL; // 如果没有,返回 NULL
}
File=fopen(Filename,"r"); //
检查这个文件是否存在
if (File) // 文件是否存在
{
fclose(File); // 如果存在,关闭文件句柄
return auxDIBImageLoad(Filename); // 加载Bitmap图像,返回一个指针
}
return NULL; // 如果加载失败,返回 NULL
}
下面这部分代码是用来加载bitmap(调用上面的代码),然后把它转换成3种纹理映射。Status变量是用来判断纹理是否加载和创建成功。
int LoadGLTextures() // 加载bitmap,然后把它转换成纹理
{
int Status=FALSE; // Status 标识位
AUX_RGBImageRec *TextureImage[1]; // 为纹理创建存储空间
memset(TextureImage,0,sizeof(void *)*1); // 设置指针为NULL
现在我们加载bitmap,然后把它转换成纹理。TextureImage[0]=LoadBMP("Data/Crate.bmp") 这行代码将会跳转到LoadBMP()这部分代码。在Data文件夹下的Crate.bmp文件将会加载进来。如果一切顺利,这个图像数据将会保存在TextureImage[0]中,Status变量设置为TRUE,然后我们创建纹理。
// 加载bitmap,检查是否出错,如果bitmap没有找到,退出
if (TextureImage[0]=LoadBMP("Data/Crate.bmp"))
{
Status=TRUE; // Status 设置为 TRUE
现在我们已经把图像数据加载到了TextureImage[0]中,我们将使用这个数据创建三个纹理。下面的代码通知OpenGL我们将生成三个纹理,这些纹理将被存储在texture[0],texture[1]和texture[2]中。
glGenTextures(3, &texture[0]); // 创建三个纹理
在第六课中,我们使用了线性过滤纹理映射。这需要大量的处理器资源,但是看起来效果很好。这节课我们使用的第一个纹理类型是GL_NEAREST。基本上这种类型的纹理没有过滤效果。它需要很少的处理器资源,但是效果很差。如果你曾经玩过一些纹理贴图看起来像块状的游戏的话,它可能使用的就是这种类型的纹理。这种类型纹理的唯一好处就是,可以在速度很慢的机器上运行流畅。
你会注意到我们在MIN 和 MAG上都使用的是GL_NEAREST 类型的纹理。你也可以把GL_NEAREST 和 GL_LINEAR混合使用,这样纹理效果看起来会好一点,但是我们专注于速度,所以我们都使用低质量类型的纹理。MIN_FILTER 过滤器是当绘制的图像比原始纹理小的时候使用的。而MAG_FILTER 过滤器的情况正好相反。
// 创建 Nearest 过滤器纹理
glBindTexture(GL_TEXTURE_2D, texture[0]);
glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_NEAREST); // ( NEW )
glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_NEAREST); // ( NEW )
glTexImage2D(GL_TEXTURE_2D, 0, 3, TextureImage[0]->sizeX, TextureImage[0]->sizeY, 0, GL_RGB, GL_UNSIGNED_BYTE, TextureImage[0]->data);
下一种类型的纹理和我们在第六课中使用的纹理类型是一样的,Linear 过滤器纹理。唯一的区别是我们把它存储在texture[1]中而不是texture[0]中,因为它是第二个纹理。如果我们想上面那样把它存储在texture[0]中,它将会覆盖GL_NEAREST 纹理(第一个纹理)。
// 创建Linear 过滤器纹理
glBindTexture(GL_TEXTURE_2D, texture[1]);
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[0]->sizeX, TextureImage[0]->sizeY, 0, GL_RGB, GL_UNSIGNED_BYTE, TextureImage[0]->data);
然后是一种新的创建纹理的方法,创建Mipmapping纹理。你可以已经注意到,当你在屏幕上绘制一个很小的图像时,会丢失大量的像素。原来看起来很好看的图案,现在看起来很糟糕。当你通知OpenGL去创建一个mipmapped 纹理时,OpenGL将尝试去创建不同尺寸的高质量的纹理。当你绘制mipmapped 纹理到屏幕上时,OpenGL将会从它创建的所有纹理中选择一个效果最好的纹理(更精细的纹理),然后把它绘制到屏幕上,而不是缩放原始图像的尺寸(丢失细节的纹理)。
在第六课中我说个,这里有绕过OpenGL中对于纹理的宽和高的尺寸必须是64,128,256等2的幂次方的限制的方法。gluBuild2DMipmaps 就是这个方法。至少我发现,创建mipmapped 纹理时,你可以使用任何你想用的bitmap图像(任何的宽和高)。OpenGL将会自动调整它到合适的宽和高。
因为这个第三个纹理,所以我们把它存储在texture[2]中。所以现在,我们有一个没有任何过滤效果的纹理——texture[0],有一个使用线性过滤效果的纹理——texture[1],和一个使用mipmapped 效果的纹理——texture[2]。这一课需要使用的纹理我们已经创建完成。
// 创建MipMapped 纹理
glBindTexture(GL_TEXTURE_2D, texture[2]);
glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR_MIPMAP_NEAREST); // ( NEW )
下面这行代码创建MipMapped 纹理。我们使用三种颜色(红,绿,蓝)创建一个2D纹理。TextureImage[0]->sizeX是bitmap的宽,TextureImage[0]->sizeY是bitmap的高,GL_RGB 是说我们以红,绿,蓝的顺序使用颜色,GL_UNSIGNED_BYTE 是说纹理数据的类型是byte类型,TextureImage[0]->data是指向我们用来创建纹理的bitmap数据。
gluBuild2DMipmaps(GL_TEXTURE_2D, 3, TextureImage[0]->sizeX, TextureImage[0]->sizeY, GL_RGB, GL_UNSIGNED_BYTE, TextureImage[0]->data); // ( NEW )
}
然后我们释放所有我们用来存储bitmap数据的存储空间。我们检查bitmap数据是否存储在TextureImage[0]中。如果是,我们再检查数据是否存储在里面。如果数据已经存储在里面,我们释放它。然后我们释放这个图像结构体,确保使用过的存储空间全部释放。
if (TextureImage[0]) //如果纹理存在
{
if (TextureImage[0]->data) // 如果纹理图像存在
{
free(TextureImage[0]->data); // 释放纹理图像内存
}
free(TextureImage[0]); // 释放图像结构体
}
最后,我们返回Status变量。如果一切顺利,Status变量的值为TRUE。如果出现错误,Status变量的值为FALSE。
return Status; // 返回Status 变量
}
现在我们加载纹理,然后初始化OpenGL设置。InitGL 函数的第一行代码使用上面的代码加载纹理。纹理创建之后,我们使用glEnable(GL_TEXTURE_2D)函数启用2D纹理映射。阴影模式设置为平滑阴影,屏幕背景颜色设置为黑色,然后启用深度测试,然后我们启用完美透视计算。
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); // 设置深度缓存
glEnable(GL_DEPTH_TEST); // 启用深度测试
glDepthFunc(GL_LEQUAL); // 深度测试类型
glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST); // 完美透视计算
接下来我们设置光照。下面的代码我们设置环绕光强度,light1 光源启用。在这节课的开始部分我们把环绕光的强度存储在LightAmbient变量中。我们将使用存储在这个数组中的值。(半强度环绕光)
glLightfv(GL_LIGHT1, GL_AMBIENT, LightAmbient); // 设置环绕光
接下来,我们设置散射光的强度,light1 光源启用。我们存储散射光的强度在LightDiffuse变量中。我们将使用存储在这个数组中的值。(饱和强度的白光)
glLightfv(GL_LIGHT1, GL_DIFFUSE, LightDiffuse); // 设置散射光
现在我们设置光源的位置。我们把光源的位置存储在LightPosition变量中。我们将使用存储在这个数组中的值。(正好在正面的中心,在x轴的位置为0.0f,在y轴的位置为 0.0f ,在z平面的位置为朝向观察者2个单位处 {移出屏幕} )
glLightfv(GL_LIGHT1, GL_POSITION,LightPosition); // 设置光源位置
最后我们启用光源light1 。我们还没有启用GL_LIGHTING ,所以现在你还不能看到任何的光照效果。光源已经设置好了,光源的位置也已经设置了,它也已经启用了,但是直到我们启用GL_LIGHTING ,否者光源不会起作用。
glEnable(GL_LIGHT1); // 启用光源 light1
return TRUE; // 初始化完成
}
下面这部分代码我们将绘制纹理映射的正方体。一些新代码会有注释。如果你不太清楚没有注释的代码的用途,看一下第六课的教程。
int DrawGLScene(GLvoid) // 我们绘制图形的地方
{
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); // 清除颜色和深度缓存
glLoadIdentity(); // 重置模型视图矩阵
下面三行代码设置纹理映射正方体的位置,并且旋转它。glTranslatef(0.0f,0.0f,z) 在z平面上移动正方体z个单位。(远离或朝着观察者)glRotatef(xrot,1.0f,0.0f,0.0f)在x轴上旋转正方体xrot度。glRotatef(yrot,0.0f,1.0f,0.0f)在y轴上旋转正方体yrot度。
glTranslatef(0.0f,0.0f,z); // 在平面z上移入/移出
glRotatef(xrot,1.0f,0.0f,0.0f); // 在x轴上旋转正方体xrot度
glRotatef(yrot,0.0f,1.0f,0.0f); // 在y轴上旋转正方体yrot度
下面的这行代码和我们在第六课中使用的很相似,只是我们使用texture[filter]来代替texture[0]。每一次我们点击'F'键,filter的值都会增加。如果这个值大于2,它会被重新设置为0。程序开始时filter的默认值为0。这和调用glBindTexture(GL_TEXTURE_2D, texture[0])的效果是一样的。通过使用filter变量我们可以选择我们创建的三个纹理中的任意一个。
glBindTexture(GL_TEXTURE_2D, texture[filter]); // 根据filter变量选择一个纹理
glBegin(GL_QUADS); // 开始绘制正方形
glNormal3f 函数是课程里新添加的。法线是垂直于多边形平面的指向外面的直线。当你使用光照时,你需要明确的指明法线。法线通知OpenGL多边形的正面朝向哪里。如果你不指明法线,会有一些意想不到的事情发生。不应该被照亮的一面被照亮了,不应该被照亮的一侧被照亮了,等等。法线的方向应该是垂直于多边形向外的。(由背面到正面)
观察正方体的正面你会发现,法线的z值是正值。这说明法线是指向观察者的。正好是我们想要的方向。在正方体的背面,法线是远离观察者的,指向屏幕的里面。也是我们想要的方向。如果正方体沿x轴或者y轴旋转180度,正面将会面向屏幕里面,而背面将会朝向观察者。无论哪一面朝向观察者,那个面的法线都是指向观察者的。因为光源更接近观察者,所以无论何时法线指向观察者它也就指向了光源。当它朝向光源时,那一面就会被照亮。法线越接近光源,那一面就会越亮。在正方体的内部,是漆黑的。因为法线的方向是向外而不是向内,所以在正方体的内部没有光线,实际上也应如此。
glBegin(GL_QUADS);
// 正面
glNormal3f( 0.0f, 0.0f, 1.0f);
// 法线指向观察者
glTexCoord2f(0.0f, 0.0f); glVertex3f(-1.0f, -1.0f, 1.0f); // 左下角
glTexCoord2f(1.0f, 0.0f); glVertex3f( 1.0f, -1.0f, 1.0f); // 右下角
glTexCoord2f(1.0f, 1.0f); glVertex3f( 1.0f, 1.0f, 1.0f); // 右上角
glTexCoord2f(0.0f, 1.0f); glVertex3f(-1.0f, 1.0f, 1.0f); // 左上角
// 背面
glNormal3f( 0.0f, 0.0f,-1.0f);
//法线远离观察者
glTexCoord2f(1.0f, 0.0f); glVertex3f(-1.0f, -1.0f, -1.0f); // 右下角
glTexCoord2f(1.0f, 1.0f); glVertex3f(-1.0f, 1.0f, -1.0f); // 右上角
glTexCoord2f(0.0f, 1.0f); glVertex3f( 1.0f, 1.0f, -1.0f); // 左上角
glTexCoord2f(0.0f, 0.0f); glVertex3f( 1.0f, -1.0f, -1.0f); // 左下角
// 上面
glNormal3f( 0.0f, 1.0f, 0.0f);
// 法线指向上面
glTexCoord2f(0.0f, 1.0f); glVertex3f(-1.0f, 1.0f, -1.0f); // 左上角
glTexCoord2f(0.0f, 0.0f); glVertex3f(-1.0f, 1.0f, 1.0f); // 左下角
glTexCoord2f(1.0f, 0.0f); glVertex3f( 1.0f, 1.0f, 1.0f); // 右下角
glTexCoord2f(1.0f, 1.0f); glVertex3f( 1.0f, 1.0f, -1.0f); // 右上角
// 底面
glNormal3f( 0.0f,-1.0f, 0.0f);
// 法线指向下面
glTexCoord2f(1.0f, 1.0f); glVertex3f(-1.0f, -1.0f, -1.0f); // 右上角
glTexCoord2f(0.0f, 1.0f); glVertex3f( 1.0f, -1.0f, -1.0f); // 左上角
glTexCoord2f(0.0f, 0.0f); glVertex3f( 1.0f, -1.0f, 1.0f); // 左下角
glTexCoord2f(1.0f, 0.0f); glVertex3f(-1.0f, -1.0f, 1.0f); // 右下角
// 右面
glNormal3f( 1.0f, 0.0f, 0.0f);
// 法线指向右边
glTexCoord2f(1.0f, 0.0f); glVertex3f( 1.0f, -1.0f, -1.0f); // 右下角
glTexCoord2f(1.0f, 1.0f); glVertex3f( 1.0f, 1.0f, -1.0f); // 右上角
glTexCoord2f(0.0f, 1.0f); glVertex3f( 1.0f, 1.0f, 1.0f); // 左上角
glTexCoord2f(0.0f, 0.0f); glVertex3f( 1.0f, -1.0f, 1.0f); // 左下角
// 左面
glNormal3f(-1.0f, 0.0f, 0.0f);
// 法线指向左边
glTexCoord2f(0.0f, 0.0f); glVertex3f(-1.0f, -1.0f, -1.0f); // 左下角
glTexCoord2f(1.0f, 0.0f); glVertex3f(-1.0f, -1.0f, 1.0f); // 右下角
glTexCoord2f(1.0f, 1.0f); glVertex3f(-1.0f, 1.0f, 1.0f); // 右上角
glTexCoord2f(0.0f, 1.0f); glVertex3f(-1.0f, 1.0f, -1.0f); // 左上角
glEnd();
下面的两行代码通过存储在xspeed和yspeed变量中的值来增加xrot 和 yrot 变量的值。在xspeed和yspeed变量中的值来越大,xrot 和 yrot 变量的值增加的越快。xrot 和 yrot 变量的值增加的越快,正方体在那个轴上旋转的越快。
xrot+=xspeed; // xrot的值加上xspeed的值
yrot+=yspeed; // yrot的值加上yspeed的值
return TRUE;
}
然后我们到WinMain()函数中。增加代码来控制光照的开/关,旋转正方体,改变纹理,把正方体移入/移出屏幕。在WinMain()函数接近底部的地方,你会看到SwapBuffers(hDC)函数调用。紧接着这行代码,加入下面的代码。
代码检查键盘上的'L'键是否被按下。第一行代码检查'L'键是否被按下。如果'L'键被按下了,但是lp不等于false,意味着'L'键已经被按下一次或者'L'键被按住了,这时不会做任何处理。
SwapBuffers(hDC); // 交换缓冲区 (双缓冲)
if (keys['L'] && !lp) // L键被按住?
{
如果lp为false,意味着'L'键没有被按下,或者已经松开,lp设置为true。它强制你要想让这段代码再次运行必须松开'L'键。如果你不检查'L'键是否被按住,光源将会一遍一遍的打开和关闭,因为程序假设当运行到这段代码时每一次你都点击了'L'键。
一旦lp被设置为true,通知计算机'L'键已经被按住了,我们打开或关闭光源。light变量只有true或false两种值。所以如果我们通过light = !light;我们是说让light取反。翻译过来就是如果light等于true,设置light不等于true(false),如果light等于false,设置light不等于false(true)。所以如果light等于true,就设置它为false,如果light等于false,就设置它为true。
lp=TRUE; // lp 设置为TRUE
light=!light; // 设置 Light 为 TRUE/FALSE
然后我们检查最后的light值。第一行是说:如果light等于false。所以整个if语句的意思是,如果light等于false,关闭光源。它将关闭所有光源。else分支是说,如果light等于true,打开光源。
if (!light) // Light等于false
{
glDisable(GL_LIGHTING); // 关闭光源
}
else // 否则
{
glEnable(GL_LIGHTING); // 打开光源
}
}
下面的代码检查我们是否松开了'L'键。如果我们松开了'L'键,设置lp等于false,说明'L'键没有按住。如果我们不检查'L'键是否松开了,我们可以打开一次光源,但是因为计算机一直认为'L'键是被按下的,所以我们不能再关闭它。
if (!keys['L']) // 'L'键是否松开?
{
lp=FALSE; // 如果松开了, lp 设置为FALSE
}
然后,我们用同样的方法处理'F'键。如果'F'键被按下,并且它没有被按住或者它重来没有被按过,这时设置fp为true,说明现在'F'键被按住了。它将增加filter变量的值。如果filter变量大于2,我们把它重置为0。
if (keys['F'] && !fp) // F键是否被按下?
{
fp=TRUE; // fp 设置为TRUE
filter+=1; // filter 变量加1
if (filter>2) // 如果大于2?
{
filter=0; // 重置为0
}
}
if (!keys['F']) // F键是否松开?
{
fp=FALSE; // 如果松开,fp 设置为FALSE
}
下面的四行代码检查我们是否按下了'PAGE UP键。如果我们按下了,将会减小z的值,因为在DrawGLScene 函数中调用了glTranslatef(0.0f,0.0f,z),所以正方体会移入屏幕。
if (keys[VK_PRIOR]) // Page Up键是否按下?
{
z-=0.02f; // 如果按下, 正方体移入屏幕
}
下面的四行代码检查我们是否按下了'PAGE DOWN'键。如果我们按下了,将会增加z的值,因为在DrawGLScene 函数中调用了glTranslatef(0.0f,0.0f,z),所以正方体会朝向观察者移动。
if (keys[VK_NEXT]) // Page Down 键是否按下?
{
z+=0.02f; // 如果按下, 朝向观察者移动正方体
}
现在,我们检查方向键。通过点击左箭头/右箭头,xspeed将会增加/减小。通过点击上箭头/下箭头,yspeed将会增加/减少。记住我在课程开始时说过的,是否xspeed或yspeed越大,正方体旋转的越快。你按住一个方向键时间越长,正方体在那个方向上旋转的越快。
if (keys[VK_UP]) // Up Arrow 键是否被按下?
{
xspeed-=0.01f; // 减小xspeed值
}
if (keys[VK_DOWN]) // Down Arrow键是否被按下?
{
xspeed+=0.01f; // 增加xspeed值
}
if (keys[VK_RIGHT]) // Right Arrow 键是否被按下?
{
yspeed+=0.01f; // 增加yspeed 值
}
if (keys[VK_LEFT]) // Left Arrow 键是否被按下?
{
yspeed-=0.01f; // 减小yspeed值
}
和前面的教程一样,确保窗口的标题是正确的。
if
(keys[VK_F1])
//如果F1被按下
{
keys[VK_F1]=FALSE;
//设置为FALSE
KillGLWindow();
//销毁当前的窗口
fullscreen=!fullscreen;
//切换全屏/窗口模式
// Recreate Our OpenGL Window
if
(!CreateGLWindow(
"
"NeHe's Textures,lighting & Keyboard Tutorial
,640,480,16,fullscreen))
{
return
0;
//如果窗口创建失败,程序退出
}
}
}
}
}
// Shutdown
KillGLWindow();
// 杀掉窗口
return
(msg.wParam);
// 退出程序
}
在这节课的最后,你应该能够创建一个高质量的,更逼真的,有纹理映射的正方体,并可以和它交互。你应该理解这节课中三种不同纹理各自的好处。通过点击指定的按键,你应该可以跟屏幕上的物体交互,最后,你应该知道如何在场景中使用光照,来使场景更加逼真。