首页 诗词 字典 板报 句子 名言 友答 励志 学校 网站地图
当前位置: 首页 > 教程频道 > 其他教程 > 开源软件 >

NeHe OpenGL课程 学习笔记1

2012-07-28 
NeHe OpenGL教程学习笔记1如果你和我一样刚刚学习OpenGL,跟着教程走了一遍但还是感觉对一些东西的理解很模

NeHe OpenGL教程 学习笔记1
如果你
和我一样刚刚学习OpenGL,跟着教程走了一遍但还是感觉对一些东西的理解很模糊,你可以继续往下看,我们一起来探讨。如果不是,很抱歉浪费了您的时间。

PS:看了快三个星期的OpenGL了,NeHe的教程看似很简单(因为你只要照着敲就能做出一些很有意思的小demo出来),不过有很多地方需要自己去弄懂和理解的,于是就先把自己的理解写下来。所以,纯属个人简介,如有错误,欢迎指正。

Lesson 1 窗口的建立
1描述
创建一个window窗口,以此作为windows的载体。
Nehe的窗口框架搭建的非常好,理解的难点随之出现。这一课有许多关于OpenGL视图环境的设置函数,这里暂且略过(因为我现在还说不清),主要描述一下windows窗口的创建以及浅谈一些消息机制吧。
2流程
1.定义一个WNDCLASS窗口结构体(这个变量名貌似取得不是很好啊)

WNDCLASSwc;// 窗口类结构

2.设置WNDCLASS的属性,也就是确定窗体的样式风格。
  这里有一个很重要的属性,也就是WNDCLASS的lpfnWndProc字段,这里指定窗口的消息处理函    数,后面会再次提到。
wc.style= CS_HREDRAW | CS_VREDRAW | CS_OWNDC;// 移动时重画,并为窗口取得DCwc.lpfnWndProc= (WNDPROC) WndProc;// WndProc处理消息wc.cbClsExtra= 0;// 无额外窗口数据wc.cbWndExtra= 0;// 无额外窗口数据wc.hInstance= hInstance;// 设置实例wc.hIcon= LoadIcon(NULL, IDI_WINLOGO);// 装入缺省图标wc.hCursor= LoadCursor(NULL, IDC_ARROW);// 装入鼠标指针wc.hbrBackground= NULL;// GL不需要背景wc.lpszMenuName= NULL;// 不需要菜单wc.lpszClassName= "OpenG";// 设定类名字

3.注册该窗口
if (!RegisterClass(&wc))// 尝试注册窗口类{MessageBox(NULL,"注册窗口失败","错误",MB_OK|MB_ICONEXCLAMATION);return FALSE;// 退出并返回FALSE}

