Graphics 版 (精华区)

发信人: JJason (UFO), 信区: Graphics
标  题: Jeff Molofee(NeHe) 的 OPENGL 教程-第07课
发信站: 哈工大紫丁香 (2002年10月31日08:40:55 星期四), 站内信件

标题     纹理滤波方式、光源:Jeff Molofee(NeHe) 的 OPENGL 教程-第七课    cker(
翻译)  
  
关键字     OpenGL CKER 
  
出处     http://nehe.gamedev.net/tutorials/ 
  


 

Jeff Molofee(NeHe) 的 OPENGL 教程 
 
第七课 
 Translated by 
CKER 
 


这一课我会教您如何使用三种不同的纹理滤波方式。教您如何使用键盘来移动场景中的对
象,还会教您在OpenGL场景中应用简单的光照。这一课包含了很多内容,如果您对前面的
课程有疑问的话,先回头复习一下。进入后面的代码之前,很好的理解基础知识十分重要
。 
我们还是在第一课的代码上加以修改。跟以前不一样的是,只要有任何大的改动,我都会
写出整段代码。程序开始,我们先加上几个新的变量。  
#include <windows.h> // Windows的头文件 
#include <stdio.h> // 标准输入/输出的头文件( 新增 ) 
#include <gl\gl.h> // OpenGL32库的头文件 
#include <gl\glu.h> // GLu32库的头文件 
#include <gl\glaux.h> // GLaux库的头文件 

HDC hDC=NULL; // 私有GDI设备描述表
HGLRC hRC=NULL; // 永久着色描述表 
HWND hWnd=NULL; // 保存我们的窗口句柄 
HINSTANCE hInstance; // 保存程序的实例 

bool keys[256]; // 用于键盘例程的数组 
bool active=TRUE; // 窗口的活动标志,缺省为TRUE 
bool fullscreen=TRUE; // 全屏标志缺省设定成全屏模式  
下面几行是新的。我们增加三个布尔变量。 light 变量跟踪光照是否打开。变量lp和fp用
来存储'L' 和'F'键是否按下的状态。后面我会解释这些变量的重要性。现在,先放在一边
吧。  
BOOL light; // 光源的开/关 
BOOL lp; // L键按下了么? 
BOOL fp; // F键按下了么?  
现在设置5个变量来控制绕x轴和y轴旋转角度的步长,以及绕x轴和y轴的旋转速度。另外还
创建了一个z变量来控制进入屏幕深处的距离。  
GLfloat xrot; // X 旋转 
GLfloat yrot; // Y 旋转 
GLfloat xspeed; // X 旋转速度 
GLfloat yspeed; // Y 旋转速度 

GLfloat z=-5.0f; // 深入屏幕的距离  
接着设置用来创建光源的数组。我们将使用两种不同的光。第一种称为环境光。环境光来
自于四面八方。所有场景中的对象都处于环境光的照射中。第二种类型的光源叫做漫射光
。漫射光由特定的光源产生,并在您的场景中的对象表面上产生反射。处于漫射光直接照
射下的任何对象表面都变得很亮,而几乎未被照射到的区域就显得要暗一些。这样在我们
所创建的木板箱的棱边上就会产生的很不错的阴影效果。 
创建光源的过程和颜色的创建完全一致。前三个参数分别是RGB三色分量,最后一个是alph
a通道参数。 
因此,下面的代码我们得到的是半亮(0.5f)的白色环境光。如果没有环境光,未被漫射光
照到的地方会变得十分黑暗。  
GLfloat LightAmbient[]= { 0.5f, 0.5f, 0.5f, 1.0f }; //环境光参数 ( 新增 )  
下一行代码我们生成最亮的漫射光。所有的参数值都取成最大值1.0f。它将照在我们木板
箱的前面,看起来挺好。  
GLfloat LightDiffuse[]= { 1.0f, 1.0f, 1.0f, 1.0f }; // 漫射光参数 ( 新增 )  
最后我们保存光源的位置。前三个参数和glTranslate中的一样。依次分别是XYZ轴上的位
移。由于我们想要光线直接照射在木箱的正面,所以XY轴上的位移都是0.0f。第三个值是Z
轴上的位移。为了保证光线总在木箱的前面,所以我们将光源的位置朝着观察者(就是您哪
。)挪出屏幕。我们通常将屏幕也就是显示器的屏幕玻璃所处的位置称作Z轴的0.0f点。所
以Z轴上的位移最后定为2.0f。假如您能够看见光源的话,它就浮在您显示器的前方。当然
,如果木箱不在显示器的屏幕玻璃后面的话,您也无法看见箱子。『译者注:我很欣赏NeH
e的耐心。说真的有时我都打烦了,这么简单的事他这么废话干嘛?但如果什么都清楚,您
还会翻着这样的页面看个没完么?』
最后一个参数取为1.0f。这将告诉OpenGL这里指定的坐标就是光源的位置,以后的教程中
我会多加解释。  
GLfloat LightPosition[]= { 0.0f, 0.0f, 2.0f, 1.0f }; // 光源位置 ( 新增 )  
filter 变量跟踪显示时所采用的纹理类型。第一种纹理(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]; // 3种纹理的储存空间 

LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM); // WndProc定义  
现在载入一个位图,并用它创建三种不同的纹理。这一课使用glaux辅助库来载入位图,因
此在编译时您应该确认是否包含了glaux库。我知道Delphi和VC++都包含了glaux库,但别
的语言不能保证都有。『译者注:glaux是OpenGL辅助库,根据OpenGL的跨平台特性,所有
平台上的代码都应通用。但辅助库不是正式的OpenGL标准库,没有出现在所有的平台上。
但正好在Win32平台上可用。呵呵,BCB当然也没问题了。』这里我只对新增的代码做注解
。如果您对某行代码有疑问的话,请查看教程六。那一课很详细的解释了载入、创建纹理
的内容。
在上一段代码后面及ReSizeGLScene()之前的位置,我们增加了下面的代码。这和第六课中
载入位图的代码几乎相同。  
AUX_RGBImageRec *LoadBMP(char *Filename) // 载入位图 

FILE *File=NULL; // 文件句柄 

if (!Filename) // 确认文件名已初始化 

return NULL; // 没有返回 NULL 


File=fopen(Filename,"r"); // 检查文件是否存在 

if (File) // 存在么? 

fclose(File); // 关闭文件句柄 
return auxDIBImageLoad(Filename); // 载入位图并返回一个指针 

return NULL; // 载入失败返回 NULL 
}  
这段代码调用前面的代码载入位图,并将其转换成3个纹理。Status 变量跟踪纹理是否已
载入并被创建了。  
int LoadGLTextures() // 载入位图并转换成纹理 

int Status=FALSE; // Status 指示器 

AUX_RGBImageRec *TextureImage[1]; // 创建纹理的存储空间 

memset(TextureImage,0,sizeof(void *)*1); // 将指针设为 NULL  
现在载入位图并转换成纹理。TextureImage[0]=LoadBMP("Data/Crate.bmp")调用我们的Lo
adBMP()函数。Data目录下的Crate.bmp将被载入。如果一切正常,图像数据将存放在Textu
reImage[0]。Status变量被设为TRUE,我们将开始创建纹理。  
// 载入位图,检查有错,或位图不存在的话退出。
if (TextureImage[0]=LoadBMP("Data/Crate.bmp")) 

