OpenGL学习笔记(五)

龙云尧个人博客,转载请注明出处。

CSDN地址:http://blog.csdn.net/michael753951/article/details/71316132

个人blog地址:http://yaoyl.cn/nehexue-xi-bi-ji-wu/


这次我们将尝试Lesson6和Lesson7的内容。这个部分我们将学习怎么给一个模型进行纹理映射(其实就是贴图)。

环境搭建

这次实验因为需要使用OpenGL的glaux.h库头使用位图对构建的图形进行纹理映射。所以我们需要进一步进行环境搭建。(注:环境搭建很麻烦,因为微软的VS环境很乱)

如何布置这个库头可以参考【 VS2008无法打开gl/glaux.h头文件的解决方法】我使用的是方法4,测试能够正确include库头。

在高版本的VS中,因为VS使用的是自己重新修改过的C++,所以在进行编译的过程中,可能会出现ERROR LNK2019报错,无法解析“_sscanf,_sscanf_s”,这个时候我们可以参考【 VS2015 无法解析的外部符号 __vsnwprintf_s】

如果我们在使用AUX_RGBImageRec定义变量的时候,系统没有报错的话,就说明我们本次基本的环境已经搭建好了。

另外,因为我们在实验中需要使用fopen,而微软的VS2015中会强行报错,为了避免不必要的麻烦,我们需要关掉fopen的报错。这个部分我们可以参考【百度经验:VS2013中如何解决error C4996: ‘fopen’问题】

开始实现

如果没出什么问题的话,到这里我们应该能够正常的编写这一刻的代码了。(如果还有什么报错请尝试自行解决或者戳我)。

本次需要在3维图像上添加纹理映射,首先需要的是读取位图像文件。读取的代码如下。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
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
}

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

// Load The Bitmap, Check For Errors, If Bitmap's Not Found Quit
if (TextureImage[0]=LoadBMP("Data/NeHe.bmp"))
{
Status=TRUE; // Set The Status To TRUE

glGenTextures(1, &texture[0]); // Create The Texture

// Typical Texture Generation Using Data From The Bitmap
glBindTexture(GL_TEXTURE_2D, texture[0]);
glTexImage2D(GL_TEXTURE_2D, 0, 3, TextureImage[0]->sizeX, TextureImage[0]->sizeY, 0, GL_RGB, GL_UNSIGNED_BYTE, TextureImage[0]->data);
glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR);
}

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
}

return Status; // Return The Status
}

第一个函数LoadBMP不需要解释,主要功能就是探寻目的位置中是否存在该图像文件。如果存在就调用auxDIBImageLoad将位图加载成渲染文件返回出来。

第二个函数LoadGLTextures要稍微注意一下,在本次实验中是很重要的一个功能函数。

函数中定义了一个LoadGLTextures数组用来存放位图的句柄,这里因为我们只读取了一张位图,所以只开了一个大小的数组。

接着调用LoadBMP将位图转换成为纹理渲染文件存进TextureImage, glGenTextures(1, &texture[0]) 告诉OpenGL我们想生成一个纹理名字,glBindTexture将纹理名字 texture[0] 绑定到纹理目标上。

然后我们调用glTexImage2D进行纹理的创建。然后使用glTexParameteri对图像进行放大和缩小的滤波器进行设置。

最后再纹理穿件完成之后,我们需要释放掉纹理渲染数组中的内容。

整个纹理渲染工作到这里也就结束了。我们对InitGL稍作修改,使用LoadGLTextures检验位图是否存在,然后调用glEnable启用映射

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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); // 真正精细的透视修正
return TRUE; // 初始化 OK
}

最后我们按照惯例,修改DrawGLScene方法,需要注意的是,在将纹理贴上模型的时候,需要调用glTexCoord2f方法,第一个参数是X坐标,0.0是纹理的左侧,0.5是纹理的中点,1.0是纹理的右侧。第二个参数是Y坐标,0.0是纹理的底部,0.5是纹理的中点,1.0是纹理的顶部。将4个点全部绑定在张芳行上面之后,便能够正常的显示了。需要注意的是,在glTexCoord2f方法中,参考系是以图像的右下角作为原点,左边为X轴正方向,上方为Y轴正方向(和绘图中的直角坐标系的设定相似)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
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,-5.0f);

