第一课是opengl窗口的实现。主要是把整个程序框架搭起来,之后的程序大多是在这个窗口的代码上再增加一些内容。
Nehe的教程中每一句都讲得很清楚,但感觉总体结构上有些不熟悉(主要是在下没学过MFC,可能对窗口方面的不是了解…)
主函数
先看主函数,主函数部分出现在最后面。int WINAPI WinMain()感觉和int main()很像,就是多了四个窗口参数,HINSTANCE hInstance —— 窗口实例;HINSTANCE hPrevInstance —— 前一个窗口实例;LPSTR lpCmdLine ——命令行参数;int nCmdShow —— 窗口显示状态。关于int WINAPI WinMain这个函数,http://bbs.youkuaiyun.com/topics/90187840 这里解释得很详细。而之所以要有两个窗口实例,是因为在以前16位系统的时候程序共享地址空间(其实个人还是不大理解…),而32位进程有自己的空间,所以现在 HINSTANCE hPrevInstance 这个参数一般都是NULL。
Windowsx消息结构 MSG msg
接下来是Windowsx消息结构 MSG msg,就好比C++中在main函数定义声明一个类(比如Clock myClock),这样我们就可以用诸如msg.message这样的方式来调用结构体中的变量,具体的介绍可以看 http://blog.sina.com.cn/s/blog_632fdaf60100htza.html 。
done 是用来退出循环的Bool 变量,它的初始值是FALSE,在循环中如果检测到ESC键被按下,那么就把done的值置为TRUE,当循环中发现done的值是TRUE时循环就中断。
在声明变量之后,是提示用户选择运行模式,程序会弹出一个消息窗口,询问用户是否希望在全屏模式下运行。如果用户单击NO按钮,fullscreen变量从缺省的TRUE改变为FALSE,程序也改在窗口模式下运行。那么我们需要了解一下MessageBox这个函数。MessageBox显示一个模态对话框,其中包含一个系统图表、 一组按钮和一个简短的特定于应用程序消息,如状态或错误的信息。消息框中返回一个整数值,该值指示用户单击了哪个按钮。具体的内容参见百度百科(详细的理解它很重要)。
CreateGLWindow函数
接下来是CreateGLWindow函数来创建窗口,这个函数在我看来十分重要,至少它是这段代码中最长的一个函数。那么现在来把编译器的滚动条拖上去,看一遍这个函数。函数一共有五个参数,第一个char* title 是创建的opengl 窗口名,接下来的 int width 和 int height 是窗口的宽度和高度, int bits 是色彩位数, bool fullscreenflag 是全屏的标志,TRUE时全屏模式,FALSE时窗口模式。
接下来函数声明了几个变量,这是为后面创建窗口而准备的参数。首先是PixelFormat 用于保存查找匹配的结果,当我们要求Windows为我们寻找相匹配的象素格式时,Windows寻找结束后将模式值保存在变量PixelFormat中,它是GLuint型的数据,相当于C语言中的 unsign int ,希望了解opengl 的数据类型的可以参考http://blog.sina.com.cn/s/blog_61e15eee0100qog9.html 。接下来的wc 是窗口类结构,它保存着我们的窗口信息,通过改变类的不同字段我们可以改变窗口的外观和行为。关于 WNDCLASS 这一结构,有兴趣的可以在百度百科详细了解 。dwExStyle和dwStyle存放扩展和通常的窗口风格信息,这两个参数是DWORD类型,即 Double Word, 每个word为2个字节的长度,DWORD 双字即为4个字节,每个字节是8位,共32位。接下来是矩形的绘制,使用了RECT这一结构,接下来我们让全局变量fullscreen等于fullscreenflag。如果我们希望在全屏幕下运行而将fullscreenflag设为TRUE,但没有让变量fullscreen等于fullscreenflag的话,fullscreen变量将保持为FALSE。当我们在全屏幕模式下销毁窗口的时候,变量fullscreen的值却不是正确的TRUE值,计算机将误以为已经处于桌面模式而无法切换回桌面。
接下来是窗口类的定义。对这一段的理解要结合 WNDCLASS 这一结构。在这个过程中,会有对WndProc()的引用,WndProc()是回调函数,由系统调用,每个窗口会有一个称为窗口过程的回调函数(WndProc),它带有四个参数,分别为:窗口句柄(Window Handle) HWND,消息ID(Message ID) UINT,和两个消息参数(wParam, lParam)WPARAM、LPARAM,WndProc的第一个参数hWnd就是当前接收消息的窗口句柄,第二个参数就是被传送过来的消息,第三、第四个参数都是附加在消息上的数据,这和MSG结构体是一样的。简单点说,就是记录下系统操作,然后处理窗口消息。
接下来是用RegisterClass(&wc) 来进行窗口类的注册。如果失败,就弹出一个对话框提示,然后退出程序。(虽然一直不知道在什么情况下会失败…)
接下来是设置各种各样的模式,都是根据设备获取数据调整,我没有都看懂,不过这些都是固定代码了,复制就好。用获取的设备参数设置好全屏模式后,进行窗口模式的创建和检查,如果窗口创建失败,如果失败,用KillGLWindow()函数重置显示区,弹出消息窗口,并退出程序。KillGLWindow() 的作用是依次释放着色描述表,设备描述表和窗口句柄,这个函数只在程序退出之前调用。
接下来描述象素格式,取得OpenGL设备描述表,找到对应与此前我们选定的象素格式的象素格式,设置象素格式,取得着色描述表,激活着色描述表,最后显示窗口,提高优先级,设置键盘的焦点至此窗口,设置透视 GL 屏幕。
在窗口显示出来后,跳转至InitGL(),这里可以设置光照、纹理、等等。这个函数在之后的课程中很重要。
整个函数很长很长,主要是在设置全屏模式和窗口模式,如果觉得不需要其中的某一种模式,可以去掉相关代码,并在之前主函数里去掉那个选择的对话框。
回到主函数,在窗口创建完成之后,进入一个循环,只要done保持FALSE,循环一直进行。进入循环,第一件事是检查是否有消息在等待,这段也直接复制…如果没有消息,绘制我们的OpenGL场景。代码的第一行查看窗口是否激活。如果按下ESC键,done变量被设为TRUE,程序将会退出。然后用DrawGLScene()来绘制场景。
DrawGLScene()
这个函数包括了所有的绘图代码,清除屏幕和深度缓存以及重置当前的模型观察矩阵是每次都不变的。具体的绘制以后再说。
用SwapBuffers(hDC)交换缓存后让绘制的屏幕显示出来。
接下来是允许用户按下F1键在全屏模式和窗口模式间切换的内容。
在循环结束后,销毁窗口,退出程序,一切就结束啦。