Status=TRUE; // Status 设为 TRUE  
现在我们已经将图像数据载入TextureImage[0]。我们将用它来创建3个纹理。下面的行告
诉OpenGL我们要创建三个纹理,它们将存放在texture[0], texture[1] 和 texture[2] 中
。  
glGenTextures(3, &texture[0]); // 创建纹理  
第六课中我们使用了线性滤波的纹理贴图。这需要机器有相当高的处理能力,但它们看起
来很不错。这一课中,我们接着要创建的第一种纹理使用 GL_NEAREST方式。从原理上讲,
这种方式没有真正进行滤波。它只占用很小的处理能力,看起来也很差。唯一的好处是这
样我们的工程在很快和很慢的机器上都可以正常运行。 
您会注意到我们在 MIN 和 MAG 时都采用了GL_NEAREST,你可以混合使用 GL_NEAREST 和 
GL_LINEAR。纹理看起来效果会好些,但我们更关心速度,所以全采用低质量贴图。MIN_FI
LTER在图像绘制时小于贴图的原始尺寸时采用。MAG_FILTER在图像绘制时大于贴图的原始
尺寸时采用。  
// 创建 Nearest 滤波贴图 
glBindTexture(GL_TEXTURE_2D, texture[0]); 
glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_NEAREST);  // ( 新增 ) 
glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_NEAREST); // ( 新增 ) 
glTexImage2D(GL_TEXTURE_2D, 0, 3, TextureImage[0]->sizeX, 
TextureImage[0]->sizeY, 0, GL_RGB, GL_UNSIGNED_BYTE, TextureImage[0]->data);  
下个纹理与第六课的相同,线性滤波。唯一的不同是这次放在了 texture[1]中。因为这是
第二个纹理。如果放在 texture[0]中的话,他将覆盖前面创建的 GL_NEAREST纹理。  
// 创建线性滤波纹理 
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,等等
。办法就是 gluBuild2DMipmaps。据我的发现,您可以使用任意的位图来创建纹理。OpenG
L将自动将它缩放到正常的大小。 
因为是第三个纹理,我们将它存到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); 
// ( 新增 )  
下面一行生成 mipmapped 纹理。我们使用三种颜色(红,绿,蓝)来生成一个2D纹理。Text
ureImage[0]->sizeX 是位图宽度,extureImage[0]->sizeY 是位图高度,GL_RGB意味着我
们依次使用RGB色彩。GL_UNSIGNED_BYTE 意味着纹理数据的单位是字节。TextureImage[0]
->data指向我们创建纹理所用的位图。   
gluBuild2DMipmaps(GL_TEXTURE_2D, 3, TextureImage[0]->sizeX, 
TextureImage[0]->sizeY, GL_RGB, GL_UNSIGNED_BYTE, TextureImage[0]->data); ( 新
增 ) 
}  
现在释放用来存放位图数据的内存。我们先查看位图数据是否存放在 TextureImage[0] 中
,如果有,删掉。然后释放位图结构以确保内存被释放。  
if (TextureImage[0]) // 纹理是否存在
{
if (TextureImage[0]->data) // 纹理图像是否存在
{
free(TextureImage[0]->data); // 释放纹理图像占用的内存
}
free(TextureImage[0]); // 释放图像结构
}
 
最后我们返回 status 变量。如果一切OK,status 变量的值为TRUE。否则为FALSE。  
return Status; // 返回 Status 变量 
}  
接着应该载入纹理并初始化OpenGL设置了。InitGL函数的第一行使用上面的代码载入纹理
。创建纹理之后,我们调用 
glEnable(GL_TEXTURE_2D)启用2D纹理映射。阴影模式设为平滑阴影( smooth shading )
。背景色设为黑色,我们启用深度测试,然后我们启用优化透视计算。  
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); // 设置环境光  
接下来我们设置漫射光的发光量。它存放在LightDiffuse数组中(全亮度白光)。  
glLightfv(GL_LIGHT1, GL_DIFFUSE, LightDiffuse); // 设置漫射光  
然后设置光源的位置。位置存放在 LightPosition 数组中(正好位于木箱前面的中心,X-
0.0f,Y-0.0f,Z方向移向观察者2个单位<位于屏幕外面>)。  
glLightfv(GL_LIGHT1, GL_POSITION,LightPosition); // 光源位置  
最后,我们启用一号光源。我们还没有启用GL_LIGHTING,所以您看不见任何光线。记住:
只对光源进行设置、定位、甚至启用,光源都不会工作。除非我们启用GL_LIGHTING。  
glEnable(GL_LIGHT1); // 启用一号光源 
return TRUE; // 初始化 OK 
}  
下一段代码绘制贴图立方体。我只对新增的代码进行注解。如果您对没有注解的代码有疑
问,回头看看第六课。  
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轴旋转 
glRotatef(yrot,0.0f,1.0f,0.0f); ); // 绕Y轴旋转  
下一行与我们在第六课中的类似。有所不同的是,这次我们绑定的纹理是texture[filter]
,而不是上一课中的texture[0]。任何时候,我们按下F键,filter 的值就会增加。如果
这个数值大于2,变量filter 将被重置为0。程序初始时,变量filter 的值也将设为0。使
用变量filter 我们就可以选择三种纹理中的任意一种。 
glBindTexture(GL_TEXTURE_2D, texture[filter]); // 选择由filter决定的纹理 
glBegin(GL_QUADS); // 开始绘制四边形 
glNormal3f是这一课的新东西。Normal就是法线的意思,所谓法线是指经过面(多边形)上
的一点且垂直于这个面(多边形)的直线。使用光源的时候必须指定一条法线。法线告诉Ope
nGL这个多边形的朝向,并指明多边形的正面和背面。如果没有指定法线,什么怪事情都可
能发生:不该照亮的面被照亮了,多边形的背面也被照亮....。对了,法线应该指向多边
形的外侧。 
看着木箱的前面您会注意到法线与Z轴正向同向。这意味着法线正指向观察者-您自己。这
正是我们所希望的。对于木箱的背面,也正如我们所要的,法线背对着观察者。如果立方
体沿着X或Y轴转个180度的话,前侧面的法线仍然朝着观察者,背面的法线也还是背对着观
察者。换句话说,不管是哪个面,只要它朝着观察者这个面的法线就指向观察者。由于光
源紧邻观察者,任何时候法线对着观察者时,这个面就会被照亮。并且法线越朝着光源,
就显得越亮一些。如果您把观察点放到立方体内部,你就会法线里面一片漆黑。因为法线
是向外指的。如果立方体内部没有光源的话,当然是一片漆黑。
 
