OpenGL学习:第三课

来源:互联网 发布:淘宝黑名单在哪里 编辑:程序博客网 时间:2024/05/22 09:05

OpenGL学习:第三课

使用http://nehe.gamedev.net/网站上,Jeff Molofee使用的基于VC的框架来学习OpenGL。有必要理解一下这个框架的实现过程。

PS:这方面的教程很多,可以下载,因为是我个人学习,我会对照MSDN,对程序中涉及到的API函数都进行了了解,做了注释,同时也算是对VC学习的一个积累。

Jeff Molofee第一课的源代码,给出了基于Win32 Application的OpenGL框架,有了这个程序框架,就可以在此之上,非常方便地学习OpenGL。

int WINAPI WinMain( HINSTANCE hInstance,HINSTANCE hPrevInstance,LPSTR   lpCmdLine,int    nCmdShow)

第一课WinMain主函数的代码如下所示:

int WINAPI WinMain( HINSTANCE hInstance,    // Instance
      HINSTANCE hPrevInstance,   // Previous Instance
      LPSTR   lpCmdLine,    // Command Line Parameters
      int    nCmdShow)    // Window Show State
{
MSG   msg;          // Windows Message Structure
BOOL done=FALSE;         // Bool Variable To Exit Loop

// Ask The User Which Screen Mode They Prefer
// 是否使用全屏显示模式?
// 如果点击“NO”,则设置fullscreen变量为FALSE,表示不全屏显示
if (MessageBox(NULL,"Would You Like To Run In Fullscreen Mode?", "Start FullScreen?",MB_YESNO|MB_ICONQUESTION)==IDNO)
{
   fullscreen=FALSE;        // Windowed Mode
}
// 下面,在显示模式上要根据fullscreen来判断

// Create Our OpenGL Window
// 调用自定义函数CreateGLWindow创建OpenGL窗口
// 如果创建失败,直接退出
if (!CreateGLWindow("NeHe's OpenGL Framework",640,480,16,fullscreen))
{
   return 0;          // Quit If Window Was Not Created
}

// 默认值BOOL done=FALSE,也就是默认情况下,进入循环运行状态
// 程序执行到这里,说明窗口已经创建成功
while(!done)          // Loop That Runs While done=FALSE
{
   // 调用API函数PeekMessage检查消息队列,如果消息队列中存在消息,则返回TRUE
   // PMPM_REMOVE说明:当调用PeekMessage函数处理完该消息的之后,从消息队列中移除
   if (PeekMessage(&msg,NULL,0,0,PM_REMOVE)) // Is There A Message Waiting?
   {
    // 如果消息映射为退出,则设置done=TRUE,终止while循环
    // 消息队列中,可以接收WM_QUIT消息映射,从而被PeekMessage函数处理,退出消息循环
    if (msg.message==WM_QUIT)     // Have We Received A Quit Message?
    {
     done=TRUE;        // If So done=TRUE
    }
    // 如果消息映射不为退出,则调用API函数TranslateMessage翻译该消息
    // TranslateMessage翻译消息过程为:根据MSG结构中的UINT message成员,将键消息转换为字符消息
    // 然后,字符消息被发送到调用线程的消息队列中,以备线程下次调用的时候读取该消息
    // 最后,调用API函数DispatchMessage将消息派发到指定的窗口进程
    else          // If Not, Deal With Window Messages
    {
     TranslateMessage(&msg);     // Translate The Message
     DispatchMessage(&msg);     // Dispatch The Message
    }
   }
   else           // If There Are No Messages
   // 调用API函数PeekMessage检查消息队列,如果消息队列中不存在消息...
   {
    // Draw The Scene. Watch For ESC Key And Quit Messages From DrawGLScene()
    // 如果程序被激活,即active=TRUE(默认情况下定义的全局BOOL变量)
    // 判断是否按下了ESC键
    if (active)         // Program Active?
    {
     // 如果按下了ESC键,则设置done为TRUE,从而导致while循环终止,退出
     if (keys[VK_ESCAPE])     // Was ESC Pressed?
     {
      done=TRUE;       // ESC Signalled A Quit
     }
     // 如果没有按下ESC键,则调用DrawGLScene()函数设置OpenGL窗口的背景
     else         // Not Time To Quit, Update Screen
     {
      DrawGLScene();      // Draw The Scene
      // 如果被设备上下文(Device Context)引用的某个窗口的像素格式buffer
      // 包含front buffer和back buffer,则交换这两个buffer
      // 如果不包含back buffer,则API函数的调用不起任何作用
//------------------// 为何要交换这两个buffer呢????
      SwapBuffers(hDC);     // Swap Buffers (Double Buffering)
     }
    }
    // F1键用于在全屏模式和窗口模式之间进行切换
    // 如果按下F1建
    if (keys[VK_F1])       // Is F1 Being Pressed?
    {
     keys[VK_F1]=FALSE;      // If So Make Key FALSE
     // 销毁当前正在显示的OpenGL窗口
     // 因为是在两种模式之间切换,所以窗口一定变化了
     // 因此,需要销毁当前窗口,并设置OpenGL窗口是否全屏显示的BOOL变量为!fullscreen
     KillGLWindow();       // Kill Our Current Window
     fullscreen=!fullscreen;     // Toggle Fullscreen / Windowed Mode
     // Recreate Our OpenGL Window
     // 调用自定义函数CreateGLWindow重新创建OpenGL窗口
     // 如果创建失败,则返回
     if (!CreateGLWindow("NeHe's OpenGL Framework",640,480,16,fullscreen))
     {
      return 0;       // Quit If Window Was Not Created
     }
    }
   }
}

// Shutdown
// 如果while循环终止了,也就是说done被设置成了TRUE
// 表示程序已经处于不再运行的状态,需要销毁OpenGL窗口
KillGLWindow();          // Kill The Window
// 返回消息MSG结构中的wParam附加消息信息
return (msg.wParam);        // Exit The Program
}

