NEHE的OpenGL-Lesson 07 Texture Filters, Lightning & Keyboard Control

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)


(译著) 如果你发现了什么问题或者疏漏, 请即时反馈给我, 这样我就能做出相应的补救或者更正。 十分欢迎你的支持和鼓励, 那将会使我更有动力。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值