// 前侧面 
glNormal3f( 0.0f, 0.0f, 1.0f); // 法线指向观察者 
glTexCoord2f(0.0f, 0.0f); glVertex3f(-1.0f, -1.0f, 1.0f); // Point 1 (Front) 
glTexCoord2f(1.0f, 0.0f); glVertex3f( 1.0f, -1.0f, 1.0f); // Point 2 (Front) 
glTexCoord2f(1.0f, 1.0f); glVertex3f( 1.0f, 1.0f, 1.0f); // Point 3 (Front) 
glTexCoord2f(0.0f, 1.0f); glVertex3f(-1.0f, 1.0f, 1.0f); // Point 4 (Front) 
// 后侧面 
glNormal3f( 0.0f, 0.0f,-1.0f); // 法线背向观察者
glTexCoord2f(1.0f, 0.0f); glVertex3f(-1.0f, -1.0f, -1.0f); // Point 1 (Back) 
glTexCoord2f(1.0f, 1.0f); glVertex3f(-1.0f, 1.0f, -1.0f); // Point 2 (Back) 
glTexCoord2f(0.0f, 1.0f); glVertex3f( 1.0f, 1.0f, -1.0f); // Point 3 (Back) 
glTexCoord2f(0.0f, 0.0f); glVertex3f( 1.0f, -1.0f, -1.0f); // Point 4 (Back) 
// 顶面 
glNormal3f( 0.0f, 1.0f, 0.0f); // 法线向上 
glTexCoord2f(0.0f, 1.0f); glVertex3f(-1.0f, 1.0f, -1.0f); // Point 1 (Top) 
glTexCoord2f(0.0f, 0.0f); glVertex3f(-1.0f, 1.0f, 1.0f); // Point 2 (Top) 
glTexCoord2f(1.0f, 0.0f); glVertex3f( 1.0f, 1.0f, 1.0f); // Point 3 (Top) 
glTexCoord2f(1.0f, 1.0f); glVertex3f( 1.0f, 1.0f, -1.0f); // Point 4 (Top) 
// 底面 
glNormal3f( 0.0f,-1.0f, 0.0f); // 法线朝下
glTexCoord2f(1.0f, 1.0f); glVertex3f(-1.0f, -1.0f, -1.0f); // Point 1 
(Bottom) 
glTexCoord2f(0.0f, 1.0f); glVertex3f( 1.0f, -1.0f, -1.0f); // Point 2 
(Bottom) 
glTexCoord2f(0.0f, 0.0f); glVertex3f( 1.0f, -1.0f, 1.0f); // Point 3 (Bottom) 
glTexCoord2f(1.0f, 0.0f); glVertex3f(-1.0f, -1.0f, 1.0f); // Point 4 (Bottom) 
// 右侧面 
glNormal3f( 1.0f, 0.0f, 0.0f); // 法线朝右
glTexCoord2f(1.0f, 0.0f); glVertex3f( 1.0f, -1.0f, -1.0f); // Point 1 (Right) 
glTexCoord2f(1.0f, 1.0f); glVertex3f( 1.0f, 1.0f, -1.0f); // Point 2 (Right) 
glTexCoord2f(0.0f, 1.0f); glVertex3f( 1.0f, 1.0f, 1.0f); // Point 3 (Right) 
glTexCoord2f(0.0f, 0.0f); glVertex3f( 1.0f, -1.0f, 1.0f); // Point 4 (Right) 
// 左侧面 
glNormal3f(-1.0f, 0.0f, 0.0f); // 法线朝左
glTexCoord2f(0.0f, 0.0f); glVertex3f(-1.0f, -1.0f, -1.0f); // Point 1 (Left) 
glTexCoord2f(1.0f, 0.0f); glVertex3f(-1.0f, -1.0f, 1.0f); // Point 2 (Left) 
glTexCoord2f(1.0f, 1.0f); glVertex3f(-1.0f, 1.0f, 1.0f); // Point 3 (Left) 
glTexCoord2f(0.0f, 1.0f); glVertex3f(-1.0f, 1.0f, -1.0f); // Point 4 (Left) 
glEnd(); // 四边形绘制结束 
下两行代码将xot和yrot的旋转值分别增加xspeed和yspeed个单位。xspeed和yspeed的值越
大,立方体转得就越快。 
xrot+=xspeed; // xrot 增加 xspeed 单位
yrot+=yspeed; // yrot 增加 yspeed 单位
return TRUE;  
}  
现在转入WinMain()主函数。我们将在这里增加开关光源、旋转木箱、切换过滤方式以及将
木箱移近移远的控制代码。在接近WinMain()函数结束的地方你会看到SwapBuffers(hDC)这
行代码。然后就在这一行后面添加如下的代码。
代码将检查L键是否按下过。如果L键已按下,但lp的值不是false的话,意味着L键还没有
松开,这时什么都不会发生。 
SwapBuffers(hDC); // 交换缓存 (双缓存) 
if (keys['L'] && !lp) // L 键已按下并且松开了? 
{  
如果lp的值是false的话,意味着L键还没按下,或者已经松开了,接着lp将被设为TRUE。
同时检查这两个条件的原因是为了防止L键被按住后,这段代码被反复执行,并导致窗体不
停闪烁。
lp设为true之后,计算机就知道L键按过了,我们则据此可以切换光源的开/关:布尔变量l
ight控制光源的开关。 
lp=TRUE; // lp 设为 TRUE 
light=!light; // 切换光源的 TRUE/FALSE 
下面几行来检查光源是否应该打开,并根据light变量的值。 
if (!light) // 如果没有光源

glDisable(GL_LIGHTING); //禁用光源

else // Otherwise 

glEnable(GL_LIGHTING); //启用光源

}  
下面的代码查看是否松开了"L"键。如果松开,变量lp将设为false。这意味着"L"键没有按
下。如果不作此检查,光源第一次打开之后,就无法再关掉了。计算机会以为"L"键一直按
着呢。 
if (!keys['L']) //L键松开了么? 