4.创建窗口,返回一个HWND类型的窗口句柄,这个很重要,各种资源的获取以及属性的设置都要用到它。
hWnd=CreateWindowEx(dwExStyle,// 扩展窗体风格TEXT("OpenGL"),// 类名字(LPCWSTR)title,// 窗口标题dwStyle |// 必须的窗体风格属性WS_CLIPSIBLINGS |// 必须的窗体风格属性WS_CLIPCHILDREN,// 必须的窗体风格属性0, 0,// 窗口位置WindowRect.right-WindowRect.left,// 计算调整好的窗口宽度WindowRect.bottom-WindowRect.top,// 计算调整好的窗口高度NULL,// 无父窗口NULL,// 无菜单hInstance,// 实例NULL))

5.显示窗口
ShowWindow(hWnd,SW_SHOW);// 显示窗口


至此,一个基本的窗口就已经搭建好并显示成功了,但是貌似和OpenGL一点关系也没有。NeHe的窗口创建和OpenGL的舞台搭建基本上是揉在一起的,所以我将其分开。下面是OpenGL的舞台搭建

1.创建像素格式,这个可以看做是OpenGL与windows窗口的连接纽带,像素格式PIXELFORMATDESCRIPTOR告诉windows窗口你需要提供一个相应的格式来满足OpenGL的显示要求
 staticPIXELFORMATDESCRIPTOR pfd=// /pfd 告诉窗口我们所希望的东东,即窗口使用的像素格式{sizeof(PIXELFORMATDESCRIPTOR),// 上述格式描述符的大小1,// 版本号PFD_DRAW_TO_WINDOW |// 格式支持窗口PFD_SUPPORT_OPENGL |// 格式必须支持OpenGLPFD_DOUBLEBUFFER,// 必须支持双缓冲PFD_TYPE_RGBA,// 申请 RGBA 格式bits,// 选定色彩深度0, 0, 0, 0, 0, 0,// 忽略的色彩位0,// 无Alpha缓存0,// 忽略Shift Bit0,// 无累加缓存0, 0, 0, 0,// 忽略聚集位16,// 16位 Z-缓存 (深度缓存)0,// 无蒙板缓存0,// 无辅助缓存PFD_MAIN_PLANE,// 主绘图层0,// Reserved0, 0, 0// 忽略层遮罩};

2.获得设备描述表,设备描述表...好吧~我第一次看的时候也懵了,什么东西?我们可以将设备理解成资源,很多资源的分配和获得都要用到它,就我目前的理解,将它理解成资源管理器也差不多,这里的资源可以包括内存空间和窗口的设备(又是设备,找不到其他词了),比如:OpenGL里面可以将需要重复绘制的图像模型先存放在内存里(有点像是享元模式),也就是常说的显示列表,这样就可以提高运行效率,和数据就需要设备描述表来进行创建。
if (!(hDC=GetDC(hWnd)))// 取得设备描述表了么?{KillGLWindow();// 重置显示区MessageBox(NULL,"不能创建一种相匹配的像素格式","错误",MB_OK|MB_ICONEXCLAMATION);return FALSE;// 返回 FALSE}

3.检测像素格式并进行设置
if (!(PixelFormat=ChoosePixelFormat(hDC,&pfd)))// Windows 找到相应的象素格式了吗?{KillGLWindow();// 重置显示区MessageBox(NULL,TEXT("不能创建一种相匹配的像素格式"),TEXT("错误"),MB_OK|MB_ICONEXCLAMATION);return FALSE;// 返回 FALSE}if(!SetPixelFormat(hDC,PixelFormat,&pfd))// 能够设置象素格式么?{KillGLWindow();// 重置显示区MessageBox(NULL,TEXT("不能设置像素格式"),TEXT("错误"),MB_OK|MB_ICONEXCLAMATION);return FALSE;// 返回 FALSE}

4.渲染描述表?暂时还没有用到过,以后了解之后会重写这里
  做到这里,OpenGL的舞台环境已经基本搭建完成
if (!(hRC=wglCreateContext(hDC)))// 能否取得OpenGL渲染描述表?{KillGLWindow();// 重置显示区MessageBox(NULL,TEXT("不能创建OpenGL渲染描述表"),TEXT("错误"),MB_OK|MB_ICONEXCLAMATION);return FALSE;// 返回 FALSE}if(!wglMakeCurrent(hDC,hRC))// 尝试激活着色描述表{KillGLWindow();// 重置显示区MessageBox(NULL,TEXT("不能激活当前的OpenGL渲然描述表"),TEXT("错误"),MB_OK|MB_ICONEXCLAMATION);return FALSE;// 返回 FALSE}

5.OpenGL初始化,一般封装成一个函数如initGL在主体程序运行前进行调用。这里是一些基本的参数,一些其他的初始化工作,如3D世界的数据读入等操作,也可以写在这个里面。
int InitGL(GLvoid){glShadeModel(GL_SMOOTH);//阴影模式glClearColor(0.0f, 0.0f, 0.0f, 0.0f);//背景色//深度缓存设置和测试glEnable(GL_DEPTH_TEST);glDepthFunc(GL_LEQUAL);//让系统修正透视glHint(GL_PERSPECTIVE_CORRECTION_HINT,GL_NICEST);buildFont();return TRUE;}



Extra 消息机制
虽然教程中没怎么说明,但是我觉得这是一个很重要的机制。

描述
windows的消息机制其实是一个不断循环的过程,对于窗口主体程序而言,其过程可以大体总结为 监听消息---取得消息---处理消息(消息处理函数)---监听消息......
流程
1.监听消息&取得消息
  为什么要一起讲?这里要讨论两个函数
PeekMessage和GetMessage
PeekMessage:无论应用程序消息队列是否有消息,PeekMessage函数都立即返回,程序得以继续执行
后面的语句(无消息则执行其它指令,有消息时一般要将消息派发出去,再执行其它
指令)。
GetMessage:函数只有在消息对立中有消息时返回,队列中无消息就会一直等,直至下
一个消息出现时才返回。在等的这段时间,应用程序不能执行任何指令。
所以GetMessage对消息有一种监听的感觉,而PeekMessage就只是一味的取数据而已了。

在NeHe的教程中使用的是PeekMessage,而在《Windows程序设计》一书中则是使用的GetMessage,原因是,在NeHe中,主函数的消息循环不仅仅起到接收外部消息的作用,它还是OpenGL动画的一个“始终”,每循环一次就会重新绘制一次从而产生动态效果。
while(!done)// 保持循环直到 done=TRUE{if (PeekMessage(&msg,NULL,0,0,PM_REMOVE))// 有消息在等待吗?{if (msg.message==WM_QUIT)// 收到退出消息?{done=TRUE;// 是,则done=TRUE}else// 不是,处理窗口消息{TranslateMessage(&msg);// 翻译消息DispatchMessage(&msg);// 发送消息}}else// 如果没有消息{// 绘制场景。监视ESC键和来自DrawGLScene()的退出消息if (active)// 程序激活的么?{if (keys[VK_ESCAPE])// ESC 按下了么?{done=TRUE;// ESC 发出退出信号}else// 不是退出的时候,刷新屏幕{DrawGLScene();// 绘制场景SwapBuffers(hDC);// 交换缓存 (双缓存)}}if (keys[VK_F1])// F1键按下了么?{keys[VK_F1]=FALSE;// 若是,使对应的Key数组中的值为 FALSEKillGLWindow();// 销毁当前的窗口fullscreen=!fullscreen;// 切换 全屏 / 窗口 模式// 重建 OpenGL 窗口if (!CreateGLWindow(TEXT("NeHe's OpenGL 程序框架"),640,480,16,fullscreen)){return 0;// 如果窗口未能创建,程序退出}}}}

看到一上代码,注意到DrawGLScene(),SwapBuffers(hDC)两个函数,如果将PeekMessage换成GetMessage的话,湖面应该就动不了了吧。
2.处理消息
任然是上面的代码,当获得消息以后,则会执行下面两行代码
TranslateMessage(&msg);// 翻译消息DispatchMessage(&msg);// 发送消息

TranslateMessage做的事情,《windows程序设计》写的是进行键盘翻译,第六章有深入探讨(懒得去看哦),我猜大概就是将键盘的消息装换成相应的ASCLL码的值吧,“望文生义”有时还是很必要的。
DispatchMessage(&msg)做的事情则是将消息交给在WNDCLASS定义阶段绑定的那个消息响应函数(还记得不?不记得翻上去看看那吧),由响应函数进行处理,下面贴出一部分
LRESULT CALLBACK WndProc(HWNDhWnd,// 窗口的句柄UINTuMsg,// 窗口的消息WPARAMwParam,// 附加的消息内容LPARAMlParam)// 附加的消息内容{switch (uMsg)// 检查Windows消息{case WM_ACTIVATE:// 监视窗口激活消息{if (!HIWORD(wParam))// 检查最小化状态{active=TRUE;// 程序处于激活状态}else{active=FALSE;// 程序不再激活}return 0;// 返回消息循环}        //各种消息类别的处理....省略}// 向 DefWindowProc传递所有未处理的消息。return DefWindowProc(hWnd,uMsg,wParam,lParam);}

DefWindowProc(hWnd,uMsg,wParam,lParam)是将我们不关心的消息扔回去让缺省的消息响应函数处理,比如WM_CLOSE消息什么的....

其他
首先,此文意在梳理学习过程中的疑问以及整合知识的结构流程。
最后附上我修改过后的OpenGL窗口文件,可在VS2010下运行,我一般将它作为每次课程的“白板”,希望有所帮助。
Nehe中文教程地址http://www.owlei.com/DancingWind/

热点排行