关于程序中调用的基于VC的自定义函数,可以参考Jeff Molofee的第一课源代码示例。我只把涉及到OpenGL内容的函数做一个学习,解释,并总结。

上面主函数的执行,具体实现过程总结如下:

1、初始化消息结构MSG msg和消息循环控制标志BOOL done。
2、判断是用全屏模式显示,还是用窗口模式显示,置fullscreen标志。
3、创建窗口,如果失败则退出,否则继续执行WinMain函数。
4、根据done标志,判断是否进入消息循环:如果是,继续向下执行,否则转到7执行。
5、
如果线程消息队列中存在消息:若是WM_QUIT消息,置done=TRUE,退出消息循环,转到7执行;若不是M_QUIT消息,翻译并派发消息到线程消息队列中;
如果线程消息队列中不存在消息:只有active标志为TRUE,即程序处于激活状态,才判断用户是否对键盘ESC键(退出程序)和F1键(显示模式切换)按下了,并做出处理;如果没有按下ESC键,则显示OpenGL窗口背景,并切换缓冲区。
6、转到4,进入消息循环。
7、关闭OpenGL窗口,退出程序。

要说明的一点是,bool active=TRUE;是一个全局的BOOL变量,初始化时是处于激活状态的,它用于标识当前的窗口程序是否因为接收到了消息而改变状态。在创建OpenGL窗口的时候,WNDCLASS结构的成员lpfnWndProc指向了(WNDPROC) WndProc这个窗口进程,所以在改变active的状态是在WndProc这个回调函数中,主要通过如下这个case语句:

   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
   }

int InitGL(GLvoid)

在自定义的CreateGLWindow函数中调用了InitGL函数,它用来初始化OpenGL,实现如下所示:

int InitGL(GLvoid)           // All Setup For OpenGL Goes Here
{
glShadeModel(GL_SMOOTH);        // Enable Smooth Shading
glClearColor(0.0f, 0.0f, 0.0f, 0.5f);     // Black Background
glClearDepth(1.0f);          // Depth Buffer Setup
glEnable(GL_DEPTH_TEST);        // Enables Depth Testing
glDepthFunc(GL_LEQUAL);         // The Type Of Depth Testing To Do
glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST); // Really Nice Perspective Calculations
return TRUE;           // Initialization Went OK
}

上面:

glShadeModel函数用来设置阴影的效果,主要有GL_SMOOTH和GL_FLAT两种效果,其中GL_SMOOTH为默认值,表示平滑阴影效果;

glClearColor函数设置背景颜色,参数基于RGBA颜色模型,需要4个分量的颜色值;

glClearDepth函数设置深度缓冲区的,它的含义就在OpenGL窗口绘制的图形深入到屏幕中的程度,深度的意义就是在三维空间中的z坐标的数值,z取0时表示在平面上,你就看不到窗口中的图形了,所以负值越小,越远离窗口平面向里,说明窗口中的图形离我们观察者的距离变远了;

glEnable(GL_DEPTH_TEST); 用来开启更新深度缓冲区的功能,也就是,如果通过比较后深度值发生变化了,会进行更新深度缓冲区的操作;

glDepthFunc(GL_LEQUAL);在调用glEnable(GL_DEPTH_TEST); 开启这个功能以后,当深度变化小于等于当前深度值时,更新深度值。

glHint函数用来规范OpenGL的行为的,使得图形能够更好地被控制以显示在窗口中。

int DrawGLScene(GLvoid)

然后,为了在OpenGL窗口中进行绘图操作,需要设定一个场景,在DrawGLScene函数中实现:

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

上面:

glClear函数用来清除屏幕为一种指定的颜色,也就是通过glClearColor函数指定的颜色,来设定当前屏幕的颜色;

glLoadIdentity函数是用一个4*4单位矩阵来替换当前的矩阵。

GLvoid ReSizeGLScene(GLsizei width, GLsizei height)

在创建一个OpenGL窗口完成之后,需要对这个窗口进行初始化,是在ReSizeGLScene函数中实现的:

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

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
}

上面:

glViewport函数用来设置在窗口中的视区的,即我们要在该函数指定的视区内所能观察的范围;

glMatrixMode函数用来设置当前矩阵模式的,在设定的矩阵模式基础之上,可以进行矩阵运算来进行各种变化,它有三个取值:

GL_MODELVIEW    模型视图矩阵
GL_PROJECTION   投影矩阵
GL_TEXTURE         纹理矩阵

gluPerspective函数设置视景,即观察者能够看到的窗口中的图形的状态,它主要是在前面进行的矩阵设置基础之上,根据参数进行矩阵运算。

总结

在学习中:

InitGL函数中初始化一般不需要变化了;

如果要进行场景的设置,可以通过在ReSizeGLScene函数中进行设置;

如果要实现一些画图的操作,当然是在DrawGLScene函数中进行编程。

原创粉丝点击