lp=FALSE; // 若是,则将lp设为FALSE 
}  
然后对"F"键作相似的检查。如果有按下"F"键并且"F"键没有处于按着的状态或者它就从没
有按下过,将变量fp设为true。这意味着这个键正被按着呢。接着将filter变量加一。如
果filter变量大于2(因为这里我们的使用的数组是texture[3],大于2的纹理不存在),我们
重置filter变量为0。 
if (keys['F'] && !fp) // F键按下了么? 

fp=TRUE; // fp 设为 TRUE 
filter+=1; // filter的值加一 
if (filter>2) // 大于2了么? 

filter=0; // 若是重置为0 


if (!keys['F']) //F键放开了么? 

fp=FALSE; // 若是fp设为FALSE 
}  
这四行检查是否按下了PageUp键。若是的话,减少z变量的值。这样DrawGLScene函数中包
含的glTranslatef(0.0f,0.0f,z)调用将使木箱离观察者更远一点。 
if (keys[VK_PRIOR]) //PageUp按下了? 

z-=0.02f; // 若按下,将木箱移向屏幕内部。 
}  
接着四行检查PageDown键是否按下,若是的话,增加z变量的值。这样DrawGLScene函数中
包含的glTranslatef(0.0f,0.0f,z)调用将使木箱向着观察者移近一点。 
if (keys[VK_NEXT]) // PageDown按下了么? 

