1 第一个OpenGL程序
俗话说,“工欲善其事,必先利其器”,一个好的开发工具能够使你将注意力其中在程序设计本身,做到事半功倍,反之,可能经常需要解决开发工具的问题。我们建议使用微软公司的Visual C++ 6.0,如果使用Visual C++ .NET也可以,当然使用Borland C++、C++ Builder或者Watcom C++也完全可以,这也要看个人的爱好。
之所以把一个“Hello World”程序单独算作一章,是因为在这一章很重要,我们要先奠定OpenGL应用程序的框架,以后的程序将使用这一框架。
1.1 环境设置
在Windows系统中,opengl32.dll、glu32.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...->Link将opengl32.lib、glu32.lib和glaux.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; //当前程序实例句柄
其中hDC、hWnd、hInst三个变量很容易理解,重要的是第一个变量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);//这里有变化
//原来的函数有两个参数HINSTANCE和nCmdShow,这里改成更加有用的
//窗口大小和像素格式
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);
w