`
SwineX
  • 浏览: 35657 次
  • 性别: Icon_minigender_1
  • 来自: 常德
社区版块
存档分类
最新评论

NeHe OpenGL教程 学习笔记1

阅读更多
如果你
和我一样刚刚学习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/
分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics