OpenGL系统设计-2 第一个OpenGL程序

 

1         第一个OpenGL程序

 

俗话说,“工欲善其事,必先利其器”,一个好的开发工具能够使你将注意力其中在程序设计本身,做到事半功倍,反之,可能经常需要解决开发工具的问题。我们建议使用微软公司的Visual C++ 6.0,如果使用Visual C++ .NET也可以,当然使用Borland C++C++ Builder或者Watcom C++也完全可以,这也要看个人的爱好。

之所以把一个“Hello World”程序单独算作一章,是因为在这一章很重要,我们要先奠定OpenGL应用程序的框架,以后的程序将使用这一框架。

 

1.1        环境设置

Windows系统中,opengl32.dllglu32.dll提供对OpenGL的支持,所以应该在编译OpenGL程序时链接相应的库文件。

    通常可以在程序的开头加入以下代码来达到链接库文件的目的,如果用到了辅助函数,还需要链接辅助函数库glaux.lib:

    #pragma comment(lib, "opengl32.lib")

    #pragma comment(lib, "glu32.lib")

    #pragma comment(lib, "glaux.lib")

 

    另一种办法是在VC中设置好工程的参数如图1-7所示通过菜单Project->Settings...->Linkopengl32.libglu32.libglaux.lib如果需要的话三个文件加入工程中。

   

 

    1-7   VC++静态库设置

 

1.2        OpenGL应用框架

    开发环境设置完毕后,我们开始第一个OpenGL程序“Hello World”。因为三维图形通常对性能和速度要求比较高,我们就使用Win32 API来写代码,而不使用MFC。实际上,真正的大型高性能三维图形软件和游戏软件是很少会使用MFC来写的。

    首先使用VC的Win32 Application Wizard创建一个Hello World的Win32程序,该工程一共包含9个文件,Hello.cpp、Hello.rc、StdAfx.cpp、Hello.h、resource.h、StdAfx.h、Hello.ico、small.ico和Readme.txt。

    把StdAfx.cpp、StdAfx.h两个文件删除,再通过菜单Project->Settings...->C/C++将Project Options编译开关中的 /Yu"stdafx.h" 去掉,然后将Hello.cpp按照以下方式进行修改。

 

#include "windows.h"           //Windows标准API的头文件

#include "resource.h"     //本程序资源定义文件

 

//增加opengl的头文件

#include "gl/gl.h"           //OpenGL32库的头文件

#include "gl/glu.h"         //glu32库的头文件

#include "gl/glaux.h"      //辅助库的头文件

 

#define WIN32_LEAN_AND_MEAN          //表示不使用MFC

#define WIDTH            640                //窗口的宽度

#define HEIGHT           480                //窗口的高度

#define BITS                16                  //程序的像素格式

 

    然后定义几个全局变量

 

HGLRC hRC=NULL;         //渲染描述表句柄

HDC hDC=NULL;           //设备描述表句柄

HWND hWnd=NULL;         //当前窗口句柄

HINSTANCE hInst;        //当前程序实例句柄

 

    其中hDChWndhInst三个变量很容易理解重要的是第一个变量hRC它是OpenGL程序的特色表示渲染描述表Rendering Context。它将所有的OpenGL调用全部连接到对应的设备描述表hDC上,hDC通过图形设备接口GDI将窗口联结为一体,通过Windows系统完成显示。

 

    然后再定义一个表示应用是否激活的标志bActive:

 

BOOL bActive;           //当前应用程序是否处于激活状态

 

    下面几行在原来的文件中有,不用修改。

 

TCHAR szTitle[MAX_LOADSTRING];      // The title bar text

TCHAR szWindowClass[MAX_LOADSTRING];    // The title bar text

 

// Foward declarations of functions included in this code module:

ATOM                MyRegisterClass(HINSTANCE hInstance);

BOOL                InitInstance(int Width, int Height, int Bits);//这里有变化

                    //原来的函数有两个参数HINSTANCEnCmdShow,这里改成更加有用的

                    //窗口大小和像素格式

 

LRESULT CALLBACK    WndProc(HWND, UINT, WPARAM, LPARAM);

 

    接下来是我们自定义的函数原型说明,用于处理OpenGL的初始化,主流程和OpenGL的关闭。

 

void glMain(){};

void glShutdown(){};

void glInit(){};

   

然后进入我们程序的入口WinMain做一些改动。

int APIENTRY WinMain(HINSTANCE hInstance,

                     HINSTANCE hPrevInstance,

                     LPSTR     lpCmdLine,

                     int       nCmdShow)

{

    // TODO: Place code here.

    MSG msg;

    //HACCEL hAccelTable;

 

    // Initialize global strings

    LoadString(hInstance, IDS_APP_TITLE, szTitle, MAX_LOADSTRING);

    LoadString(hInstance, IDC_HELLO, szWindowClass, MAX_LOADSTRING);

    MyRegisterClass(hInstance);

    hInst = hInstance;

 

    // Perform application initialization:

    if (!InitInstance (WIDTH, HEIGHT, BITS))

    {

        return FALSE;

    }

 

    //hAccelTable = LoadAccelerators(hInstance, (LPCTSTR)IDC_HELLO); 

 

    // 下面的消息主循环有变化

 

    //初始化OpenGL

    glInit();

 

    while(TRUE)

    {

        //检查队列是否有消息,若有就取出。这里使用PM_REMOVE表示取出后从队列中删除。

    if (PeekMessage(&msg,NULL,0,0,PM_REMOVE))

{

       //如果是WM_QUIT消息就停止

       if (msg.message == WM_QUIT)

           break;

   

       TranslateMessage(&msg);

 

       //将消息发送到窗口函数WndProc

       DispatchMessage(&msg);

    } // end if

   

   

//只有程序处于激活状态时才进入OpenGL的主程序进行绘制

    if(bActive)

    {

       glMain();

    }

      

    } // end while

 

    //程序结束前关闭OpenGL

    glShutdown();

 

    return msg.wParam;

 

} // end WinMain

 

    WinMain函数中我们看到了增加两个函数glMain()glShutdown()。其中对OpenGl的各种变换、处理、显示都在glMain()中,它是我们的OpenGL应用真正的主函数。当程序收到退出消息时,先关闭OpenGL,这些在glShutdown()中实现。

   

    接下来我们看MyRegusterClass函数,该函数仅仅是注册窗口函数,几乎不用改变。

ATOM MyRegisterClass(HINSTANCE hInstance)

{

    WNDCLASSEX wcex;

 

    wcex.cbSize = sizeof(WNDCLASSEX);

 

    wcex.style          = CS_HREDRAW | CS_VREDRAW;

    wcex.lpfnWndProc    = (WNDPROC)WndProc;

    wcex.cbClsExtra     = 0;

    wcex.cbWndExtra     = 0;

    wcex.hInstance      = hInstance;

    wcex.hIcon          = LoadIcon(hInstance, (LPCTSTR)IDI_HELLO1);

    wcex.hCursor        = LoadCursor(NULL, IDC_ARROW);

    wcex.hbrBackground  = (HBRUSH)(COLOR_WINDOW+1);

    wcex.lpszMenuName   = NULL; //这里不使用菜单因此在对应的资源中

                                //菜单资源和About对话框资源都可以删除。

    wcex.lpszClassName  = szWindowClass;

    wcex.hIconSm        = LoadIcon(wcex.hInstance, (LPCTSTR)IDI_SMALL);

 

    return RegisterClassEx(&wcex);

}

   

    再下来是InitInstance函数,这个函数很重要整个OpenGL就从这里开始。它和VC生成的InitInstance()函数不同。原始的InitInstance有两个参数:程序实例hInstance和窗口显示方式nCmdShow。其中hInstance可以用全局变量hInst代替,nCmdShow可以使用固定的方式来实现。因此可以使用三个参数来创建窗口,这三个参数就是窗口的宽、高以及程序的像素格式。

    在InitInstance中,首先根据设定大小使用CreateWindowEx创建一个带有标题栏、可变大小边框、可最大最小化的窗口。然后使用Win32提供的OpenGL扩展函数ChoosePixelFormat选择像素格式,再调用SetPixelFormat设置程序的像素格式。接下来使用调用扩展函数wglMakeCurrent将当前的DC和OpenGL的RC连接起来。如果没有问题,就调用ShowWindow显示窗口。

    Windows创建的窗口大小是包括了客户区(Client Area),标题栏的。所以如果以指定的大小(例如640x480)调用CreateWindowEx,那么OpenGL在绘图时有可能会覆盖到标题栏上,这是我们应该避免的。因此,在调用CreateWindowEx创建窗口前,可以先使用AdjustWindowRectEx对窗口的大小进行调整,将得到的大小作为CreateWindowEx的参数,这样,得到的窗口本身已经比预设的值要大,其绘图客户区的大小就是预设的值。OpenGL在作图时就只会在客户区绘图,这正是我们所期望的。

 

 

BOOL InitInstance(int width, int height, int bits)

