第一章介绍了如何在Windows下仅使用VS或SDK自带库创建OpenGL环境的方法,并极简单的介绍了Windows平台上OpenGL的历史。微软开发OpenGL的历史如此之短,之后就全力开发自己的亲儿子:DirectX。从此之后Windows平台对OpenGL的内置支持就停留在OpenGL 1.1版。当然,在Vista之后,微软甚至开发了一个OpenGL到DirectX的转换层,支持到OpenGL 1.4,这使得系统在没有安装支持OpenGL的显卡驱动时仍然能够运行一部分基于OpenGL的程序。
OpenGL并非为Windows而生,事实上在除了Windows的其它几乎所有操作系统平台上,OpenGL都扮演着更为重要的角色。
那么,在Windows平台上如何使用最新版本的OpenGL函数呢?这些函数从何而来呢?这一章主要谈论这两个问题。
2.1 访问OpenGL最新版本中的函数
在第一章提到过,与其他任何库一样,OpenGL必须有能力操作系统中的显示设备。而现代操作系统都不允许应用直接操作硬件设备,因此对硬件的操作要么需要开发硬件驱动,要么需要使用操作系统提供的操作接口。
微软已经差不多18年没有对OpenGL的支持做过重大更新了,显然windows操作系统不可能提供更多有关OpenGL的接口函数。这样,新的OpenGL函数只能通过驱动程序提供。
事实也正是如此,OpenGL提供了一组规范,但这些规范不是给应用程序人员用的,是提供给显卡开发商的,根据这组规范,显卡开发商开发的显卡驱动可以提供对OpenGL的支持。比如在我的机器上,GeForce 605的显卡驱动就提供了对OpenGL4.3.0的支持。
所有库的开发中,能够设计一个预留的“升级接口”是非常高明的,OpenGL就是如此。opengl32.dll是windows系统自带的对OpenGL驱动访问的接口库,从windows 95开始,只要装好系统它就存在。在windows 7上通过VS的dumpbin.exe查看opengl32.dll可以看到它导出了368个函数。
其中:
125 7C 00003CED glGetString
356 163 0003C245 wglGetProcAddress
这两个函数非常重要,glGetString函数在第一章中已经使用过了,利用它可以了解系统中OpenGL相关的各种信息。
wglGetProcAddress也是个非常重要的函数,通过给出OpenGL函数名,就可以利用wglGetProcAddress获取函数指针,从而可以访问到openGL最新版本中的所有函数。
注意:只要opengl32.dll能支持的OpenGL函数,当应用程序调用时,OpenGL32.dll总是将其“转发”给显卡驱动的实现,因此总是最新的实现,而不是1.1版本的实现。
现在,可以整理一个在windows平台使用OpenGL最新版本函数的步骤:
1) 使用第1章中的知识点创建OpenGL环境
2) 从OpenGL官方网站或任何其它文档中查找OpenGL函数名
3) 调用wglGetProcAddress(“functionname”)获得函数地址
4) 组织参数,利用函数指针调用特定函数
2.2 使用第三方库
按照2.1节中的步骤去使用OpenGL在理论上是完全可行的,但在实践上存在两个主要困难:
1)每次由程序员手工编码完成2.1节中的4个步骤是非常烦人的事情
2)仅仅有了函数指针还是不够的,还需要知道调用这个函数所需要的数据类型,
相关宏定义、相关枚举值,……,总之这是一个非常复杂的事情
因此,就有了第三方库,第三方库基本上就是根据OpenGL规范,由一群非常熟悉OpenGL的人弄出来的(否则怎么能知道每个枚举值?知道每个结构体的定义?)。
第三方库有不少,但最常见于各类书籍和代码的有两个:1)glut/freeglut 2)glew。当然其它的还有glee、glfw……。
2.2.1 GLEW库
Glew更多的是提供了对OpenGL扩展函数的支持。当然也包括基本OpenGL函数的支持。但它不提供其它类似窗体,键盘事件的支持函数。在与freeglut混和使用时,应该将#include <gl/glew.h>放在#include<gl/glut.h>之前,这样可以保证使用了最新版本的OpenGL函数。
Glew又分为了glew和glew_mx,前者是单线程版,后者是线程安全版。因为OpenGL的Rendering Context限制了每线程一个。如果需要在一个程序里同时出现多个OpenGL窗口,用glew mx是更好的方案。
2.2.2 glut/freeglut
Glut和freeglut,前者已经停止开发了,至少是进展缓慢。后者是前者的替代品,目前正在迅速开发中,能保持与最新的OpenGL一致。该库的主要优点是提供了一组简单的“窗体、鼠标、键盘事件”操作函数,能方便OpenGL学习程序的开发。当然该库也支持基本的OpenGL函数。
2.3 如何选择第三方库
如果只是需要使用最新的OpenGL函数,那么就选择Glew库,这时只需要使用第1章中的知识创建好OpenGL环境,然后使用glew中导出的函数即可。
如果想按照一些书籍上的例子进行实践,那一般是要选择freeglut或将其与glew结合使用。
既然有了第三方库,后续的章节就不再研究如何使用“纯粹”的windows自带的OpenGL库,而是研究如何使用第三方库进行开发。
2.4使用glew的示例
这一节对1.3中的测试程序进行尽可能少的扩充,使其成为一个简单的仅仅使用glew库,且是窗口形式的OpenGL程序。创建一个空的win32项目,链接库需要opengl32.lib 和glew32.lib
#include <Windows.h>
#include <gl/glew.h>
#include <GL/wglew.h>
///////////////////////////////////////////////////////////////////////////////
//窗口回调函数
LRESULT CALLBACK WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
switch (uMsg)
{
case WM_SIZE://与OpenGL交互
glColor3f(1.0,1.0,0.0);
glBegin(GL_TRIANGLES);
glVertex2f(-1.0,0.0);
glVertex2f(1.0,0.0);
glVertex2f(0.0,1.0);
glEnd();
glFlush();
break;
case WM_ACTIVATE://与OpenGL交互
glColor3f(1.0,0.0,1.0);
glBegin(GL_TRIANGLES);
glVertex2f(-1.0,0.0);
glVertex2f(1.0,0.0);
glVertex2f(0.0,1.0);
glEnd();
glFlush();
break;
case WM_CLOSE:
if(MessageBox(hWnd,TEXT("你要关闭窗口吗?"),TEXT("提示!"),MB_OKCANCEL) == IDOK)
DestroyWindow(hWnd);
break;
case WM_DESTROY:
PostQuitMessage(0);
return 0;
default:
return DefWindowProc(hWnd,uMsg,wParam,lParam);
}
return 0;
}
////////////////////////////////////////////////////////////////////
// 入口函数
int _stdcall WinMain(HINSTANCE hInst,HINSTANCE hPreInstance,LPSTR lpCmdLine,int nShowCmd)
{
HWND hWnd;
HGLRC hRC;
HDC hDC;
HINSTANCE hInstance;
hInstance = hInst;
WNDCLASS windClass;
TCHAR szWindowName[50] = TEXT("OpenGL_Window");
TCHAR szClassName[50] = TEXT("OpenGL_Class");
//初始化窗口结构体
windClass.lpszClassName = szClassName;
windClass.lpfnWndProc = (WNDPROC)WndProc;
windClass.hInstance = hInstance;
windClass.hCursor = LoadCursor(NULL, IDC_ARROW);
windClass.hIcon = LoadIcon(NULL, IDI_WINLOGO);
windClass.hbrBackground = NULL;
windClass.lpszMenuName = NULL;
windClass.style = CS_HREDRAW | CS_OWNDC | CS_VREDRAW;
windClass.cbClsExtra = 0;
windClass.cbWndExtra = 0;
//注册窗口类
if(!RegisterClass( &windClass )) return 1;
//创建窗口
hWnd = CreateWindowEx(0, // 窗体扩展风格
szClassName, // 窗体类名称
szWindowName, // 窗体名称
0, // 窗体风格
0, // 窗体在桌面上的位置,x坐标
0, // 窗体在桌面上的位置, y坐标
0, // 宽
0, // 高
NULL, // 父窗口
NULL, // 菜单
hInstance, // 实例
NULL);
hDC = GetDC(hWnd);
PIXELFORMATDESCRIPTOR pfd;
SetPixelFormat( hDC, 1,&pfd);
hRC = wglCreateContext( hDC );
wglMakeCurrent( hDC, hRC );
//一旦wgl被初始化,可以撤消相关环境
//之后可以利用wgl函数,调用最新版的OpenGL建立真正使用的环境
GLenum ret = glewInit();
if (GLEW_OK != ret)
{
MessageBox(NULL,(LPCSTR)glewGetErrorString(ret),"glew初始化",MB_OK);
}
//释放OpenGL环境,wgl函数已经在调用glewInit()时完成了初始化
//释放环境后仍然可以使用
wglMakeCurrent(NULL, NULL);
wglDeleteContext(hRC);
ReleaseDC(hWnd, hDC);
//DestroyWindow(hWnd);
//下面创建真正可用的OpenGL渲染环境
//第1)步:创建一个真正可用的窗口
DWORD dwExtStyle;
DWORD dwWindStyle;
dwExtStyle = WS_EX_APPWINDOW | WS_EX_WINDOWEDGE;
dwWindStyle = WS_OVERLAPPEDWINDOW;
ShowCursor(TRUE);
// Create the window again
hWnd = CreateWindowEx(dwExtStyle, // 窗体扩展风格
szClassName, // 窗体类名称
szWindowName, // 窗体名称
dwWindStyle | WS_CLIPSIBLINGS | WS_CLIPCHILDREN,// 窗体风格
CW_USEDEFAULT, // 窗体在桌面上的位置,x坐标
CW_USEDEFAULT, // 窗体在桌面上的位置, y坐标
200, // 宽
200, // 高
NULL, // 父窗口
NULL, // 菜单
hInstance, // 实例
NULL);
hDC = GetDC(hWnd);
int nPixCount = 0;
//利用OpenGL查询函数查找最符合要求的OpenGL环境特性
//需要#include <gl/wglew.h>
int pixAttribs[] = { WGL_SUPPORT_OPENGL_ARB, GL_TRUE, //要求支持OpenGL
WGL_DRAW_TO_WINDOW_ARB, GL_TRUE, //可以绘制到某个窗口
WGL_ACCELERATION_ARB, WGL_FULL_ACCELERATION_ARB, //必须完全支持硬件加速
WGL_RED_BITS_ARB, 8, //红色为8位精度
WGL_GREEN_BITS_ARB, 8, //绿色为8位精度
WGL_BLUE_BITS_ARB, 8, //蓝色为8位精度
WGL_DEPTH_BITS_ARB, 16, //深度为16位精度
WGL_PIXEL_TYPE_ARB, WGL_TYPE_RGBA_ARB, //使用RGBA类型的像素
0}; //必须以0结尾
int nPixelFormat = -1;
//查询系统中是否存在满足要求的环境像素格式
wglChoosePixelFormatARB(hDC, &pixAttribs[0], NULL, 1, &nPixelFormat, (UINT*)&nPixCount);
if(nPixelFormat == -1)
{
//不存在满足要求的环境,则释放数据,结束程序
ReleaseDC(hWnd, hDC);
DestroyWindow(hWnd);
MessageBox(NULL,"OpenGL不支持查询的格式","错误",MB_OK);
return 1;
}
else
{
//找到满足要求的格式,则设置其为当前像素格式,并创建OpenGL环境
SetPixelFormat( hDC, nPixelFormat, &pfd );
//指定OpenGL版本为1.10
GLint attribs[] = {WGL_CONTEXT_MAJOR_VERSION_ARB, 1,WGL_CONTEXT_MINOR_VERSION_ARB, 1, 0 };
hRC = wglCreateContextAttribsARB(hDC, 0, attribs);
if (hRC == NULL)
{
MessageBox(NULL,"无法创建OpenGL环境","错误",MB_OK);
return 2;
}
//成功则绑定OpenGL渲染环境(hRC)至窗口设备环境(hDC)
wglMakeCurrent( hDC, hRC );
//显示窗口
ShowWindow( hWnd, SW_SHOW );
UpdateWindow(hWnd);
//使用旧风格的OpenGL创建一个三角形
//新风格的OpenGL编程需要创建GLSL文件
//为了简单,先使用这种古老的glBegin...glEnd风格
glColor3f(1.0,0.0,0.0);
glBegin(GL_TRIANGLES);
glVertex2f(-1.0,0.0);
glVertex2f(1.0,0.0);
glVertex2f(0.0,1.0);
glEnd();
glFlush();
}
//进入窗口主循环
MSG Msg;
while(GetMessage(&Msg,NULL,NULL,NULL))
{
TranslateMessage(&Msg);
DispatchMessage(&Msg);
}
ReleaseDC(hWnd,hDC);
return 0;
}
2.5 运行结果
调整窗口大小时,消息处理函数会收到WM_SIZE消息,窗口从最小化或被遮挡等状态重新变成“激活”状态时,消息处理函数会收到WM_ACTIVATED消息,为了显示与OpenGL的交互,在两个消息中绘制了不同颜色的三角形。
说明:
左图:初始状态下绘制的三角形
中图:调整窗口大小时绘制的三角形
右图:激活窗口时绘制的三角形