glRotatef(xrot,1.0f,0.0f,0.0f);
glRotatef(yrot,0.0f,1.0f,0.0f);
glRotatef(zrot,0.0f,0.0f,1.0f);

glBindTexture(GL_TEXTURE_2D, texture[0]);

glBegin(GL_QUADS);
// Front Face
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);
// Back Face
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);
// Top Face
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);
// Bottom Face
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);
// Right face
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);
// Left Face
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();

xrot+=0.3f;
yrot+=0.2f;
zrot+=0.4f;
return TRUE; // Keep Going
}

方法中,在begin之前,我们先预先定义好图像的旋转方式(不一定非要放在begin之前)。

接着调用glBindTexture进行绑定。我们就能够开始进行纹理渲染的绑定操作了。

操作中我们在每次定义点的同时,将纹理的四个角也固定在对应的点上,便能够完成绑定工作。

注意,用原引博客的话说,当您想改变纹理时,应该绑定新的纹理。有一点值得指出的是,您不能在 glBegin() 和 glEnd() 之间绑定纹理,必须在 glBegin() 之前或 glEnd() 之后绑定。

到这一步,这一课内容也就结束了。

section5.1

课程7的实现

我们接下来尝试课程7的内容。

课程7将带领我们建立一个光照系统,我们也将在这个课程中尝试添加按键控制。通过这个课程,我们会对OpenGL中的光照系统的实现、控制以及如何实现按键控制有一个入门的了解。本次实验基于课程6的内容进行进一步扩展。

实验中,因为我们需要首先添加一些群居变量用来对按键状态进行识别,同时也还要添加一些全局变量用来记录图像的旋转角度。

因此我们在全局变量中添加如下变量。

1
2
3
4
5
6
7
8
9
10
11
12
GLfloat xrot;               // X 旋转角度
GLfloat yrot; // Y 旋转角度
GLfloat xspeed; // X 旋转速度
GLfloat yspeed; // Y 旋转速度
GLfloat z=-5.0f; // Depth Into The Screen

GLfloat LightAmbient[]= { 0.5f, 0.5f, 0.5f, 1.0f };
GLfloat LightDiffuse[]= { 1.0f, 1.0f, 1.0f, 1.0f };
GLfloat LightPosition[]= { 0.0f, 0.0f, 2.0f, 1.0f };

GLuint filter; // 滤波器类型
GLuint texture[3]; // 3中纹理文件指针

上面代码中,中间3行数组分别表示环境光变量数组,漫射光变量数组,以及光源位置数组。

其中,我们需要了解一下环境光和漫射光的意义是什么。环境光来自于四面八方。所有场景中的对象都处于环境光的照射中;漫射光由特定的光源产生,并在您的场景中的对象表面上产生反射。处于漫射光直接照射下的任何对象表面都变得很亮,而几乎未被照射到的区域就显得要暗一些。

LightAmbient和LightDiffuse创建光源的过程和颜色的创建完全一致。前三个参数分别是RGB三色分量,最后一个是alpha通道参数。

而LightPosition中,前三个参数表示光源的XYZ左坐标,最后一个参数将告诉OpenGL这里指定的坐标就是光源的位置。

然后我们在上一次实验的基础上稍稍稍修改LoadGLTextures函数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
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

// 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

glGenTextures(3, &texture[0]); // Create Three Textures

// Create Nearest Filtered Texture
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);

// 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);

// 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);

gluBuild2DMipmaps(GL_TEXTURE_2D, 3, TextureImage[0]->sizeX, TextureImage[0]->sizeY, GL_RGB, GL_UNSIGNED_BYTE, TextureImage[0]->data);
}

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
}

return Status; // Return The Status
}

