网络传输服务进程 将 socket 事件 保存至 socket 的事件队列中。此外, 网络传输服务进程 还会向 socket window 发送消息 WM_SOCKET_NOTIFY , 通知有 socket 事件 产生,见下文对 socket window 的详细说明。
调用 CSocket::Create 函数后,socket 被创建。 socket 创建过程中调用 CAsyncSocket::AttachHandle(SOCKET hSocket, CAsyncSocket* pSocket, BOOL bDead) 。该函数的作用是:
2、阻塞模式
阻塞模式下 Server 端与 Client 端之间的通信处于同步状态下。在 Server 端直接实例化 CSocket 类,调用 Create 方法创建 socket ,然后调用方法 Listen 开始侦听,最后用一个 while 循环阻塞调用 Accept 函数用于等待来自 Client 端的连接,如果这个 socket 在主线程(主程序)中运行,这将导致主线程的阻塞。因此,需要创建一个新的线程以运行 socket 服务。
调试跟踪至 CSocket::Accept 函数源码:
while(!Accept(...)) { // The socket is marked as nonblocking and no connections are present to be accepted. if (GetLastError() == WSAEWOULDBLOCK)它不断调用 CAsyncSocket::Accept ( CSocket 派生自 CAsyncSocket 类)判断 Server 端 socket 的事件队列中是否存在正在引入的连接事件 - FD_ACCEPT (见 1 ),换句话说,就是判断是否有来自 Client 端 socket 的连接请求。
PumpMessage(FD_ACCEPT); else return FALSE; }
PumpMessage(FD_ACCEPT);
PumpMessage 作为一个消息泵使得 socket window 中的消息能够维持在活动状态。实际跟踪进入 PumpMessage 中,发现这个消息泵与 Accept 函数的调用并不相关,它只是使很少的 socket window 消息(典型的是 WM_PAINT 窗口重绘消息)处于活动状态,而绝大部分的 socket window 消息被阻塞,被阻塞的消息中含有 WM_SOCKET_NOTIFY。
很显然,如果没有来自 Client 端 socket 的连接请求, CSocket 就会不断调用 Accept 产生循环阻塞,直到有来自 Client 端 socket 的连接请求而解除阻塞。
阻塞解除后,表示 Server 端 socket 和 Client 端 socket 已成功连接, Server 端与 Client 端彼此相互调用 Send 和 Receive 方法开始通信。
3、非阻塞模式
在非阻塞模式下 利用 socket 事件 的消息机制, Server 端与 Client 端之间的通信处于异步状态下。
通常需要从 CSocket 类派生一个新类,派生新类的目的是重载 socket 事件 的消息函数,然后在 socket 事件 的消息函数中添入合适的代码以完成 Client 端与 Server 端之间的通信,与阻塞模式相比,非阻塞模式无需创建一个新线程。
这里将讨论当 Server 端 socket 事件 - FD_ACCEPT 被触发后,该事件的处理函数 OnAccept 是如何进一步被触发的。其它事件的处理函数如 OnConnect, OnReceive 等的触发方式与此类似。
在 1 中已提到 Client/Server 端通信时, Server 端 socket 正在接收来自 Client 端 socket 连接请求,这将会触发 FD_ACCEPT 事件,同时 Server 端的 网络传输服务进程 向 Server 端的 socket window (CSocketWnd )发送事件通知消息 WM_SOCKET_NOTIFY , 通知有 FD_ACCEPT 事件产生 , CsocketWnd 在收到事件通知消息后,调用消息处理函数 OnSocketNotify:
LRESULT CSocketWnd::OnSocketNotify(WPARAM wParam, LPARAM lParam) { CSocket::AuxQueueAdd(WM_SOCKET_NOTIFY, wParam, lParam); CSocket::ProcessAuxQueue(); return 0L ; }消息参数 wParam 是 socket 的句柄, lParam 是 socket 事件 。这里稍作解释一下,CSocketWnd 类是作为 CSocket 类的 友元类 ,这意味着它可以访问 CSocket 类中的保护和私有成员函数和变量, AuxQueueAdd 和 ProcessAuxQueue 是 CSocket 类的静态成员函数,如果你对友元不熟悉,请迅速找本有关 C++ 书看一下友元的使用方法吧!
CAsyncSocket* pSocket = CAsyncSocket::LookupHandle((SOCKET)wParam, TRUE);
其实也就是由 socket 句柄得到发送事件通知消息的 socket 指针 pSocket:从 m_pmapSocketHandle 中查找(见 1 )!
最后, WSAGETSELECTEVENT(lParam) 会取出事件类型,在一个简单的 switch 语句中判断事件类型并调用事件处理函数。在这里,事件类型是 FD_ACCEPT ,当然就调用 pSocket->OnAccept !
结束语
Server 端 socket 处于阻塞调用模式下,它必须在一个新创建的线程中工作,防止主线程被阻塞。
当有多个 Client 端 socket 与 Server 端 socket 连接及通信时, Server 端采用阻塞模式就显得不适合了,应该采用非阻塞模式 , 利用 socket 事件 的消息机制来接受多个 Client 端 socket 的连接请求并进行通信。
在非阻塞模式下,利用 CSocketWnd 作为所有 sockets 的消息池,是实现 socket 事件 的消息机制的关键技术。文中存在用词不妥和可能存在的技术问题,请大家原谅,也请批评指正,谢谢!
注:
源代码参考:
static UINT WM_MY_MESSAGE=RegisterWindowMessage("User"); |
DWORD result; SendMessageTimeout(wnd->m_hWnd, // 目标窗口 WM_MY_MESSAGE, // 消息 0, // WPARAM 0, // LPARAM SMTO_ABORTIFHUNG | SMTO_NORMAL, TIMEOUT_INTERVAL, &result); |
ON_REGISTERED_MESSAGE(WM_MY_MESSAGE,OnMyMessage) //定义消息映射 视类定义消息处理函数: // 消息处理函数 LRESULT CMessageView::OnMyMessage(WPARAM wParam, LPARAM lParam) { // TODO: 处理用户自定义消息 ... return 0; } //发送消息的测试函数 void CMainFrame::OnTest() { CView * active = GetActiveView();//获取当前视类指针 if(active != NULL) active->PostMessage(WM_MY_MESSAGE,0,0); } |
//发送消息的测试函数
在视类中向主框架发送消息:
在其它类中向不同的类发送消息可依次方法类推,这样我们的程序就可以的不受限制向其它类和进程发送消息,而避免了种种意想不到的风险。 下面一个例子程序为多文档程序里在一对话框中向视类发送消息,详述了发送自定义消息的具体过程。 实现步骤:
在Message.h头文件中添加如下语句:
第四步:在视类中添加自定义消息: 在头文件MessageView.h中添加消息映射
添加相应的0消息处理函数
在MessageView.h中添加布尔变量 public:BOOL test; 在视类构造函数中初始化 test变量:test=FALSE; 修改CMessageView::OnDraw()函数
第五步:显示测试对话框 在MainFrame类中包含对话框头文件:
运行程序,在测试菜单打开对话框,点击测试按钮即可看到结果。 |
定时器在VC中的使用频繁,以下讨论定义器的使用方法。
定时器的原型是:
WINUSERAPI UINT WINAPI SetTimer ( HWND hWnd , UINT nIDEvent, UINT uElapse, TIMERPROC lpTimerFunc);
hWnd 是欲设置定时器的窗体句柄。定时时间到时,系统会向该窗体发送WM_TIMER消息。
nIDEvent 定时器标识符。在一个窗体内可以使用多个定时器,不同的定时器根据nIDEvent来区分。
uElapse 定时时间,单位是毫秒。
lpTimerFunc 定时器的回调函数。如果该值为NULL,定时时间到时,定时器发送的消息WM_TIMER由窗体映像该消息的函数处理;否则由回调函数处理,说白一点,这里的回调函数就是取代OnTimer的处理函数。
通常,我们在使用定时器时,只用到三个参数,即
UINT CWnd::SetTimer(
UINT nIDEvent,
UINT nElapse,
void (CALLBACK EXPORT* lpfnTimer)( HWND, UINT, UINT, DWORD)
);
其实,这个函数只是MFC对API的封装,其实现函数为:
_AFXWIN_INLINE UINT CWnd::SetTimer(UINT nIDEvent, UINT nElapse,
void (CALLBACK* lpfnTimer)(HWND, UINT, UINT, DWORD))
{
ASSERT(::IsWindow(m_hWnd));
return ::SetTimer(m_hWnd, nIDEvent, nElapse,(TIMERPROC)lpfnTimer);
}
由此可见,CWnd::SetTimer只是将API函数SetTimer的第一个参数设置成它自己的句柄而已。
有了上面的认识,对定时器的使用就清楚了,下面举例说明定时器的具体使用。
1.打开VC,新建一基于对话框的工程,工程名为Test在对话框上添加一按钮,将其ID改为IDC_BUTTON_START,Caption改为Start. 映像该按钮的BN_CLICKED消息,void CTestDlg::OnButtonStart();
2.再在对话框上添加一按钮,ID为ID_BUTTON_STOP,Caption改为Stop,映像消息为void CTestDlg::OnButtonStop();
3.添加一个Lable,ID改为IDC_STATIC_TIME,用于记数,表明定时器函数的执行。
4.映像对话框的WM_TIMER消息,void CTestDlg::OnTimer(UINT nIDEvent);
以上的实现函数如下所示:
void CTestDlg::OnButtonStart()
{
SetTimer(1,1000,NULL);//启动定时器1,定时时间是1秒
}
void CTestDlg::OnButtonStop()
{
KillTimer(1); //关闭定时器1。
}
void CTestDlg::OnTimer(UINT nIDEvent)
{
static int nTimer=0;
CString strTmp="";
strTmp.Format("Timer: %d",nTimer++);
CWnd *pWnd=GetDlgItem(IDC_STATIC_TIME);
pWnd->SetWindowText(strTmp); //在Lable
这段代码用来加载位图文件。如果文件不存在,返回 NULL 告知程序无法加载位图。在我开始解释这段代码之前,关于用作纹理的图像我想有几点十分重要,并且您必须明白。此图像的宽和高必须是2的n次方;宽度和高度最小必须是64象素;并且出于兼容性的原因,图像的宽度和高度不应超过256象素。如果您的原始素材的宽度和高度不是64,128,256象素的话,使用图像处理软件重新改变图像的大小。可以肯定有办法能绕过这些限制,但现在我们只需要用标准的纹理尺寸。 首先,我们创建一个文件句柄。句柄是个用来鉴别资源的数值,它使程序能够访问此资源。我们开始先将句柄设为 NULL 。 |
|||||||||
AUX_RGBImageRec *LoadBMP(char *Filename) // 载入位图图象 { FILE *File=NULL; // 文件句柄 |
|||||||||
接下来检查文件名是否已提供。因为 LoadBMP() 可以无参数调用,所以我们不得不检查一下。您可不想什么都没载入吧.....:) | |||||||||
if (!Filename) // 确保文件名已提供。 { return NULL; // 如果没提供,返回 NULL } |
|||||||||
接着检查文件是否存在。下面这一行尝试打开文件。 | |||||||||
File=fopen(Filename,"r"); //尝试打开文件 | |||||||||
如果我们能打开文件的话,很显然文件是存在的。使用 fclose(File) 关闭文件。 auxDIBImageLoad(Filename) 读取图象数据并将其返回。 | |||||||||
if (File) // 文件存在么? { fclose(File); // 关闭句柄 return auxDIBImageLoad(Filename); //载入位图并返回指针 } |
|||||||||
如果我们不能打开文件,我们将返回NULL。这意味着文件无法载入。程序在后面将检查文件是否已载入。如果没有,我们将退出程序并弹出错误消息。 | |||||||||
return NULL; // 如果载入失败,返回 NULL } |
|||||||||
下一部分代码载入位图(调用上面的代码)并转换成纹理。 | |||||||||
int LoadGLTextures() // 载入位图(调用上面的代码)并转换成纹理 { |
|||||||||
然后设置一个叫做 Status 的变量。我们使用它来跟踪是否能够载入位图以及能否创建纹理。 Status 缺省设为 FALSE (表示没有载入或创建任何东东)。 | |||||||||
int Status=FALSE; // Status 状态指示器 | |||||||||
现在我们创建存储位图的图像记录。次记录包含位图的宽度、高度和数据。 | |||||||||
AUX_RGBImageRec *TextureImage[1]; // 创建纹理的存储空间 | |||||||||
清除图像记录,确保其内容为空。 | |||||||||
memset(TextureImage,0,sizeof(void *)*1); // 将指针设为 NULL | |||||||||
现在载入位图,并将其转换为纹理。 TextureImage[0]=LoadBMP("Data/NeHe.bmp") 调用 LoadBMP() 的代码。载入 Data 目录下的 NeHe.bmp 位图文件。如果一切正常,图像数据将存放在 TextureImage[0] 中, Status 被设为 TRUE ,然后我们开始创建纹理。 | |||||||||
// 载入位图,检查有无错误,如果位图没找到则退出。 if (TextureImage[0]=LoadBMP("Data/NeHe.bmp")) { Status=TRUE; // 将 Status 设为 TRUE |
|||||||||
现在使用中 TextureImage[0] 的数据创建纹理。第一行 glGenTextures(1, &texture[0]) 告诉OpenGL我们想生成一个纹理名字(如果您想载入多个纹理,加大数字)。值得注意的是,开始我们使用 GLuint texture[1] 来创建一个纹理的存储空间,您也许会认为第一个纹理就是存放在 &texture[1] 中的,但这是错的。正确的地址应该是 &texture[0] 。同样如果使用 GLuint texture[2] 的话,第二个纹理存放在 texture[1] 中。『译者注:学C的,在这里应该没有障碍,数组就是从零开始的嘛。』 第二行 glBindTexture(GL_TEXTURE_2D, texture[0]) 告诉OpenGL将纹理名字 texture[0] 绑定到纹理目标上。2D纹理只有高度(在 Y 轴上)和宽度(在 X 轴上)。主函数将纹理名字指派给纹理数据。本例中我们告知OpenGL, &texture[0] 处的内存已经可用。我们创建的纹理将存储在 &texture[0] 的 指向的内存区域。 |
|||||||||
glGenTextures(1, &texture[0]); // 创建纹理 // 使用来自位图数据生成 的典型纹理 glBindTexture(GL_TEXTURE_2D, texture[0]); |
|||||||||
下来我们创建真正的纹理。下面一行告诉OpenGL此纹理是一个2D纹理 ( GL_TEXTURE_2D )。数字零代表图像的详细程度,通常就由它为零去了。数字三是数据的成分数。因为图像是由红色数据,绿色数据,蓝色数据三种组分组成。 TextureImage[0]->sizeX 是纹理的宽度。如果您知道宽度,您可以在这里填入,但计算机可以很容易的为您指出此值。 TextureImage[0]->sizey 是纹理的高度。数字零是边框的值,一般就是零。 GL_RGB 告诉OpenGL图像数据由红、绿、蓝三色数据组成。 GL_UNSIGNED_BYTE 意味着组成图像的数据是无符号字节类型的。最后... TextureImage[0]->data 告诉OpenGL纹理数据的来源。此例中指向存放在 TextureImage[0] 记录中的数据。 |
|||||||||
// 生成纹理 glTexImage2D(GL_TEXTURE_2D, 0, 3, TextureImage[0]->sizeX, TextureImage[0]->sizeY, 0, GL_RGB, GL_UNSIGNED_BYTE, TextureImage[0]->data); |
|||||||||
下面的两行告诉OpenGL在显示图像时,当它比放大得原始的纹理大 ( GL_TEXTURE_MAG_FILTER )或缩小得比原始得纹理小( GL_TEXTURE_MIN_FILTER )时OpenGL采用的滤波方式。通常这两种情况下我都采用 GL_LINEAR 。这使得纹理从很远处到离屏幕很近时都平滑显示。使用 GL_LINEAR 需要CPU和显卡做更多的运算。如果您的机器很慢,您也许应该采用 GL_NEAREST 。过滤的纹理在放大的时候,看起来斑驳的很『译者注:马赛克啦』。您也可以结合这两种滤波方式。在近处时使用 GL_LINEAR ,远处时 GL_NEAREST 。 | |||||||||
glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR); // 线形滤波 glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR); // 线形滤波 } |
|||||||||
现在我们释放前面用来存放位图数据的内存。我们先查看位图数据是否存放在处。如果是的话,再查看数据是否已经存储。如果已经存储的话,删了它。接着再释放 TextureImage[0] 图像结构以保证所有的内存都能释放。 | |||||||||
if (TextureImage[0]) // 纹理是否存在 { if (TextureImage[0]->data) // 纹理图像是否存在 { free(TextureImage[0]->data); // 释放纹理图像占用的内存 } free(TextureImage[0]); // 释放图像结构 } |
|||||||||
最后返回状态变量。如果一切OK,变量 Status 的值为 TRUE 。否则为 FALSE 。 | |||||||||
return Status; // 返回 Status
----------------------------------------------------------- int InitGL(GLvoid) // 此处开始对OpenGL进行所有设置 ............. ---------------------------------------------------------------------------------------------
|
接着设置用来创建光源的数组。我们将使用两种不同的光。第一种称为环境光。环境光来自于四面八方。所有场景中的对象都处于环境光的照射中。第二种类型的光源叫做漫射光。漫射光由特定的光源产生,并在您的场景中的对象表面上产生反射。处于漫射光直接照射下的任何对象表面都变得很亮,而几乎未被照射到的区域就显得要暗一些。这样在我们所创建的木板箱的棱边上就会产生的很不错的阴影效果。 创建光源的过程和颜色的创建完全一致。前三个参数分别是RGB三色分量,最后一个是alpha通道参数。 因此,下面的代码我们得到的是半亮(0.5f)的白色环境光。如果没有环境光,未被漫射光照到的地方会变得十分黑暗。 |
|||||||||||||||||
GLfloat LightAmbient[]= { 0.5f, 0.5f, 0.5f, 1.0f }; //环境光参数 ( 新增 ) | |||||||||||||||||
下一行代码我们生成最亮的漫射光。所有的参数值都取成最大值1.0f。它将照在我们木板箱的前面,看起来挺好。 | |||||||||||||||||
GLfloat LightDiffuse[]= { 1.0f, 1.0f, 1.0f, 1.0f }; // 漫射光参数 ( 新增 ) | |||||||||||||||||
最后我们保存光源的位置。前三个参数和glTranslate中的一样。依次分别是XYZ轴上的位移。由于我们想要光线直接照射在木箱的正面,所以XY轴上的位移都是0.0f。第三个值是Z轴上的位移。为了保证光线总在木箱的前面,所以我们将光源的位置朝着观察者(就是您哪。)挪出屏幕。我们通常将屏幕也就是显示器的屏幕玻璃所处的位置称作Z轴的0.0f点。所以Z轴上的位移最后定为2.0f。假如您能够看见光源的话,它就浮在您显示器的前方。当然,如果木箱不在显示器的屏幕玻璃后面的话,您也无法看见箱子。『译者注:我很欣赏NeHe的耐心。说真的有时我都打烦了,这么简单的事他这么废话干嘛?但如果什么都清楚,您还会翻着这样的页面看个没完么?』 最后一个参数取为1.0f。这将告诉OpenGL这里指定的坐标就是光源的位置,以后的教程中我会多加解释。 |
|||||||||||||||||
GLfloat LightPosition[]= { 0.0f, 0.0f, 2.0f, 1.0f }; // 光源位置
|
int DrawGLScene(GLvoid) // 此过程中包括所有的绘制代码 { glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); // 清除屏幕及深度缓存 glLoadIdentity(); // 重置视口 |
|
当您调用glLoadIdentity()之后,您实际上讲当前点移到了屏幕中心,X坐标轴从左至右,Y坐标轴从下至上,Z坐标轴从里至外。OpenGL屏幕中心的坐标值是X和Y轴上的0.0f点。中心左面的坐标值是负值,右面是正值。移向屏幕顶端是正值,移向屏幕底端是负值。移入屏幕深处是负值,移出屏幕则是正值。 glTranslatef(x, y, z)沿着 X, Y 和 Z 轴移动。根据前面的次序,下面的代码沿着X轴左移1.5个单位,Y轴不动(0.0f),最后移入屏幕6.0f个单位。注意在glTranslatef(x, y, z)中当您移动的时候,您并不是相对屏幕中心移动,而是相对与当前所在的屏幕位置。 |
|
glTranslatef(-1.5f,0.0f,-6.0f); // 左移 1.5 单位,并移入屏幕 6.0 | |
现在我们已经移到了屏幕的左半部分,并且将视图推入屏幕背后足够的距离以便我们可以看见全部的场景-创建三角形。glBegin(GL_TRIANGLES)的意思是开始绘制三角形,glEnd() 告诉OpenGL三角形已经创建好了。通常您会需要画3个顶点,可以使用GL_TRIANGLES。在绝大多数的显卡上,绘制三角形是相当快速的。如果要画四个顶点,使用GL_QUADS的话会更方便。但据我所知,绝大多数的显卡都使用三角形来为对象着色。最后,如果您想要画更多的顶点时,可以使用GL_POLYGON。 本节的简单示例中,我们只画一个三角形。如果要画第二个三角形的话,可以在这三点之后,再加三行代码(3点)。所有六点代码都应包含在glBegin(GL_TRIANGLES) 和 glEnd()之间。在他们之间再不会有多余的点出现,也就是说,(GL_TRIANGLES) 和 glEnd()之间的点都是以三点为一个集合的。这同样适用于四边形。如果您知道实在绘制四边形的话,您必须在第一个四点之后,再加上四点为一个集合的点组。另一方面,多边形可以由任意个顶点,(GL_POLYGON)不在乎glBegin(GL_TRIANGLES) 和 glEnd()之间有多少行代码。 glBegin之后的第一行设置了多边形的第一个顶点,glVertex 的第一个参数是X坐标,然后依次是Y坐标和Z坐标。第一个点是上顶点,然后是左下顶点和右下顶点。glEnd()告诉OpenGL没有其他点了。这样将显示一个填充的三角形。 {译者:这里要注意的是存在两种不同的坐标变换方式,glTranslatef(x, y, z)中的x, y, z是相对与您当前所在点的位移,但glVertex(x,y,z)是相对于glTranslatef(x, y, z)移动后的新原点的位移。因而这里可以认为glTranslate移动的是坐标原点,glVertex中的点是相对最新的坐标原点的坐标值。} |
// stdafx.h : include file for standard system include files,
// or project specific include files that are used frequently, but
// are changed infrequently
//
#if !defined(AFX_STDAFX_H__A9DB83DB_A9FD_11D0_BFD1_444553540000__INCLUDED_)
#define AFX_STDAFX_H__A9DB83DB_A9FD_11D0_BFD1_444553540000__INCLUDED_
#if _MSC_VER > 1000
#pragma once
#endif // _MSC_VER > 1000
#define WIN32_LEAN_AND_MEAN // Exclude rarely-used stuff from Windows headers
#include
#include
#include
#include
#include
#include
#include
#include
#include
#pragma comment( lib, "winmm.lib")
#pragma comment( lib, "opengl32.lib") // OpenGL32连接库
#pragma comment( lib, "glu32.lib") // GLu32连接库
#pragma comment( lib, "glaux.lib") // GLaux连接库
// TODO: reference additional headers your program requires here
//{{AFX_INSERT_LOCATION}}
// Microsoft Visual C++ will insert additional declarations immediately before the previous line.
#endif // !defined(AFX_STDAFX_H__A9DB83DB_A9FD_11D0_BFD1_444553540000__INCLUDED_)
----------------------------------------------------------------------------------
// stdafx.cpp : source file that includes just the standard includes
// OpenGL的基本图形.pch will be the pre-compiled header
// stdafx.obj will contain the pre-compiled type information
#include "stdafx.h"
// TODO: reference any additional headers you need in STDAFX.H
// and not in this file
----------------------------------------------------------------------------------------
#include "stdafx.h"
#include "OpenGL.h"
//
OpenGL* m_OpenGL;
HDC hDC; // GDI设备句柄,将窗口连接到 GDI( 图形设备接口)
HGLRC hRC=NULL; // 渲染描述句柄,将OpenGL调用连接到设备描述表
HWND hWnd=NULL; // 保存 Windows 分配给程序的窗口句柄
int Width = 800;// 窗口宽
int Height= 600;// 窗口高
int bits = 16; // 颜色深度
void GameLoop()
{ MSG msg;
BOOL fMessage;
PeekMessage(&msg, NULL, 0U, 0U, PM_NOREMOVE);
while(msg.message != WM_QUIT) // 消息循环
{ fMessage = PeekMessage(&msg, NULL, 0U, 0U, PM_REMOVE);
if(fMessage) //有消息
{ TranslateMessage(&msg);
DispatchMessage(&msg);
}
else m_OpenGL->Render(); //无消息
}
}
LRESULT WINAPI MsgProc(HWND hWnd,UINT message,WPARAM wParam,LPARAM lParam )// 消息处理
{ switch(message)
{ case WM_CREATE: // 建立窗口
hDC = GetDC(hWnd); // 获取当前窗口的设备句柄
m_OpenGL->SetupPixelFormat(hDC);// 调用显示模式安装功能
return 0; break;
case WM_CLOSE: // 关闭窗口
m_OpenGL->CleanUp(); // 结束处理
PostQuitMessage(0);
return 0; break;
case WM_SIZE: // 窗口尺寸变化
Height = HIWORD(lParam); // 窗口的高
Width = LOWORD(lParam); // 窗口的宽
if (Height==0) Height=1; // 防止被0 除
m_OpenGL->init(Width,Height);
return 0; break;
case WM_DESTROY: // 退出消息
PostQuitMessage(0);
return 0; break;
case WM_KEYUP: // 按ESC退出,全屏模式必需要加入的退出方式。
switch (wParam)
{ case VK_ESCAPE:
m_OpenGL->CleanUp(); // 结束处理
PostQuitMessage(0);
return 0;break;
}
default: break;
}
return (DefWindowProc(hWnd, message, wParam, lParam));
}
INT WINAPI WinMain(HINSTANCE hInst,HINSTANCE,LPSTR,INT )// WinMain程序入口
{ // 注册窗口类
bool fullScreen =TRUE;
DWORD dwExStyle; // Window 扩展风格
DWORD dwStyle; // Window 窗口风格
RECT windowRect; // 窗口尺寸
int nX=0,nY=0;
/*
if (MessageBox(NULL,"使用全屏模式吗?", "将进入OpenGL,选择显示模式",
MB_YESNO|MB_ICONQUESTION|MB_SYSTEMMODAL)==IDNO)
{fullScreen =false;} // 选择窗口模式
if (fullScreen) // 选择全屏模式
{ DEVMODE dmScr; // 设备模式
memset(&dmScr,0,sizeof(dmScr)); // 确保内存分配
dmScr.dmSize=sizeof(dmScr); // Devmode 结构的大小
dmScr.dmPelsWidth = Width; // 屏幕宽
dmScr.dmPelsHeight= Height; // 屏幕高
dmScr.dmBitsPerPel= 16; // 色彩深度
dmScr.dmDisplayFrequency=75; // 刷屏速度
dmScr.dmFields=DM_BITSPERPEL|DM_PELSWIDTH|DM_PELSHEIGHT|DM_DISPLAYFREQUENCY;
if (ChangeDisplaySettings(&dmScr, CDS_FULLSCREEN) != DISP_CHANGE_SUCCESSFUL)
{fullScreen=FALSE;}
dwExStyle=WS_EX_APPWINDOW; // Window 扩展风格
dwStyle=WS_POPUP; // Window 窗口风格
ShowCursor(FALSE); // 隐藏鼠标
}
else*/
{ dwExStyle=WS_EX_APPWINDOW|WS_EX_WINDOWEDGE; // 使窗口具有3D外观
dwStyle=WS_OVERLAPPEDWINDOW; // 使用标准窗口
//WS_OVERLAPPEDWINDOW是有标题栏,窗口菜单,最大、小化按钮和可调整尺寸的窗口
int wid=GetSystemMetrics(SM_CXSCREEN); // 获取当前屏幕宽
int hei=GetSystemMetrics(SM_CYSCREEN); // 获取当前屏幕高
nX=(wid-Width)/2;nY=(hei-Height)/2; // 计算窗口居中用
}
//-------------------------------------------------------------------
AdjustWindowRectEx(&windowRect,dwStyle,FALSE,dwExStyle);
//根据窗口风格来调整窗口尺寸达到要求的大小
char cc[]="tml";
WNDCLASSEX wc = { sizeof(WNDCLASSEX), CS_CLASSDC, MsgProc, 0L, 0L,
GetModuleHandle(NULL), NULL, NULL, NULL, NULL,
cc, NULL };
RegisterClassEx( &wc );
m_OpenGL=new OpenGL();//
hWnd = CreateWindowEx(NULL,cc,"学OpenGL编3D游戏 [ 2.OpenGL的基本图形 ])",
dwStyle|WS_CLIPCHILDREN|WS_CLIPSIBLINGS,
nX, nY,Width, Height,
NULL,NULL,hInst,NULL); // 创建窗口
ShowWindow( hWnd, SW_SHOWDEFAULT ); // 显示窗口
UpdateWindow( hWnd ); // 刷新窗口
GameLoop(); // 进入消息循环
return 0;
}
--------------------------------------------------------------------------------------------------------
// OpenGL.h: interface for the OpenGL class.
//
//
#if !defined(AFX_OPENGL_H__17B7289C_7956_41C5_89B9_621E3C435389__INCLUDED_)
#define AFX_OPENGL_H__17B7289C_7956_41C5_89B9_621E3C435389__INCLUDED_
#if _MSC_VER > 1000
#pragma once
#endif // _MSC_VER > 1000
#include "bsipic.h"
class OpenGL
{ public: OpenGL();
virtual ~OpenGL();
public:
bsipic m_bsipic; // 定义bsipic类变量
HDC hDC; // GDI设备描述表
HGLRC hRC; // 永久着色描述表
BOOL SetupPixelFormat(HDC hDC);
void init(int Width, int Height);
void Render();
void CleanUp();
void play();
};
#endif // !defined(AFX_OPENGL_H__17B7289C_7956_41C5_89B9_621E3C435389__INCLUDED_)
------------------------------------------------------------------------------------------------------------
// OpenGL.cpp: implementation of the OpenGL class.
//
#include "stdafx.h"
#include "OpenGL.h"
//
#include "stdafx.h"
#include "OpenGL.h"
//
extern HWND hWnd;
float r;
//
OpenGL::OpenGL()
{
}
OpenGL::~OpenGL()
{ CleanUp();
}
BOOL OpenGL::SetupPixelFormat(HDC hDC0)//检测安装OpenGL
{ int nPixelFormat; // 象素点格式
hDC=hDC0;
PIXELFORMATDESCRIPTOR pfd = {
sizeof(PIXELFORMATDESCRIPTOR), // pfd结构的大小
1, // 版本号
PFD_DRAW_TO_WINDOW | // 支持在窗口中绘图
PFD_SUPPORT_OPENGL | // 支持 OpenGL
PFD_DOUBLEBUFFER, // 双缓存模式
PFD_TYPE_RGBA, // RGBA 颜色模式
16, // 24 位颜色深度
0, 0, 0, 0, 0, 0, // 忽略颜色位
0, // 没有非透明度缓存
0, // 忽略移位位
0, // 无累加缓存
0, 0, 0, 0, // 忽略累加位
16, // 32 位深度缓存
0, // 无模板缓存
0, // 无辅助缓存
PFD_MAIN_PLANE, // 主层
0, // 保留
0, 0, 0 // 忽略层,可见性和损毁掩模
};
if (!(nPixelFormat = ChoosePixelFormat(hDC, &pfd)))
{ MessageBox(NULL,"没找到合适的显示模式","Error",MB_OK|MB_ICONEXCLAMATION);
return FALSE;
}
SetPixelFormat(hDC,nPixelFormat,&pfd);//设置当前设备的像素点格式
hRC = wglCreateContext(hDC); //获取渲染描述句柄
wglMakeCurrent(hDC, hRC); //激活渲染描述句柄
return TRUE;
}
void OpenGL::init(int Width, int Height)
{ glViewport(0,0,Width,Height); // 设置OpenGL视口大小。
glMatrixMode(GL_PROJECTION); // 设置当前矩阵为投影矩阵。
glLoadIdentity(); // 重置当前指定的矩阵为单位矩阵
gluPerspective // 设置透视图
( 54.0f, // 透视角设置为 45 度
(GLfloat)Width/(GLfloat)Height, // 窗口的宽与高比
0.1f, // 视野透视深度:近点1.0f
3000.0f // 视野透视深度:始点0.1f远点1000.0f
);
// 这和照象机很类似,第一个参数设置镜头广角度,第二个参数是长宽比,后面是远近剪切。
glMatrixMode(GL_MODELVIEW); // 设置当前矩阵为模型视图矩阵
glLoadIdentity(); // 重置当前指定的矩阵为单位矩阵
//====================================================
}
void OpenGL::Render()//OpenGL图形处理
{ glClearColor(0.0f, 0.0f, 0.6f, 1.0f); // 设置刷新背景色
glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT);// 刷新背景
glLoadIdentity(); // 重置当前的模型观察矩阵
play();
glFlush(); // 更新窗口
SwapBuffers(hDC); // 切换缓冲区
r+=1;if(r>360) r=0;
}
void OpenGL::CleanUp()
{ wglMakeCurrent(hDC, NULL); //清除OpenGL
wglDeleteContext(hRC); //清除OpenGL
}
void OpenGL::play()
{ glPushMatrix();
glPointSize(4);
glTranslatef (-5, 4,-13);glRotatef(r,1.0,1.0,1.0);
glColor3f(1.0f, 0.0f, 0.0f);m_bsipic.Point();
glPopMatrix();
glPushMatrix();
glTranslatef ( 0, 4,-13);glRotatef(r,1.0,1.0,1.0);
glColor3f(0.0f, 1.0f, 0.0f);m_bsipic.Line();
glPopMatrix();
glPushMatrix();
glTranslatef ( 5, 4,-13);glRotatef(r,1.0,1.0,1.0);
glColor3f(0.0f, 0.0f, 1.0f);m_bsipic.Triangle();
glPopMatrix();
glPushMatrix();
glTranslatef (-5, 0,-13);glRotatef(r,1.0,1.0,1.0);
glColor3f(1.0f, 1.0f, 0.0f);m_bsipic.Square();
glPopMatrix();
glPushMatrix();
glTranslatef ( 0, 0,-13);glRotatef(r,1.0,1.0,1.0);
glColor3f(0.0f, 1.0f, 1.0f);m_bsipic.Esquare();
glPopMatrix();
glPushMatrix();
glTranslatef ( 5, 0,-13);glRotatef(r,1.0,1.0,1.0);
glColor3f(1.0f, 0.0f, 1.0f);m_bsipic.Park();
glPopMatrix();
glPushMatrix();
glTranslatef (-5,-4,-13);glRotatef(r,1.0,1.0,1.0);
glColor3f(1.0f, 1.0f, 1.0f);m_bsipic.Pillar();
glPopMatrix();
glPushMatrix();
glTranslatef ( 0, -4,-13);glRotatef(r,1.0,1.0,1.0);
glColor3f(0.7f, 0.7f, 0.7f);auxSolidCone(1,1);
glPopMatrix();
glPushMatrix();
glTranslatef ( 5,-4,-13);glRotatef(r,1.0,1.0,1.0);
glColor3f(0.4f, 0.4f, 0.4f);auxWireTeapot(1);
glPopMatrix();
}
----------------------------------------------------------------------------------------------------
// bsipic.h: interface for the bsipic class.
//
//
#if !defined(AFX_BSIPIC_H__726BAD2E_C0FE_4F4C_8282_BD53CCED1A9D__INCLUDED_)
#define AFX_BSIPIC_H__726BAD2E_C0FE_4F4C_8282_BD53CCED1A9D__INCLUDED_
#if _MSC_VER > 1000
#pragma once
#endif // _MSC_VER > 1000
class bsipic
{
public:
bsipic();
virtual ~bsipic();
void Point();
void Line();
void Triangle();
void Square();
void Esquare();
void Park();
void Pillar();
};
#endif // !defined(AFX_BSIPIC_H__726BAD2E_C0FE_4F4C_8282_BD53CCED1A9D__INCLUDED_)
------------------------------------------------------------------------------------------------------------
// bsipic.cpp: implementation of the bsipic class.
//
#include "stdafx.h"
#include "bsipic.h"
//
bsipic::bsipic()
{
}
bsipic::~bsipic()
{
}
///
void bsipic::Point()//画点
{ glBegin(GL_POINTS);//
glVertex3f( 0.0f, 1.0f,-1.0f);//a点
glVertex3f(-1.0f,-1.0f, 0.0f);//b点
glVertex3f( 1.0f,-1.0f, 0.0f);//c点
glEnd();
}
void bsipic::Line()//画线
{ glBegin(GL_LINE_LOOP); //
glVertex3f( 0.0f, 1.0f,-1.0f);//a点
glVertex3f(-1.0f,-1.0f, 0.0f);//b点
glVertex3f( 1.0f,-1.0f, 0.0f);//c点
glEnd();
}
void bsipic::Triangle()//画面
{ glBegin(GL_POLYGON);//
glVertex3f( 0.0f, 1.0f,-1.0f);//a点
glVertex3f(-1.0f,-1.0f, 0.0f);//b点
glVertex3f( 1.0f,-1.0f, 0.0f);//c点
glEnd();
}
void bsipic::Square()//画正方面
{ glBegin(GL_POLYGON);//
glVertex3f(0.0f,0.0f ,0.0f);//a点
glVertex3f(1.0f,0.0f, 0.0f);//b点
glVertex3f(1.0f,0.0f,-1.0f);//c点
glVertex3f(0.0f,0.0f,-1.0f);//d点
glEnd();
}
void bsipic::Esquare()//画正方体
{ glBegin(GL_QUAD_STRIP);//
glVertex3f(0.0f,0.0f ,0.0f);//a0点
glVertex3f(0.0f,1.0f ,0.0f);//a1点
glVertex3f(1.0f,0.0f, 0.0f);//b0点
glVertex3f(1.0f,1.0f, 0.0f);//b1点
glVertex3f(1.0f,0.0f,-1.0f);//c0点
glVertex3f(1.0f,1.0f,-1.0f);//c1点
glVertex3f(0.0f,0.0f,-1.0f);//d0点
glVertex3f(0.0f,1.0f,-1.0f);//d1点
glVertex3f(0.0f,0.0f ,0.0f);//a0点
glVertex3f(0.0f,1.0f ,0.0f);//a1点
glEnd();
glBegin(GL_POLYGON);//
glVertex3f(0.0f,0.0f ,0.0f);//a0点
glVertex3f(1.0f,0.0f, 0.0f);//b0点
glVertex3f(1.0f,0.0f,-1.0f);//c0点
glVertex3f(0.0f,0.0f,-1.0f);//d0点
glVertex3f(0.0f,1.0f ,0.0f);//a1点
glVertex3f(1.0f,1.0f, 0.0f);//b1点
glVertex3f(1.0f,1.0f,-1.0f);//c1点
glVertex3f(0.0f,1.0f,-1.0f);//d1点
glEnd();
}
void bsipic::Park ()//画园
{ glBegin(GL_TRIANGLE_FAN);//
glVertex3f(0,0,0.0f );
for(int i=0;i<=390;i+=30)
{float p=(float)(i*3.14/180);
glVertex3f((float)sin(p),(float)cos(p),0.0f );
}
glEnd();
}
void bsipic::Pillar () //园柱
{glBegin(GL_QUAD_STRIP);//
for(int i=0;i<=390;i+=30)
{ float p=(float)(i*3.14/180);
glVertex3f((float)sin(p)/2,(float)cos(p)/2,1.0f );
glVertex3f((float)sin(p)/2,(float)cos(p)/2,0.0f );
}
glEnd();
}
代码的前4行包括了我们使用的每个库文件的头文件。如下所示: | |
#include #include #include #include |
// Windows的头文件 // OpenGL32库的头文件 // GLu32库的头文件 // GLaux库的头文件 |
接下来您需要设置您计划在您的程序中使用的所有变量。本节中的例程将创建一个空的OpenGL窗口,因此我们暂时还无需设置大堆的变量。余下需要设置的变量不多,但十分重要。您将会在您以后所写的每一个OpenGL程序中用到它们。 第一行设置的变量是Rendering Context(着色描述表)。每一个OpenGL都被连接到一个着色描述表上。着色描述表将所有的OpenGL调用命令连接到Device Context(设备描述表)上。我将OpenGL的着色描述表定义为 hRC 。要让您的程序能够绘制窗口的话,还需要创建一个设备描述表,也就是第二行的内容。Windows的设备描述表被定义为 hDC 。DC将窗口连接到GDI(Graphics Device Interface图形设备接口)。而RC将OpenGL连接到DC。第三行的变量 hWnd 将保存由Windows给我们的窗口指派的句柄。最后,第四行为我们的程序创建了一个Instance(实例)。 |
|
HGLRC hRC=NULL; HDC hDC=NULL; HWND hWnd=NULL; HINSTANCE hInstance; |
// 永久着色描述表 // 私有GDI设备描述表 // 保存我们的窗口句柄 // 保存程序的实例 |
下面的第一行设置一个用来监控键盘动作的数组。有许多方法可以监控键盘的动作,但这里的方法很可靠,并且可以处理多个键同时按下的情况。 active 变量用来告知程序窗口是否处于最小化的状态。如果窗口已经最小化的话,我们可以做从暂停代码执行到退出程序的任何事情。我喜欢暂停程序。这样可以使得程序不用在后台保持运行。 fullscreen 变量的作用相当明显。如果我们的程序在全屏状态下运行, fullscreen 的值为TRUE,否则为FALSE。这个全局变量的设置十分重要,它让每个过程都知道程序是否运行在全屏状态下。 |
|
bool keys[256]; bool active=TRUE; bool fullscreen=TRUE; |
// 用于键盘例程的数组 // 窗口的活动标志,缺省为TRUE // 全屏标志缺省设定成全屏模式 |
现在我们需要先定义WndProc()。必须这么做的原因是CreateGLWindow()有对WndProc()的引用,但WndProc()在CreateGLWindow()之后才出现。在C语言中,如果我们想要访问一个当前程序段之后的过程和程序段的话,必须在程序开始处先申明所要访问的程序段。所以下面的一行代码先行定义了WndProc(),使得CreateGLWindow()能够引用WndProc()。 | |
LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM); | // WndProc的定义 |
下面的代码的作用是重新设置OpenGL场景的大小,而不管窗口的大小是否已经改变(假定您没有使用全屏模式)。甚至您无法改变窗口的大小时(例如您在全屏模式下),它至少仍将运行一次--在程序开始时设置我们的透视图。OpenGL场景的尺寸将被设置成它显示时所在窗口的大小。 | |
GLvoid ReSizeGLScene(GLsizei width, GLsizei height) { if (height==0) { height=1; } glViewport(0, 0, width, height); |
// 重置并初始化GL窗口大小 // 防止被零除 // 将Height设为1 // 重置当前的视口(Viewport) |
下面几行为透视图设置屏幕。意味着越远的东西看起来越小。这么做创建了一个现实外观的场景。此处透视按照基于窗口宽度和高度的45度视角来计算。0.1f,100.0f是我们在场景中所能绘制深度的起点和终点。 glMatrixMode(GL_PROJECTION)指明接下来的两行代码将影响projection matrix(投影矩阵)。投影矩阵负责为我们的场景增加透视。 glLoadIdentity()近似于重置。它将所选的矩阵状态恢复成其原始状态。调用 glLoadIdentity()之后我们为场景设置透视图。 glMatrixMode(GL_MODELVIEW)指明任何新的变换将会影响 modelview matrix(模型观察矩阵)。模型观察矩阵中存放了我们的物体讯息。最后我们重置模型观察矩阵。如果您还不能理解这些术语的含义,请别着急。在以后的教程里,我会向大家解释。只要知道如果您想获得一个精彩的透视场景的话,必须这么做。 |
|
glMatrixMode(GL_PROJECTION); glLoadIdentity(); // 计算窗口的外观比例 gluPerspective(45.0f,(GLfloat)width/(GLfloat)height,0.1f,100.0f); glMatrixMode(GL_MODELVIEW); glLoadIdentity(); } |
// 选择投影矩阵 // 重置投影矩阵 // 选择模型观察矩阵 // 重置模型观察矩阵 |
接下的代码段中,我们将对OpenGL进行所有的设置。我们将设置清除屏幕所用的颜色,打开深度缓存,启用smooth shading(阴影平滑),等等。这个例程直到OpenGL窗口创建之后才会被调用。此过程将有返回值。但我们此处的初始化没那么复杂,现在还用不着担心这个返回值。 | |
int InitGL(GLvoid) { |
// 此处开始对OpenGL进行所有设置 |
下一行启用smooth shading(阴影平滑)。阴影平滑通过多边形精细的混合色彩,并对外部光进行平滑。我将在另一个教程中更详细的解释阴影平滑。 | |
glShadeModel(GL_SMOOTH); | // 启用阴影平滑 |
下一行设置清除屏幕时所用的颜色。如果您对色彩的工作原理不清楚的话,我快速解释一下。色彩值的范围从0.0f到1.0f。0.0f代表最黑的情况,1.0f就是最亮的情况。glClearColor 后的第一个参数是Red Intensity(红色分量),第二个是绿色,第三个是蓝色。最大值也是1.0f,代表特定颜色分量的最亮情况。最后一个参数是Alpha值。当它用来清除屏幕的时候,我们不用关心第四个数字。现在让它为0.0f。我会用另一个教程来解释这个参数。 通过混合三种原色(红、绿、蓝),您可以得到不同的色彩。希望您在学校里学过这些。因此,当您使用glClearColor(0.0f,0.0f,1.0f,0.0f),您将用亮蓝色来清除屏幕。如果您用 glClearColor(0.5f,0.0f,0.0f,0.0f)的话,您将使用中红色来清除屏幕。不是最亮(1.0f),也不是最暗 (0.0f)。要得到白色背景,您应该将所有的颜色设成最亮(1.0f)。要黑色背景的话,您该将所有的颜色设为最暗(0.0f)。 |
|
glClearColor(0.0f, 0.0f, 0.0f, 0.0f); | // 黑色背景 |
接下来的三行必须做的是关于depth buffer(深度缓存)的。将深度缓存设想为屏幕后面的层。深度缓存不断的对物体进入屏幕内部有多深进行跟踪。我们本节的程序其实没有真正使用深度缓存,但几乎所有在屏幕上显示3D场景OpenGL程序都使用深度缓存。它的排序决定那个物体先画。这样您就不会将一个圆形后面的正方形画到圆形上来。深度缓存是OpenGL十分重要的部分。 | |
glClearDepth(1.0f); glEnable(GL_DEPTH_TEST); glDepthFunc(GL_LEQUAL); |
// 设置深度缓存 // 启用深度测试 // 所作深度测试的类型 |
接着告诉OpenGL我们希望进行最好的透视修正。这会十分轻微的影响性能。但使得透视图看起来好一点。 | |
glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST); | // 真正精细的透视修正 |
最后,我们返回TRUE。如果我们希望检查初始化是否OK,我们可以查看返回的 TRUE或FALSE的值。如果有错误发生的话,您可以加上您自己的代码返回FALSE。目前,我们不管它。 | |
return TRUE; } |
// 初始化 OK |
下一段包括了所有的绘图代码。任何您所想在屏幕上显示的东东都将在此段代码中出现。以后的每个教程中我都会在例程的此处增加新的代码。如果您对OpenGL已经有所了解的话,您可以在 glLoadIdentity()调用之后,返回TRUE值之前,试着添加一些OpenGL代码来创建基本的形。如果您是OpenGL新手,等着我的下个教程。目前我们所作的全部就是将屏幕清除成我们前面所决定的颜色,清除深度缓存并且重置场景。我们仍没有绘制任何东东。 返回TRUE值告知我们的程序没有出现问题。如果您希望程序因为某些原因而中止运行,在返回TRUE值之前增加返回FALSE的代码告知我们的程序绘图代码出错。程序即将退出。 |
|
int DrawGLScene(GLvoid) { glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); glLoadIdentity(); return TRUE; } |
// 从这里开始进行所有的绘制 // 清除屏幕和深度缓存 // 重置当前的模型观察矩阵 // 一切 OK |
下一段代码只在程序退出之前调用。KillGLWindow() 的作用是依次释放着色描述表,设备描述表和窗口句柄。我已经加入了许多错误检查。如果程序无法销毁窗口的任意部分,都会弹出带相应错误消息的讯息窗口,告诉您什么出错了。使您在您的代码中查错变得更容易些。 | |
GLvoid KillGLWindow(GLvoid) { |
// 正常销毁窗口 |
我们在KillGLWindow()中所作的第一件事是检查我们是否处于全屏模式。如果是,我们要切换回桌面。我们本应在禁用全屏模式前先销毁窗口,但在某些显卡上这么做可能会使得桌面崩溃。所以我们还是先禁用全屏模式。这将防止桌面出现崩溃,并在Nvidia和3dfx显卡上都工作的很好! | |
if (fullscreen) { |
// 我们处于全屏模式吗? |
我们使用ChangeDisplaySettings(NULL,0)回到原始桌面。将NULL作为第一个参数,0作为第二个参数传递强制Windows使用当前存放在注册表中的值(缺省的分辨率、色彩深度、刷新频率,等等)来有效的恢复我们的原始桌面。切换回桌面后,我们还要使得鼠标指针重新可见。 | |
ChangeDisplaySettings(NULL,0); ShowCursor(TRUE); } |
// 是的话,切换回桌面 // 显示鼠标指针 |
接下来的代码查看我们是否拥有着色描述表(hRC)。如果没有,程序将跳转至后面的代码查看是否拥有设备描述表。 | |
if (hRC) { |
// 我们拥有着色描述表吗? |
如果存在着色描述表的话,下面的代码将查看我们能否释放它(将 hRC从hDC分开)。这里请注意我使用的的查错方法。基本上我只是让程序尝试释放着色描述表(通过调用wglMakeCurrent(NULL,NULL),然后我再查看释放是否成功。巧妙的将数行代码结合到了一行。 | |
if (!wglMakeCurrent(NULL,NULL)) { |
// 我们能否释放DC和RC描述表? |
如果不能释放DC和RC描述表的话,MessageBox()将弹出错误消息,告知我们DC和RC无法被释放。NULL意味着消息窗口没有父窗口。其右的文字将在消息窗口上出现。"SHUTDOWN ERROR"出现在窗口的标题栏上。MB_OK的意思消息窗口上带有一个写着OK字样的按钮。 MB_ICONINFORMATION将在消息窗口中显示一个带圈的小写的i(看上去更正式一些)。 |
|
MessageBox(NULL,"Release Of DC And RC Failed.","SHUTDOWN ERROR",MB_OK | MB_ICONINFORMATION); } |
|
下一步我们试着删除着色描述表。如果不成功的话弹出错误消息。 | |
if (!wglDeleteContext(hRC)) { |
// 我们能否删除RC? |
如果无法删除着色描述表的话,将弹出错误消息告知我们RC未能成功删除。然后hRC被设为NULL。 | |
MessageBox(NULL,"Release Rendering Context Failed.","SHUTDOWN ERROR",MB_OK | MB_ICONINFORMATION); } hRC=NULL; // 将RC设为 NULL } |
|
现在我们查看是否存在设备描述表,如果有尝试释放它。如果不能释放设备描述表将弹出错误消息,然后hDC设为NULL。 | |
if (hDC && !ReleaseDC(hWnd,hDC)) // 我们能否释放 DC? { MessageBox(NULL,"Release Device Context Failed.","SHUTDOWN ERROR",MB_OK | MB_ICONINFORMATION); hDC=NULL; // 将 DC 设为 NULL } |
|
现在我们来查看是否存在窗口句柄,我们调用 DestroyWindow( hWnd )来尝试销毁窗口。如果不能的话弹出错误窗口,然后hWnd被设为NULL。 | |
if (hWnd && !DestroyWindow(hWnd)) // 能否销毁窗口? { MessageBox(NULL,"Could Not Release hWnd.","SHUTDOWN ERROR",MB_OK | MB_ICONINFORMATION); hWnd=NULL; // 将 hWnd 设为 NULL } |
|
最后要做的事是注销我们的窗口类。这允许我们正常销毁窗口,接着在打开其他窗口时,不会收到诸如"Windows Class already registered"(窗口类已注册)的错误消息。 | |
if (!UnregisterClass("OpenGL",hInstance)) // 能否注销类? { MessageBox(NULL,"Could Not Unregister Class.","SHUTDOWN ERROR",MB_OK | MB_ICONINFORMATION); hInstance=NULL; // 将 hInstance 设为 NULL } } |
|
接下来的代码段创建我们的OpenGL窗口。我花了很多时间来做决定是否创建固定的全屏模式这样不需要许多额外的代码,还是创建一个容易定制的友好的窗口但需要更多的代码。当然最后我选择了后者。我经常在EMail中收到诸如此类的问题:怎样创建窗口而不使用全屏幕?怎样改变窗口的标题栏?怎样改变窗口的分辨率或pixel format(象素格式)?以下的代码完成了所有这一切!尽管最好要学学材质,这会让您写自己的OpenGL程序变得容易的多! 正如您所见,此过程返回布尔变量(TRUE 或 FALSE)。他还带有5个参数:窗口的标题栏,窗口的宽度,窗口的高度,色彩位数(16/24/32),和全屏标志(TRUE --全屏模式, FALSE--窗口模式 )。返回的布尔值告诉我们窗口是否成功创建。 |
|
BOOL CreateGLWindow(char* title, int width, int height, int bits, bool fullscreenflag) { |
|
当我们要求Windows为我们寻找相匹配的象素格式时,Windows寻找结束后将模式值保存在变量PixelFormat中。 | |
GLuint PixelFormat; | // 保存查找匹配的结果 |
wc用来保存我们的窗口类的结构。窗口类结构中保存着我们的窗口信息。通过改变类的不同字段我们可以改变窗口的外观和行为。每个窗口都属于一个窗口类。当您创建窗口时,您必须为窗口注册类。 | |
WNDCLASS wc; | // 窗口类结构 |
dwExStyle和dwStyle存放扩展和通常的窗口风格信息。我使用变量来存放风格的目的是为了能够根据我需要创建的窗口类型(是全屏幕下的弹出窗口还是窗口模式下的带边框的普通窗口);来改变窗口的风格。 | |
DWORD dwExStyle; DWORD dwStyle; |
// 扩展窗口风格 // 窗口风格 |
下面的5行代码取得矩形的左上角和右下角的坐标值。我们将使用这些值来调整我们的窗口使得其上的绘图区的大小恰好是我们所需的分辨率的值。通常如果我们创建一个640x480的窗口,窗口的边框会占掉一些分辨率的值。 | |
RECT WindowRect; WindowRect.left=(long)0; WindowRect.right=(long)width; WindowRect.top=(long)0; WindowRect.bottom=(long)height; |
// 取得矩形的左上角和右下角的坐标值 // 将Left 设为 0 // 将Right 设为要求的宽度 // 将Top 设为 0 // 将Bottom 设为要求的高度 |
下一行代码我们让全局变量fullscreen等于fullscreenflag。如果我们希望在全屏幕下运行而将fullscreenflag设为TRUE,但没有让变量fullscreen等于fullscreenflag的话,fullscreen变量将保持为FALSE。当我们在全屏幕模式下销毁窗口的时候,变量fullscreen的值却不是正确的TRUE值,计算机将误以为已经处于桌面模式而无法切换回桌面。上帝啊,但愿这一切都有意义。就是一句话,fullscreen的值必须永远fullscreenflag的值,否则就会有问题。{CKER也觉得此处太废话,懂的人都要不懂啦.....:( } | |
fullscreen=fullscreenflag; | // 设置全局全屏标志 |
下一部分的代码中,我们取得窗口的实例,然后定义窗口类。 CS_HREDRAW 和 CS_VREDRAW 的意思是无论何时,只要窗口发生变化时就强制重画。CS_OWNDC为窗口创建一个私有的DC。这意味着DC不能在程序间共享。WndProc是我们程序的消息处理过程。由于没有使用额外的窗口数据,后两个字段设为零。然后设置实例。接着我们将hIcon设为NULL,因为我们不想给窗口来个图标。鼠标指针设为标准的箭头。背景色无所谓(我们在GL中设置)。我们也不想要窗口菜单,所以将其设为NULL。类的名字可以您想要的任何名字。出于简单,我将使用"OpenGL"。 |
|
hInstance = GetModuleHandle(NULL); wc.style = CS_HREDRAW | CS_VREDRAW | CS_OWNDC; wc.lpfnWndProc = (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; wc.lpszMenuName = NULL; wc.lpszClassName = "OpenGL"; |
// 取得我们窗口的实例 // 移动时重画,并为窗口取得DC // WndProc处理消息 // 无额外窗口数据 // 无额外窗口数据 // 设置实例 // 装入缺省图标 // 装入鼠标指针 // GL不需要背景 // 不需要菜单 // 设定类名字 |
现在注册类名字。如果有错误发生,弹出错误消息窗口。按下上面的OK按钮后,程序退出。 | |
if (!RegisterClass(&wc)) // 尝试注册窗口类 { MessageBox(NULL,"Failed To Register The Window Class.","ERROR",MB_OK|MB_ICONEXCLAMATION); return FALSE; file://退出并返回FALSE } |
|
查看程序应该在全屏模式还是窗口模式下运行。如果应该是全屏模式的话,我们将尝试设置全屏模式。 | |
if (fullscreen) { |
// 要尝试全屏模式吗? |
下一部分的代码看来很多人都会有问题要问关于.......切换到全屏模式。在切换到全屏模式时,有几件十分重要的事您必须牢记。必须确保您在全屏模式下所用的宽度和高度等同于窗口模式下的宽度和高度。最最重要的是要在创建窗口之前设置全屏模式。这里的代码中,您无需再担心宽度和高度,它们已被设置成与显示模式所对应的大小。 | |
DEVMODE dmScreenSettings; memset(&dmScreenSettings,0,sizeof(dmScreenSettings)); dmScreenSettings.dmSize=sizeof(dmScreenSettings); dmScreenSettings.dmPelsWidth = width; dmScreenSettings.dmPelsHeight = height; dmScreenSettings.dmBitsPerPel = bits; dmScreenSettings.dmFields=DM_BITSPERPEL|DM_PELSWIDTH|DM_PELSHEIGHT; |
// 设备模式 // 确保内存分配 // Devmode 结构的大小 // 所选屏幕宽度 // 所选屏幕高度 // 每象素所选的色彩深度 |
上面的代码中,我们分配了用于存储视频设置的空间。设定了屏幕的宽,高,色彩深度。下面的代码我们尝试设置全屏模式。我们在dmScreenSettings中保存了所有的宽,高,色彩深度讯息。下一行使用ChangeDisplaySettings来尝试切换成与dmScreenSettings所匹配模式。我使用参数CDS_FULLSCREEN来切换显示模式,因为这样做不仅移去了屏幕底部的状态条,而且它在来回切换时,没有移动或改变您在桌面上的窗口。 | |
// 尝试设置显示模式并返回结果。注: CDS_FULLSCREEN 移去了状态条。 if (ChangeDisplaySettings(&dmScreenSettings,CDS_FULLSCREEN)!=DISP_CHANGE_SUCCESSFUL) { |
|
如果模式未能设置成功,我们将进入以下的代码。如果不能匹配全屏模式,弹出消息窗口,提供两个选项:在窗口模式下运行或退出。 | |
// 若模式失败,提供两个选项:退出或在窗口内运行。 if (MessageBox(NULL,"The Requested Fullscreen Mode Is Not Supported By/nYour Video Card. Use Windowed Mode Instead?","NeHe GL",MB_YESNO|MB_ICONEXCLAMATION)==IDYES) { |
|
如果用户选择窗口模式,变量fullscreen 的值变为FALSE,程序继续运行。 | |
fullscreen=FALSE; } else { |
// 选择窗口模式(Fullscreen=FALSE) |
如果用户选择退出,弹出消息窗口告知用户程序将结束。并返回FALSE告诉程序窗口未能成功创建。程序退出。 | |
// Pop Up A Message Box Letting User Know The Program Is Closing. MessageBox(NULL,"Program Will Now Close.","ERROR",MB_OK|MB_ICONSTOP); return FALSE; //退出并返回 FALSE } } } |
|
由于全屏模式可能失败,用户可能决定在窗口下运行,我们需要在设置屏幕/窗口之前,再次检查fullscreen的值是TRUE或FALSE。 | |
if (fullscreen) { |
// 仍处于全屏模式吗? |
如果我们仍处于全屏模式,设置扩展窗体风格为WS_EX_APPWINDOW,这将强制我们的窗体可见时处于最前面。再将窗体的风格设为WS_POPUP。这个类型的窗体没有边框,使我们的全屏模式得以完美显示。 最后我们禁用鼠标指针。当您的程序不是交互式的时候,在全屏模式下禁用鼠标指针通常是个好主意。 |
|
dwExStyle=WS_EX_APPWINDOW; dwStyle=WS_POPUP; ShowCursor(FALSE); } else { |
// 扩展窗体风格 // 窗体风格 // 隐藏鼠标指针 |
如果我们使用窗口而不是全屏模式,我们在扩展窗体风格中增加了 WS_EX_WINDOWEDGE,增强窗体的3D感观。窗体风格改用 WS_OVERLAPPEDWINDOW,创建一个带标题栏、可变大小的边框、菜单和最大化/最小化按钮的窗体。 | |
dwExStyle=WS_EX_APPWINDOW | WS_EX_WINDOWEDGE; dwStyle=WS_OVERLAPPEDWINDOW; } |
// 扩展窗体风格 // 窗体风格 |
下一行代码根据创建的窗体类型调整窗口。调整的目的是使得窗口大小正好等于我们要求的分辨率。通常边框会占用窗口的一部分。使用AdjustWindowRectEx 后,我们的OpenGL场景就不会被边框盖住。实际上窗口变得更大以便绘制边框。全屏模式下,此命令无效。 | |
AdjustWindowRectEx(&WindowRect, dwStyle, FALSE, dwExStyle); | // 调整窗口达到真正要求的大小 |
下一段代码开始创建窗口并检查窗口是否成功创建。我们将传递CreateWindowEx()所需的所有参数。如扩展风格、类名字(与您在注册窗口类时所用的名字相同)、窗口标题、窗体风格、窗体的左上角坐标(0,0 是个安全的选择)、窗体的宽和高。我们没有父窗口,也不想要菜单,这些参数被设为NULL。还传递了窗口的实例,最后一个参数被设为NULL。 注意我们在窗体风格中包括了 WS_CLIPSIBLINGS 和 WS_CLIPCHILDREN。要让OpenGL正常运行,这两个属性是必须的。他们阻止别的窗体在我们的窗体内/上绘图。 |
|
if (!(hWnd=CreateWindowEx( dwExStyle, "OpenGL", title, WS_CLIPSIBLINGS | WS_CLIPCHILDREN | dwStyle, 0, 0, WindowRect.right-WindowRect.left, WindowRect.bottom-WindowRect.top, NULL, NULL, hInstance, NULL))) |
// 扩展窗体风格 // 类名字 // 窗口标题 // 必须的窗体风格属性 // 必须的窗体风格属性 // 选择的窗体属性 // 窗口位置 // 计算调整好的窗口宽度 // 计算调整好的窗口高度 // 无父窗口 // 无菜单 // 实例 // 不向WM_CREATE传递任何东东 |
下来我们检查看窗口是否正常创建。如果成功, hWnd保存窗口的句柄。如果失败,弹出消息窗口,并退出程序。 | |
{ KillGLWindow(); // 重置显示区 MessageBox(NULL,"Window Creation Error.","ERROR",MB_OK|MB_ICONEXCLAMATION); return FALSE; // 返回 FALSE } |
|
下面的代码描述象素格式。我们选择了通过RGBA(红、绿、蓝、alpha通道)支持OpenGL和双缓存的格式。我们试图找到匹配我们选定的色彩深度(16位、24位、32位)的象素格式。最后设置16位Z-缓存。其余的参数要么未使用要么不重要(stencil buffer模板缓存和accumulation buffer聚集缓存除外)。 | |
static PIXELFORMATDESCRIPTOR pfd= { sizeof(PIXELFORMATDESCRIPTOR), 1, PFD_DRAW_TO_WINDOW | PFD_SUPPORT_OPENGL | PFD_DOUBLEBUFFER, PFD_TYPE_RGBA, bits, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, PFD_MAIN_PLANE, 0, 0, 0, 0 }; |
//pfd 告诉窗口我们所希望的东东 file://上诉格式描述符的大小 // 版本号 // 格式必须支持窗口 // 格式必须支持OpenGL // 必须支持双缓冲 // 申请 RGBA 格式 // 选定色彩深度 // 忽略的色彩位 // 无Alpha缓存 // 忽略Shift Bit // 无聚集缓存 // 忽略聚集位 // 16位 Z-缓存 (深度缓存) // 无模板缓存 // 无辅助缓存 // 主绘图层 // 保留 // 忽略层遮罩 |
如果前面创建窗口时没有错误发生,我们接着尝试取得OpenGL设备描述表。若无法取得DC,弹出错误消息程序退出(返回FALSE)。 | |
if (!(hDC=GetDC(hWnd))) //取得设备描述表了么? { KillGLWindow(); // 重置显示区 MessageBox(NULL,"Can't Create A GL Device Context.","ERROR",MB_OK|MB_ICONEXCLAMATION); return FALSE; // 返回 FALSE } |
|
设法为OpenGL窗口取得设备描述表后,我们尝试找到对应与此前我们选定的象素格式的象素格式。如果Windows不能找到的话,弹出错误消息,并退出程序(返回FALSE)。 | |
if (!(PixelFormat=ChoosePixelFormat(hDC,&pfd))) // Windows 找到相应的象素格式了吗? { KillGLWindow(); // 重置显示区 MessageBox(NULL,"Can't Find A Suitable PixelFormat.","ERROR",MB_OK|MB_ICONEXCLAMATION); return FALSE; // 返回 FALSE } |
|
Windows 找到相应的象素格式后,尝试设置象素格式。如果无法设置,弹出错误消息,并退出程序(返回FALSE)。 | |
if(!SetPixelFormat(hDC,PixelFormat,&pfd)) // 能够设置象素格式么? { KillGLWindow(); // 重置显示区 MessageBox(NULL,"Can't Set The PixelFormat.","ERROR",MB_OK|MB_ICONEXCLAMATION); return FALSE; // 返回 FALSE } |
|
正常设置象素格式后,尝试取得着色描述表。如果不能取得着色描述表的话,弹出错误消息,并退出程序(返回FALSE)。 | |
if (!(hRC=wglCreateContext(hDC))) // 能否取得着色描述表? { KillGLWindow(); // 重置显示区 MessageBox(NULL,"Can't Create A GL Rendering Context.","ERROR",MB_OK|MB_ICONEXCLAMATION); return FALSE; // 返回 FALSE } |
|
如果到现在仍未出现错误的话,我们已经设法取得了设备描述表和着色描述表。接着要做的是激活着色描述表。如果无法激活,弹出错误消息,并退出程序(返回FALSE)。 | |
if(!wglMakeCurrent(hDC,hRC)) // 尝试激活着色描述表 { KillGLWindow(); // 重置显示区 MessageBox(NULL,"Can't Activate The GL Rendering Context.","ERROR",MB_OK|MB_ICONEXCLAMATION); return FALSE; // 返回 FALSE } |
|
一切顺利的话,OpenGL窗口已经创建完成,接着可以显示它啦。将它设为前端窗口(给它更高的优先级),并将焦点移至此窗口。然后调用ReSizeGLScene 将屏幕的宽度和高度设置给透视OpenGL屏幕。 | |
ShowWindow(hWnd,SW_SHOW); SetForegroundWindow(hWnd); SetFocus(hWnd); ReSizeGLScene(width, height); |
// 显示窗口 // 略略提高优先级 // 设置键盘的焦点至此窗口 // 设置透视 GL 屏幕 |
跳转至 InitGL(),这里可以设置光照、纹理、等等任何需要设置的东东。您可以在 InitGL()内部自行定义错误检查,并返回 TRUE (一切正常)或FALSE (有什么不对)。例如,如果您在InitGL()内装载纹理并出现错误,您可能希望程序停止。如果您返回 FALSE的话,下面的代码会弹出错误消息,并退出程序。 | |
if (!InitGL()) // 初始化新建的GL窗口 { KillGLWindow(); // 重置显示区 MessageBox(NULL,"Initialization Failed.","ERROR",MB_OK|MB_ICONEXCLAMATION); return FALSE; // 返回 FALSE } |
|
到这里可以安全的推定创建窗口已经成功了。我们向WinMain()返回TRUE,告知WinMain()没有错误,以防止程序退出。 | |
return TRUE; // 成功 } |
|
下面的代码处理所有的窗口消息。当我们注册好窗口类之后,程序跳转到这部分代码处理窗口消息。 | |
LRESULT CALLBACK WndProc( HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { |
// 窗口的句柄 // 窗口的消息 // 附加的消息内容 // 附加的消息内容 |
下来的代码比对uMsg的值,然后转入case处理,uMsg 中保存了我们要处理的消息名字。 | |
switch (uMsg) { |
// 检查Windows消息 |
如果uMsg等于WM_ACTIVE,查看窗口是否仍然处于激活状态。如果窗口已被最小化,将变量active设为FALSE。如果窗口已被激活,变量active的值为TRUE。 | |
case WM_ACTIVATE: { if (!HIWORD(wParam)) { active=TRUE; } else { active=FALSE; } return 0; } |
// 监视窗口激活消息 // 检查最小化状态 // 程序处于激活状态 // 程序不再激活 // 返回消息循环 |
如果消息是WM_SYSCOMMAND(系统命令),再次比对wParam。如果wParam 是 SC_SCREENSAVE 或 SC_MONITORPOWER的话,不是有屏幕保护要运行,就是显示器想进入节电模式。返回0可以阻止这两件事发生。 | |
case WM_SYSCOMMAND: { switch (wParam) { case SC_SCREENSAVE: case SC_MONITORPOWER: return 0; } break; } |
// 中断系统命令Intercept System Commands // 检查系统调用Check System Calls // 屏保要运行? // 显示器要进入节电模式? // 阻止发生 // 退出 |
如果 uMsg是WM_CLOSE,窗口将被关闭。我们发出退出消息,主循环将被中断。变量done被设为TRUE,WinMain()的主循环中止,程序关闭。 | |
case WM_CLOSE: { PostQuitMessage(0); return 0; } |
//收到Close消息? // 发出退出消息 |
如果键盘有键按下,通过读取wParam的信息可以找出键值。我将键盘数组keys[ ]相应的数组组成员的值设为TRUE。这样以后就可以查找key[ ]来得知什么键被按下。允许同时按下多个键。 | |
case WM_KEYDOWN: { keys[wParam] = TRUE; return 0; } |
// 有键按下么? // 如果是,设为TRUE // 返回 |
同样,如果键盘有键释放,通过读取wParam的信息可以找出键值。然后将键盘数组keys[ ]相应的数组组成员的值设为FALSE。这样查找key[ ]来得知什么键被按下,什么键被释放了。键盘上的每个键都可以用0-255之间的一个数来代表。举例来说,当我们按下40所代表的键时,keys[40]的值将被设为TRUE。放开的话,它就被设为FALSE。这也是key数组的原理。 | |
case WM_KEYUP: { keys[wParam] = FALSE; return 0; } |
// 有键放开么? // 如果是,设为FALSE // 返回 |
当调整窗口时,uMsg 最后等于消息WM_SIZE。读取lParam的LOWORD 和HIWORD可以得到窗口新的宽度和高度。将他们传递给ReSizeGLScene(),OpenGL场景将调整为新的宽度和高度。 | |
case WM_SIZE: { ReSizeGLScene(LOWORD(lParam),HIWORD(lParam)); return 0; } } |
// 调整OpenGL窗口大小 // LoWord=Width,HiWord=Height // 返回 |
其余无关的消息被传递给DefWindowProc,让Windows自行处理。 | |
//向 DefWindowProc传递所有未处理的消息。 return DefWindowProc(hWnd,uMsg,wParam,lParam); } |
|
下面是我们的Windows程序的入口。将会调用窗口创建例程,处理窗口消息,并监视人机交互。 | |
int WINAPI WinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) { |
//实例 // 前一个实例 // 命令行参数 // 窗口显示状态 |
我们设置两个变量。msg 用来检查是否有消息等待处理。done的初始值设为FALSE。这意味着我们的程序仍未完成运行。只要程序done保持FALSE,程序继续运行。一旦done的值改变为TRUE,程序退出。 | |
MSG msg; BOOL done=FALSE; |
// Windowsx消息结构 // 用来退出循环的Bool 变量 |
这段代码完全可选。程序弹出一个消息窗口,询问用户是否希望在全屏模式下运行。如果用户单击NO按钮,fullscreen变量从缺省的TRUE改变为FALSE,程序也改在窗口模式下运行。 | |
// 提示用户选择运行模式 if (MessageBox(NULL,"Would You Like To Run In Fullscreen Mode?", "Start FullScreen?",MB_YESNO|MB_ICONQUESTION)==IDNO) { fullscreen=FALSE; file://窗口模式 } |
|
接着创建OpenGL窗口。CreateGLWindow函数的参数依次为标题、宽度、高度、色彩深度,以及全屏标志。就这么简单!我很欣赏这段代码的简洁。如果未能创建成功,函数返回FALSE。程序立即退出。 | |
// 创建OpenGL窗口 if (!CreateGLWindow("NeHe's OpenGL Framework",640,480,16,fullscreen)) { return 0; // 失败退出 } |
|
下面是循环的开始。只要done保持FALSE,循环一直进行。 | |
while(!done) // 保持循环直到 done=TRUE { |
|
我们要做的第一件事是检查是否有消息在等待。使用PeekMessage()可以在不锁住我们的程序的前提下对消息进行检查。许多程序使用GetMessage(),也可以很好的工作。但使用GetMessage(),程序在收到paint消息或其他别的什么窗口消息之前不会做任何事。 | |
if (PeekMessage(&msg,NULL,0,0,PM_REMOVE)) { |
//有消息在等待吗? |
下面的代码查看是否出现退出消息。如果当前的消息是由PostQuitMessage(0)引起的WM_QUIT,done变量被设为TRUE,程序将退出。 | |
if (msg.message==WM_QUIT) { done=TRUE; } else { |
//收到退出消息? // 是,则done=TRUE // 不是,处理窗口消息 |
如果不是退出消息,我们翻译消息,然后发送消息,使得WndProc() 或 Windows能够处理他们。 | |
TranslateMessage(&msg); DispatchMessage(&msg); } } else { |
// 翻译消息 // 发送消息 // 如果没有消息 |
如果没有消息,绘制我们的OpenGL场景。代码的第一行查看窗口是否激活。如果按下ESC键,done变量被设为TRUE,程序将会退出。 | |
// 绘制场景。监视ESC键和来自DrawGLScene()的退出消息 if (active) { if (keys[VK_ESCAPE]) { done=TRUE; } else { |
// 程序激活的么? // ESC 按下了么? // ESC 发出退出信号 // 不是退出的时候,刷新屏幕 |
如果程序是激活的且ESC没有按下,我们绘制场景并交换缓存(使用双缓存可以实现无闪烁的动画)。我们实际上在另一个看不见的"屏幕"上绘图。当我们交换缓存后,我们当前的屏幕被隐藏,现在看到的是刚才看不到的屏幕。这也是我们看不到场景绘制过程的原因。场景只是即时显示。 | |
DrawGLScene(); SwapBuffers(hDC); } } |
// 绘制场景 // 交换缓存 (双缓存) |
下面的一点代码是最近新加的(05-01-00)。允许用户按下F1键在全屏模式和窗口模式间切换。 | |
if (keys[VK_F1]) { keys[VK_F1]=FALSE; KillGLWindow(); fullscreen=!fullscreen; // 重建 OpenGL 窗口 if (!CreateGLWindow("NeHe's OpenGL Framework",640,480,16,fullscreen)) { return 0; } } } } |
// F1键按下了么? // 若是,使对应的Key数组中的值为 FALSE // 销毁当前的窗口 // 切换 全屏 / 窗口 模式 // 如果窗口未能创建,程序退出 |
If the done variable is no longer FALSE, the program quits. We kill the OpenGL window properly so that everything is freed up, and we exit the program. 如果done变量不再是FALSE,程序退出。正常销毁OpenGL窗口,将所有的内存释放,退出程序。 |
|
// 关闭程序 KillGLWindow(); return (msg.wParam); } |
// 销毁窗口 // 退出程序 |
昨天下午遇到一个问题:使用DB2自带的驱动db2java.zip文件中的type2类型的驱动访问DB2,总是报错:
java.sql.SQLException: java.lang.UnsatisfiedLinkError: no db2jdbc in java.library.path
或者:
java.lang.ClassNotFoundException: COM.ibm.db2.jdbc.app.DB2Driver
要么就报:没有合适的驱动。
我开始查classpath,把多余的驱动都删除了,还是报错,后来我又把db2java.zip文件改名为db2java.jar,也还是不行。
折腾了一个小时,我意识到访问DB2和访问Oracle不太一样,于是google,结果找到这篇文章:
http://www-128.ibm.com/developerworks/cn/db2/library/techarticles/0402chenjunwei/0402chenjunwei.html
原来DB2的驱动还分几个版本,不同的DB2使用的版本的效果也不太一样。我开始怀疑使是我的驱动的问题,于是拿MyEclipse来试,结果MyEclipse也连接不上,但用DB2的客户端可以连接到远程服务器,后来在同事的帮助下,MyEclipse连上了,解决办法就是把我改成jar的后缀再改回来:o(。
但使用程序还是访问不了DB2,经过MyEclipse的测试,可以肯定那个驱动是没有问题的,所以,还是使用方法或者配置不对,我又搜了很多文章,结果发现这个问题很多人问,国外的帖子也很多,但没有详细回答的,又郁闷:o(,后来从几篇文章中零碎的找到一些提示,是db2jdbc.dll文件找不到,于是我试着把这个文件从DB2的bin目录下复制到System32目录下
,还是不行,我又把它复制到Java_Home/bin下面,重启机器,OK!一定要记住:是bin下面!!!
db2java.zip文件要改名为db2java.jar,并且放到Common/lib下。
解决方法很简单,问题是很多人知道了,这样可以节省大家的时间,但没人写下来。
另外还有一篇参考文章:
http://www-912.ibm.com/s_dir/slkbase.NSF/0/3f2a44217ec5c05786256c3e007194b3?OpenDocument
但对这个问题并没有提出什么有意义的意见。
jdbc.sql.Driver d = 以上的语句您已经很熟了吧 但 java.sql包中的 java.sql.Driver, jdbc.sql.Connection等提供给程序开发人员统一的开发接口,数据库提供商提供相应的实现,对程序开发人员来讲只要知道这些接口都有哪些方法就可以了,但我们可以深入一些 看看到底这里面都做了那些事, 同时也可以学习其中的编程模式(如Interface模式等) ... 目的是把指定的Class装载到JVM中来。(注意class的装载、初始化过程) 2 sun的JdbcOdbcDriver 源码: public interface JdbcOdbcDriverInterface 3 连接过程 public class DriverManager { // Gets the classloader of the code that called this method, may if (user != null) { return (getConnection(url, info, callerCL)); |
Class.forName("com.ibm.db2.jdbc.app.DB2Driver").newInstance();
String url="jdbc:db2://localhost:5000/sample"; //sample为你的数据库名
String user="admin";
String password="";
Connection conn= DriverManager.getConnection(url,user,password);