{

    uint PixelFormat;  //像素格式

       

    DWORD dwExStyle; // 扩展窗口风格

    DWORD dwStyle;  // 窗口风格

 

    //设置像素格式

    static PIXELFORMATDESCRIPTOR pfd=

    {

        sizeof(PIXELFORMATDESCRIPTOR),  //本结构的大小

            1,              //像素格式描述符的版本号

            PFD_DRAW_TO_WINDOW |        //该像素格式需要支持窗口

            PFD_SUPPORT_OPENGL |        //该像素格式需要支持OpenGL

            PFD_DOUBLEBUFFER,       //需要支持双缓冲

            PFD_TYPE_RGBA,      //RGBA格式

            bits,               //像素格式的颜色位数

            0,              //红色位

            0,              //RedShift

            0,              //绿色位

            0,              //GreenShift

            0,              //蓝色位

            0,              //BlueShift

            0,              //Alpha

            0,              //AlphaShift

            0,              //cAccumBits

            0,              //cAccumRedBits

            0,              //cAccumGreenBits

            0,              //cAccumBlueBits

            0,              //cAccumAlpha  

            16,             //cDepthBits16Z-缓冲

            0,              //cStencilBits模板缓冲

            0,              //cAuxBuffers辅助缓冲

            PFD_MAIN_PLANE,     //iLayerType在主绘图层绘图

            0,              //保留字节

            0,              //dwLayerMask

            0,              //dwVisibleMask

            0};                 //dwDamageMask

 

   

    //用一个矩形结构保存窗口的大小然后根据该矩形的大小调整窗口大小。

    RECT Rt;

    Rt.left= 0;

    Rt.right= width;

    Rt.top= 0;

    Rt.bottom= height; 

 

    dwExStyle=WS_EX_APPWINDOW | WS_EX_WINDOWEDGE; //

    dwStyle=WS_OVERLAPPEDWINDOW;    //

   

    //对窗口大小进行调整达到真正的要求

    AdjustWindowRectEx(&Rt, dwStyle, FALSE, dwExStyle);

 

 

    //开始创建窗口注意窗口大小使用了调整后的大小

    if (!(hWnd=CreateWindowEx( dwExStyle,

        szWindowClass, 

        szTitle,

        WS_CLIPSIBLINGS |   //避免其他应用在本窗口绘图

        WS_CLIPCHILDREN |   //避免其他应用在本窗口绘图

        dwStyle,

        0, 0,

        Rt.right-Rt.left,

        Rt.bottom-Rt.top,

        NULL,

        NULL,

        hInst,

        NULL))) 

    {

        //如果窗口创建失败则关闭OpenGL使用的资源并且弹出一个显示错误的对话框。

        glShutdown();                                        

        MessageBox(NULL,"Fail to Create Window!","ERROR",MB_OK);

        return FALSE;                         

    }

 

   

    //取得当前窗口的设备描述表DC

    hDC = GetDC(hWnd);

    if(!hDC)            //如果失败则释放OpenGL占用的资源并返回

    {

        glShutdown();                                     

        MessageBox(NULL,"Fail to get DC from window!","ERROR",MB_OK);

        return FALSE;                                     

    }

 

    //使用Win32OpenGL扩展函数选择指定的像素格式

    PixelFromat = ChoosePixelFormat(hDC, &pfd);

    if(!PixelFormat)

    {          

        //如果找不到匹配的像素格式则释放OpenGL占用的资源并返回

        glShutdown();                                                     

        MessageBox(NULL,"Fail to Choose Pixel Format!","ERROR",MB_OK);

        return FALSE;                                                     

    }

 

    //将设备描述表设置成指定的像素格式

    if(!SetPixelFormat(hDC,PixelFormat,&pfd))

    {

        glShutdown();

        MessageBox(NULL,"Fail to Set Pixel Format!","ERROR",MB_OK);

        return FALSE;                                            

    }

 

    //通过扩展函数创建基于取得的设备描述表hDC的渲染描述表hRC

    if (!(hRC=wglCreateContext(hDC)))    

    {

        glShutdown();

        MessageBox(NULL,"Fail to create RC from DC!","ERROR",MB_OK);

        return FALSE;                                            

    }

 

    //将取得的设备描述表和渲染描述表关联起来

    if(!wglMakeCurrent(hDC,hRC))        

    {

        glShutdown();                

        MessageBox(NULL,"Fail to link RC to DC","ERROR",MB_OK);

        return FALSE;                                       

    }

 

    //显示窗口显示方式是SW_NORMAL

    ShowWindow(hWnd,SW_SHOW);

    return TRUE; //InitInstance初始化成功返回TRUE

}

 

    在窗口消息处理函数WndProc由于没有菜单我们就把WM_COMMAND的消息处理分支去掉同时由于我们的绘图由OpenGL控制因此也把WM_PAINT消息处理分支去掉。然后添加WM_ACTIVATE消息处理分支,以根据程序是否激活来决定是否处理。WndProc函数如下:

 

LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)

{

    //把不需要的注释掉

    //int wmId, wmEvent;

    //PAINTSTRUCT ps;

    //HDC hdc;

    //TCHAR szHello[MAX_LOADSTRING];

    //LoadString(hInst, IDS_HELLO, szHello, MAX_LOADSTRING);

 

    switch (message)

    {

        //判断窗口是否激活

        case WM_ACTIVATE:

        {

            if (!HIWORD(wParam))

            {

                bActive=TRUE;

            }

            else

            {

                bActive=FALSE;

            }

            return 0;

        } 

 

        case WM_DESTROY:

            PostQuitMessage(0);

            break;

        default:

            return DefWindowProc(hWnd, message, wParam, lParam);

   }

   return 0;

}

 

    About函数也去掉,然后进行编译,运行之后可以看到只有一个空的窗口如图1-8所示。

 

 

图1-8   OpenGL框架

 

你可能感兴趣的:(null,application,扩展,callback,开发工具,initialization)