在读过上一次的代码之后,我们可以很容易的知道这段代码就是将Data/Crate.bmp这张位图转换成纹理并且存放在texture数组中,并且3次渲染的画面都一致(基于这一点,其实我们也可以自己实现不同的3张图片做出3种不同的,不同的是3章图像的glTexParameteri进行滤波的方式并不相同,第一张渲染使用最邻近过滤(GL_NEAREST),第二张使用线性滤波(GL_LINEAR),第三张使用选择最邻近的mip层,使用线性过滤(GL_LINEAR_MIPMAP_NEAREST)。然后就可以在立方体上渲染出不同的团了)。其他的部分改变不大。

上面对纹理映射的滤波器的选择可以参考【OpenGL超级宝典笔记——纹理映射Mipmap】这篇博客,在上面有很详细的解释和说明。

然后在滤波器设置结束之后,最后一行还添加了一个gluBuild2DMipmaps方法,这个在上面的【OpenGL超级宝典笔记——纹理映射Mipmap】中也有提到,是一个能够将任意图像正常缩放到适当大小的方法函数,这样就不用我们费心进行图像的预处理工作了。

好了,到这里LoadGLTextures就设置完毕了。

接着就到InitGL函数了,我们在第六课中已经设置了GLEnable以及glHint进行纹理绑定工作了。我们需要在后面添加新的方法,用来实现光源的初始化。具体参考代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
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

glLightfv(GL_LIGHT1, GL_AMBIENT, LightAmbient); // Setup The Ambient Light
glLightfv(GL_LIGHT1, GL_DIFFUSE, LightDiffuse); // Setup The Diffuse Light
glLightfv(GL_LIGHT1, GL_POSITION,LightPosition); // Position The Light
glEnable(GL_LIGHT1); // Enable Light One
return TRUE; // Initialization Went OK
}

新增了一个1号光源GL_LIGHT1。在对GL_LIGHT新增的3个glLightfv方法中,第一个表示设置环境光(Ambient Light),传入我们已经定义好的全局变量LightAmbient;第二个是漫射光(Diffuse Light),传入我们已经定义好的全局变量LightDiffuse;第三个是光源位置,传入我们定义好的LightPosition。最后使用glEnable启用光源。

到这里点光源的初始化操作就结束了。和设置渲染一样,我们接着要在DrawGLScene中添加光源的某些设置,

在OpenGL中,法线是指经过面(多边形)上的一点且垂直于这个面(多边形)的直线。使用光源的时候必须指定一条法线。法线告诉OpenGL这个多边形的朝向,并指明多边形的正面和背面。如果没有指定法线,什么怪事情都可能发生:不该照亮的面被照亮了,多边形的背面也被照亮….。对了,法线应该指向多边形的外侧。

所以我们在DrawGLScene中绘制图像的时候,也要同时设置法线。一般来说在绘制一个平面之前,我们就要预先定义好这个平面的法线。参考代码如下。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
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);

glRotatef(xrot,1.0f,0.0f,0.0f);
glRotatef(yrot,0.0f,1.0f,0.0f);

glBindTexture(GL_TEXTURE_2D, texture[filter]);

glBegin(GL_QUADS);
// Front Face
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);
// Back Face
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);
// Top Face
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);
// Bottom Face
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);
// Right face
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);
// Left Face
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();

xrot+=xspeed;
yrot+=yspeed;
return TRUE; // Keep Going
}

每新定义一个平面,我们都需要调用glNormal3f定义好一个法向量。法向量的3个参数分别表示其在X轴,Y轴,Z轴上的分量的长度。法向量的值是由面上的点计算出来的。

到这一步,我们编译运行以后就已经能够看到在点光源下的一个木箱子的图案,接下来我们将为其添加键盘控制功能。这部分在WinMain内。

我们在课程1上已经知道,nehe教程中,按键的反馈是在WinMain的一个while循环中,不断检测事件,然后对不同事件进行相应的反馈。里面已经添加了F1进行全屏控制的时间控制,我们要做的就是模仿F1的工作机制,添加其他按键的反馈。

