记录我的第一个OpenGL程序
初涉OpenGL,整理半天配置,再跟着教程一步一步来理解学习。
第一个程序,感觉良好。记录下自己的第一个程序,虽然大部分都是理解+粘贴的,哈哈。
以下是Win32下的C++代码。
头文件:
View Code
1 #include <Windows.h> 2 #include <gl/glew.h> 3 #include <gl/glut.h> 4 5 HGLRC hRC = NULL; 6 HDC hDC = NULL; 7 HWND hWnd = NULL; 8 HINSTANCE hInstance = NULL; 9 10 bool keys[256]; // 保存键盘按键的数组 11 bool active=TRUE; // 窗口的活动标志,缺省为TRUE 12 bool fullscreen=TRUE; // 全屏标志缺省,缺省设定成全屏模式 13 14 GLfloat rtri = 0; // 用于三角形的角度 15 GLfloat rquad = 0; // 用于四边形的角度 16 17 GLvoid ReSizeGLScene(GLsizei width, GLsizei height); // 重置OpenGL窗口大小 18 19 int InitGL(GLvoid);// 此处开始对OpenGL进行所有设置 20 21 int DrawGLScene(GLvoid);// 从这里开始进行所有的绘制 22 23 GLvoid KillGLWindow(GLvoid);// 正常销毁窗口 24 25 LRESULT CALLBACK WndProc( HWND hWnd, // 窗口的句柄 26 UINT uMsg, // 窗口的消息 27 WPARAM wParam, // 附加的消息内容 28 LPARAM lParam);
源文件
1 #include "OpenGL.h" 2 3 GLvoid ReSizeGLScene(GLsizei width, GLsizei height) 4 { 5 if (height==0) // 防止被零除 6 { 7 height=1; // 将Height设为1 8 } 9 10 glViewport(0, 0, width, height); // 重置当前的视口 11 12 glMatrixMode(GL_PROJECTION); // 选择投影矩阵 13 glLoadIdentity(); // 重置投影矩阵 14 15 // 设置视口的大小 16 gluPerspective(45.0f,(GLfloat)width/(GLfloat)height,0.1f,100.0f); 17 18 glMatrixMode(GL_MODELVIEW); // 选择模型观察矩阵 19 glLoadIdentity(); 20 } 21 22 int InitGL(GLvoid) 23 {// 此处开始对OpenGL进行所有设置 24 glShadeModel(GL_SMOOTH); // 启用阴影平滑 25 glClearColor(0.0f, 0.0f, 0.0f, 0.0f); // 黑色背景 26 27 glClearDepth(1.0f); // 设置深度缓存 28 glEnable(GL_DEPTH_TEST); // 启用深度测试 29 glDepthFunc(GL_LEQUAL); // 所作深度测试的类型 30 31 glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST); // 告诉系统对透视进行修正 32 33 return TRUE; // 初始化 OK 34 } 35 double pos = -1.5; 36 int t = 1; 37 int DrawGLScene(GLvoid) // 此过程中包括所有的绘制代码 38 { 39 glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); // 清除屏幕及深度缓存 40 glLoadIdentity(); // 重置当前的模型观察矩阵 41 42 // 当您调用glLoadIdentity()之后,您实际上将当前点移到了屏幕中心,X坐标轴从左至右,Y坐标轴从下至上,Z坐标轴从里至外。OpenGL屏幕中心的坐标值是X和Y轴上的0.0f点。中心左面的坐标值是负值,右面是正值。移向屏幕顶端是正值,移向屏幕底端是负值。移入屏幕深处是负值,移出屏幕则是正值。 43 // glTranslatef(x, y, z)沿着 X, Y 和 Z 轴移动。根据前面的次序,下面的代码沿着X轴左移1.5个单位,Y轴不动(0.0f),最后移入屏幕6.0f个单位。注意在glTranslatef(x, y, z)中当您移动的时候,您并不是相对屏幕中心移动,而是相对与当前所在的屏幕位置。 44 45 46 47 glTranslatef(-1.5f,0.0f,-6.0f); // 左移 1.5 单位,并移入屏幕 6.0 48 49 // 现在我们已经移到了屏幕的左半部分,并且将视图推入屏幕背后足够的距离以便我们可以看见全部的场景-创建三角形。glBegin(GL_TRIANGLES)的意思是开始绘制三角形,glEnd() 告诉OpenGL三角形已经创建好了。通常您会需要画3个顶点,可以使用GL_TRIANGLES。在绝大多数的显卡上,绘制三角形是相当快速的。如果要画四个顶点,使用GL_QUADS的话会更方便。但据我所知,绝大多数的显卡都使用三角形来为对象着色。最后,如果您想要画更多的顶点时,可以使用GL_POLYGON。 50 // 本节的简单示例中,我们只画一个三角形。如果要画第二个三角形的话,可以在这三点之后,再加三行代码(3点)。所有六点代码都应包含在glBegin(GL_TRIANGLES) 和 glEnd()之间。在他们之间再不会有多余的点出现,也就是说,(GL_TRIANGLES) 和 glEnd()之间的点都是以三点为一个集合的。这同样适用于四边形。如果您知道实在绘制四边形的话,您必须在第一个四点之后,再加上四点为一个集合的点组。另一方面,多边形可以由任意个顶点,(GL_POLYGON)不在乎glBegin(GL_TRIANGLES) 和 glEnd()之间有多少行代码。 51 // 52 // glBegin之后的第一行设置了多边形的第一个顶点,glVertex 的第一个参数是X坐标,然后依次是Y坐标和Z坐标。第一个点是上顶点,然后是左下顶点和右下顶点。glEnd()告诉OpenGL没有其他点了。这样将显示一个填充的三角形。 53 // 下一行代码是新的。glRotatef(Angle,Xvector,Yvector,Zvector)负责让对象绕某个轴旋转。这个命令有很多用处。 Angle 通常是个变量代表对象转过的角度。 Xvector , Yvector 和 Zvector 三个参数则共同决定旋转轴的方向。比如(1,0,0)所描述的矢量经过X坐标轴的1个单位处并且方向向右。(-1,0,0)所描述的矢量经过X坐标轴的1个单位处,但方向向左。 54 // D. Michael Traub:提供了对 Xvector , Yvector 和 Zvector 的上述解释。 55 // 为了更好的理解X, Y 和 Z的旋转,我举些例子... 56 // X轴-您正在使用一台台锯。锯片中心的轴从左至右摆放(就像OpenGL中的X轴)。尖利的锯齿绕着X轴狂转,看起来要么向上转,要么向下转。取决于锯片开始转时的方向。这与我们在OpenGL中绕着X轴旋转什么的情形是一样的。(译者注:这会儿您要把脸蛋凑向显示器的话,保准被锯开了花 ^-^。) 57 // 58 // Y轴-假设您正处于一个巨大的龙卷风中心,龙卷风的中心从地面指向天空(就像OpenGL中的Y轴)。垃圾和碎片围着Y轴从左向右或是从右向左狂转不止。这与我们在OpenGL中绕着Y轴旋转什么的情形是一样的。 59 // 60 // Z轴-您从正前方看着一台风扇。风扇的中心正好朝着您(就像OpenGL中的Z轴)。风扇的叶片绕着Z轴顺时针或逆时针狂转。这与我们在OpenGL中绕着Z轴旋转什么的情形是一样的。 61 // 62 // 下面的一行代码中,如果rtri等于7,我们将三角形绕着Y轴从左向右旋转7 。您也可以改变参数的值,让三角形绕着X和Y轴同时旋转。 63 glRotatef(rtri,0.0f,1.0f,0.0f); // 绕Y轴旋转三角形 64 65 glBegin(GL_TRIANGLES); // 绘制三角形 66 // 平滑着色 (Smooth coloring) 67 // glColor3f(1.0,0,0); // 设置当前色为红色 68 glColor3f(1.0,0,0); // 3i表示参数为三个整型,由0到INT_MAX对应3f的0-1.0,必须注意INT_MAX不是255 69 glVertex3f( 0.0f, 1.0f, 0.0f); // 上顶点 70 71 glColor3f(0,1.0,0); // 设置当前色为绿色 72 glVertex3f(-1.0f,-1.0f, 0.0f); // 左下 73 74 glColor3f(0,0,1.0); // 设置当前色为蓝色 75 glVertex3f( 1.0f,-1.0f, 0.0f); // 右下 76 77 glEnd(); // 三角形绘制结束 78 79 // 在屏幕的左半部分画完三角形后,我们要移到右半部分来画正方形。为此要再次使用glTranslate。这次右移,所以X坐标值为正值。因为前面左移了1.5个单位,这次要先向右移回屏幕中心(1.5个单位),再向右移动1.5个单位。总共要向右移3.0个单位。 80 81 // 您会注意下面的代码中我们增加了另一个glLoadIdentity()调用。目的是为了重置模型观察矩阵。如果我们没有重置,直接调用glTranslate的话,会出现意料之外的结果。因为坐标轴已经旋转了,很可能没有朝着您所希望的方向。所以我们本来想要左右移动对象的,就可能变成上下移动了,取决于您将坐标轴旋转了多少角度。试试将glLoadIdentity() 注释掉之后,会出现什么结果。 82 // 重置模型观察矩阵之后,X,Y,Z轴都以复位,我们调用glTranslate。您会注意到这次我们只向右一了1.5单位,而不是上节课的3.0单位。因为我们重置场景的时候,焦点又回到了场景的中心(0.0处)。这样就只需向右移1.5单位就够了。 83 // 当我们移到新位置后,绕X轴旋转四边形。正方形将上下转动。 84 85 glLoadIdentity(); // 重置模型观察矩阵 86 glTranslatef(1.5f,0.0f,-6.0f); // 右移1.5单位,并移入屏幕 6.0 87 glRotatef(rquad,1.0f,0.0f,0.0f); // 绕X轴旋转四边形 88 89 // 现在使用GL_QUADS绘制正方形。与绘制三角形的代码相类似,画四边形也很简单。唯一的区别是用GL_QUADS来替换了GL_TRIANGLES。并增加了一个点。我们使用顺时针次序来画正方形-左上-右上-右下-左下。采用顺时针绘制的是对象的后表面。这就是说我们所看见的是正方形的背面。逆时针画出来的正方形才是正面朝着我们的。现在这对您来说并不重要,但以后您必须知道。 90 91 glColor3f(0.5,0.5,1.0); 92 // 单调着色 (Flat coloring) 93 glBegin(GL_QUADS); // 绘制正方形 94 glVertex3f(-1.0f, 1.0f, 0.0f); // 左上 95 glVertex3f( 1.0f, 1.0f, 0.0f); // 右上 96 // glColor3f(0.5,1.0,1.0); 97 glVertex3f( 1.0f,-1.0f, 0.0f); // 左下 98 glVertex3f(-1.0f,-1.0f, 0.0f); // 右下 99 glEnd(); // 正方形绘制结束 100 101 rtri+=0.2f; // 增加三角形的旋转变量 102 rquad-=0.15f; // 减少四边形的旋转变量 103 return TRUE; // 继续运行 104 } 105 106 107 GLvoid KillGLWindow(GLvoid) // 正常销毁窗口 108 { 109 if (fullscreen) // 我们处于全屏模式吗? 110 { 111 ChangeDisplaySettings(NULL,0); // 是的话,切换回桌面 112 ShowCursor(TRUE); // 显示鼠标指针 113 } 114 if (hRC) // 我们拥有OpenGL渲染描述表吗? 115 { 116 if (!wglMakeCurrent(NULL,NULL)) // 我们能否释放DC和RC描述表? 117 { 118 119 // 如果不能释放DC和RC描述表的话,MessageBox()将弹出错误消息,告知我们DC和RC无法被释放。NULL意味着消息窗口没有父窗口。其右的文字将在消息窗口上出现。"SHUTDOWN ERROR"出现在窗口的标题栏上。MB_OK的意思消息窗口上带有一个写着OK字样的按钮。 120 // MB_ICONINFORMATION将在消息窗口中显示一个带圈的小写的i(看上去更正式一些)。 121 122 MessageBox(NULL,"释放DC或RC失败。","关闭错误",MB_OK | MB_ICONINFORMATION); 123 } 124 if (!wglDeleteContext(hRC)) // 我们能否删除RC? 125 { 126 // 如果无法删除着色描述表的话,将弹出错误消息告知我们RC未能成功删除。然后hRC被设为NULL。 127 128 MessageBox(NULL,"释放RC失败。","关闭错误",MB_OK | MB_ICONINFORMATION); 129 } 130 hRC=NULL; 131 } 132 133 if (hDC && !ReleaseDC(hWnd,hDC)) // 我们能否释放 DC? 134 { 135 MessageBox(NULL,"释放DC失败。","关闭错误",MB_OK | MB_ICONINFORMATION); 136 hDC=NULL; // 将 DC 设为 NULL 137 } 138 139 // 现在我们来查看是否存在窗口句柄,我们调用 DestroyWindow( hWnd )来尝试销毁窗口。如果不能的话弹出错误窗口,然后hWnd被设为NULL。 140 141 if (hWnd && !DestroyWindow(hWnd)) // 能否销毁窗口? 142 { 143 MessageBox(NULL,"释放窗口句柄失败。","关闭错误",MB_OK | MB_ICONINFORMATION); 144 hWnd=NULL; // 将 hWnd 设为 NULL 145 } 146 147 // 最后要做的事是注销我们的窗口类。这允许我们正常销毁窗口,接着在打开其他窗口时,不会收到诸如"Windows Class already registered"(窗口类已注册)的错误消息。 148 149 if (!UnregisterClass("OpenG",hInstance)) // 能否注销类? 150 { 151 MessageBox(NULL,"不能注销窗口类。","关闭错误",MB_OK | MB_ICONINFORMATION); 152 hInstance=NULL; // 将 hInstance 设为 NULL 153 } 154 } 155 156 BOOL CreateGLWindow(char* title, int width, int height, int bits, bool fullscreenflag) 157 { 158 159 // 当我们要求Windows为我们寻找相匹配的象素格式时,Windows寻找结束后将模式值保存在变量PixelFormat中。 160 161 GLuint PixelFormat; // 保存查找匹配的结果 162 163 // wc用来保存我们的窗口类的结构。窗口类结构中保存着我们的窗口信息。通过改变类的不同字段我们可以改变窗口的外观和行为。每个窗口都属于一个窗口类。当您创建窗口时,您必须为窗口注册类。 164 165 WNDCLASS wc; // 窗口类结构 166 167 // dwExStyle和dwStyle存放扩展和通常的窗口风格信息。我使用变量来存放风格的目的是为了能够根据我需要创建的窗口类型(是全屏幕下的弹出窗口还是窗口模式下的带边框的普通窗口);来改变窗口的风格。 168 169 DWORD dwExStyle; // 扩展窗口风格 170 DWORD dwStyle; // 窗口风格 171 172 // 下面的5行代码取得矩形的左上角和右下角的坐标值。我们将使用这些值来调整我们的窗口使得其上的绘图区的大小恰好是我们所需的分辨率的值。通常如果我们创建一个640x480的窗口,窗口的边框会占掉一些分辨率的值。 173 174 RECT WindowRect; // 取得矩形的左上角和右下角的坐标值 175 WindowRect.left=(long)0; // 将Left 设为 0 176 WindowRect.right=(long)width; // 将Right 设为要求的宽度 177 WindowRect.top=(long)0; // 将Top 设为 0 178 WindowRect.bottom=(long)height; // 将Bottom 设为要求的高度 179 180 // 下一行代码我们让全局变量fullscreen等于fullscreenflag。如果我们希望在全屏幕下运行而将fullscreenflag设为TRUE,但没有让变量fullscreen等于fullscreenflag的话,fullscreen变量将保持为FALSE。当我们在全屏幕模式下销毁窗口的时候,变量fullscreen的值却不是正确的TRUE值,计算机将误以为已经处于桌面模式而无法切换回桌面。上帝啊,但愿这一切都有意义。就是一句话,fullscreen的值必须永远fullscreenflag的值,否则就会有问题。 181 182 fullscreen=fullscreenflag; // 设置全局全屏标志 183 184 // 下一部分的代码中,我们取得窗口的实例,然后定义窗口类。 185 // CS_HREDRAW 和 CS_VREDRAW 的意思是无论何时,只要窗口发生变化时就强制重画。CS_OWNDC为窗口创建一个私有的DC。这意味着DC不能在程序间共享。WndProc是我们程序的消息处理过程。由于没有使用额外的窗口数据,后两个字段设为零。然后设置实例。接着我们将hIcon设为NULL,因为我们不想给窗口来个图标。鼠标指针设为标准的箭头。背景色无所谓(我们在GL中设置)。我们也不想要窗口菜单,所以将其设为NULL。类的名字可以您想要的任何名字。出于简单,我将使用"OpenG"。 186 187 188 189 hInstance = GetModuleHandle(NULL); // 取得我们窗口的实例 190 wc.style = CS_HREDRAW | CS_VREDRAW | CS_OWNDC; // 移动时重画,并为窗口取得DC 191 wc.lpfnWndProc = (WNDPROC) WndProc; // WndProc处理消息 192 wc.cbClsExtra = 0; // 无额外窗口数据 193 wc.cbWndExtra = 0; // 无额外窗口数据 194 wc.hInstance = hInstance; // 设置实例 195 wc.hIcon = LoadIcon(NULL, IDI_WINLOGO); // 装入缺省图标 196 wc.hCursor = LoadCursor(NULL, IDC_ARROW); // 装入鼠标指针 197 wc.hbrBackground = NULL; // GL不需要背景 198 wc.lpszMenuName = NULL; // 不需要菜单 199 wc.lpszClassName = "OpenG"; // 设定类名字 200 201 // 现在注册类名字。如果有错误发生,弹出错误消息窗口。按下上面的OK按钮后,程序退出 202 203 if (!RegisterClass(&wc)) // 尝试注册窗口类 204 { 205 MessageBox(NULL,"注册窗口失败","错误",MB_OK|MB_ICONEXCLAMATION); 206 return FALSE; // 退出并返回FALSE 207 } 208 209 // 查看程序应该在全屏模式还是窗口模式下运行。如果应该是全屏模式的话,我们将尝试设置全屏模式。 210 211 if (fullscreen) // 要尝试全屏模式吗? 212 { 213 214 // 下一部分的代码看来很多人都会有问题要问关于.......切换到全屏模式。在切换到全屏模式时,有几件十分重要的事您必须牢记。必须确保您在全屏模式下所用的宽度和高度等同于窗口模式下的宽度和高度。最最重要的是要在创建窗口之前设置全屏模式。这里的代码中,您无需再担心宽度和高度,它们已被设置成与显示模式所对应的大小。 215 216 DEVMODE dmScreenSettings; // 设备模式 217 memset(&dmScreenSettings,0,sizeof(dmScreenSettings)); // 确保内存清空为零 218 dmScreenSettings.dmSize=sizeof(dmScreenSettings); // Devmode 结构的大小 219 dmScreenSettings.dmPelsWidth = width; // 所选屏幕宽度 220 dmScreenSettings.dmPelsHeight = height; // 所选屏幕高度 221 dmScreenSettings.dmBitsPerPel = bits; // 每象素所选的色彩深度 222 dmScreenSettings.dmFields=DM_BITSPERPEL|DM_PELSWIDTH|DM_PELSHEIGHT; 223 224 // 上面的代码中,我们分配了用于存储视频设置的空间。设定了屏幕的宽,高,色彩深度。下面的代码我们尝试设置全屏模式。我们在dmScreenSettings中保存了所有的宽,高,色彩深度讯息。下一行使用ChangeDisplaySettings来尝试切换成与dmScreenSettings所匹配模式。我使用参数CDS_FULLSCREEN来切换显示模式,因为这样做不仅移去了屏幕底部的状态条,而且它在来回切换时,没有移动或改变您在桌面上的窗口。 225 226 // 尝试设置显示模式并返回结果。注: CDS_FULLSCREEN 移去了状态条。 227 if (ChangeDisplaySettings(&dmScreenSettings,CDS_FULLSCREEN)!=DISP_CHANGE_SUCCESSFUL) 228 { 229 230 // 如果模式未能设置成功,我们将进入以下的代码。如果不能匹配全屏模式,弹出消息窗口,提供两个选项:在窗口模式下运行或退出。 231 232 // 若模式失败,提供两个选项:退出或在窗口内运行。 233 if (MessageBox(NULL,"全屏模式在当前显卡上设置失败!\n使用窗口模式?","SunboyL G",MB_YESNO|MB_ICONEXCLAMATION)==IDYES) 234 { 235 236 // 如果用户选择窗口模式,变量fullscreen 的值变为FALSE,程序继续运行。 237 238 fullscreen=FALSE; // 选择窗口模式(Fullscreen=FALSE) 239 } 240 else 241 { 242 243 // 如果用户选择退出,弹出消息窗口告知用户程序将结束。并返回FALSE告诉程序窗口未能成功创建。程序退出。 244 245 // 弹出一个对话框,告诉用户程序结束 246 MessageBox(NULL,"程序将被关闭","错误",MB_OK|MB_ICONSTOP); 247 return FALSE; // 退出并返回 FALSE 248 } 249 } 250 } 251 252 // 由于全屏模式可能失败,用户可能决定在窗口下运行,我们需要在设置屏幕/窗口之前,再次检查fullscreen的值是TRUE或FALSE。 253 254 if (fullscreen) // 仍处于全屏模式吗? 255 { 256 257 // 如果我们仍处于全屏模式,设置扩展窗体风格为WS_EX_APPWINDOW,这将强制我们的窗体可见时处于最前面。再将窗体的风格设为WS_POPUP。这个类型的窗体没有边框,使我们的全屏模式得以完美显示。 258 // 最后我们禁用鼠标指针。当您的程序不是交互式的时候,在全屏模式下禁用鼠标指针通常是个好主意。 259 260 dwExStyle=WS_EX_APPWINDOW; // 扩展窗体风格 261 dwStyle=WS_POPUP; // 窗体风格 262 ShowCursor(FALSE); // 隐藏鼠标指针 263 } 264 else 265 { 266 267 // 如果我们使用窗口而不是全屏模式,我们在扩展窗体风格中增加了 WS_EX_WINDOWEDGE,增强窗体的3D感观。窗体风格改用 WS_OVERLAPPEDWINDOW,创建一个带标题栏、可变大小的边框、菜单和最大化/最小化按钮的窗体。 268 269 dwExStyle=WS_EX_APPWINDOW | WS_EX_WINDOWEDGE; // 扩展窗体风格 270 dwStyle=WS_OVERLAPPEDWINDOW; // 窗体风格 271 } 272 273 // 下一行代码根据创建的窗体类型调整窗口。调整的目的是使得窗口大小正好等于我们要求的分辨率。通常边框会占用窗口的一部分。使用AdjustWindowRectEx 后,我们的OpenGL场景就不会被边框盖住。实际上窗口变得更大以便绘制边框。全屏模式下,此命令无效。 274 275 AdjustWindowRectEx(&WindowRect, dwStyle, FALSE, dwExStyle); // 调整窗口达到真正要求的大小 276 277 // 下一段代码开始创建窗口并检查窗口是否成功创建。我们将传递CreateWindowEx()所需的所有参数。如扩展风格、类名字(与您在注册窗口类时所用的名字相同)、窗口标题、窗体风格、窗体的左上角坐标(0,0 是个安全的选择)、窗体的宽和高。我们没有父窗口,也不想要菜单,这些参数被设为NULL。还传递了窗口的实例,最后一个参数被设为NULL。 278 // 注意我们在窗体风格中包括了 WS_CLIPSIBLINGS 和 WS_CLIPCHILDREN。要让OpenGL正常运行,这两个属性是必须的。他们阻止别的窗体在我们的窗体内/上绘图。 279 280 281 282 if (!(hWnd=CreateWindowEx( dwExStyle, // 扩展窗体风格 283 "OpenG", // 类名字 284 title, // 窗口标题 285 WS_CLIPSIBLINGS | // 必须的窗体风格属性 286 WS_CLIPCHILDREN | // 必须的窗体风格属性 287 dwStyle, // 选择的窗体属性 288 0, 0, // 窗口位置 289 WindowRect.right-WindowRect.left, // 计算调整好的窗口宽度 290 WindowRect.bottom-WindowRect.top, // 计算调整好的窗口高度 291 NULL, // 无父窗口 292 NULL, // 无菜单 293 hInstance, // 实例 294 NULL))) // 不向WM_CREATE传递任何东东 295 296 // 下来我们检查看窗口是否正常创建。如果成功, hWnd保存窗口的句柄。如果失败,弹出消息窗口,并退出程序。 297 298 { 299 KillGLWindow(); // 重置显示区 300 MessageBox(NULL,"不能创建一个窗口设备描述表","错误",MB_OK|MB_ICONEXCLAMATION); 301 return FALSE; // 返回 FALSE 302 } 303 304 // 下面的代码描述象素格式。我们选择了通过RGBA(红、绿、蓝、alpha通道)支持OpenGL和双缓存的格式。我们试图找到匹配我们选定的色彩深度(16位、24位、32位)的象素格式。最后设置16位Z-缓存。其余的参数要么未使用要么不重要(stencil buffer模板缓存和accumulation buffer聚集缓存除外)。 305 306 static PIXELFORMATDESCRIPTOR pfd= // /pfd 告诉窗口我们所希望的东东,即窗口使用的像素格式 307 { 308 sizeof(PIXELFORMATDESCRIPTOR), // 上述格式描述符的大小 309 1, // 版本号 310 PFD_DRAW_TO_WINDOW | // 格式支持窗口 311 PFD_SUPPORT_OPENGL | // 格式必须支持OpenGL 312 PFD_DOUBLEBUFFER, // 必须支持双缓冲 313 PFD_TYPE_RGBA, // 申请 RGBA 格式 314 bits, // 选定色彩深度 315 0, 0, 0, 0, 0, 0, // 忽略的色彩位 316 0, // 无Alpha缓存 317 0, // 忽略Shift Bit 318 0, // 无累加缓存 319 0, 0, 0, 0, // 忽略聚集位 320 16, // 16位 Z-缓存 (深度缓存) 321 0, // 无蒙板缓存 322 0, // 无辅助缓存 323 PFD_MAIN_PLANE, // 主绘图层 324 0, // Reserved 325 0, 0, 0 // 忽略层遮罩 326 }; 327 328 // 如果前面创建窗口时没有错误发生,我们接着尝试取得OpenGL设备描述表。若无法取得DC,弹出错误消息程序退出(返回FALSE)。 329 330 if (!(hDC=GetDC(hWnd))) // 取得设备描述表了么? 331 { 332 KillGLWindow(); // 重置显示区 333 MessageBox(NULL,"不能创建一种相匹配的像素格式","错误",MB_OK|MB_ICONEXCLAMATION); 334 return FALSE; // 返回 FALSE 335 } 336 337 // 设法为OpenGL窗口取得设备描述表后,我们尝试找到对应与此前我们选定的象素格式的象素格式。如果Windows不能找到的话,弹出错误消息,并退出程序(返回FALSE)。 338 339 if (!(PixelFormat=ChoosePixelFormat(hDC,&pfd))) // Windows 找到相应的象素格式了吗? 340 { 341 KillGLWindow(); // 重置显示区 342 MessageBox(NULL,"不能设置像素格式","错误",MB_OK|MB_ICONEXCLAMATION); 343 return FALSE; // 返回 FALSE 344 } 345 346 // Windows 找到相应的象素格式后,尝试设置象素格式。如果无法设置,弹出错误消息,并退出程序(返回FALSE)。 347 348 if(!SetPixelFormat(hDC,PixelFormat,&pfd)) // 能够设置象素格式么? 349 { 350 KillGLWindow(); // 重置显示区 351 MessageBox(NULL,"不能设置像素格式","错误",MB_OK|MB_ICONEXCLAMATION); 352 return FALSE; // 返回 FALSE 353 } 354 355 // 正常设置象素格式后,尝试取得着色描述表。如果不能取得着色描述表的话,弹出错误消息,并退出程序(返回FALSE)。 356 357 if (!(hRC=wglCreateContext(hDC))) // 能否取得着色描述表? 358 { 359 KillGLWindow(); // 重置显示区 360 MessageBox(NULL,"不能创建OpenGL渲染描述表","错误",MB_OK|MB_ICONEXCLAMATION); 361 return FALSE; // 返回 FALSE 362 } 363 364 // 如果到现在仍未出现错误的话,我们已经设法取得了设备描述表和着色描述表。接着要做的是激活着色描述表。如果无法激活,弹出错误消息,并退出程序(返回FALSE)。 365 366 if(!wglMakeCurrent(hDC,hRC)) // 尝试激活着色描述表 367 { 368 KillGLWindow(); // 重置显示区 369 MessageBox(NULL,"不能激活当前的OpenGL渲然描述表","错误",MB_OK|MB_ICONEXCLAMATION); 370 return FALSE; // 返回 FALSE 371 } 372 373 // 一切顺利的话,OpenGL窗口已经创建完成,接着可以显示它啦。将它设为前端窗口(给它更高的优先级),并将焦点移至此窗口。然后调用ReSizeGLScene 将屏幕的宽度和高度设置给透视OpenGL屏幕。 374 375 ShowWindow(hWnd,SW_SHOW); // 显示窗口 376 SetForegroundWindow(hWnd); // 略略提高优先级 377 SetFocus(hWnd); // 设置键盘的焦点至此窗口 378 ReSizeGLScene(width, height); // 设置透视 GL 屏幕 379 380 // 跳转至 InitGL(),这里可以设置光照、纹理、等等任何需要设置的东东。您可以在 InitGL()内部自行定义错误检查,并返回 TRUE (一切正常)或FALSE (有什么不对)。例如,如果您在InitGL()内装载纹理并出现错误,您可能希望程序停止。如果您返回 FALSE的话,下面的代码会弹出错误消息,并退出程序。 381 382 if (!InitGL()) // 初始化新建的GL窗口 383 { 384 KillGLWindow(); // 重置显示区 385 MessageBox(NULL,"Initialization Failed.","ERROR",MB_OK|MB_ICONEXCLAMATION); 386 return FALSE; // 返回 FALSE 387 } 388 389 // 到这里可以安全的推定创建窗口已经成功了。我们向WinMain()返回TRUE,告知WinMain()没有错误,以防止程序退出。 390 391 return TRUE; // 成功 392 } 393 394 LRESULT CALLBACK WndProc( HWND hWnd, // 窗口的句柄 395 UINT uMsg, // 窗口的消息 396 WPARAM wParam, // 附加的消息内容 397 LPARAM lParam) // 附加的消息内容 398 { 399 400 // 下面的代码比对uMsg的值,然后转入case处理,uMsg 中保存了我们要处理的消息名字。 401 402 switch (uMsg) // 检查Windows消息 403 { 404 405 // 如果uMsg等于WM_ACTIVE,查看窗口是否仍然处于激活状态。如果窗口已被最小化,将变量active设为FALSE。如果窗口已被激活,变量active的值为TRUE。 406 407 case WM_ACTIVATE: // 监视窗口激活消息 408 { 409 if (!HIWORD(wParam)) // 检查最小化状态 410 { 411 active=TRUE; // 程序处于激活状态 412 } 413 else 414 { 415 active=FALSE; // 程序不再激活 416 } 417 418 return 0; // 返回消息循环 419 } 420 421 // 如果消息是WM_SYSCOMMAND(系统命令),再次比对wParam。如果wParam 是 SC_SCREENSAVE 或 SC_MONITORPOWER的话,不是有屏幕保护要运行,就是显示器想进入节电模式。返回0可以阻止这两件事发生。 422 423 case WM_SYSCOMMAND: // 系统中断命令 424 { 425 switch (wParam) // 检查系统调用 426 { 427 case SC_SCREENSAVE: // 屏保要运行? 428 case SC_MONITORPOWER: // 显示器要进入节电模式? 429 return 0; // 阻止发生 430 } 431 break; // 退出 432 } 433 434 // 如果 uMsg是WM_CLOSE,窗口将被关闭。我们发出退出消息,主循环将被中断。变量done被设为TRUE,WinMain()的主循环中止,程序关闭。 435 436 case WM_CLOSE: // 收到Close消息? 437 { 438 PostQuitMessage(0); // 发出退出消息 439 return 0; // 返回 440 } 441 442 // 如果键盘有键按下,通过读取wParam的信息可以找出键值。我将键盘数组keys[ ]相应的数组组成员的值设为TRUE。这样以后就可以查找key[ ]来得知什么键被按下。允许同时按下多个键。 443 444 case WM_KEYDOWN: // 有键按下么? 445 { 446 keys[wParam] = TRUE; // 如果是,设为TRUE 447 return 0; // 返回 448 } 449 450 // 同样,如果键盘有键释放,通过读取wParam的信息可以找出键值。然后将键盘数组keys[ ]相应的数组组成员的值设为FALSE。这样查找key[ ]来得知什么键被按下,什么键被释放了。键盘上的每个键都可以用0-255之间的一个数来代表。举例来说,当我们按下40所代表的键时,keys[40]的值将被设为TRUE。放开的话,它就被设为FALSE。这也是key数组的原理。 451 452 case WM_KEYUP: // 有键放开么? 453 { 454 keys[wParam] = FALSE; // 如果是,设为FALSE 455 return 0; // 返回 456 } 457 458 // 当调整窗口时,uMsg 最后等于消息WM_SIZE。读取lParam的LOWORD 和HIWORD可以得到窗口新的宽度和高度。将他们传递给ReSizeGLScene(),OpenGL场景将调整为新的宽度和高度。 459 460 case WM_SIZE: // 调整OpenGL窗口大小 461 { 462 ReSizeGLScene(LOWORD(lParam),HIWORD(lParam)); // LoWord=Width,HiWord=Height 463 return 0; // 返回 464 } 465 } 466 467 // 其余无关的消息被传递给DefWindowProc,让Windows自行处理。 468 469 // 向 DefWindowProc传递所有未处理的消息。 470 return DefWindowProc(hWnd,uMsg,wParam,lParam); 471 } 472 473 int WINAPI WinMain( HINSTANCE hInstance, // 当前窗口实例 474 HINSTANCE hPrevInstance, // 前一个窗口实例 475 LPSTR lpCmdLine, // 命令行参数 476 int nCmdShow) // 窗口显示状态 477 { 478 479 // 我们设置两个变量。msg 用来检查是否有消息等待处理。done的初始值设为FALSE。这意味着我们的程序仍未完成运行。只要程序done保持FALSE,程序继续运行。一旦done的值改变为TRUE,程序退出。 480 481 MSG msg; // Windowsx消息结构 482 BOOL done=FALSE; // 用来退出循环的Bool 变量 483 484 // 这段代码完全可选。程序弹出一个消息窗口,询问用户是否希望在全屏模式下运行。如果用户单击NO按钮,fullscreen变量从缺省的TRUE改变为FALSE,程序也改在窗口模式下运行。 485 486 // 提示用户选择运行模式 487 if (MessageBox(NULL,"你想在全屏模式下运行么?", "设置全屏模式",MB_YESNO|MB_ICONQUESTION)==IDNO) 488 { 489 fullscreen=FALSE; // FALSE为窗口模式 490 } 491 492 // 接着创建OpenGL窗口。CreateGLWindow函数的参数依次为标题、宽度、高度、色彩深度,以及全屏标志。就这么简单!我很欣赏这段代码的简洁。如果未能创建成功,函数返回FALSE。程序立即退出。 493 494 // 创建OpenGL窗口 495 if (!CreateGLWindow("SunboyL's 第一个多边形程序",640,480,16,fullscreen)) 496 { 497 return 0; // 失败退出 498 } 499 500 // 下面是循环的开始。只要done保持FALSE,循环一直进行。 501 502 while(!done) // 保持循环直到 done=TRUE 503 { 504 505 // 我们要做的第一件事是检查是否有消息在等待。使用PeekMessage()可以在不锁住我们的程序的前提下对消息进行检查。许多程序使用GetMessage(),也可以很好的工作。但使用GetMessage(),程序在收到paint消息或其他别的什么窗口消息之前不会做任何事。 506 507 if (PeekMessage(&msg,NULL,0,0,PM_REMOVE)) // 有消息在等待吗? 508 { 509 510 // 下面的代码查看是否出现退出消息。如果当前的消息是由PostQuitMessage(0)引起的WM_QUIT,done变量被设为TRUE,程序将退出。 511 512 if (msg.message==WM_QUIT) // 收到退出消息? 513 { 514 done=TRUE; // 是,则done=TRUE 515 } 516 else // 不是,处理窗口消息 517 { 518 519 // 如果不是退出消息,我们翻译消息,然后发送消息,使得WndProc() 或 Windows能够处理他们。 520 521 TranslateMessage(&msg); // 翻译消息 522 DispatchMessage(&msg); // 发送消息 523 } 524 } 525 else // 如果没有消息 526 { 527 528 // 如果没有消息,绘制我们的OpenGL场景。代码的第一行查看窗口是否激活。如果按下ESC键,done变量被设为TRUE,程序将会退出。 529 530 // 绘制场景。监视ESC键和来自DrawGLScene()的退出消息 531 if (active) // 程序激活的么? 532 { 533 if (keys[VK_ESCAPE]) // ESC 按下了么? 534 { 535 done=TRUE; // ESC 发出退出信号 536 } 537 else // 不是退出的时候,刷新屏幕 538 { 539 540 // 如果程序是激活的且ESC没有按下,我们绘制场景并交换缓存(使用双缓存可以实现无闪烁的动画)。我们实际上在另一个看不见的"屏幕"上绘图。当我们交换缓存后,我们当前的屏幕被隐藏,现在看到的是刚才看不到的屏幕。这也是我们看不到场景绘制过程的原因。场景只是即时显示。 541 542 DrawGLScene(); // 绘制场景 543 SwapBuffers(hDC); // 交换缓存 (双缓存) 544 } 545 } 546 547 // 下面的一点代码是最近新加的(05-01-00)。允许用户按下F1键在全屏模式和窗口模式间切换。 548 549 550 if (keys[VK_F1]) // F1键按下了么? 551 { 552 keys[VK_F1]=FALSE; // 若是,使对应的Key数组中的值为 FALSE 553 KillGLWindow(); // 销毁当前的窗口 554 fullscreen=!fullscreen; // 切换 全屏 / 窗口 模式 555 // 重建 OpenGL 窗口(修改) 556 if (!CreateGLWindow("SunboyL's 第一个多边形程序",640,480,16,fullscreen)) 557 { 558 return 0; // 如果窗口未能创建,程序退出 559 } 560 } 561 } 562 } 563 564 // 如果done变量不再是FALSE,程序退出。正常销毁OpenGL窗口,将所有的内存释放,退出程序。 565 566 // 关闭程序 567 KillGLWindow(); // 销毁窗口 568 return (msg.wParam); // 退出程序 569 }
不错的学习地址,算是我的OpenGL启蒙网站了:http://www.imgaara.com/opengl/nehe/
还有一个,暂时我自己都没看的:http://www.owlei.com/DancingWind/