z+=0.02f; //若按下的话,将木箱移向观察者。 
}  
现在检查方向键。按下左右方向键xspeed相应减少或增加。按下上下方向键yspeed相应减
少或增加。记住在以后的教程中如果xspeed、yspeed的值增加的话,立方体就转的更快。
如果一直按着某个方向键,立方体会在那个方向上转的越快。 
if (keys[VK_UP]) // Up方向键按下了么? 

xspeed-=0.01f; //若是,减少xspeed 

if (keys[VK_DOWN]) //Down方向键按下了么? 

xspeed+=0.01f; //若是,增加xspeed 

if (keys[VK_RIGHT]) //Right方向键按下了么? 

yspeed+=0.01f; //若是,增加yspeed 

if (keys[VK_LEFT]) //Left方向键按下了么? 

yspeed-=0.01f; //若是, 减少yspeed 

像前几课一样,我们最后还需要更正窗体的标题。 
if (keys[VK_F1]) // F1按下了么? 

keys[VK_F1]=FALSE; //若是将其设为FALSE 
KillGLWindow(); //销毁当前窗口 
fullscreen=!fullscreen; // 切换全屏/窗口模式 
// 重建GL窗口
if (!CreateGLWindow("NeHe's Textures, Lighting & Keyboard 
Tutorial",640,480,16,fullscreen)) 

return 0; // 若无法创建窗口,程序退出 






// 关闭
KillGLWindow(); // 销毁窗口
return (msg.wParam); // 退出程序
}  
这一课完了之后,您应该学会创建和使用这三种不同的纹理映射过滤方式。并使用键盘和
场景中的对象交互。最后,您应该学会在场景中应用简单的光源,使得场景看起来更逼真
。 
『译者:NeHe的文档似乎很简单,似乎很罗嗦。但仔细想来这样的高手您又见过几个?还
是那句话,我不是高手,希望您是,真诚的。』 
下面是源代码下载链接。祝您好运!  
* DOWNLOAD Visual C++ Code For This Lesson. 
* DOWNLOAD Visual C++ / OpenIL Code For This Lesson. ( Conversion by Denton 
Woods ) 
* DOWNLOAD Visual Basic Code For This Lesson. ( Conversion by Peter De Tagyos 

* DOWNLOAD Visual Fortran Code For This Lesson. ( Conversion by Jean-Philippe 
Perois ) 
* DOWNLOAD GLUT Code For This Lesson. ( Conversion by Andy Restad ) 
* DOWNLOAD Cygwin (FREE Language) Code For This Lesson. ( Conversion by 
Stephan Ferraro ) 
* DOWNLOAD Delphi Code For This Lesson. ( Conversion by Brad Choate ) 
* DOWNLOAD MASM Code For This Lesson. ( Conversion by Nico (Scalp) ) 
* DOWNLOAD Linux/GLX Code For This Lesson. ( Conversion by Mihael Vrbanec ) 
* DOWNLOAD MacOS X/GLUT Code For This Lesson. ( Conversion by Raal Goff ) 
* DOWNLOAD Linux Code For This Lesson. ( Conversion by Richard Campbell ) 
* DOWNLOAD Irix Code For This Lesson. ( Conversion by Lakmal Gunasekara ) 
* DOWNLOAD Solaris Code For This Lesson. ( Conversion by Lakmal Gunasekara ) 
* DOWNLOAD Mac OS Code For This Lesson. ( Conversion by Anthony Parker ) 
* DOWNLOAD Power Basic Code For This Lesson. ( Conversion by Angus Law ) 
* DOWNLOAD BeOS Code For This Lesson. ( Conversion by Chris Herborth ) 
* DOWNLOAD Java Code For This Lesson. ( Conversion by Darren Hodges ) 
* DOWNLOAD MingW32 & Allegro Code For This Lesson. ( Conversion by Peter Puck 

* DOWNLOAD Borland C++ Builder 4.0 Code For This Lesson. ( Conversion by 
Patrick Salmons )  
 
 
--

     人生,就是一团欲望:
     欲望没有满足的时候就是痛苦,
     欲望被满足的时候就是无聊;
     人生就是在痛苦与无聊之间徘徊。

※ 来源:·哈工大紫丁香 bbs.hit.edu.cn·[FROM: 202.118.229.69]
[百宝箱] [返回首页] [上级目录] [根目录] [返回顶部] [刷新] [返回]
Powered by KBS BBS 2.0 (http://dev.kcn.cn)
页面执行时间:204.674毫秒