GLFW:一个(简单的)OpenGL 框架库






4.89/5 (23投票s)
一个易于使用的库,可快速设置和运行OpenGL应用程序。
背景
我将尽量保持事情的简单性,但我必须假设您至少具备编写Win32应用程序的基础知识,以及在Win32环境下进行OpenGL编程的最小知识。
引言
在我看来,编写OpenGL应用程序中最复杂(和/或最无聊)的事情之一就是准备所需的环境。即使是使用OpenGL在窗口中绘制3D场景的纯Win32应用程序的设置过程,与使用Direct3D完成相同过程相比,也相对简单,但它涉及许多步骤,这些步骤可能会给初学者带来一些麻烦。这个问题可以通过OpenGL实用工具包库(最广为人知的名称是GLUT)来巧妙地解决,该库允许您从纯Win32控制台应用程序开始,通过编写一些函数和进行一些库例程调用来设置和运行OpenGL应用程序。但是,即使GLUT确实很棒,它也不适合所有情况:它不像“干净”的Win32应用程序那样具有所有灵活性,也不能用于商业应用程序。因此,我决定编写自己的框架/库,以简化我的OpenGL应用程序的设置过程,并为它们提供一些其他有用的功能。
介绍GLFW库
OpenGL(简单)框架或GLFW是一个静态库,可以链接到您的Win32应用程序,以简化创建和设置适合使用OpenGL渲染3D或2D场景的窗口所需的流程。该库还为您提供其他有用功能,例如
- 支持在多达10个不同窗口上进行多窗口渲染。
- 自动导入从1.2版本到您系统当前实现版本的所有OpenGL核心函数的入口点。
- 处理OpenGL扩展的实用函数。
GLFW的设计旨在易于使用,并附有详细的编译HTML格式(CHM)文档。该文档提供了所有\c glfwXXX()
例程的参考,以及解释如何使用该库的编程指南。我还添加了一些可以在\Examples目录中找到的示例应用程序代码。
该库仍处于BETA阶段,因此,上述某些功能的开发仍在进行中(或需要深入的测试/优化会话);但是,我认为它已经可以使用了,至少可以作为其功能演示。要获取库的当前限制和问题列表,请参阅文档文件中的“已知问题”页面。
关于导入OpenGL v1.2+例程
您可能知道,Microsoft对OpenGL的支持已停止在1.1版本。但是,您仍然可以通过一个名为wglGetProcAddress()
的特殊函数从您的显卡驱动程序访问OpenGL 1.2及以上版本的例程,该函数类似于用于获取DLL库中函数入口点的GetProcAddress()
例程。由于例程数量众多,通常的做法是只检索我们知道将在应用程序中使用(或更有可能使用)的OpenGL例程的入口点。在我看来,这是另一个乏味的问题,所以我决定让GLFW库为您检索**所有**例程的入口点:当创建第一个窗口时,该库会检测您的系统支持的OpenGL版本,并自动检索直到该版本的所有例程指针,并且它们的名称将与相关例程的名称相同(即,指向glWindowPos2i()
函数的指针的名称将是glWindowPos2i
)。这些指针可以通过包含glfw.h头文件来供用户访问。(它们在glfw_ext.h头文件中声明为extern
,该头文件包含在glfw.h中。请**不要**直接在代码中包含glfw_ext.h头文件!)不支持的例程指针以及属于您的系统不支持的OpenGL版本的核心OpenGL函数的指针将设置为NULL
,因此,**始终**在使用它们调用相关函数之前检查NULL
指向条件,否则,如果您的系统经常崩溃,请不要感到惊讶;-)
GLFW教程
在本节中,我将展示GLFW的一个示例用法:我将向您展示如何使用我的库设置和运行OpenGL应用程序。生成的输出将是一个简单的RGB填充三角形在一个黑色窗口中;但是,即使它不是很复杂,它也能完美地满足我们的要求。该操作包括我们将逐个分析的各种步骤。让我们开始吧。
步骤1 - 创建一个Win32项目并包含所有必需的东西
首先要做的是安装库。要做到这一点,请按照您将在库文档的“GLFW安装”部分找到的说明进行操作。完成之后,创建一个新的Win32项目(此过程取决于所使用的编译器)。编译器将自动为您生成一个.cpp源文件。现在,是时候添加所有必需的东西(库和头文件)了。这可以直接从代码中完成
// Include files #include "stdafx.h" #include <windows.h> #include <tchar.h> #include <stdio.h> #include <stdlib.h> #include "glfw.h" // GLFW library header file. // Import libraries #ifdef _DEBUG #pragma comment ( lib, "glfwVC6d.lib" ) #else #pragma comment ( lib, "glfwVC6r.lib" ) #endif
在上面的代码中,我们使用的是Visual C++ 6.0编译器,因此我们链接了glfwVC6x.lib库变体。请注意,没有为opengl32.lib和glu32.lib库提供链接指令,也没有为标准OpenGL头文件(gl.h和glu.h)提供#include
指令。这是因为GLFW库会自动为您链接OpenGL库,并且所有必需的头文件都包含在glfw.h头文件中。
步骤2 - 创建应用程序框架
您可能知道,所有显示窗口的Win32应用程序至少需要两个例程:WinMain()
函数,它是程序入口点,以及Windows消息处理程序过程。这两个函数应该在您创建项目时由编译器自动为您创建。无论如何,请确保它们看起来像下面的代码
// // Window message handler. // static LRESULT WINAPI MsgProc ( HWND hWnd, UINT iMsg, WPARAM wParam, LPARAM lParam ) { switch( iMsg ) { case WM_DESTROY: // Destroy window. break; case WM_SIZE: // Change the window size. break; case WM_PAINT: // Repaint window. ValidateRect( hWnd, NULL ); break; } return DefWindowProc ( hWnd, iMsg, wParam, lParam ); } // MsgProc() // // WinMain routine. // INT APIENTRY WinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, INT nCmdShow ) { MSG sMsg; // Begin the messages pump. while ( GetMessage( &sMsg, NULL, 0, 0 ) ) { TranslateMessage( &sMsg ); DispatchMessage( &sMsg ); } return 0; } // WinMain()
如果您看一下MsgProc()
函数,您会注意到switch语句中有三个消息处理程序的情况:WM_DESTROY
、WM_SIZE
和WM_PAINT
。这三个消息是我们必须处理的。
步骤3 - 使用GLFW创建窗口
现在,我们必须添加创建我们将用于绘制的窗口的代码。这将使用GLFW库提供的例程来完成:以下是WinMain()
例程的新版本,已修改以添加窗口创建代码
// // WinMain routine. // INT APIENTRY WinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, INT nCmdShow ) { MSG sMsg; // Initialize the GLFW library. glfwInit(); GLWF_WINPAR sWinPar; // Window parameters data structure. GLFW_WCTXT *pWinCntx;// Window Context pointer. // Initialize and fill the window parameters data structure: ZeroMemory( (PVOID)&sWinPar, sizeof( GLWF_WINPAR ) ); sWinPar.m_iPosX = 100; sWinPar.m_iPosY = 100; sWinPar.m_iWidth = 800; sWinPar.m_iHeight = 600; sWinPar.m_iBpp = GLFW_32BPP; sWinPar.m_iZDepth = GLFW_16BPP; sWinPar.m_szTitle = _T( "My 1st GLFW application." ); sWinPar.n_bFullScreen = FALSE; sWinPar.m_fpWindProc = MsgProc; // Create the window: glfwCreateWindow( &sWinPar, &pWinCntx ); // Set the Window Context as current: glfwSetCurrWinContext( pWinCntx ); // Begin the messages pump. while ( GetMessage( &sMsg, NULL, 0, 0 ) ) { TranslateMessage( &sMsg ); DispatchMessage( &sMsg ); } return 0; } // WinMain()
上面的代码将创建一个位于屏幕上、位置为(100, 100)的800 x 600 x32 bpp空白窗口。第一个值得注意的地方是对glfwInit()
例程的调用。此函数将在开始使用GLFW库之前初始化它。现在是创建窗口的代码:该函数声明了一个GLWF_WINPAR
数据结构(sWinPar
)和一个指向GLFW_WCTXT
数据结构的指针(pWinCntx
)。sWinPar
数据结构用于定义要创建的窗口的参数:大小、位置、位深度、标题等……同时注意,指向我们之前创建的窗口消息处理程序MsgProc()
的指针用于设置结构的m_fpWindProc
字段的值。一旦用所需的值设置了此结构的字段,就可以调用glfwCreateWindow()
函数了。此例程的参数是指向sWinPar
结构和指向pWinCntx
数据结构指针的指针(是的,是指向数据结构指针的指针)。pWinCntx
参数将用于将分配的窗口上下文的指针返回给调用者。调用glfwCreateWindow()
后,窗口即被创建。我们需要做的最后一件事是将返回的窗口上下文设置为当前活动的上下文。这通过调用glfwSetCurrWinContext()
例程来完成。请注意,在上面的代码示例中,我们故意省略了错误检查。请记住,在您的应用程序中始终检查glwfXXX()
函数返回的值,否则它们可能会无故崩溃。我们努力的结果可以在下图看到
步骤4 - 处理窗口消息
现在是时候修改MsgProc()
例程,添加一些代码来处理发送到我们窗口的消息了。如前所述,我们至少必须处理以下三个消息:WM_SIZE
、WM_PAINT
和WM_DESTROY
。前两个消息分别在我们用户调整窗口大小时以及需要重绘场景时发送到我们的窗口。我们将定义这两个消息发生时要调用的函数,但我们将在下一步(因为它们涉及OpenGL代码)延迟它们的实现。相反,我们将关注最后一个消息:WM_DESTROY
。当窗口被销毁时(由于用户关闭了应用程序或调用了glfwDestroyWindow()
例程),此消息将发送到我们的窗口。在这种情况下,我们必须释放分配的窗口上下文,为此,我们必须使用glfwDestroyContext()
例程。此函数需要一个指向要释放的窗口上下文的指针作为参数。可以通过使用glfwGetWinContext()
例程从窗口句柄获取,或者,如果我们知道要释放的窗口上下文是当前活动的上下文,则可以使用glfwGetCurrWinContext()
函数。我们将使用第一种方法,现在我们可以更新我们应用程序的代码了
// // Used when window size is changed. // static void ChangeWindowSize ( GLsizei iWidth, GLsizei iHeight ) { // Do something.. } // ChangeWindowSize() // // Render the scene using OpenGL // static void RenderTheScene ( void ) { // Do something.. } // RenderTheScene() // // Window message handler. // static LRESULT WINAPI MsgProc ( HWND hWnd, UINT iMsg, WPARAM wParam, LPARAM lParam ) { // Get the context of the window: GLFW_WCTXT *pContext; pContext = glfwGetWinContext( hWnd ); switch( iMsg ) { case WM_DESTROY: // Destroy window. // Destroy the Window Context: glfwDestroyContext( pContext ); // Send the quit message: PostQuitMessage( 0 ); break; case WM_SIZE: // Change the window size. ChangeWindowSize( LOWORD(lParam), HIWORD(lParam) ); break; case WM_PAINT: // Repaint window. RenderTheScene(); SwapBuffers( pContext->m_hDC ); ValidateRect( pContext->m_hWin, NULL ); break; } return DefWindowProc( hWnd, iMsg, wParam, lParam ); } // MsgProc()
步骤5 - 添加OpenGL代码
好的,现在我们来到了有趣的部分!我们必须为之前留空的两个例程编写代码:ChangeWindowSize()
和RenderTheScene()
。让我们从ChangeWindowSize()
开始:当用户调整窗口大小时会调用此函数。这里需要重置投影矩阵和视口
#define NEAR_PLANE 1.0 #define FAR_PLANE 200.0 #define FIELD_OF_VIEW 60.0 // // Used when window size is changed. // static void ChangeWindowSize ( GLsizei iWidth, GLsizei iHeight ) { GLfloat fAspectRatio; if ( iHeight == 0 ) iHeight = 1; // Set the new view port: glViewport( 0, 0, iWidth, iHeight ); // Setup the projection matrix: glMatrixMode( GL_PROJECTION ); glLoadIdentity(); fAspectRatio = ( (GLfloat)iWidth ) / ( (GLfloat)iHeight ); gluPerspective( FIELD_OF_VIEW, fAspectRatio, NEAR_PLANE, FAR_PLANE ); glMatrixMode( GL_MODELVIEW ); glLoadIdentity(); // Place the camera: gluLookAt( 0.0f, 0.0f, 80.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0 ); } // ChangeWindowSize()
现在,是时候编写渲染代码了。为了给出一个简单的示例,我们的RenderTheScene()
函数将在屏幕上绘制一个彩色三角形。这是代码
// // Render the scene using OpenGL // static void RenderTheScene ( void ) { // Clear the screen. glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT ); // Draw the scene: glBegin( GL_TRIANGLES ); glColor3f( 1.0f, 0.0f, 0.0f ); glVertex3f( -25.0f, -25.0f, 0.0f ); glColor3f( 0.0f, 1.0f, 0.0f ); glVertex3f( 0.0f, 25.0f, 0.0f ); glColor3f( 0.0f, 0.0f, 1.0f ); glVertex3f( 25.0f, -25.0f, 0.0f ); glEnd(); glFlush(); } // RenderTheScene()
最后要做的是在WinMain()
例程中添加一些代码来设置OpenGL环境,紧随glfwSetCurrWinContext()
函数调用之后
... // Set the Window Context as current: glfwSetCurrWinContext( pWinCntx ); // Set the shade model: glShadeModel( GL_SMOOTH ); // Set the clear screen color: glClearColor( 0.0f, 0.0f, 0.0f, 0.0f ); // Begin the messages pump. ...
步骤6 - 尽情享受!
现在我们的应用程序完成了(本教程也完成了)。您可以在下图看到结果。您现在可以修改代码来处理更多Windows消息,并且可以添加一个计时器来通过旋转三角形来动画场景。请注意,如果您希望应用程序以全屏模式运行而不是窗口模式运行,只需将sWinPar
结构m_fpWindProc
字段中使用的值从FALSE
更改为TRUE
。有关最后一个问题的更多详细信息,请参阅文档中的“编写全屏应用程序”部分。
演示应用程序
演示应用程序将向您展示一个非常简单的多窗口渲染示例,使用GLFW库。它将创建两个不同的窗口,都显示一个旋转的立方体。由于其简单性,我认为检查代码比在PC上运行它要好(将来,我将编写一个更复杂、更有趣的演示位;目前,我认为这个已经足够了)。
未来发展
正如我之前提到的,该库的开发尚未完成,因为仍有许多事情我需要/希望做。即使我对版本1.01.00R(第一个被认为是官方“发布”版本)中要添加的改进没有确定的计划,它肯定会包含对文档“已知问题”部分列出的所有问题的修复。我想我还会添加对向量数学的基本支持(至少,一种计算表面法线的方法),可能使用SSE2技术……
结论
我想指出,我的目的不是要写出比GLUT库更好的东西:我只是想为所有新手OpenGL程序员提供一种加速OpenGL应用程序设置的替代方法,同时保持纯Win32应用程序的所有强大功能和灵活性。如果您查看GLFW库所有核心例程的代码,您会发现它们使用了glXXX()
、gluXXX()
以及(显然)wglXXX()
函数,所以您可以将GLFW视为经典OS相关OpenGL例程的某种“高级”包装器。但是,它还提供了一些其他有用的功能(列表将在下一个库版本中增加)。
正如我在工作中通常做的那样,我正在等待您的反馈:您可以通过CodeProject评论系统与我联系。请将您的所有疑问、问题、评论、建议和错误报告发送给我。
致谢
非常、非常感谢DoxyGen开发团队!你们拯救了我的生命;-)
参考书目和网络引用
这是我在库开发过程中用作参考的书籍列表
- Shreiner, Woo, Neider, Davis:“OpenGL编程指南第5版”,Addison-Wesley。
- Shreider:“OpenGL参考手册,第4版”,Addison-Wesley。
- Wright, Lipchak:“OpenGL SuperBible,第3版”,SAMS。
- Fosner:“OpenGL for Windows 95 and Windows NT”,Addison-Wesley。
- LaMothe:“Windows游戏编程大师技巧,第2版”,SAMS。
网络资源
- 官方OpenGL网站.
- https://www.opengl.org/registry/:下载OpenGL规范、ARB扩展文档以及glext.h/wglext.h头文件。