OpenGL学习笔记(五)
2017-05-07 12:21
204 查看
龙云尧个人博客,转载请注明出处。
CSDN地址:http://blog.csdn.net/michael753951/article/details/71316132
个人blog地址:http://yaoyl.cn/nehexue-xi-bi-ji-wu/
这次我们将尝试Lesson6和Lesson7的内容。这个部分我们将学习怎么给一个模型进行纹理映射(其实就是贴图)。
如何布置这个库头可以参考【 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维图像上添加纹理映射,首先需要的是读取位图像文件。读取的代码如下。
第一个函数LoadBMP不需要解释,主要功能就是探寻目的位置中是否存在该图像文件。如果存在就调用auxDIBImageLoad将位图加载成渲染文件返回出来。
第二个函数LoadGLTextures要稍微注意一下,在本次实验中是很重要的一个功能函数。
函数中定义了一个LoadGLTextures数组用来存放位图的句柄,这里因为我们只读取了一张位图,所以只开了一个大小的数组。
接着调用LoadBMP将位图转换成为纹理渲染文件存进TextureImage, glGenTextures(1, &texture[0]) 告诉OpenGL我们想生成一个纹理名字,glBindTexture将纹理名字 texture[0] 绑定到纹理目标上。
然后我们调用glTexImage2D进行纹理的创建。然后使用glTexParameteri对图像进行放大和缩小的滤波器进行设置。
最后再纹理穿件完成之后,我们需要释放掉纹理渲染数组中的内容。
整个纹理渲染工作到这里也就结束了。我们对InitGL稍作修改,使用LoadGLTextures检验位图是否存在,然后调用glEnable启用映射
最后我们按照惯例,修改DrawGLScene方法,需要注意的是,在将纹理贴上模型的时候,需要调用glTexCoord2f方法,第一个参数是X坐标,0.0是纹理的左侧,0.5是纹理的中点,1.0是纹理的右侧。第二个参数是Y坐标,0.0是纹理的底部,0.5是纹理的中点,1.0是纹理的顶部。将4个点全部绑定在张芳行上面之后,便能够正常的显示了。需要注意的是,在glTexCoord2f方法中,参考系是以图像的右下角作为原点,左边为X轴正方向,上方为Y轴正方向(和绘
146d7
图中的直角坐标系的设定相似)。
方法中,在begin之前,我们先预先定义好图像的旋转方式(不一定非要放在begin之前)。
接着调用glBindTexture进行绑定。我们就能够开始进行纹理渲染的绑定操作了。
操作中我们在每次定义点的同时,将纹理的四个角也固定在对应的点上,便能够完成绑定工作。
注意,用原引博客的话说,当您想改变纹理时,应该绑定新的纹理。有一点值得指出的是,您不能在 glBegin() 和 glEnd() 之间绑定纹理,必须在 glBegin() 之前或 glEnd() 之后绑定。
到这一步,这一课内容也就结束了。
课程7将带领我们建立一个光照系统,我们也将在这个课程中尝试添加按键控制。通过这个课程,我们会对OpenGL中的光照系统的实现、控制以及如何实现按键控制有一个入门的了解。本次实验基于课程6的内容进行进一步扩展。
实验中,因为我们需要首先添加一些群居变量用来对按键状态进行识别,同时也还要添加一些全局变量用来记录图像的旋转角度。
因此我们在全局变量中添加如下变量。
上面代码中,中间3行数组分别表示环境光变量数组,漫射光变量数组,以及光源位置数组。
其中,我们需要了解一下环境光和漫射光的意义是什么。环境光来自于四面八方。所有场景中的对象都处于环境光的照射中;漫射光由特定的光源产生,并在您的场景中的对象表面上产生反射。处于漫射光直接照射下的任何对象表面都变得很亮,而几乎未被照射到的区域就显得要暗一些。
LightAmbient和LightDiffuse创建光源的过程和颜色的创建完全一致。前三个参数分别是RGB三色分量,最后一个是alpha通道参数。
而LightPosition中,前三个参数表示光源的XYZ左坐标,最后一个参数将告诉OpenGL这里指定的坐标就是光源的位置。
然后我们在上一次实验的基础上稍稍稍修改LoadGLTextures函数。
在读过上一次的代码之后,我们可以很容易的知道这段代码就是将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号光源GL_LIGHT1。在对GL_LIGHT新增的3个glLightfv方法中,第一个表示设置环境光(Ambient Light),传入我们已经定义好的全局变量LightAmbient;第二个是漫射光(Diffuse Light),传入我们已经定义好的全局变量LightDiffuse;第三个是光源位置,传入我们定义好的LightPosition。最后使用glEnable启用光源。
到这里点光源的初始化操作就结束了。和设置渲染一样,我们接着要在DrawGLScene中添加光源的某些设置,
在OpenGL中,法线是指经过面(多边形)上的一点且垂直于这个面(多边形)的直线。使用光源的时候必须指定一条法线。法线告诉OpenGL这个多边形的朝向,并指明多边形的正面和背面。如果没有指定法线,什么怪事情都可能发生:不该照亮的面被照亮了,多边形的背面也被照亮….。对了,法线应该指向多边形的外侧。
所以我们在DrawGLScene中绘制图像的时候,也要同时设置法线。一般来说在绘制一个平面之前,我们就要预先定义好这个平面的法线。参考代码如下。
每新定义一个平面,我们都需要调用glNormal3f定义好一个法向量。法向量的3个参数分别表示其在X轴,Y轴,Z轴上的分量的长度。法向量的值是由面上的点计算出来的。
到这一步,我们编译运行以后就已经能够看到在点光源下的一个木箱子的图案,接下来我们将为其添加键盘控制功能。这部分在WinMain内。
我们在课程1上已经知道,nehe教程中,按键的反馈是在WinMain的一个while循环中,不断检测事件,然后对不同事件进行相应的反馈。里面已经添加了F1进行全屏控制的时间控制,我们要做的就是模仿F1的工作机制,添加其他按键的反馈。
在以前的while循环中,按键的触发设置代码如下。
在确认ESC按键未被按下之前,OpenGL将会不停的绘制图片。在这个窗口刷新工作结束以后,我们开始了F1的按键判断。由此,我们可以在这个基础上,进一步扩展了。扩展的代码如下。
如果按下L建,就对光源GL_LIGHTING进行改变(我们在初始化InitGL的时候,就曾经调用过glEnable,不知道你是否还有印象)。其他部分都不难,这里就不一一解释了。
另外需要说明的是,因为在NEHE教程的前文中,按键反馈是在刷新屏幕
最后,实验正确运行的话,你应该能够在窗口中正常的运行你的第一个有按键反馈的窗口游戏了。L打开灯光,F设置不同的滤波器,上下左右方向键分别设置不同的旋转方向,PageUp和PageDown分别将图形靠近或者远离我们。
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维图像上添加纹理映射,首先需要的是读取位图像文件。读取的代码如下。
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启用映射
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轴正方向(和绘
146d7
图中的直角坐标系的设定相似)。
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() 之后绑定。
到这一步,这一课内容也就结束了。
课程7的实现
我们接下来尝试课程7的内容。课程7将带领我们建立一个光照系统,我们也将在这个课程中尝试添加按键控制。通过这个课程,我们会对OpenGL中的光照系统的实现、控制以及如何实现按键控制有一个入门的了解。本次实验基于课程6的内容进行进一步扩展。
实验中,因为我们需要首先添加一些群居变量用来对按键状态进行识别,同时也还要添加一些全局变量用来记录图像的旋转角度。
因此我们在全局变量中添加如下变量。
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函数。
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进行纹理绑定工作了。我们需要在后面添加新的方法,用来实现光源的初始化。具体参考代码如下:
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中绘制图像的时候,也要同时设置法线。一般来说在绘制一个平面之前,我们就要预先定义好这个平面的法线。参考代码如下。
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循环中,按键的触发设置代码如下。
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的按键判断。由此,我们可以在这个基础上,进一步扩展了。扩展的代码如下。
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分别将图形靠近或者远离我们。
相关文章推荐
- OpenGL光照 学习笔记(转)
- OpenGL学习笔记-OpenGL的变换和矩阵
- VS2005下QT学习笔记-OpenGL编程
- OpenGL 学习笔记(1)
- Opengl编程学习笔记(五)——从FRAGMENT到PIXEL(framebuffer 帧缓存)
- OPENGL学习笔记1
- 我的OpenGL学习笔记二
- OpenGL学习笔记一
- OpenGL学习笔记(一)
- opengl vc2005平台学习 学习笔记(一)--环境的搭建
- OpenGL学习笔记
- OpenGL学习笔记(二)
- OpenGL ES学习笔记之四
- OpenGL学习笔记一——环境搭建
- OpenGL学习笔记(三)
- OpenGL ES学习笔记之三
- OpenGL学习笔记(二)
- OpenGL ES学习笔记之五
- 我的OpenGL学习笔记一
- 【转】 opengl编程学习笔记(三)(2D绘图)