Lesson 07 Texture Filters, Lightning & Keyboard Control
在此篇教程中, 我将会教给你如何使用三种不同的纹理过滤, 如何通过键盘操作来移动物体, 还有如何在场景中应用简单的光照。 如果对前面的教程还有问题, 一定要回去弄明白, 因为之前的基础对你理解此篇教程是非常重要的。
我们还要使用第一篇教程的代码作为基础。 照常, 如果有什么主要的改动我会给出完整的代码段。 那么, 我们就从增加一些新的变量开始。
#include
<windows.h>
// Header File For Windows
#include
<stdio.h>
// Header File For Standard Input/Output ( ADD )
#include
<gl\gl.h>
// Header File For The OpenGL32 Library
#include
<gl\glu.h>
// Header File For The GLu32 Library
#include
<gl\glaux.h>
// Header File For The GLaux Library
HDC
hDC=NULL;
// Private GDI Device Context
HGLRC
hRC=NULL;
// Permanent Rendering Context
HWND
hWnd=NULL;
// Holds Our Window Handle
HINSTANCE
hInstance;
// Holds The Instance Of The Application
bool
keys[256];
// Array Used For The Keyboard Routine
bool
active=TRUE;
// Window Active Flag
bool
fullscreen=TRUE;
// Fullscreen Flag
下面我们增加了3个BOOL变量, 也就是说它们只有TRUE和FALSE两种状态。 变量light 用于记录光照是否打开, 变量 lp 和 fp 用于保存键盘上的 L 键和 F 键是否被按下了。 对这些变量在后面将会有解释。
BOOL
light;
// Lighting ON / OFF
BOOL
lp;
// L Pressed?
BOOL
fp;
// F Pressed?
接下来我们设置5个变量, 它们分别用于控制木箱在x轴上的旋转角度(xrot), 在y轴上的旋转角度(yrot), 在x轴上的旋转速度(xspeed), 在y轴上的旋转速度(yspeed), 和其在屏幕中的深度(z)。
GLfloat
xrot;
// X Rotation
GLfloat
yrot;
// Y Rotation
GLfloat xspeed;
// X Rotation Speed
GLfloat yspeed;
// Y Rotation Speed
GLfloat
z=-5.0f;
// Depth Into The Screen
现在我们设置用于创建光照的数组。 我们将使用两种不同类型的光。 第一种叫做环境光, 它没有一个固定的方向(译注:看起来来自四面八方), 可以照亮场景中的所有物体。 第二种叫做散射光, 它来自于你的光源, 那些被散射光照射到的物体的表面将会明亮起来, 而那些不被散射光照射到的地方则会暗淡下来。 这会使木箱的表面出现漂亮的阴影效果。
光照的创建方法与颜色的创建方法相类似。 如果第一个参数是1.0f, 下两个都是0.0f, 那将是一个亮红的灯光。 如果第三个参数是1.0f, 而前两个是0.0f, 那将是一个亮蓝的灯光。 最后一个参数是alpha值, 现在我们就设为 1.0f。
所以下面这行代码, 我们保存了一些用于创建 半亮度白色环境光 的变量。 因为参数都是 0.5f, 所以是一个介于无光(黑) 和 全亮(白) 的灯光。 没有环境光的话, 漫射光照不到的地方将会非常黑暗。
GLfloat LightAmbient[]= { 0.5f, 0.5f, 0.5f, 1.0f };
// Ambient Light Values ( NEW )
下一行代码我们保存一些变量, 用于创建一个非常明亮、 全亮度的漫射光。 所有值都是 1.0f, 意味着那好是我们可以达到的最亮的灯光。 它将把木箱的前面照亮。
GLfloat LightDiffuse[]= { 1.0f, 1.0f, 1.0f, 1.0f };
// Diffuse Light Values ( NEW )
最后我们保存光源的位置。 前三个参数当然是用于指定其坐标, x, y 和 z。 我们想照亮木箱的前面, 所以在x和y轴上都不必动, 0.0f 即可。 而要使光源在木箱前面, 我们要使其离开屏幕, 照向显示器(译注:这是一个比喻)。 你可以想象显示器的玻璃平面就是 z 轴上 0.0f 的平面, 我们把光源放在2.0f的位置上, 所以如果你真的能看到这个光源的话, 它应该在你的显示器玻璃屏上飘浮着。 如果我们也把木箱移出来的话, 当然灯光就不能保证在木箱的前面了, 有可能到了后面。 不过这没关系, 因为此时我们其实已经看不到木箱了, 所以光源在哪并不影响。
其实解释第三个参数并不容易。 要知道, -2.0f 比 -5.0f 离你更近, 而 -100.0f 已经在屏幕里的远处了。 当你到达 0.0f 时, 图像看起来会很大, 充斥了整个屏幕; 而如果你向里到达一个极限值的时候, 图像就看不见了, “越过了屏幕”。 我说的在屏幕之外就是这个意思, 物体就在那, 但你看不到。
最后一个参数是 1.0f, 这告知 OpenGL 指定的坐标就是光源的位置, 这在以后会有解释。
GLfloat LightPosition[]= { 0.0f, 0.0f, 2.0f, 1.0f };
// Light Position ( NEW )
变量 filter 用于指定显示哪一个纹理。 第一个纹理(纹理0) 使用GL_NEAREST 过滤(无平滑), 第二个纹理(纹理1)使用GL_LINEAR 过滤, 得到平滑的图像。 第三个纹理(纹理2) 使用 mipmap 纹理, 创建非常漂亮的纹理外观。变量filter 的值可以是0,1或2,用于指定使用哪一个纹理。 开始的时候默认是第一个纹理。
(译注:mipmap 纹理按照观察距离选择不同尺寸的纹理, 实现多层次的细节。mipmap 纹理显示更好的外观, 但占用更多的内存。 这个教程使用的是自动mipmap纹理生成。 详细请看红皮书)
代码行 GLuint texture[3] 用于给3个不同的纹理创建存储空间, 它们分别存储于texture[0], texture[1] 和texture[2]。
GLuint
filter;
// Which Filter To Use
GLuint
texture[3];
// Storage for 3 textures
LRESULT
CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);
// Declaration For WndProc
现在读入位图, 并以其创建3个不同的纹理。 教程使用了aux库读入位图, 所以要保证aux库被包含了。 我知道 Delphi 和Visual C++ 都有aux库, 其它语言我不知道。 在此我只解释新的代码, 其它的代码在之前的教程已经有解释了。读入位图, 创建纹理这些都解释的非常详细。
我们也增加了下面这个代码段, 与lesson 06 中的一模一样, 用于读入位图。
AUX_RGBImageRec *LoadBMP(char *Filename)
// Loads A Bitmap Image
{
FILE *File=NULL;
// File Handle
if (!Filename)
// Make Sure A Filename Was Given
{
return NULL;
// If Not Return NULL
}
File=fopen(Filename,"r");
// Check To See If The File Exists
if (File)
// Does The File Exist?
{
fclose(File);
// Close The Handle
return auxDIBImageLoad(Filename);
// Load The Bitmap And Return A Pointer
}
return NULL;
// If Load Failed Return NULL
}
下面的代码段调用上面的代码以读入位图, 然后创建3个纹理。 变量Status 用于记录纹理是否被读入并创建了。
int LoadGLTextures()
// Load Bitmaps And Convert To Textures
{
int Status=FALSE;
// Status Indicator
AUX_RGBImageRec *TextureImage[1];
// Create Storage Space For The Texture
memset(TextureImage,0,sizeof(void *)*1);
// Set The Pointer To NULL
代码 TextureImage[0]=LoadBMP("Data/Crate.bmp") 调用上面的代码段读入位图, Data 目录中的 Crate.bmp 将被读入。 如果顺利, 图像数据将存储于TextureImage[0], Status 被设置为 TRUE, 然后我们开始创建纹理。
// Load The Bitmap, Check For Errors, If Bitmap's Not Found Quit
if (TextureImage[0]=LoadBMP("Data/Crate.bmp"))
{
Status=TRUE;
// Set The Status To TRUE
现在已经读入了图像数据到TextureImage[0] 中, 我们将用它创建3个纹理。 下一行代码告知OpenGL我们要创建3个纹理, 并分别保存于texture[0], texture[1] 和 texture[2]。
(译注: 命名纹理对象)
glGenTextures(3, &texture[0]);
// Create Three Textures
在lesson 06 中, 我们使用了linear(线性)纹理过滤, 这种过滤需要大量的运算, 但效果漂亮。 这次, 第一纹理我们使用GL_NEAREST 过滤, 它几乎不做过滤, 低运算量, 但效果很差。 如果某个游戏的纹理看起来都是锯齿, 可能使用的就是这种过滤。 不过通常它可以在慢速电脑上运行良好。
你会注意到我们对MIN 和MAG 都使用了GL_NEAREST过滤。其实你可以混合使用GL_NEAREST和GL_LINEAR, 那将能使纹理看起来好一些, 但会牺牲一些速度。 所以我们把两个都设为最低质量。 MIN_FILTER 指定当纹理尺寸小于其原始尺寸时所使用的过滤, 而MAG_FILTER 指定当纹理尺寸大于其原始尺寸时所使用的过滤。
// Create Nearest Filtered Texture
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);
下面我们使用和lesson 06中同样的linear过滤建造纹理, 不同的是这次储存在texture[1] 而不是texture[0] 中, 因为这是我们的第二个纹理了。 如果再次存储于texture[0] 中, 将会覆盖掉上面的第一个GL_NEAREST 纹理。
// Create Linear Filtered Texture
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 创建mipmap 纹理的时候, OpenGL 会尝试为纹理创建不同尺寸的高质量图像。之后, 当你绘制这个mipmap 纹理的时候, OpenGL 会自动从刚才其创建的纹理图像(包含更多细节)中选择一个最佳外观的来绘制, 而不是缩放最原始的纹理图像(将丢失细节)。
在lesson 06 中我曾经说过, 为了解决纹理尺寸必须2的幂这个限制, 可以适当的调整其尺寸。 现在gluBuild2DMipmaps 就可以为我们做这样的事情。我发现创建mipmap 纹理的时候可以使用任意尺寸的图像, OpenGL 会自动的调整其尺寸。
因为这是第三个纹理, 所以要存储在texture[2] 中。 现在我们有了未过滤的纹理texture[0] 和 linear过滤的纹理texture[1], 而texture[2] 用来创建mipmap 纹理。
// Create MipMapped Texture
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
下一行代码用于创建mipmap纹理。 我们要创建一个 2D 纹理, 使用3种颜色(RGB), TextureImage[0]->sizeX 是图像宽度, TextureImage[0]->sizeY 是图像高度, GL_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); // NEW
}
现在我们释放掉所有用于保存位图的内存。
if (TextureImage[0])
// If Texture Exists
{
if (TextureImage[0]->data)
// If Texture Image Exists
{
free(TextureImage[0]->data);
// Free The Texture Image Memory
}
free(TextureImage[0]);
// Free The Image Structure
}
最后返回状态。 如果一切顺利, 变量Status 将为 TRUE; 否则为 FALSE。
return Status;
// Return The Status
}
在这里我们读入纹理, 初始化OpenGL 设置。 InitGL 中第一行代码调用上面的代码段以读入纹理。 然后, 使用glEnable(GL_TEXTURE_2D) 打开纹理影射, 使用平滑阴影模型, 设置背景为黑色, 开启深度测试, 还有打开最好的透视修正。
int InitGL(GLvoid)
// All Setup For OpenGL Goes Here
{
if (!LoadGLTextures())
// Jump To Texture Loading Routine
{
return FALSE;
// If Texture Didn't Load Return FALSE
}
glEnable(GL_TEXTURE_2D);
// Enable Texture Mapping
glShadeModel(GL_SMOOTH);
// Enable Smooth Shading
glClearColor(0.0f, 0.0f, 0.0f, 0.5f);
// Black Background
glClearDepth(1.0f);
// Depth Buffer Setup
glEnable(GL_DEPTH_TEST);
// Enables Depth Testing
glDepthFunc(GL_LEQUAL);
// The Type Of Depth Testing To Do
glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST);
// Really Nice Perspective Calculations
现在设置光照。 这一行代码设置light1发出的环境光。 此篇教程的开始, 我们存储了环境光参数到LightAmbient 中, 现在我们将使用这些参数(半强度环境光)。
glLightfv(GL_LIGHT1, GL_AMBIENT, LightAmbient);
// Setup The Ambient Light
现在我们设置light1发出的散射光。 参数开始我们已经保存在LightDiffuse 中了, 全亮度白光。
glLightfv(GL_LIGHT1, GL_DIFFUSE, LightDiffuse);
// Setup The Diffuse Light
现在设置这个光源的位置, 之前我们已经保存在LightPosition 中了。
glLightfv(GL_LIGHT1, GL_POSITION,LightPosition);
// Position The Light
最后, 我们开启light1。 light1已经设置好了, 但是, 在我们没有开启GL_LIGHTING 之前, 仍然不会有光照。
glEnable(GL_LIGHT1);
// Enable Light One
return TRUE;
// Initialization Went OK
}
下一段代码我们就要开始绘制木箱了。
int DrawGLScene(GLvoid)
// Here's Where We Do All The Drawing
{
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
// Clear The Screen And The Depth Buffer
glLoadIdentity();
// Reset The View
接下来的三行代码移动和旋转带有纹理的立方体(木箱)。 glTranslatef(0.0f,0.0f,z) 在 z轴上移动木箱, glRotatef(xrot,1.0f,0.0f,0.0f) 和glRotatef(yrot,0.0f,1.0f,0.0f) 分别使用xrot 和 yrot 变量控制木箱在 x 轴和y轴上的旋转。
glTranslatef(0.0f,0.0f,z);
// Translate Into/Out Of The Screen By z
glRotatef(xrot,1.0f,0.0f,0.0f);
// Rotate On The X Axis By xrot
glRotatef(yrot,0.0f,1.0f,0.0f);
// Rotate On The Y Axis By yrot
下一行代码与 lesson 06 中的相似, 但我们使用了texture[filter] 而非 texture[0]。 当我们按下F键时, 变量filter 会有增量, 当其大于2时, 就会回到0, 程序开始时filter 会初始化为0。 我们通过这种方式来选择使用哪一个纹理。
glBindTexture(GL_TEXTURE_2D, texture[filter]);
// Select A Texture Based On filter
glBegin(GL_QUADS);
// Start Drawing Quads
glNormal3f 是教程中新出现的函数。 法线(normal) 是垂直于多边形表面的向量, 当使用光照的时候必须要指定法线向量, 它用于告诉OpenGL多边形的朝向(译注:OpenGL通过法线向量确定在顶点处物体接收了多少光)。 如果不指定法线向量, 将会发生一些奇怪的事情, 例如光照不到的面却变亮了,等等。
木箱正面的法线向量是指向z轴正方向的, 也就是指向你; 而木箱背面的法线向量指向z轴负方向, 也就是指向显示器里面, 这正是我们希望的。 而当木箱在x轴或y轴上旋转180度时, 木箱原来的正面就会朝向显示器里面, 原来的背面就会朝向你。 但无论哪一面朝向你, 那一面的法线向量也都会跟着朝向你。 由于光源是离你很近的, 所以当某一面朝向你的时候其法线也就指向了光源, 这时, 那一面就会被照得亮起来, 越朝向光源就越亮。 如果你进入到木箱的中心, 将发现那里非常黑暗。 因为法线都指向外面, 所以这里面没有被照亮, 也正应如此。
// Front Face
glNormal3f( 0.0f, 0.0f, 1.0f);
// Normal Pointing Towards Viewer
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)
// Back Face
glNormal3f( 0.0f, 0.0f,-1.0f);
// Normal Pointing Away From Viewer
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)
// Top Face
glNormal3f( 0.0f, 1.0f, 0.0f);
// Normal Pointing Up
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)
// Bottom Face
glNormal3f( 0.0f,-1.0f, 0.0f);
// Normal Pointing Down
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)
// Right face
glNormal3f( 1.0f, 0.0f, 0.0f);
// Normal Pointing Right
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)
// Left Face
glNormal3f(-1.0f, 0.0f, 0.0f);
// Normal Pointing Left
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();
// Done Drawing Quads
下两行代码分别给 xrot 和 yrot 做增量, 增量的大小分别由xspeed 和 yspeed 决定。 所以, xspeed 和 yspeed的值越高, 木箱旋转的就越快。
xrot+=xspeed;
// Add xspeed To xrot
yrot+=yspeed;
// Add yspeed To yrot
return TRUE;
// Keep Going
}
现在让我们移至函数 WinMain() 中。 我们将在WinMain() 中增加一些代码以增加一些功能, 开关光照, 旋转木箱, 切换纹理过滤, 和前后移动木箱。 在 WinMain() 的底部, 有一行代码是 SwapBuffers(hDC), 现在要在其后面增加代码如下。
代码检测 L键是否被按下了。 如果 L键被按下了, 但lp的值却不为false, 意味着L键一直处于压下状态, 这时什么都不会发生。
SwapBuffers(hDC);
// Swap Buffers (Double Buffering)
if (keys['L'] && !lp)
// L Key Being Pressed Not Held?
{
如果lp 的值为false, 意味着 L键不处于压下状态, 或早已释放了。 现在我们把 lp置为 true。 这样是为了避免如果按住L键光照就会因为反复的开、关而闪烁, 因为计算机每次到达这段代码的时候都会以为你按了一次L键。
一旦lp被置为true, 会告诉计算机 L键处于压下状态了。 然后我们转换光照的开与关。 变量light只能是true或false, 所以我们用light=!light, 这样, 如果它是true的话就会变成false, 是false的话就会变成true。
lp=TRUE;
// lp Becomes TRUE
light=!light;
// Toggle Light TRUE/FALSE
然后我们根据变量light来判断是要打开还是要关闭光照。
if (!light)
// If Not Light
{
glDisable(GL_LIGHTING);
// Disable Lighting
}
else
// Otherwise
{
glEnable(GL_LIGHTING);
// Enable Lighting
}
}
下面的代码用于检测我们是否已经释放 L键了, 是的话就置变量 lp为false。 如果不这样做的话计算机就会以为 L键一直处于压下状态, 我们就不能再关掉光照了。
if (!keys['L'])
// Has L Key Been Released?
{
lp=FALSE;
// If So, lp Becomes FALSE
}
现在, 通过变量fp, 我们用同样的方式检测 F键是否被按下了。 F键被按下一次, 变量filter就会增加1, 当其大于2时(因为根本没有texture[3] 这个纹理) 就会回到0。
if (keys['F'] && !fp)
// Is F Key Being Pressed?
{
fp=TRUE;
// fp Becomes TRUE
filter+=1;
// filter Value Increases By One
if (filter>2)
// Is Value Greater Than 2?
{
filter=0;
// If So, Set filter To 0
}
}
if (!keys['F'])
// Has F Key Been Released?
{
fp=FALSE;
// If So, fp Becomes FALSE
}
下面的代码检测我们是否按下了“Page Up”键。 如果是的话变量z就会有减量, 这样DrawGLScene中的 glTranslatef(0.0f,0.0f,z) 就会把木箱向屏幕里的方向移动。
if (keys[VK_PRIOR])
// Is Page Up Being Pressed?
{
z-=0.02f;
// If So, Move Into The Screen
}
下面的代码检测我们是否按下了“Page Down”键。 如果是的话变量z就会有增量, 这样DrawGLScene中的 glTranslatef(0.0f,0.0f,z) 就会把木箱向我们移动。
if (keys[VK_NEXT])
// Is Page Down Being Pressed?
{
z+=0.02f;
// If So, Move Towards The Viewer
}
现在我们检测方向键是否被按下了。 按下左或右键, 变量xspeed 就会相应地减量或增量; 按下上或下键, 变量yspeed 就会相应地减量或增量。 教程中曾说过, xspeed 或 yspeed 的(绝对)值越高, 木箱旋转的就会越快。 所以你按住某个方向键的时间越长, 木箱在对应的方向就会旋转的越快。
if (keys[VK_UP])
// Is Up Arrow Being Pressed?
{
xspeed-=0.01f;
// If So, Decrease xspeed
}
if (keys[VK_DOWN])
// Is Down Arrow Being Pressed?
{
xspeed+=0.01f;
// If So, Increase xspeed
}
if (keys[VK_RIGHT])
// Is Right Arrow Being Pressed?
{
yspeed+=0.01f;
// If So, Increase yspeed
}
if (keys[VK_LEFT])
// Is Left Arrow Being Pressed?
{
yspeed-=0.01f;
// If So, Decrease yspeed
}
像以往一样, 我们要保证窗口标题的正确。
if (keys[VK_F1])
// Is F1 Being Pressed?
{
keys[VK_F1]=FALSE;
// If So Make Key FALSE
KillGLWindow();
// Kill Our Current Window
fullscreen=!fullscreen;
// Toggle Fullscreen / Windowed Mode
// Recreate Our OpenGL Window
if (!CreateGLWindow("NeHe's Textures, Lighting & Keyboard Tutorial",640,480,16,fullscreen))
{
return 0;
// Quit If Window Was Not Created
}
}
}
}
}
// Shutdown
KillGLWindow();
// Kill The Window
return (msg.wParam);
// Exit The Program
}
好了,现在你应该可以创建具有高质量、真实感纹理的立方体了。 你也应该明白了3种纹理过滤各自的优缺, 还有你可以通过键盘按键控制屏幕上的物体。 最后, 你应该懂得了如何使用简单的灯光, 让场景看起来更加真实。
Jeff Molofee (NeHe)
(译著) 如果你发现了什么问题或者疏漏, 请即时反馈给我, 这样我就能做出相应的补救或者更正。 十分欢迎你的支持和鼓励, 那将会使我更有动力。