新书推介:《语义网技术体系》
作者:瞿裕忠,胡伟,程龚
   XML论坛     W3CHINA.ORG讨论区     >>计算机科学论坛<<     SOAChina论坛     Blog     开放翻译计划     新浪微博  
 
  • 首页
  • 登录
  • 注册
  • 软件下载
  • 资料下载
  • 核心成员
  • 帮助
  •   Add to Google

    >> 本版讨论高级C/C++编程、代码重构(Refactoring)、极限编程(XP)、泛型编程等话题
    [返回] 计算机科学论坛计算机技术与应用『 C/C++编程思想 』 → [推荐]NeHe OpenGL教程(中英文版附带VC++源码)Lesson 01-lesson 02 查看新帖用户列表

      发表一个新主题  发表一个新投票  回复主题  (订阅本版) 您是本帖的第 85275 个阅读者浏览上一篇主题  刷新本主题   树形显示贴子 浏览下一篇主题
     * 贴子主题: [推荐]NeHe OpenGL教程(中英文版附带VC++源码)Lesson 01-lesson 02 举报  打印  推荐  IE收藏夹 
       本主题类别:     
     一分之千 帅哥哟,离线,有人找我吗?射手座1984-11-30
      
      
      威望:1
      等级:研一(随老板参加了WWW大会还和Tim Berners-Lee合了影^_^)
      文章:632
      积分:4379
      门派:XML.ORG.CN
      注册:2006/12/31

    姓名:(无权查看)
    城市:(无权查看)
    院校:(无权查看)
    给一分之千发送一个短消息 把一分之千加入好友 查看一分之千的个人资料 搜索一分之千在『 C/C++编程思想 』的所有贴子 引用回复这个贴子 回复这个贴子 查看一分之千的博客楼主
    发贴心情 [推荐]NeHe OpenGL教程(中英文版附带VC++源码)Lesson 01-lesson 02


    第一课第二课源码下载:

    第一课  中文版

    第01课
    [/IMG] http://www.owlei.com/DancingWind/Pic/lesson01.jpg[/IMG] 创建一个OpenGL窗口:

    在这个教程里,我将教你在Windows环境中创建OpenGL程序.它将显示一个空的OpenGL窗口,可以在窗口和全屏模式下切换,按ESC退出.它是我们以后应用程序的框架.

    理解OpenGL如何工作非常重要,你可以在教程的末尾下载源程序,但我强烈建议你至少读一遍教程,然后再开始编程.

    欢迎来到我的 OpenGL教程。我是个对 OpenGL充满激情的普通男孩! 我第一次听说 OpenGL是 3Dfx 发布 Voodoo1 卡的 OpenGL硬件加速驱动的时候。我立刻意识到 OpenGL是那种必须学习的东西。不幸的是当时很难从书本或网络上找到关于 OpenGL的讯息。我花了 N 个 小时来调试自己书写的代码,甚至在 IRC和 EMail 上花更多的时间来恳求别人帮忙。但我发现那 些懂得 OpenGL 高手们保留了他们的精华,对共享知识也不感兴趣。实在让人灰心 !
    我创建这个网站的目的是为了帮助那些对 OpenGL有兴趣却又需要帮助的人。在我的每个教程中,我都会尽可能详细的来解释每一行代码的作用。我会努力让我的代码更简单(您无需学习 MFC代码)!就算您是个VC 、OPENGL的绝对新手也应该可以读通代码,并清楚的知道发生了什么。我的站点只是许多提供 OpenGL教程的站点中的一个。如果您是 OpenGL的高级程序员的话,我的站点可能太简单了,但如果您才开始的话,我想这个站点会教会您许多东西!
    教程的这一节在2000年一月彻底重写了一遍。将会教您如何设置一个 OpenGL窗口。它可以只是一个窗口或是全屏幕的、可以任意 大小、任意色彩深度。此处的代码很稳定且很强大,您可以在您所有的OpenGL项目中使用。我所有的教程都将基于此节的代码!所有的错误都有被报告。所以应该没有内存泄漏,代码也很容易阅读和修改。感谢Fredric Echols对代码所做的修改!

    现在就让我们直接从代码开始吧。第一件事是打开VC然后创建一个新工程。如果您不知道如何创建的话,您也许不该学习OpenGL,而应该先学学VC。某些版本的VC需要将 bool 改成 BOOL , true 改成 TRUE , false 改成 FALSE ,请自行修改。

    在您创建一个新的Win32程序(不是console控制台程序)后,您还需要链接OpenGL库文件。在VC中操作如下:Project-> Settings,然后单击LINK标签。在"Object/Library Modules"选项中的开始处(在 kernel32.lib 前)增加 OpenGL32.lib GLu32.lib 和 GLaux.lib 后单击OK按钮。现在可以开始写您的OpenGL程序了。

    代码的前4行包括了我们使用的每个库文件的头文件。如下所示:


    #include <windows.h>  // Windows的头文件
    #include <glew.h>  // 包含最新的gl.h,glu.h库
    #include <glut.h>  // 包含OpenGL实用库
    接下来您需要设置您计划在您的程序中使用的所有变量。本节中的例程将创建一个空的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;       // OpenGL渲染描述表句柄
    HWND            hWnd=NULL;       // 保存我们的窗口句柄
    HINSTANCE       hInstance;       // 保存程序的实例

    下面的第一行设置一个用来监控键盘动作的数组。有许多方法可以监控键盘的动作,但这里的方法很可靠,并且可以处理多个键同时按下的情况。
    active 变量用来告知程序窗口是否处于最小化的状态。如果窗口已经最小化的话,我们可以做从暂停代码执行到退出程序的任何事情。我喜欢暂停程序。这样可以使得程序不用在后台保持运行。
    fullscreen 变量的作用相当明显。如果我们的程序在全屏状态下运行, fullscreen 的值为TRUE,否则为FALSE。这个全局变量的设置十分重要,它让每个过程都知道程序是否运行在全屏状态下。
    bool keys[256];        // 保存键盘按键的数组
    bool active=TRUE;        // 窗口的活动标志,缺省为TRUE
    bool fullscreen=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)    // 重置OpenGL窗口大小
    {
     if (height==0)        // 防止被零除
     {
      height=1;       // 将Height设为1
     }

     glViewport(0, 0, width, height);     // 重置当前的视口

    下面几行为透视图设置屏幕。意味着越远的东西看起来越小。这么做创建了一个现实外观的场景。此处透视按照基于窗口宽度和高度的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)        // 我们拥有OpenGL渲染描述表吗?
     {

    如果存在着色描述表的话,下面的代码将查看我们能否释放它(将 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,"释放DC或RC失败。","关闭错误",MB_OK | MB_ICONINFORMATION);
      }

    下一步我们试着删除着色描述表。如果不成功的话弹出错误消息。 [IMG]

      if (!wglDeleteContext(hRC))     // 我们能否删除RC?
      {

    如果无法删除着色描述表的话,将弹出错误消息告知我们RC未能成功删除。然后hRC被设为NULL。

       MessageBox(NULL,"释放RC失败。","关闭错误",MB_OK | MB_ICONINFORMATION);
      }
      hRC=NULL;       // 将RC设为 NULL
     }

    现在我们查看是否存在设备描述表,如果有尝试释放它。如果不能释放设备描述表将弹出错误消息,然后hDC设为NULL。
     if (hDC && !ReleaseDC(hWnd,hDC))     // 我们能否释放 DC?
     {
      MessageBox(NULL,"释放DC失败。","关闭错误",MB_OK | MB_ICONINFORMATION);
      hDC=NULL;       // 将 DC 设为 NULL
     }

    现在我们来查看是否存在窗口句柄,我们调用 DestroyWindow( hWnd )来尝试销毁窗口。如果不能的话弹出错误窗口,然后hWnd被设为NULL。
     if (hWnd && !DestroyWindow(hWnd))     // 能否销毁窗口?
     {
      MessageBox(NULL,"释放窗口句柄失败。","关闭错误",MB_OK | MB_ICONINFORMATION);
      hWnd=NULL;       // 将 hWnd 设为 NULL
     }

    最后要做的事是注销我们的窗口类。这允许我们正常销毁窗口,接着在打开其他窗口时,不会收到诸如"Windows Class already registered"(窗口类已注册)的错误消息。 [IMG]

     if (!UnregisterClass("OpenG",hInstance))    // 能否注销类?
     {
      MessageBox(NULL,"不能注销窗口类。","关闭错误",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;      // 将Left   设为 0
     WindowRect.right=(long)width;      // 将Right  设为要求的宽度
     WindowRect.top=(long)0;       // 将Top    设为 0
     WindowRect.bottom=(long)height;      // 将Bottom 设为要求的高度

    下一行代码我们让全局变量fullscreen等于fullscreenflag。如果我们希望在全屏幕下运行而将fullscreenflag设为TRUE,但没有让变量fullscreen等于fullscreenflag的话,fullscreen变量将保持为FALSE。当我们在全屏幕模式下销毁窗口的时候,变量fullscreen的值却不是正确的TRUE值,计算机将误以为已经处于桌面模式而无法切换回桌面。上帝啊,但愿这一切都有意义。就是一句话,fullscreen的值必须永远fullscreenflag的值,否则就会有问题。  

     fullscreen=fullscreenflag;      // 设置全局全屏标志

    下一部分的代码中,我们取得窗口的实例,然后定义窗口类。
    CS_HREDRAW 和 CS_VREDRAW 的意思是无论何时,只要窗口发生变化时就强制重画。CS_OWNDC为窗口创建一个私有的DC。这意味着DC不能在程序间共享。WndProc是我们程序的消息处理过程。由于没有使用额外的窗口数据,后两个字段设为零。然后设置实例。接着我们将hIcon设为NULL,因为我们不想给窗口来个图标。鼠标指针设为标准的箭头。背景色无所谓(我们在GL中设置)。我们也不想要窗口菜单,所以将其设为NULL。类的名字可以您想要的任何名字。出于简单,我将使用"OpenG"。

     hInstance  = GetModuleHandle(NULL);   // 取得我们窗口的实例
     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";     // 设定类名字

    现在注册类名字。如果有错误发生,弹出错误消息窗口。按下上面的OK按钮后,程序退出

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

    查看程序应该在全屏模式还是窗口模式下运行。如果应该是全屏模式的话,我们将尝试设置全屏模式。
     if (fullscreen)        // 要尝试全屏模式吗?
     {

    下一部分的代码看来很多人都会有问题要问关于.......切换到全屏模式。在切换到全屏模式时,有几件十分重要的事您必须牢记。必须确保您在全屏模式下所用的宽度和高度等同于窗口模式下的宽度和高度。最最重要的是要在创建窗口之前设置全屏模式。这里的代码中,您无需再担心宽度和高度,它们已被设置成与显示模式所对应的大小。
      DEVMODE dmScreenSettings;      // 设备模式
      memset(&dmScreenSettings,0,sizeof(dmScreenSettings));   // 确保内存清空为零
      dmScreenSettings.dmSize=sizeof(dmScreenSettings);   // Devmode 结构的大小
      dmScreenSettings.dmPelsWidth = width;    // 所选屏幕宽度
      dmScreenSettings.dmPelsHeight = height;    // 所选屏幕高度
      dmScreenSettings.dmBitsPerPel = bits;     // 每象素所选的色彩深度
      dmScreenSettings.dmFields=DM_BITSPERPEL|DM_PELSWIDTH|DM_PELSHEIGHT;

    上面的代码中,我们分配了用于存储视频设置的空间。设定了屏幕的宽,高,色彩深度。下面的代码我们尝试设置全屏模式。我们在dmScreenSettings中保存了所有的宽,高,色彩深度讯息。下一行使用ChangeDisplaySettings来尝试切换成与dmScreenSettings所匹配模式。我使用参数CDS_FULLSCREEN来切换显示模式,因为这样做不仅移去了屏幕底部的状态条,而且它在来回切换时,没有移动或改变您在桌面上的窗口。
      // 尝试设置显示模式并返回结果。注: CDS_FULLSCREEN 移去了状态条。
      if (ChangeDisplaySettings(&dmScreenSettings,CDS_FULLSCREEN)!=DISP_CHANGE_SUCCESSFUL)
      {

    如果模式未能设置成功,我们将进入以下的代码。如果不能匹配全屏模式,弹出消息窗口,提供两个选项:在窗口模式下运行或退出。
       // 若模式失败,提供两个选项:退出或在窗口内运行。
       if (MessageBox(NULL,"全屏模式在当前显卡上设置失败!\n使用窗口模式?","NeHe G",MB_YESNO|MB_ICONEXCLAMATION)==IDYES)
       {

    如果用户选择窗口模式,变量fullscreen 的值变为FALSE,程序继续运行。
        fullscreen=FALSE;    // 选择窗口模式(Fullscreen=FALSE)
       }
       else
       {

    如果用户选择退出,弹出消息窗口告知用户程序将结束。并返回FALSE告诉程序窗口未能成功创建。程序退出。
        // 弹出一个对话框,告诉用户程序结束
        MessageBox(NULL,"程序将被关闭","错误",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,    // 扩展窗体风格
         "OpenG",    // 类名字
         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,"不能创建一个窗口设备描述表","错误",MB_OK|MB_ICONEXCLAMATION);
      return FALSE;       // 返回 FALSE
     }

    下面的代码描述象素格式。我们选择了通过RGBA(红、绿、蓝、alpha通道)支持OpenGL和双缓存的格式。我们试图找到匹配我们选定的色彩深度(16位、24位、32位)的象素格式。最后设置16位Z-缓存。其余的参数要么未使用要么不重要(stencil buffer模板缓存和accumulation buffer聚集缓存除外)。
     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        // 忽略层遮罩
     };

    如果前面创建窗口时没有错误发生,我们接着尝试取得OpenGL设备描述表。若无法取得DC,弹出错误消息程序退出(返回FALSE)。
     if (!(hDC=GetDC(hWnd)))       // 取得设备描述表了么?
     {
      KillGLWindow();       // 重置显示区
      MessageBox(NULL,"不能创建一种相匹配的像素格式","错误",MB_OK|MB_ICONEXCLAMATION);
      return FALSE;       // 返回 FALSE
     }

    设法为OpenGL窗口取得设备描述表后,我们尝试找到对应与此前我们选定的象素格式的象素格式。如果Windows不能找到的话,弹出错误消息,并退出程序(返回FALSE)。
     if (!(PixelFormat=ChoosePixelFormat(hDC,&pfd)))    // Windows 找到相应的象素格式了吗?
     {
      KillGLWindow();       // 重置显示区
      MessageBox(NULL,"不能设置像素格式","错误",MB_OK|MB_ICONEXCLAMATION);
      return FALSE;       // 返回 FALSE
     }

    Windows 找到相应的象素格式后,尝试设置象素格式。如果无法设置,弹出错误消息,并退出程序(返回FALSE)。
     if(!SetPixelFormat(hDC,PixelFormat,&pfd))    // 能够设置象素格式么?
     {
      KillGLWindow();       // 重置显示区
      MessageBox(NULL,"不能设置像素格式","错误",MB_OK|MB_ICONEXCLAMATION);
      return FALSE;       // 返回 FALSE
     }

    正常设置象素格式后,尝试取得着色描述表。如果不能取得着色描述表的话,弹出错误消息,并退出程序(返回FALSE)。  

     if (!(hRC=wglCreateContext(hDC)))     // 能否取得着色描述表?
     {
      KillGLWindow();       // 重置显示区
      MessageBox(NULL,"不能创建OpenGL渲染描述表","错误",MB_OK|MB_ICONEXCLAMATION);
      return FALSE;       // 返回 FALSE
     }

    如果到现在仍未出现错误的话,我们已经设法取得了设备描述表和着色描述表。接着要做的是激活着色描述表。如果无法激活,弹出错误消息,并退出程序(返回FALSE)。
     if(!wglMakeCurrent(hDC,hRC))      // 尝试激活着色描述表
     {
      KillGLWindow();       // 重置显示区
      MessageBox(NULL,"不能激活当前的OpenGL渲然描述表","错误",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;       // 退出
      }

    如果 uMsg是WM_CLOSE,窗口将被关闭。我们发出退出消息,主循环将被中断。变量done被设为TRUE,WinMain()的主循环中止,程序关闭。
      case WM_CLOSE:       // 收到Close消息?
      {
       PostQuitMessage(0);     // 发出退出消息
       return 0;      // 返回
      }

    如果键盘有键按下,通过读取wParam的信息可以找出键值。我将键盘数组keys[ ]相应的数组组成员的值设为TRUE。这样以后就可以查找key[ ]来得知什么键被按下。允许同时按下多个键。
      case WM_KEYDOWN:      // 有键按下么?
      {
       keys[wParam] = TRUE;     // 如果是,设为TRUE
       return 0;      // 返回
      }

    同样,如果键盘有键释放,通过读取wParam的信息可以找出键值。然后将键盘数组keys[ ]相应的数组组成员的值设为FALSE。这样查找key[ ]来得知什么键被按下,什么键被释放了。键盘上的每个键都可以用0-255之间的一个数来代表。举例来说,当我们按下40所代表的键时,keys[40]的值将被设为TRUE。放开的话,它就被设为FALSE。这也是key数组的原理。

      case WM_KEYUP:       // 有键放开么?
      {
       keys[wParam] = FALSE;     // 如果是,设为FALSE
       return 0;      // 返回
      }

    当调整窗口时,uMsg 最后等于消息WM_SIZE。读取lParam的LOWORD 和HIWORD可以得到窗口新的宽度和高度。将他们传递给ReSizeGLScene(),OpenGL场景将调整为新的宽度和高度。
      case WM_SIZE:       // 调整OpenGL窗口大小
      {
       ReSizeGLScene(LOWORD(lParam),HIWORD(lParam));  // LoWord=Width,HiWord=Height
       return 0;      // 返回
      }
     }

    其余无关的消息被传递给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;        // Windowsx消息结构
     BOOL done=FALSE;       // 用来退出循环的Bool 变量

    这段代码完全可选。程序弹出一个消息窗口,询问用户是否希望在全屏模式下运行。如果用户单击NO按钮,fullscreen变量从缺省的TRUE改变为FALSE,程序也改在窗口模式下运行。
     // 提示用户选择运行模式
     if (MessageBox(NULL,"你想在全屏模式下运行么?", "设置全屏模式",MB_YESNO|MB_ICONQUESTION)==IDNO)
     {
      fullscreen=FALSE;      // FALSE为窗口模式
     }

    接着创建OpenGL窗口。CreateGLWindow函数的参数依次为标题、宽度、高度、色彩深度,以及全屏标志。就这么简单!我很欣赏这段代码的简洁。如果未能创建成功,函数返回FALSE。程序立即退出。

     // 创建OpenGL窗口
     if (!CreateGLWindow("NeHe's OpenGL程序框架",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;     // 是,则done=TRUE
       }
       else       // 不是,处理窗口消息
       {

    如果不是退出消息,我们翻译消息,然后发送消息,使得WndProc() 或 Windows能够处理他们。
        TranslateMessage(&msg);    // 翻译消息
        DispatchMessage(&msg);    // 发送消息
       }
      }
      else        // 如果没有消息
      {

    如果没有消息,绘制我们的OpenGL场景。代码的第一行查看窗口是否激活。如果按下ESC键,done变量被设为TRUE,程序将会退出。
       // 绘制场景。监视ESC键和来自DrawGLScene()的退出消息
       if (active)      // 程序激活的么?
       {
        if (keys[VK_ESCAPE])    // ESC 按下了么?
        {
         done=TRUE;    // ESC 发出退出信号
        }
        else      // 不是退出的时候,刷新屏幕
        {

    如果程序是激活的且ESC没有按下,我们绘制场景并交换缓存(使用双缓存可以实现无闪烁的动画)。我们实际上在另一个看不见的"屏幕"上绘图。当我们交换缓存后,我们当前的屏幕被隐藏,现在看到的是刚才看不到的屏幕。这也是我们看不到场景绘制过程的原因。场景只是即时显示。  

         DrawGLScene();    // 绘制场景
         SwapBuffers(hDC);   // 交换缓存 (双缓存)
        }
       }

    下面的一点代码是最近新加的(05-01-00)。允许用户按下F1键在全屏模式和窗口模式间切换。

       if (keys[VK_F1])     // F1键按下了么?
       {
        keys[VK_F1]=FALSE;    // 若是,使对应的Key数组中的值为 FALSE
        KillGLWindow();     // 销毁当前的窗口
        fullscreen=!fullscreen;    // 切换 全屏 / 窗口 模式
        // 重建 OpenGL 窗口
        if (!CreateGLWindow("NeHe's OpenGL 程序框架",640,480,16,fullscreen))
        {
         return 0;    // 如果窗口未能创建,程序退出
        }
       }
      }
     }

    如果done变量不再是FALSE,程序退出。正常销毁OpenGL窗口,将所有的内存释放,退出程序。

     // 关闭程序
     KillGLWindow();        // 销毁窗口
     return (msg.wParam);       // 退出程序
    }

    在这一课中,我已试着尽量详细解释一切。每一步都与设置有关,并创建了一个全屏OpenGL程序。当您按下ESC键程序就会退出,并监视窗口是否激活。我花了整整2周时间来写代码,一周时间来改正BUG并讨论编程指南,2天( 整整22小时来写HTML文件)。如果您有什么意见或建议请给我EMAIL。如果您认为有什么不对或可以改进,请告诉我。我想做最好的OpenGL教程并对您的反馈感兴趣。

    按此在新窗口浏览图片 版权与使用声明:
    我是个对学习和生活充满激情的普通男孩,在网络上我以DancingWind为昵称,我的联系方式是zhouwei02@mails.tsinghua.edu.cn,如果你有任何问题,都可以联系我。
    引子
    网络是一个共享的资源,但我在自己的学习生涯中浪费大量的时间去搜索可用的资料,在现实生活中花费了大量的金钱和时间在书店中寻找资料,于是我给自己起了个昵称DancingWind,其意义是想风一样从各个知识的站点中吸取成长的养料。在飘荡了多年之后,我决定把自己收集的资料整理为一个统一的资源库。

    版权声明
    所有DancingWind发表的内容,大多都来自共享的资源,所以我没有资格把它们据为己有,或声称自己为这些资源作出了一点贡献。故任何人都可以复制,修改,重新发表,甚至以自己的名义发表,我都不会追究,但你在做以上事情的时候必须保证内容的完整性,给后来的人一个完整的教程。最后,任何人不能以这些资料的任何部分,谋取任何形式的报酬。

    发展计划
    在国外,很多资料都是很多人花费几年的时间慢慢积累起来的。如果任何人有兴趣与别人共享你的知识,我很欢迎你与我联系,但你必须同意我上面的声明。

    感谢
    感谢我的母亲一直以来对我的支持和在生活上的照顾。
    感谢我深爱的女友田芹,一直以来默默的在精神上和生活中对我的支持,她甚至把买衣服的钱都用来给我买书了,她真的是我见过的最好的女孩,希望我能带给她幸福。

    [此贴子已经被作者于2007-10-12 11:54:36编辑过]

       收藏   分享  
    顶(3)
      




    ----------------------------------------------
    越学越无知

    点击查看用户来源及管理<br>发贴IP:*.*.*.* 2007/10/12 10:39:00
     
     一分之千 帅哥哟,离线,有人找我吗?射手座1984-11-30
      
      
      威望:1
      等级:研一(随老板参加了WWW大会还和Tim Berners-Lee合了影^_^)
      文章:632
      积分:4379
      门派:XML.ORG.CN
      注册:2006/12/31

    姓名:(无权查看)
    城市:(无权查看)
    院校:(无权查看)
    给一分之千发送一个短消息 把一分之千加入好友 查看一分之千的个人资料 搜索一分之千在『 C/C++编程思想 』的所有贴子 引用回复这个贴子 回复这个贴子 查看一分之千的博客2
    发贴心情 
    第一课 英文

    Lesson 01
       
    Welcome to my OpenGL tutorials. I am an average guy with a passion for OpenGL! The first time I heard about OpenGL was back when 3Dfx released their Hardware accelerated OpenGL driver for the Voodoo 1 card. Immediately I knew OpenGL was something I had to learn. Unfortunately, it was very hard to find any information about OpenGL in books or on the net. I spent hours trying to make code work and even more time begging people for help in email and on IRC. I found that those people that understood OpenGL considered themselves elite, and had no interest in sharing their knowledge. VERY frustrating!

    I created this web site so that people interested in learning OpenGL would have a place to come if they needed help. In each of my tutorials I try to explain, in as much detail as humanly possible, what each line of code is doing. I try to keep my code simple (no MFC code to learn)! An absolute newbie to both Visual C++ and OpenGL should be able to go through the code, and have a pretty good idea of what's going on. My site is just one of many sites offering OpenGL tutorials. If you're a hardcore OpenGL programmer, my site may be too simplistic, but if you're just starting out, I feel my site has a lot to offer!

    This tutorial was completely rewritten January 2000. This tutorial will teach you how to set up an OpenGL window. The window can be windowed or fullscreen, any size you want, any resolution you want, and any color depth you want. The code is very flexible and can be used for all your OpenGL projects. All my tutorials will be based on this code! I wrote the code to be flexible, and powerful at the same time. All errors are reported. There should be no memory leaks, and the code is easy to read and easy to modify. Thanks to Fredric Echols for his modifications to the code!

    I'll start this tutorial by jumping right into the code. The first thing you will have to do is build a project in Visual C++. If you don't know how to do that, you should not be learning OpenGL, you should be learning Visual C++. The downloadable code is Visual C++ 6.0 code. Some versions of VC++ require that bool is changed to BOOL, true is changed to TRUE, and false is changed to FALSE. By making the changes mentioned, I have been able to compile the code on Visual C++ 4.0 and 5.0 with no other problems.

    After you have created a new Win32 Application (NOT a console application) in Visual C++, you will need to link the OpenGL libraries. In Visual C++ go to Project, Settings, and then click on the LINK tab. Under "Object/Library Modules" at the beginning of the line (before kernel32.lib) add OpenGL32.lib GLu32.lib and GLaux.lib. Once you've done this click on OK. You're now ready to write an OpenGL Windows program.

    The first 4 lines include the header files for each library we are using. The lines look like this:   
       

    #include <windows.h>        // Header File For Windows
    #include <gl\gl.h>        // Header File For The OpenGL32 Library
    #include <gl\glu.h>        // Header File For The GLu32 Library
    #include <gl\glaux.h>        // Header File For The GLaux Library

       
    Next you need to set up all the variables you plan to use in your program. This program will create a blank OpenGL window, so we won't need to set up a lot of variables just yet. The few variables that we do set up are very important, and will be used in just about every OpenGL program you write using this code.

    The first line sets up a Rendering Context. Every OpenGL program is linked to a Rendering Context. A Rendering Context is what links OpenGL calls to the Device Context. The OpenGL Rendering Context is defined as hRC. In order for your program to draw to a Window you need to create a Device Context, this is done in the second line. The Windows Device Context is defined as hDC. The DC connects the Window to the GDI (Graphics Device Interface). The RC connects OpenGL to the DC.

    In the third line the variable hWnd will hold the handle assigned to our window by Windows, and finally, the fourth line creates an Instance (occurrence) for our program.   
       

    HGLRC           hRC=NULL;       // Permanent Rendering Context
    HDC             hDC=NULL;       // Private GDI Device Context
    HWND            hWnd=NULL;       // Holds Our Window Handle
    HINSTANCE       hInstance;       // Holds The Instance Of The Application

       
    The first line below sets up an array that we will use to monitor key presses on the keyboard. There are many ways to watch for key presses on the keyboard, but this is the way I do it. It's reliable, and it can handle more than one key being pressed at a time.

    The active variable will be used to tell our program whether or not our Window has been minimized to the taskbar or not. If the Window has been minimized we can do anything from suspend the code to exit the program. I like to suspend the program. That way it won't keep running in the background when it's minimized.

    The variable fullscreen is fairly obvious. If our program is running in fullscreen mode, fullscreen will be TRUE, if our program is running in Windowed mode, fullscreen will be FALSE. It's important to make this global so that each procedure knows if the program is running in fullscreen mode or not.   
       

    bool keys[256];        // Array Used For The Keyboard Routine
    bool active=TRUE;        // Window Active Flag Set To TRUE By Default
    bool fullscreen=TRUE;       // Fullscreen Flag Set To Fullscreen Mode By Default

       
    Now we have to define WndProc(). The reason we have to do this is because CreateGLWindow() has a reference to WndProc() but WndProc() comes after CreateGLWindow(). In C if we want to access a procedure or section of code that comes after the section of code we are currently in we have to declare the section of code we wish to access at the top of our program. So in the following line we define WndProc() so that CreateGLWindow() can make reference to WndProc().   
       

    LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);    // Declaration For WndProc

       
    The job of the next section of code is to resize the OpenGL scene whenever the window (assuming you are using a Window rather than fullscreen mode) has been resized. Even if you are not able to resize the window (for example, you're in fullscreen mode), this routine will still be called at least once when the program is first run to set up our perspective view. The OpenGL scene will be resized based on the width and height of the window it's being displayed in.   
       

    GLvoid ReSizeGLScene(GLsizei width, GLsizei height)    // Resize And Initialize The GL Window
    {
     if (height==0)        // Prevent A Divide By Zero By
     {
      height=1;       // Making Height Equal One
     }

     glViewport(0, 0, width, height);     // Reset The Current Viewport

       
    The following lines set the screen up for a perspective view. Meaning things in the distance get smaller. This creates a realistic looking scene. The perspective is calculated with a 45 degree viewing angle based on the windows width and height. The 0.1f, 100.0f is the starting point and ending point for how deep we can draw into the screen.

    glMatrixMode(GL_PROJECTION) indicates that the next 2 lines of code will affect the projection matrix. The perspective matrix is responsible for adding perspective to our scene. glLoadIdentity() is similar to a reset. It restores the selected matrix to it's original state. After glLoadIdentity() has been called we set up our perspective view for the scene. glMatrixMode(GL_MODELVIEW) indicates that any new transformations will affect the modelview matrix. The modelview matrix is where our object information is stored. Lastly we reset the modelview matrix. Don't worry if you don't understand this stuff, I will be explaining it all in later tutorials. Just know that it HAS to be done if you want a nice perspective scene.   
       

     glMatrixMode(GL_PROJECTION);      // Select The Projection Matrix
     glLoadIdentity();       // Reset The Projection Matrix

     // Calculate The Aspect Ratio Of The Window
     gluPerspective(45.0f,(GLfloat)width/(GLfloat)height,0.1f,100.0f);

     glMatrixMode(GL_MODELVIEW);      // Select The Modelview Matrix
     glLoadIdentity();       // Reset The Modelview Matrix
    }

       
    In the next section of code we do all of the setup for OpenGL. We set what color to clear the screen to, we turn on the depth buffer, enable smooth shading, etc. This routine will not be called until the OpenGL Window has been created. This procedure returns a value but because our initialization isn't that complex we wont worry about the value for now.   
       

    int InitGL(GLvoid)        // All Setup For OpenGL Goes Here
    {

       
    The next line enables smooth shading. Smooth shading blends colors nicely across a polygon, and smoothes out lighting. I will explain smooth shading in more detail in another tutorial.   
       

     glShadeModel(GL_SMOOTH);      // Enables Smooth Shading

       
    The following line sets the color of the screen when it clears. If you don't know how colors work, I'll quickly explain. The color values range from 0.0f to 1.0f. 0.0f being the darkest and 1.0f being the brightest. The first parameter after glClearColor is the Red Intensity, the second parameter is for Green and the third is for Blue. The higher the number is to 1.0f, the brighter that specific color will be. The last number is an Alpha value. When it comes to clearing the screen, we wont worry about the 4th number. For now leave it at 0.0f. I will explain its use in another tutorial.

    You create different colors by mixing the three primary colors for light (red, green, blue). Hope you learned primaries in school. So, if you had glClearColor(0.0f,0.0f,1.0f,0.0f) you would be clearing the screen to a bright blue. If you had glClearColor(0.5f,0.0f,0.0f,0.0f) you would be clearing the screen to a medium red. Not bright (1.0f) and not dark (0.0f). To make a white background, you would set all the colors as high as possible (1.0f). To make a black background you would set all the colors to as low as possible (0.0f).   
       

     glClearColor(0.0f, 0.0f, 0.0f, 0.0f);     // Black Background

       
    The next three lines have to do with the Depth Buffer. Think of the depth buffer as layers into the screen. The depth buffer keeps track of how deep objects are into the screen. We won't really be using the depth buffer in this program, but just about every OpenGL program that draws on the screen in 3D will use the depth buffer. It sorts out which object to draw first so that a square you drew behind a circle doesn't end up on top of the circle. The depth buffer is a very important part of OpenGL.   
       

     glClearDepth(1.0f);       // Depth Buffer Setup
     glEnable(GL_DEPTH_TEST);      // Enables Depth Testing
     glDepthFunc(GL_LEQUAL);       // The Type Of Depth Test To Do

       
    Next we tell OpenGL we want the best perspective correction to be done. This causes a very tiny performance hit, but makes the perspective view look a bit better.   
       

     glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST);   // Really Nice Perspective Calculations

       
    Finally we return TRUE. If we wanted to see if initialization went ok, we could check to see if TRUE or FALSE was returned. You can add code of your own to return FALSE if an error happens. For now we won't worry about it.   
       

     return TRUE;        // Initialization Went OK
    }

       
    This section is where all of your drawing code will go. Anything you plan to display on the screen will go in this section of code. Each tutorial after this one will add code to this section of the program. If you already have an understanding of OpenGL, you can try creating basic shapes by adding OpenGL code below glLoadIdentity() and before return TRUE. If you're new to OpenGL, wait for my next tutorial. For now all we will do is clear the screen to the color we previously decided on, clear the depth buffer and reset the scene. We wont draw anything yet.

    The return TRUE tells our program that there were no problems. If you wanted the program to stop for some reason, adding a return FALSE line somewhere before return TRUE will tell our program that the drawing code failed. The program will then quit.   
       

    int DrawGLScene(GLvoid)        // Here's Where We Do All The Drawing
    {
     glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);   // Clear The Screen And The Depth Buffer
     glLoadIdentity();       // Reset The Current Modelview Matrix
     return TRUE;        // Everything Went OK
    }

       
    The next section of code is called just before the program quits. The job of KillGLWindow() is to release the Rendering Context, the Device Context and finally the Window Handle. I've added a lot of error checking. If the program is unable to destroy any part of the Window, a message box with an error message will pop up, telling you what failed. Making it a lot easier to find problems in your code.   
       

    GLvoid KillGLWindow(GLvoid)       // Properly Kill The Window
    {

       
    The first thing we do in KillGLWindow() is check to see if we are in fullscreen mode. If we are, we'll switch back to the desktop. We should destroy the Window before disabling fullscreen mode, but on some video cards if we destroy the Window BEFORE we disable fullscreen mode, the desktop will become corrupt. So we'll disable fullscreen mode first. This will prevent the desktop from becoming corrupt, and works well on both Nvidia and 3dfx video cards!   
       

     if (fullscreen)        // Are We In Fullscreen Mode?
     {

       
    We use ChangeDisplaySettings(NULL,0) to return us to our original desktop. Passing NULL as the first parameter and 0 as the second parameter forces Windows to use the values currently stored in the Windows registry (the default resolution, bit depth, frequency, etc) effectively restoring our original desktop. After we've switched back to the desktop we make the cursor visible again.   
       

      ChangeDisplaySettings(NULL,0);     // If So Switch Back To The Desktop
      ShowCursor(TRUE);      // Show Mouse Pointer
     }

       
    The code below checks to see if we have a Rendering Context (hRC). If we don't, the program will jump to the section of code below that checks to see if we have a Device Context.   
       

     if (hRC)        // Do We Have A Rendering Context?
     {

       
    If we have a Rendering Context, the code below will check to see if we are able to release it (detach the hRC from the hDC). Notice the way I'm checking for errors. I'm basically telling our program to try freeing it (with wglMakeCurrent(NULL,NULL), then I check to see if freeing it was successful or not. Nicely combining a few lines of code into one line.   
       

      if (!wglMakeCurrent(NULL,NULL))     // Are We Able To Release The DC And RC Contexts?
      {

       
    If we were unable to release the DC and RC contexts, MessageBox() will pop up an error message letting us know the DC and RC could not be released. NULL means the message box has no parent Window. The text right after NULL is the text that appears in the message box. "SHUTDOWN ERROR" is the text that appears at the top of the message box (title). Next we have MB_OK, this means we want a message box with one button labelled "OK". MB_ICONINFORMATION makes a lower case i in a circle appear inside the message box (makes it stand out a bit more).   
       

       MessageBox(NULL,"Release Of DC And RC Failed.","SHUTDOWN ERROR",MB_OK | MB_ICONINFORMATION);
      }

       
    Next we try to delete the Rendering Context. If we were unsuccessful an error message will pop up.   
       

      if (!wglDeleteContext(hRC))     // Are We Able To Delete The RC?
      {

       
    If we were unable to delete the Rendering Context the code below will pop up a message box letting us know that deleting the RC was unsuccessful. hRC will be set to NULL.   
       

       MessageBox(NULL,"Release Rendering Context Failed.","SHUTDOWN ERROR",MB_OK | MB_ICONINFORMATION);
      }
      hRC=NULL;       // Set RC To NULL
     }

       
    Now we check to see if our program has a Device Context and if it does, we try to release it. If we're unable to release the Device Context an error message will pop up and hDC will be set to NULL.   
       

     if (hDC && !ReleaseDC(hWnd,hDC))     // Are We Able To Release The DC
     {
      MessageBox(NULL,"Release Device Context Failed.","SHUTDOWN ERROR",MB_OK | MB_ICONINFORMATION);
      hDC=NULL;       // Set DC To NULL
     }

       
    Now we check to see if there is a Window Handle and if there is, we try to destroy the Window using DestroyWindow(hWnd). If we are unable to destroy the Window, an error message will pop up and hWnd will be set to NULL.   
       

     if (hWnd && !DestroyWindow(hWnd))     // Are We Able To Destroy The Window?
     {
      MessageBox(NULL,"Could Not Release hWnd.","SHUTDOWN ERROR",MB_OK | MB_ICONINFORMATION);
      hWnd=NULL;       // Set hWnd To NULL
     }

       
    Last thing to do is unregister our Windows Class. This allows us to properly kill the window, and then reopen another window without receiving the error message "Windows Class already registered".   
       

     if (!UnregisterClass("OpenGL",hInstance))    // Are We Able To Unregister Class
     {
      MessageBox(NULL,"Could Not Unregister Class.","SHUTDOWN ERROR",MB_OK | MB_ICONINFORMATION);
      hInstance=NULL;       // Set hInstance To NULL
     }
    }

       
    The next section of code creates our OpenGL Window. I spent a lot of time trying to decide if I should create a fixed fullscreen Window that doesn't require a lot of extra code, or an easy to customize user friendly Window that requires a lot more code. I decided the user friendly Window with a lot more code would be the best choice. I get asked the following questions all the time in email: How can I create a Window instead of using fullscreen? How do I change the Window's title? How do I change the resolution or pixel format of the Window? The following code does all of that! Therefore it's better learning material and will make writing OpenGL programs of your own a lot easier!

    As you can see the procedure returns BOOL (TRUE or FALSE), it also takes 5 parameters: title of the Window, width of the Window, height of the Window, bits (16/24/32), and finally fullscreenflag TRUE for fullscreen or FALSE for windowed. We return a boolean value that will tell us if the Window was created successfully.   
       

    BOOL CreateGLWindow(char* title, int width, int height, int bits, bool fullscreenflag)
    {

       
    When we ask Windows to find us a pixel format that matches the one we want, the number of the mode that Windows ends up finding for us will be stored in the variable PixelFormat.   
       

     GLuint  PixelFormat;      // Holds The Results After Searching For A Match

       
    wc will be used to hold our Window Class structure. The Window Class structure holds information about our window. By changing different fields in the Class we can change how the window looks and behaves. Every window belongs to a Window Class. Before you create a window, you MUST register a Class for the window.   
       

     WNDCLASS wc;       // Windows Class Structure

       
    dwExStyle and dwStyle will store the Extended and normal Window Style Information. I use variables to store the styles so that I can change the styles depending on what type of window I need to create (A popup window for fullscreen or a window with a border for windowed mode)   
       

     DWORD  dwExStyle;      // Window Extended Style
     DWORD  dwStyle;      // Window Style

       
    The following 5 lines of code grab the upper left, and lower right values of a rectangle. We'll use these values to adjust our window so that the area we draw on is the exact resolution we want. Normally if we create a 640x480 window, the borders of the window take up some of our resolution.   
       

     RECT WindowRect;       // Grabs Rectangle Upper Left / Lower Right Values
     WindowRect.left=(long)0;      // Set Left Value To 0
     WindowRect.right=(long)width;      // Set Right Value To Requested Width
     WindowRect.top=(long)0;       // Set Top Value To 0
     WindowRect.bottom=(long)height;      // Set Bottom Value To Requested Height

       
    In the next line of code we make the global variable fullscreen equal fullscreenflag. So if we made our Window fullscreen, the variable fullscreenflag would be TRUE. If we didn't make the variable fullscreen equal fullscreenflag, the variable fullscreen would stay FALSE. If we were killing the window, and the computer was in fullscreen mode, but the variable fullscreen was FALSE instead of TRUE like it should be, the computer wouldn't switch back to the desktop, because it would think it was already showing the desktop. God I hope that makes sense. Basically to sum it up, fullscreen has to equal whatever fullscreenflag equals, otherwise there will be problems.   
       

     fullscreen=fullscreenflag;      // Set The Global Fullscreen Flag

       
    In the next section of code, we grab an instance for our Window, then we define the Window Class.

    The style CS_HREDRAW and CS_VREDRAW force the Window to redraw whenever it is resized. CS_OWNDC creates a private DC for the Window. Meaning the DC is not shared across applications. WndProc is the procedure that watches for messages in our program. No extra Window data is used so we zero the two fields. Then we set the instance. Next we set hIcon to NULL meaning we don't want an ICON in the Window, and for a mouse pointer we use the standard arrow. The background color doesn't matter (we set that in GL). We don't want a menu in this Window so we set it to NULL, and the class name can be any name you want. I'll use "OpenGL" for simplicity.   
       

     hInstance  = GetModuleHandle(NULL);   // Grab An Instance For Our Window
     wc.style  = CS_HREDRAW | CS_VREDRAW | CS_OWNDC;  // Redraw On Move, And Own DC For Window
     wc.lpfnWndProc  = (WNDPROC) WndProc;    // WndProc Handles Messages
     wc.cbClsExtra  = 0;      // No Extra Window Data
     wc.cbWndExtra  = 0;      // No Extra Window Data
     wc.hInstance  = hInstance;     // Set The Instance
     wc.hIcon  = LoadIcon(NULL, IDI_WINLOGO);   // Load The Default Icon
     wc.hCursor  = LoadCursor(NULL, IDC_ARROW);   // Load The Arrow Pointer
     wc.hbrBackground = NULL;      // No Background Required For GL
     wc.lpszMenuName  = NULL;      // We Don't Want A Menu
     wc.lpszClassName = "OpenGL";     // Set The Class Name

       
    Now we register the Class. If anything goes wrong, an error message will pop up. Clicking on OK in the error box will exit the program.   
       

     if (!RegisterClass(&wc))      // Attempt To Register The Window Class
     {
      MessageBox(NULL,"Failed To Register The Window Class.","ERROR",MB_OK|MB_ICONEXCLAMATION);
      return FALSE;       // Exit And Return FALSE
     }

       
    Now we check to see if the program should run in fullscreen mode or windowed mode. If it should be fullscreen mode, we'll attempt to set fullscreen mode.   
       

     if (fullscreen)        // Attempt Fullscreen Mode?
     {

       
    The next section of code is something people seem to have a lot of problems with... switching to fullscreen mode. There are a few very important things you should keep in mind when switching to full screen mode. Make sure the width and height that you use in fullscreen mode is the same as the width and height you plan to use for your window, and most importantly, set fullscreen mode BEFORE you create your window. In this code, you don't have to worry about the width and height, the fullscreen and the window size are both set to be the size requested.   
       

      DEVMODE dmScreenSettings;     // Device Mode
      memset(&dmScreenSettings,0,sizeof(dmScreenSettings));  // Makes Sure Memory's Cleared
      dmScreenSettings.dmSize=sizeof(dmScreenSettings);  // Size Of The Devmode Structure
      dmScreenSettings.dmPelsWidth = width;   // Selected Screen Width
      dmScreenSettings.dmPelsHeight = height;   // Selected Screen Height
      dmScreenSettings.dmBitsPerPel = bits;    // Selected Bits Per Pixel
      dmScreenSettings.dmFields=DM_BITSPERPEL|DM_PELSWIDTH|DM_PELSHEIGHT;

       
    In the code above we clear room to store our video settings. We set the width, height and bits that we want the screen to switch to. In the code below we try to set the requested full screen mode. We stored all the information about the width, height and bits in dmScreenSettings. In the line below ChangeDisplaySettings tries to switch to a mode that matches what we stored in dmScreenSettings. I use the parameter CDS_FULLSCREEN when switching modes, because it's supposed to remove the start bar at the bottom of the screen, plus it doesn't move or resize the windows on your desktop when you switch to fullscreen mode and back.   
       

      // Try To Set Selected Mode And Get Results.  NOTE: CDS_FULLSCREEN Gets Rid Of Start Bar.
      if (ChangeDisplaySettings(&dmScreenSettings,CDS_FULLSCREEN)!=DISP_CHANGE_SUCCESSFUL)
      {

       
    If the mode couldn't be set the code below will run. If a matching fullscreen mode doesn't exist, a messagebox will pop up offering two options... The option to run in a window or the option to quit.   
       

       // If The Mode Fails, Offer Two Options.  Quit Or Run In A Window.
       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)
       {

       
    If the user decided to use windowed mode, the variable fullscreen becomes FALSE, and the program continues running.   
       

        fullscreen=FALSE;    // Select Windowed Mode (Fullscreen=FALSE)
       }
       else
       {

       
    If the user decided to quit, a messagebox will pop up telling the user that the program is about to close. FALSE will be returned telling our program that the window was not created successfully. The program will then quit.   
       

        // 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;     // Exit And Return FALSE
       }
      }
     }

       
    Because the fullscreen code above may have failed and the user may have decided to run the program in a window instead, we check once again to see if fullscreen is TRUE or FALSE before we set up the screen / window type.   
       

     if (fullscreen)        // Are We Still In Fullscreen Mode?
     {

       
    If we are still in fullscreen mode we'll set the extended style to WS_EX_APPWINDOW, which force a top level window down to the taskbar once our window is visible. For the window style we'll create a WS_POPUP window. This type of window has no border around it, making it perfect for fullscreen mode.

    Finally, we disable the mouse pointer. If your program is not interactive, it's usually nice to disable the mouse pointer when in fullscreen mode. It's up to you though.   
       

      dwExStyle=WS_EX_APPWINDOW;     // Window Extended Style
      dwStyle=WS_POPUP;      // Windows Style
      ShowCursor(FALSE);      // Hide Mouse Pointer
     }
     else
     {

       
    If we're using a window instead of fullscreen mode, we'll add WS_EX_WINDOWEDGE to the extended style. This gives the window a more 3D look. For style we'll use WS_OVERLAPPEDWINDOW instead of WS_POPUP. WS_OVERLAPPEDWINDOW creates a window with a title bar, sizing border, window menu, and minimize / maximize buttons.   
       

      dwExStyle=WS_EX_APPWINDOW | WS_EX_WINDOWEDGE;   // Window Extended Style
      dwStyle=WS_OVERLAPPEDWINDOW;     // Windows Style
     }

       
    The line below adjust our window depending on what style of window we are creating. The adjustment will make our window exactly the resolution we request. Normally the borders will overlap parts of our window. By using the AdjustWindowRectEx command none of our OpenGL scene will be covered up by the borders, instead, the window will be made larger to account for the pixels needed to draw the window border. In fullscreen mode, this command has no effect.   
       

     AdjustWindowRectEx(&WindowRect, dwStyle, FALSE, dwExStyle);  // Adjust Window To True Requested Size

       
    In the next section of code, we're going to create our window and check to see if it was created properly. We pass CreateWindowEx() all the parameters it requires. The extended style we decided to use. The class name (which has to be the same as the name you used when you registered the Window Class). The window title. The window style. The top left position of your window (0,0 is a safe bet). The width and height of the window. We don't want a parent window, and we don't want a menu so we set both these parameters to NULL. We pass our window instance, and finally we NULL the last parameter.

    Notice we include the styles WS_CLIPSIBLINGS and WS_CLIPCHILDREN along with the style of window we've decided to use. WS_CLIPSIBLINGS and WS_CLIPCHILDREN are both REQUIRED for OpenGL to work properly. These styles prevent other windows from drawing over or into our OpenGL Window.   
       

     if (!(hWnd=CreateWindowEx( dwExStyle,    // Extended Style For The Window
         "OpenGL",    // Class Name
         title,     // Window Title
         WS_CLIPSIBLINGS |   // Required Window Style
         WS_CLIPCHILDREN |   // Required Window Style
         dwStyle,    // Selected Window Style
         0, 0,     // Window Position
         WindowRect.right-WindowRect.left, // Calculate Adjusted Window Width
         WindowRect.bottom-WindowRect.top, // Calculate Adjusted Window Height
         NULL,     // No Parent Window
         NULL,     // No Menu
         hInstance,    // Instance
         NULL)))     // Don't Pass Anything To WM_CREATE

       
    Next we check to see if our window was created properly. If our window was created, hWnd will hold the window handle. If the window wasn't created the code below will pop up an error message and the program will quit.   
       

     {
      KillGLWindow();       // Reset The Display
      MessageBox(NULL,"Window Creation Error.","ERROR",MB_OK|MB_ICONEXCLAMATION);
      return FALSE;       // Return FALSE
     }

       
    The next section of code describes a Pixel Format. We choose a format that supports OpenGL and double buffering, along with RGBA (red, green, blue, alpha channel). We try to find a pixel format that matches the bits we decided on (16bit,24bit,32bit). Finally we set up a 16bit Z-Buffer. The remaining parameters are either not used or are not important (aside from the stencil buffer and the (slow) accumulation buffer).   
       

     static PIXELFORMATDESCRIPTOR pfd=     // pfd Tells Windows How We Want Things To Be
     {
      sizeof(PIXELFORMATDESCRIPTOR),     // Size Of This Pixel Format Descriptor
      1,        // Version Number
      PFD_DRAW_TO_WINDOW |      // Format Must Support Window
      PFD_SUPPORT_OPENGL |      // Format Must Support OpenGL
      PFD_DOUBLEBUFFER,      // Must Support Double Buffering
      PFD_TYPE_RGBA,       // Request An RGBA Format
      bits,        // Select Our Color Depth
      0, 0, 0, 0, 0, 0,      // Color Bits Ignored
      0,        // No Alpha Buffer
      0,        // Shift Bit Ignored
      0,        // No Accumulation Buffer
      0, 0, 0, 0,       // Accumulation Bits Ignored
      16,        // 16Bit Z-Buffer (Depth Buffer)
      0,        // No Stencil Buffer
      0,        // No Auxiliary Buffer
      PFD_MAIN_PLANE,       // Main Drawing Layer
      0,        // Reserved
      0, 0, 0        // Layer Masks Ignored
     };

       
    If there were no errors while creating the window, we'll attempt to get an OpenGL Device Context. If we can't get a DC an error message will pop onto the screen, and the program will quit (return FALSE).   
       

     if (!(hDC=GetDC(hWnd)))       // Did We Get A Device Context?
     {
      KillGLWindow();       // Reset The Display
      MessageBox(NULL,"Can't Create A GL Device Context.","ERROR",MB_OK|MB_ICONEXCLAMATION);
      return FALSE;       // Return FALSE
     }

       
    If we managed to get a Device Context for our OpenGL window we'll try to find a pixel format that matches the one we described above. If Windows can't find a matching pixel format, an error message will pop onto the screen and the program will quit (return FALSE).   
       

     if (!(PixelFormat=ChoosePixelFormat(hDC,&pfd)))    // Did Windows Find A Matching Pixel Format?
     {
      KillGLWindow();       // Reset The Display
      MessageBox(NULL,"Can't Find A Suitable PixelFormat.","ERROR",MB_OK|MB_ICONEXCLAMATION);
      return FALSE;       // Return FALSE
     }

       
    If windows found a matching pixel format we'll try setting the pixel format. If the pixel format cannot be set, an error message will pop up on the screen and the program will quit (return FALSE).   
       

     if(!SetPixelFormat(hDC,PixelFormat,&pfd))    // Are We Able To Set The Pixel Format?
     {
      KillGLWindow();       // Reset The Display
      MessageBox(NULL,"Can't Set The PixelFormat.","ERROR",MB_OK|MB_ICONEXCLAMATION);
      return FALSE;       // Return FALSE
     }

       
    If the pixel format was set properly we'll try to get a Rendering Context. If we can't get a Rendering Context an error message will be displayed on the screen and the program will quit (return FALSE).   
       

     if (!(hRC=wglCreateContext(hDC)))     // Are We Able To Get A Rendering Context?
     {
      KillGLWindow();       // Reset The Display
      MessageBox(NULL,"Can't Create A GL Rendering Context.","ERROR",MB_OK|MB_ICONEXCLAMATION);
      return FALSE;       // Return FALSE
     }

       
    If there have been no errors so far, and we've managed to create both a Device Context and a Rendering Context all we have to do now is make the Rendering Context active. If we can't make the Rendering Context active an error message will pop up on the screen and the program will quit (return FALSE).   
       

     if(!wglMakeCurrent(hDC,hRC))      // Try To Activate The Rendering Context
     {
      KillGLWindow();       // Reset The Display
      MessageBox(NULL,"Can't Activate The GL Rendering Context.","ERROR",MB_OK|MB_ICONEXCLAMATION);
      return FALSE;       // Return FALSE
     }

       
    If everything went smoothly, and our OpenGL window was created we'll show the window, set it to be the foreground window (giving it more priority) and then set the focus to that window. Then we'll call ReSizeGLScene passing the screen width and height to set up our perspective OpenGL screen.   
       

     ShowWindow(hWnd,SW_SHOW);      // Show The Window
     SetForegroundWindow(hWnd);      // Slightly Higher Priority
     SetFocus(hWnd);        // Sets Keyboard Focus To The Window
     ReSizeGLScene(width, height);      // Set Up Our Perspective GL Screen

       
    Finally we jump to InitGL() where we can set up lighting, textures, and anything else that needs to be setup. You can do your own error checking in InitGL(), and pass back TRUE (everythings OK) or FALSE (somethings not right). For example, if you were loading textures in InitGL() and had an error, you may want the program to stop. If you send back FALSE from InitGL() the lines of code below will see the FALSE as an error message and the program will quit.   
       

     if (!InitGL())        // Initialize Our Newly Created GL Window
     {
      KillGLWindow();       // Reset The Display
      MessageBox(NULL,"Initialization Failed.","ERROR",MB_OK|MB_ICONEXCLAMATION);
      return FALSE;       // Return FALSE
     }

       
    If we've made it this far, it's safe to assume the window creation was successful. We return TRUE to WinMain() telling WinMain() there were no errors. This prevents the program from quitting.   
       

     return TRUE;        // Success
    }

       
    This is where all the window messages are dealt with. When we registred the Window Class we told it to jump to this section of code to deal with window messages.   
       

    LRESULT CALLBACK WndProc( HWND hWnd,     // Handle For This Window
        UINT uMsg,     // Message For This Window
        WPARAM wParam,     // Additional Message Information
        LPARAM lParam)     // Additional Message Information
    {

       
    The code below sets uMsg as the value that all the case statements will be compared to. uMsg will hold the name of the message we want to deal with.   
       

     switch (uMsg)        // Check For Windows Messages
     {

       
    if uMsg is WM_ACTIVE we check to see if our window is still active. If our window has been minimized the variable active will be FALSE. If our window is active, the variable active will be TRUE.   
       

      case WM_ACTIVATE:      // Watch For Window Activate Message
      {
       if (!HIWORD(wParam))     // Check Minimization State
       {
        active=TRUE;     // Program Is Active
       }
       else
       {
        active=FALSE;     // Program Is No Longer Active
       }

       return 0;      // Return To The Message Loop
      }

       
    If the message is WM_SYSCOMMAND (system command) we'll compare wParam against the case statements. If wParam is SC_SCREENSAVE or SC_MONITORPOWER either a screensaver is trying to start or the monitor is trying to enter power saving mode. By returning 0 we prevent both those things from happening.   
       

      case WM_SYSCOMMAND:      // Intercept System Commands
      {
       switch (wParam)      // Check System Calls
       {
        case SC_SCREENSAVE:    // Screensaver Trying To Start?
        case SC_MONITORPOWER:    // Monitor Trying To Enter Powersave?
        return 0;     // Prevent From Happening
       }
       break;       // Exit
      }

       
    If uMsg is WM_CLOSE the window has been closed. We send out a quit message that the main loop will intercept. The variable done will be set to TRUE, the main loop in WinMain() will stop, and the program will close.   
       

      case WM_CLOSE:       // Did We Receive A Close Message?
      {
       PostQuitMessage(0);     // Send A Quit Message
       return 0;      // Jump Back
      }

       
    If a key is being held down we can find out what key it is by reading wParam. I then make that keys cell in the array keys[ ] become TRUE. That way I can read the array later on and find out which keys are being held down. This allows more than one key to be pressed at the same time.   
       

      case WM_KEYDOWN:      // Is A Key Being Held Down?
      {
       keys[wParam] = TRUE;     // If So, Mark It As TRUE
       return 0;      // Jump Back
      }

       
    If a key has been released we find out which key it was by reading wParam. We then make that keys cell in the array keys[] equal FALSE. That way when I read the cell for that key I'll know if it's still being held down or if it's been released. Each key on the keyboard can be represented by a number from 0-255. When I press the key that represents the number 40 for example, keys[40] will become TRUE. When I let go, it will become FALSE. This is how we use cells to store keypresses.   
       

      case WM_KEYUP:       // Has A Key Been Released?
      {
       keys[wParam] = FALSE;     // If So, Mark It As FALSE
       return 0;      // Jump Back
      }

       
    Whenever we resize our window uMsg will eventually become the message WM_SIZE. We read the LOWORD and HIWORD values of lParam to find out the windows new width and height. We pass the new width and height to ReSizeGLScene(). The OpenGL Scene is then resized to the new width and height.   
       

      case WM_SIZE:       // Resize The OpenGL Window
      {
       ReSizeGLScene(LOWORD(lParam),HIWORD(lParam));  // LoWord=Width, HiWord=Height
       return 0;      // Jump Back
      }
     }

       
    Any messages that we don't care about will be passed to DefWindowProc so that Windows can deal with them.   
       

     // Pass All Unhandled Messages To DefWindowProc
     return DefWindowProc(hWnd,uMsg,wParam,lParam);
    }

       
    This is the entry point of our Windows Application. This is where we call our window creation routine, deal with window messages, and watch for human interaction.   
       

    int WINAPI WinMain( HINSTANCE hInstance,    // Instance
       HINSTANCE hPrevInstance,    // Previous Instance
       LPSTR  lpCmdLine,    // Command Line Parameters
       int  nCmdShow)    // Window Show State
    {

       
    We set up two variables. msg will be used to check if there are any waiting messages that need to be dealt with. the variable done starts out being FALSE. This means our program is not done running. As long as done remains FALSE, the program will continue to run. As soon as done is changed from FALSE to TRUE, our program will quit.   
       

     MSG msg;        // Windows Message Structure
     BOOL done=FALSE;       // Bool Variable To Exit Loop

       
    This section of code is completely optional. It pops up a messagebox that asks if you would like to run the program in fullscreen mode. If the user clicks on the NO button, the variable fullscreen changes from TRUE (it's default) to FALSE and the program runs in windowed mode instead of fullscreen mode.   
       

     // Ask The User Which Screen Mode They Prefer
     if (MessageBox(NULL,"Would You Like To Run In Fullscreen Mode?", "Start FullScreen?",MB_YESNO|MB_ICONQUESTION)==IDNO)
     {
      fullscreen=FALSE;      // Windowed Mode
     }

       
    This is how we create our OpenGL window. We pass the title, the width, the height, the color depth, and TRUE (fullscreen) or FALSE (window mode) to CreateGLWindow. That's it! I'm pretty happy with the simplicity of this code. If the window was not created for some reason, FALSE will be returned and our program will immediately quit (return 0).   
       

     // Create Our OpenGL Window
     if (!CreateGLWindow("NeHe's OpenGL Framework",640,480,16,fullscreen))
     {
      return 0;       // Quit If Window Was Not Created
     }

       
    This is the start of our loop. As long as done equals FALSE the loop will keep repeating.   
       

     while(!done)        // Loop That Runs Until done=TRUE
     {

       
    The first thing we have to do is check to see if any window messages are waiting. By using PeekMessage() we can check for messages without halting our program. A lot of programs use GetMessage(). It works fine, but with GetMessage() your program doesn't do anything until it receives a paint message or some other window message.   
       

      if (PeekMessage(&msg,NULL,0,0,PM_REMOVE))   // Is There A Message Waiting?
      {

       
    In the next section of code we check to see if a quit message was issued. If the current message is a WM_QUIT message caused by PostQuitMessage(0) the variable done is set to TRUE, causing the program to quit.   
       

       if (msg.message==WM_QUIT)    // Have We Received A Quit Message?
       {
        done=TRUE;     // If So done=TRUE
       }
       else       // If Not, Deal With Window Messages
       {

       
    If the message isn't a quit message we translate the message then dispatch the message so that WndProc() or Windows can deal with it.   
       

        TranslateMessage(&msg);    // Translate The Message
        DispatchMessage(&msg);    // Dispatch The Message
       }
      }
      else        // If There Are No Messages
      {

       
    If there were no messages we'll draw our OpenGL scene. The first line of code below checks to see if the window is active. If the ESC key is pressed the variable done is set to TRUE, causing the program to quit.   
       

       // Draw The Scene.  Watch For ESC Key And Quit Messages From DrawGLScene()
       if (active)      // Program Active?
       {
        if (keys[VK_ESCAPE])    // Was ESC Pressed?
        {
         done=TRUE;    // ESC Signalled A Quit
        }
        else      // Not Time To Quit, Update Screen
        {

       
    If the program is active and esc was not pressed we render the scene and swap the buffer (By using double buffering we get smooth flicker free animation). By using double buffering, we are drawing everything to a hidden screen that we can not see. When we swap the buffer, the screen we see becomes the hidden screen, and the screen that was hidden becomes visible. This way we don't see our scene being drawn out. It just instantly appears.   
       

         DrawGLScene();    // Draw The Scene
         SwapBuffers(hDC);   // Swap Buffers (Double Buffering)
        }
       }

       
    The next bit of code is new and has been added just recently (05-01-00). It allows us to press the F1 key to switch from fullscreen mode to windowed mode or windowed mode to fullscreen mode.   
       

       if (keys[VK_F1])     // Is F1 Being Pressed?
       {
        keys[VK_F1]=FALSE;    // If So Make Key FALSE
        KillGLWindow();     // Kill Our Current Window
        fullscreen=!fullscreen;    // Toggle Fullscreen / Windowed Mode
        // Recreate Our OpenGL Window
        if (!CreateGLWindow("NeHe's OpenGL Framework",640,480,16,fullscreen))
        {
         return 0;    // Quit If Window Was Not Created
        }
       }
      }
     }

       
    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.   
       

     // Shutdown
     KillGLWindow();        // Kill The Window
     return (msg.wParam);       // Exit The Program
    }

       
    In this tutorial I have tried to explain in as much detail, every step involved in setting up, and creating a fullscreen OpenGL program of your own, that will exit when the ESC key is pressed and monitor if the window is active or not. I've spent roughly 2 weeks writing the code, one week fixing bugs & talking with programming gurus, and 2 days (roughly 22 hours writing this HTML file). If you have comments or questions please email me. If you feel I have incorrectly commented something or that the code could be done better in some sections, please let me know. I want to make the best OpenGL tutorials I can and I'm interested in hearing your feedback.

    Jeff Molofee (NeHe)

    ----------------------------------------------
    越学越无知

    点击查看用户来源及管理<br>发贴IP:*.*.*.* 2007/10/12 11:05:00
     
     一分之千 帅哥哟,离线,有人找我吗?射手座1984-11-30
      
      
      威望:1
      等级:研一(随老板参加了WWW大会还和Tim Berners-Lee合了影^_^)
      文章:632
      积分:4379
      门派:XML.ORG.CN
      注册:2006/12/31

    姓名:(无权查看)
    城市:(无权查看)
    院校:(无权查看)
    给一分之千发送一个短消息 把一分之千加入好友 查看一分之千的个人资料 搜索一分之千在『 C/C++编程思想 』的所有贴子 引用回复这个贴子 回复这个贴子 查看一分之千的博客3
    发贴心情 

    第二课 中文


    你的第一个多边形:按此在新窗口浏览图片

    在第一个教程的基础上,我们添加了一个三角形和一个四边形。也许你认为这很简单,但你已经迈出了一大步,要知道任何在OpenGL中绘制的模型都会被分解为这两种简单的图形。

    读完了这一课,你会学到如何在空间放置模型,并且会知道深度缓存的概念。

      
       
       
    第一课中,我教您如何创建一个OpenGL窗口。这一课中,我将教您如何创建三角形和四边形。我们讲使用来创建GL_TRIANGLES一个三角形,GL_QUADS来创建一个四边形。
    在第一课代码的基础上,我们只需在DrawGLScene()过程中增加代码。下面我重写整个过程。如果您计划修改上节课的代码,只需用下面的代码覆盖原来的DrawGLScene()就可以了。
      
       

    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没有其他点了。这样将显示一个填充的三角形。

      
       

     glBegin(GL_TRIANGLES);       // 绘制三角形
      glVertex3f( 0.0f, 1.0f, 0.0f);     // 上顶点
      glVertex3f(-1.0f,-1.0f, 0.0f);     // 左下
      glVertex3f( 1.0f,-1.0f, 0.0f);     // 右下
     glEnd();        // 三角形绘制结束

       
    在屏幕的左半部分画完三角形后,我们要移到右半部分来画正方形。为此要再次使用glTranslate。这次右移,所以X坐标值为正值。因为前面左移了1.5个单位,这次要先向右移回屏幕中心(1.5个单位),再向右移动1.5个单位。总共要向右移3.0个单位。  
       

     glTranslatef(3.0f,0.0f,0.0f);      // 右移3单位

       
    现在使用GL_QUADS绘制正方形。与绘制三角形的代码相类似,画四边形也很简单。唯一的区别是用GL_QUADS来替换了GL_TRIANGLES。并增加了一个点。我们使用顺时针次序来画正方形-左上-右上-右下-左下。采用顺时针绘制的是对象的后表面。这就是说我们所看见的是正方形的背面。逆时针画出来的正方形才是正面朝着我们的。现在这对您来说并不重要,但以后您必须知道。  
       

     glBegin(GL_QUADS);       //  绘制正方形
      glVertex3f(-1.0f, 1.0f, 0.0f);     // 左上
      glVertex3f( 1.0f, 1.0f, 0.0f);     // 右上
      glVertex3f( 1.0f,-1.0f, 0.0f);     // 左下
      glVertex3f(-1.0f,-1.0f, 0.0f);     // 右下
     glEnd();        // 正方形绘制结束
     return TRUE;        // 继续运行
    }

       
    最后换掉窗口模式下的标题内容。  
       

       if (keys[VK_F1])     // F1键按下了么?
       {
        keys[VK_F1]=FALSE;    // 若是,使对应的Key数组中的值为 FALSE
        KillGLWindow();     // 销毁当前的窗口
        fullscreen=!fullscreen;    // 切换 全屏 / 窗口 模式
        // 重建 OpenGL 窗口(修改)
        if (!CreateGLWindow("NeHe's 第一个多边形程序",640,480,16,fullscreen))
        {
         return 0;    // 如果窗口未能创建,程序退出
        }
       }

       
    在这一课中,我已试着尽量详细的解释与多边形绘制有关的步骤。并创建了一个绘制三角形和正方形的OpenGL程序。如果您有什么意见或建议请给我EMAIL。如果您认为有什么不对或可以改进,请告诉我。我想做最好的OpenGL教程并对您的反馈感兴趣。

    ----------------------------------------------
    越学越无知

    点击查看用户来源及管理<br>发贴IP:*.*.*.* 2007/10/12 11:49:00
     
     一分之千 帅哥哟,离线,有人找我吗?射手座1984-11-30
      
      
      威望:1
      等级:研一(随老板参加了WWW大会还和Tim Berners-Lee合了影^_^)
      文章:632
      积分:4379
      门派:XML.ORG.CN
      注册:2006/12/31

    姓名:(无权查看)
    城市:(无权查看)
    院校:(无权查看)
    给一分之千发送一个短消息 把一分之千加入好友 查看一分之千的个人资料 搜索一分之千在『 C/C++编程思想 』的所有贴子 引用回复这个贴子 回复这个贴子 查看一分之千的博客4
    发贴心情 
    Lesson 02
       
    In the first tutorial I taught you how to create an OpenGL Window. In this tutorial I will teach you how to create both Triangles and Quads. We will create a triangle using GL_TRIANGLES, and a square using GL_QUADS.

    Using the code from the first tutorial, we will be adding to the DrawGLScene() procedure. I will rewrite the entire procedure below. If you plan to modify the last lesson, you can replace the DrawGLScene() procedure with the code below, or just add the lines of code below that do not exist in the last tutorial.   
       

    int DrawGLScene(GLvoid)       // Here's Where We Do All The Drawing
    {
     glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);  // Clear The Screen And The Depth Buffer
     glLoadIdentity();      // Reset The View

       
    When you do a glLoadIdentity() what you are doing is moving back to the center of the screen with the X axis running left to right, the Y axis moving up and down, and the Z axis moving into, and out of the screen.

    The center of an OpenGL screen is 0.0f on the X and Y axis. To the left of center would be a negative number. To the right would be a positive number. Moving towards the top of the screen would be a positive number, moving to the bottom of the screen would be a negative number. Moving deeper into the screen is a negative number, moving towards the viewer would be a positive number.

    glTranslatef(x, y, z) moves along the X, Y and Z axis, in that order. The line of code below moves left on the X axis 1.5 units. It does not move on the Y axis at all (0.0), and it moves into the screen 6.0 units. When you translate, you are not moving a set amount from the center of the screen, you are moving a set amount from wherever you currently were on the screen.   
       

     glTranslatef(-1.5f,0.0f,-6.0f);     // Move Left 1.5 Units And Into The Screen 6.0

       
    Now that we have moved to the left half of the screen, and we've set the view deep enough into the screen (-6.0) that we can see our entire scene we will create the Triangle. glBegin(GL_TRIANGLES) means we want to start drawing a triangle, and glEnd() tells OpenGL we are done creating the triangle. Typically if you want 3 points, use GL_TRIANGLES. Drawing triangles is fairly fast on most video cards. If you want 4 points use GL_QUADS to make life easier. From what I've heard, most video cards render objects as triangles anyways. Finally if you want more than 4 points, use GL_POLYGON.

    In our simple program, we draw just one triangle. If we wanted to draw a second triangle, we could include another 3 lines of code (3 points) right after the first three. All six lines of code would be between glBegin(GL_TRIANGLES) and glEnd(). There's no point in putting a glBegin(GL_TRIANGLES) and a glEnd() around every group of 3 points. This applies to quads as well. If you know you're drawing all quads, you can include the second group of four lines of code right after the first four lines. A polygon on the other hand (GL_POLYGON) can be made up of any amount of point so it doesn't matter how many lines you have between glBegin(GL_POLYGON) and glEnd().

    The first line after glBegin, sets the first point of our polygon. The first number of glVertex is for the X axis, the second number is for the Y axis, and the third number is for the Z axis. So in the first line, we don't move on the X axis. We move up one unit on the Y axis, and we don't move on the Z axis. This gives us the top point of the triangle. The second glVertex moves left one unit on the X axis and down one unit on the Y axis. This gives us the bottom left point of the triangle. The third glVertex moves right one unit, and down one unit. This gives us the bottom right point of the triangle. glEnd() tells OpenGL there are no more points. The filled triangle will be displayed.   
       

     glBegin(GL_TRIANGLES);      // Drawing Using Triangles
      glVertex3f( 0.0f, 1.0f, 0.0f);    // Top
      glVertex3f(-1.0f,-1.0f, 0.0f);    // Bottom Left
      glVertex3f( 1.0f,-1.0f, 0.0f);    // Bottom Right
     glEnd();       // Finished Drawing The Triangle

       
    Now that we have the triangle displayed on the left half of the screen, we need to move to the right half of the screen to display the square. In order to do this we use glTranslate again. This time we must move to the right, so X must be a positive value. Because we've already moved left 1.5 units, to get to the center we have to move right 1.5 units. After we reach the center we have to move another 1.5 units to the right of center. So in total we need to move 3.0 units to the right.   
       

     glTranslatef(3.0f,0.0f,0.0f);     // Move Right 3 Units

       
    Now we create the square. We'll do this using GL_QUADS. A quad is basically a 4 sided polygon. Perfect for making a square. The code for creating a square is very similar to the code we used to create a triangle. The only difference is the use of GL_QUADS instead of GL_TRIANGLES, and an extra glVertex3f for the 4th point of the square. We'll draw the square top left, top right, bottom right, bottom left (clockwise). By drawing in a clockwise order, the square will be drawn as a back face. Meaning the side of the quad we see is actually the back. Objects drawn in a counter clockwise order will be facing us. Not important at the moment, but later on you will need to know this.   
       

     glBegin(GL_QUADS);      // Draw A Quad
      glVertex3f(-1.0f, 1.0f, 0.0f);    // Top Left
      glVertex3f( 1.0f, 1.0f, 0.0f);    // Top Right
      glVertex3f( 1.0f,-1.0f, 0.0f);    // Bottom Right
      glVertex3f(-1.0f,-1.0f, 0.0f);    // Bottom Left
     glEnd();       // Done Drawing The Quad
     return TRUE;       // Keep Going
    }

       
    Finally change the code to toggle window / fullscreen mode so that the title at the top of the window is proper.   
       

       if (keys[VK_F1])    // Is F1 Being Pressed?
       {
        keys[VK_F1]=FALSE;   // If So Make Key FALSE
        KillGLWindow();    // Kill Our Current Window
        fullscreen=!fullscreen;   // Toggle Fullscreen / Windowed Mode
        // Recreate Our OpenGL Window ( Modified )
        if (!CreateGLWindow("NeHe's First Polygon Tutorial",640,480,16,fullscreen))
        {
         return 0;   // Quit If Window Was Not Created
        }
       }

       
    Markus Knauer Adds: In the book ("OpenGL Programming Guide: The Official Guide to Learning OpenGL, Release 1", J. Neider, T. Davis, M. Woo, Addison-Wesley, 1993) the following paragraph will clearly explain what NeHe means when he refers to movement by units in OpenGL:

    "[I mentioned] inches and millimeters - do these really have anything to do with OpenGL? The answer is, in a word, no. The projection and other transformations are inheritly unitless. If you want to think of the near and far clipping planes as located at 1.0 and 20.0 meters, inches, kilometers, or leagues, it's up to you. The only rule is that you have to use consistent unit of measurement."

    In this tutorial I have tried to explain in as much detail, every step involved in drawing polygons, and quads on the screen using OpenGL. If you have comments or questions please email me. If you feel I have incorrectly commented something or that the code could be done better in some sections, please let me know. I want to make the best OpenGL tutorials I can. I'm interested in hearing your feedback.

    Jeff Molofee (NeHe)

    ----------------------------------------------
    越学越无知

    点击查看用户来源及管理<br>发贴IP:*.*.*.* 2007/10/12 11:51:00
     
     snowtower 帅哥哟,离线,有人找我吗?
      
      
      等级:大一新生
      文章:2
      积分:67
      门派:XML.ORG.CN
      注册:2007/10/16

    姓名:(无权查看)
    城市:(无权查看)
    院校:(无权查看)
    给snowtower发送一个短消息 把snowtower加入好友 查看snowtower的个人资料 搜索snowtower在『 C/C++编程思想 』的所有贴子 引用回复这个贴子 回复这个贴子 查看snowtower的博客5
    发贴心情 
    强!
    兄弟你这是自己翻译的?太仰慕了
    点击查看用户来源及管理<br>发贴IP:*.*.*.* 2007/10/16 3:03:00
     
     wanggangzero 帅哥哟,离线,有人找我吗?处女座1985-9-1
      
      
      等级:大一新生
      文章:0
      积分:65
      门派:XML.ORG.CN
      注册:2006/1/14

    姓名:(无权查看)
    城市:(无权查看)
    院校:(无权查看)
    给wanggangzero发送一个短消息 把wanggangzero加入好友 查看wanggangzero的个人资料 搜索wanggangzero在『 C/C++编程思想 』的所有贴子 引用回复这个贴子 回复这个贴子 查看wanggangzero的博客6
    发贴心情 回复英文版
    兄弟,你写个中文版挺好的,干嘛还要翻译成英文?撑着了?好多国人头疼的是OpenGL的英文书好多,也很好,就是中文版少;以至于,每每想要深造一下,都倍感难以推进,但是,谁也不缺英文资料啊?而且范例也不错哦。总之一句话,你的中文版好,但是英文版还是留着自己看吧,不要拿出来炫了。同志们绝对没有利用你的资料再去学英文的兴趣。
    点击查看用户来源及管理<br>发贴IP:*.*.*.* 2007/11/22 20:44:00
     
     卷积内核 帅哥哟,离线,有人找我吗?
      
      
      威望:8
      头衔:总统
      等级:博士二年级(版主)
      文章:3942
      积分:27590
      门派:XML.ORG.CN
      注册:2004/7/21

    姓名:(无权查看)
    城市:(无权查看)
    院校:(无权查看)
    给卷积内核发送一个短消息 把卷积内核加入好友 查看卷积内核的个人资料 搜索卷积内核在『 C/C++编程思想 』的所有贴子 访问卷积内核的主页 引用回复这个贴子 回复这个贴子 查看卷积内核的博客7
    发贴心情 
    以下是引用wanggangzero在2007-11-22 20:44:00的发言:
    兄弟,你写个中文版挺好的,干嘛还要翻译成英文?撑着了?好多国人头疼的是OpenGL的英文书好多,也很好,就是中文版少;以至于,每每想要深造一下,都倍感难以推进,但是,谁也不缺英文资料啊?而且范例也不错哦。总之一句话,你的中文版好,但是英文版还是留着自己看吧,不要拿出来炫了。同志们绝对没有利用你的资料再去学英文的兴趣。

    这是英文原稿,而不是从英文翻译成中文的。现在很多程序高手还是喜欢看英文版的哦,能看出作者最原本的意图。现在所以还是鼓励大家多看英文版的东西,因为很多优秀的资料都是英文版的,等翻译过来你再看有可能你已经跟不上计算机业界的发展潮流了。

    ----------------------------------------------
    事业是国家的,荣誉是单位的,成绩是领导的,工资是老婆的,财产是孩子的,错误是自己的。

    点击查看用户来源及管理<br>发贴IP:*.*.*.* 2007/11/23 8:00:00
     
     一分之千 帅哥哟,离线,有人找我吗?射手座1984-11-30
      
      
      威望:1
      等级:研一(随老板参加了WWW大会还和Tim Berners-Lee合了影^_^)
      文章:632
      积分:4379
      门派:XML.ORG.CN
      注册:2006/12/31

    姓名:(无权查看)
    城市:(无权查看)
    院校:(无权查看)
    给一分之千发送一个短消息 把一分之千加入好友 查看一分之千的个人资料 搜索一分之千在『 C/C++编程思想 』的所有贴子 引用回复这个贴子 回复这个贴子 查看一分之千的博客8
    发贴心情 
    以下是引用wanggangzero在2007-11-22 20:44:00的发言:
    兄弟,你写个中文版挺好的,干嘛还要翻译成英文?撑着了?好多国人头疼的是OpenGL的英文书好多,也很好,就是中文版少;以至于,每每想要深造一下,都倍感难以推进,但是,谁也不缺英文资料啊?而且范例也不错哦。总之一句话,你的中文版好,但是英文版还是留着自己看吧,不要拿出来炫了。同志们绝对没有利用你的资料再去学英文的兴趣。

    兄弟,看到你的话无语了。

    ----------------------------------------------
    越学越无知

    点击查看用户来源及管理<br>发贴IP:*.*.*.* 2007/11/24 15:57:00
     
     一分之千 帅哥哟,离线,有人找我吗?射手座1984-11-30
      
      
      威望:1
      等级:研一(随老板参加了WWW大会还和Tim Berners-Lee合了影^_^)
      文章:632
      积分:4379
      门派:XML.ORG.CN
      注册:2006/12/31

    姓名:(无权查看)
    城市:(无权查看)
    院校:(无权查看)
    给一分之千发送一个短消息 把一分之千加入好友 查看一分之千的个人资料 搜索一分之千在『 C/C++编程思想 』的所有贴子 引用回复这个贴子 回复这个贴子 查看一分之千的博客9
    发贴心情 
    以下是引用卷积内核在2007-11-23 8:00:00的发言:
    [quote]以下是引用wanggangzero在2007-11-22 20:44:00的发言:
    兄弟,你写个中文版挺好的,干嘛还要翻译成英文?撑着了?好多国人头疼的是OpenGL的英文书好多,也很好,就是中文版少;以至于,每每想要深造一下,都倍感难以推进,但是,谁也不缺英文资料啊?而且范例也不错哦。总之一句话,你的中文版好,但是英文版还是留着自己看吧,不要拿出来炫了。同志们绝对没有利用你的资料再去学英文的兴趣。
    [/quote]

    这是英文原稿,而不是从英文翻译成中文的。现在很多程序高手还是喜欢看英文版的哦,能看出作者最原本的意图。现在所以还是鼓励大家多看英文版的东西,因为很多优秀的资料都是英文版的,等翻译过来你再看有可能你已经跟不上计算机业界的发展潮流了。



    多谢斑竹支持哈哈~~~

    ----------------------------------------------
    越学越无知

    点击查看用户来源及管理<br>发贴IP:*.*.*.* 2007/11/24 15:58:00
     
     vipwkuc4 帅哥哟,离线,有人找我吗?
      
      
      等级:大一新生
      文章:1
      积分:54
      门派:XML.ORG.CN
      注册:2007/12/27

    姓名:(无权查看)
    城市:(无权查看)
    院校:(无权查看)
    给vipwkuc4发送一个短消息 把vipwkuc4加入好友 查看vipwkuc4的个人资料 搜索vipwkuc4在『 C/C++编程思想 』的所有贴子 引用回复这个贴子 回复这个贴子 查看vipwkuc4的博客10
    发贴心情 
    LZ 赞]
    点击查看用户来源及管理<br>发贴IP:*.*.*.* 2007/12/27 14:45:00
     
     GoogleAdSense
      
      
      等级:大一新生
      文章:1
      积分:50
      门派:无门无派
      院校:未填写
      注册:2007-01-01
    给Google AdSense发送一个短消息 把Google AdSense加入好友 查看Google AdSense的个人资料 搜索Google AdSense在『 C/C++编程思想 』的所有贴子 访问Google AdSense的主页 引用回复这个贴子 回复这个贴子 查看Google AdSense的博客广告
    2024/6/22 20:29:58

    本主题贴数18,分页: [1] [2]

    管理选项修改tag | 锁定 | 解锁 | 提升 | 删除 | 移动 | 固顶 | 总固顶 | 奖励 | 惩罚 | 发布公告
    W3C Contributing Supporter! W 3 C h i n a ( since 2003 ) 旗 下 站 点
    苏ICP备05006046号《全国人大常委会关于维护互联网安全的决定》《计算机信息网络国际联网安全保护管理办法》
    312.500ms