在以前的while循环中,按键的触发设置代码如下。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
while(!done)                                    // Loop That Runs While done=FALSE
{
if (PeekMessage(&msg,NULL,0,0,PM_REMOVE)) // Is There A Message Waiting?
{
if (msg.message==WM_QUIT) // Have We Received A Quit Message?
{
done=TRUE; // If So done=TRUE
}
else // If Not, Deal With Window Messages
{
TranslateMessage(&msg); // Translate The Message
DispatchMessage(&msg); // Dispatch The Message
}
}
else // If There Are No Messages
{
// Draw The Scene. Watch For ESC Key And Quit Messages From DrawGLScene()
if ((active && !DrawGLScene()) || keys[VK_ESCAPE]) // Active? Was There A Quit Received?
{
done=TRUE; // ESC or DrawGLScene Signalled A Quit
}
else // Not Time To Quit, Update Screen
{
SwapBuffers(hDC); // Swap Buffers (Double Buffering)
}

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 Texture Mapping Tutorial",640,480,16,fullscreen))
{
return 0; // Quit If Window Was Not Created
}
}
}
}

在确认ESC按键未被按下之前,OpenGL将会不停的绘制图片。在这个窗口刷新工作结束以后,我们开始了F1的按键判断。由此,我们可以在这个基础上,进一步扩展了。扩展的代码如下。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
while (!done)                                   // Loop That Runs While done=FALSE
{
if (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) // Is There A Message Waiting?
{
if (msg.message == WM_QUIT) // Have We Received A Quit Message?
{
done = TRUE; // If So done=TRUE
}
else // If Not, Deal With Window Messages
{
TranslateMessage(&msg); // Translate The Message
DispatchMessage(&msg); // Dispatch The Message
}
}
else // If There Are No Messages
{
// Draw The Scene. Watch For ESC Key And Quit Messages From DrawGLScene()
if ((active && !DrawGLScene()) || keys[VK_ESCAPE]) // Active? Was There A Quit Received?
{
done = TRUE; // ESC or DrawGLScene Signalled A Quit
}
else // Not Time To Quit, Update Screen
{
SwapBuffers(hDC); // Swap Buffers (Double Buffering)

}
if (keys['L'] && !lp)
{
lp = TRUE;
light = !light;
if (!light)
{
glDisable(GL_LIGHTING);
}
else
{
glEnable(GL_LIGHTING);
}
}
if (!keys['L'])
{
lp = FALSE;
}
if (keys['F'] && !fp)
{
fp = TRUE;
filter += 1;
if (filter>2)
{
filter = 0;
}
}
if (!keys['F'])
{
fp = FALSE;
}
if (keys[VK_PRIOR])
{
z -= 0.02f;
}
if (keys[VK_NEXT])
{
z += 0.02f;
}
if (keys[VK_UP])
{
xspeed -= 0.01f;
}
if (keys[VK_DOWN])
{
xspeed += 0.01f;
}
if (keys[VK_RIGHT])
{
yspeed += 0.01f;
}
if (keys[VK_LEFT])
{
yspeed -= 0.01f;
}

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
}
}
}
}

如果按下L建,就对光源GL_LIGHTING进行改变(我们在初始化InitGL的时候,就曾经调用过glEnable,不知道你是否还有印象)。其他部分都不难,这里就不一一解释了。

另外需要说明的是,因为在NEHE教程的前文中,按键反馈是在刷新屏幕if ((active && !DrawGLScene()) || keys[VK_ESCAPE])这个判断结束之后才进行的按键反馈,而在其之后的代码中,却又将ESC之外的按键触发部分放进了判断条件之内。我这里模仿的是作者以前的按键触发设置,所以代码和作者lesson7的代码稍有不同。不过最终效果其实一致——只不过我的会在按下ESC之后延迟一帧才推出,而作者按下ESC立刻退出,因为刷新频率很快,所以其实想过一只。

最后,实验正确运行的话,你应该能够在窗口中正常的运行你的第一个有按键反馈的窗口游戏了。L打开灯光,F设置不同的滤波器,上下左右方向键分别设置不同的旋转方向,PageUp和PageDown分别将图形靠近或者远离我们。

Donate
  • Copyright: Copyright is owned by the author. For commercial reprints, please contact the author for authorization. For non-commercial reprints, please indicate the source.

请我喝杯咖啡吧~

支付宝
微信