如果你
和我一样刚刚学习OpenGL,跟着教程走了一遍但还是感觉对一些东西的
理解很模糊,你可以继续往下看,我们一起来探讨。如果不是,很抱歉浪费了您的时间。
PS:看了快三个星期的OpenGL了,NeHe的教程看似很简单(因为你只要照着敲就能做出一些很有意思的小demo出来),不过有很多地方需要自己去弄懂和理解的,于是就先把自己的理解写下来。所以,纯属个人简介,如有错误,欢迎指正。
Lesson 1 窗口的建立
1描述
创建一个window窗口,以此作为windows的载体。
Nehe的窗口框架搭建的非常好,理解的难点随之出现。这一课有许多关于OpenGL视图环境的设置函数,这里暂且略过(因为我现在还说不清),主要描述一下windows窗口的创建以及浅谈一些消息机制吧。
2流程
1.定义一个WNDCLASS窗口结构体(这个变量名貌似取得不是很好啊)
WNDCLASS wc; // 窗口类结构
2.设置WNDCLASS的属性,也就是确定窗体的样式风格。
这里有一个很重要的属性,也就是WNDCLASS的lpfnWndProc字段,这里指定窗口的消息处理函 数,后面会再次提到。
wc.style = CS_HREDRAW | CS_VREDRAW | CS_OWNDC; // 移动时重画,并为窗口取得DC
wc.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的显示要求
static PIXELFORMATDESCRIPTOR pfd= // /pfd 告诉窗口我们所希望的东东,即窗口使用的像素格式
{
sizeof(PIXELFORMATDESCRIPTOR), // 上述格式描述符的大小
1, // 版本号
PFD_DRAW_TO_WINDOW | // 格式支持窗口
PFD_SUPPORT_OPENGL | // 格式必须支持OpenGL
PFD_DOUBLEBUFFER, // 必须支持双缓冲
PFD_TYPE_RGBA, // 申请 RGBA 格式
bits, // 选定色彩深度
0, 0, 0, 0, 0, 0, // 忽略的色彩位
0, // 无Alpha缓存
0, // 忽略Shift Bit
0, // 无累加缓存
0, 0, 0, 0, // 忽略聚集位
16, // 16位 Z-缓存 (深度缓存)
0, // 无蒙板缓存
0, // 无辅助缓存
PFD_MAIN_PLANE, // 主绘图层
0, // Reserved
0, 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数组中的值为 FALSE
KillGLWindow(); // 销毁当前的窗口
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( HWND hWnd, // 窗口的句柄
UINT uMsg, // 窗口的消息
WPARAM wParam, // 附加的消息内容
LPARAM lParam) // 附加的消息内容
{
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/
- base.rar (5.1 KB)
- 下载次数: 0