65.9K
CodeProject 正在变化。 阅读更多。
Home

简单的 OpenGL 框架

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.46/5 (9投票s)

2012年4月23日

CPOL

20分钟阅读

viewsIcon

69626

downloadIcon

592

GLW 是一个简单、紧凑、可直接使用的框架,用于开发简单的 OpenGL 游戏和演示。它的重点是易用性、平台抽象和占用空间小。

 

Classical OpenGL Gears running in GLW framework.

在 GLW 框架中运行的经典 OpenGL 齿轮。

欢迎

欢迎来到 GLW – OpenGL Window!正如标题所示,GLW 是一个非常简单的 OpenGL 框架。你可以将其用作即插即用框架,在你的应用程序中打开一个 OpenGL 窗口。所谓即插即用,我的意思是只需将源文件和头文件复制到你自己的代码中,即可跳过任何库链接和加载。GLW 只包含一个头文件和一个源文件,所以只需包含 gwl.h,你就可以开始了。

或者,如果你希望将其用作共享库,你也可以将其编译为 dll。其中包含带源代码的解决方案和两个项目。一个使用静态 GLW(即插即用源代码),另一个链接到共享库。我已将 GLUT 3.6 源代码中的标准 GL Gears 改编为本文的演示。为了测试大小,有 3 种不同的项目配置,其中一种是极度优化大小的(不用于实际用途)。事实证明,在静态构建中,GLW 会为 exe 增加大约 3 kb。Dll 大约是 6.5 kb(磁盘上为 7 kb)。

Compiled binaries comparedin build folder.

 

在 build 文件夹中比较编译后的二进制文件

引言

GLW 以比标准的 Win32 API 更友好的 C 语言风格抽象了窗口和输入。实际上,为了使用它,你根本不需要深入研究 win32。如果你曾使用过 GLUT、GLFW 或类似的工具,你会立刻熟悉 GLW。GLW 被设计为 GLUT 的一个非常小的子集。然而,它是类 GLUT 的,但不是 GLUT 的克隆,所以不要期望 GLW API 与 GLUT 有 1:1 的对应关系。

背景

很久以前,我曾自己编写过一个名为 GLW 的库,它更像是一个 GLUT 克隆。我一直对这类框架是如何实现的很感兴趣,所以我自己构建了一个。我还处理了同时处理多个键盘、鼠标和游戏手柄/游戏摇杆(使用 raw_input)。我在自己的项目中使用了它,但有些细节过于混乱,无法公之于众。我也从未涉足 Linux 部分:)。从那时起,我的人生转向了其他事情,游戏编程对我来说已经搁置了好几年。不过,几天前我感到有些想做些有趣的事情,所以自然而然地,玩转 OpenGL 首先出现在我的列表上。于是我拾起了我旧的库,意识到我真的不需要它的大部分内容来进行简单的测试和演示,所以我清理了不必要的东西,留下了一个非常精简但仍然可用的框架,用于编写简单的演示或游戏。我的目标不是追求最少的千字节数,而是追求易用性和学习曲线的简短。

当然,编程 Win32 并不难,但同时它又丑陋且复杂得不必要。你还将代码与 Windows 平台绑定,如果将来想移植到其他平台,会变得很困难。我认为最好是抽象掉那些(丑陋的)平台细节,然后创建一个你工作的干净 API。即使是个人使用。如果你需要一个严肃的框架,如前所述,有 GLUT 及其克隆,有 Qt、SDL 和许多其他库和框架。几乎任何 3D 引擎或工具包都会提供比编写纯粹的 Win32 或 MFC 时更具平台独立性和更人性化的窗口 API。如果你需要一个易于使用和学习且功能基础的框架,那么 GLW 可能适合你。GLW 旨在用于小型、个人演示和项目,如果你想快速组合一些小的测试或演示。

用法

如果你使用 Visual Studio(我使用 VS2008 Express),创建一个 Win32 空控制台项目(exe)。在你的主源文件中,包含 glw.h,你就可以开始了。确保你也将 glw.c 复制过来,否则你的链接器将无法正常工作。

VS 2008 project setup.

GLW 应用程序的 Visual Studio 2008 项目设置。

如上所示,如果你在 Visual Studio 中创建一个新项目,请创建一个空的控制台应用程序。我建议你直接将 GLW 源文件放入你的代码中,因为在任何打开窗口的程序中,你都将几乎使用它的所有内容。如果你仍然想链接到 dll,你必须定义预处理器指令 USEGLWDLL。

渲染

要打开一个用于渲染的窗口,只需从你的 main 函数调用 glwMainLoop()

#include "glw.h"

int main(int argc, char** argv){

    return glwMainLoop();
}

结果是在屏幕上出现一个简单的窗口

Default window from GLW.

GLW 的默认窗口

如果你想删除在后台启动的控制台窗口,你可以将以下内容添加到你的源文件(或在项目属性中更改链接器设置):

#pragma comment(linker, "/subsystem:\"windows\" /entry:\"mainCRTStartup\"")

不要在 gears1 或 gears2 测试中禁用控制台窗口;它用于打印一些信息。要关闭窗口并退出应用程序,请按 Escape 键。

嗯,这没什么了不起的,只是一个空窗口;但它已经初始化了 OpenGL,并具有合理的默认 3D 投影(第一人称视角)。

绘图在 glwUpdateFunc 回调中完成(GLUT 的 glutRenderFunc)。绘制一个简单的三角形:

static void draw(){
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

    glColor3f(0,1,0);	
    glBegin(GL_TRIANGLES);
      glVertex3f( 0, 1,0);
      glVertex3f(-1,-1,0);
      glVertex3f( 1,-1,0);
    glEnd();	 
}

使用 glwSetUpdateFunc(draw) 将该函数设置为 glw 渲染回调

int main(int argc, char** argv){
	
    glwSetUpdateFunc(draw);
    return glwMainLoop();
}

仅供娱乐,这是输出

Default window from GLW.

带有三角形的 GLW 窗口

通常,对于 GLW 使用的任何回调,都有一个形式为:glwSetCallbacknameFunc( glwCallbackFuncf ) 的设置器。所有回调函数都以 glwCallbacknameFunc( … ) 的形式命名。

定义的 and 回调不多;glw 只处理窗口和输入,所以自然只有处理这些方面的 and 回调。

顺便说一句;我正在使用“旧方法”将数据发送到 OpenGL,这只是为了在这个演示中进行演示。GLW 可以很好地与着色器、vbo 以及你可能想使用的所有其他现代 OpenGL 东西一起使用。

从视角看 GLW 窗口

如你所见,GLW 具有合理的透视设置,因此你可以立即开始编写绘图代码。但是,如果你想自己设置透视,你必须定义

glwResizeFunc(int width, int height)

并用

 

glwSetResizeFunc

回调来设置。宽度和高度当然是窗口的宽度和高度。例如,你可以做一些类似的事情

void reshape(int w, int h){	
	float a;
	if (h==0)
	    h=1;
	
	a = (GLfloat) h / (GLfloat) w;
	glViewport(0, 0, (GLint) w, (GLint) h);
	glMatrixMode(GL_PROJECTION);
	glLoadIdentity();
	glFrustum(-1.0, 1.0, -a, a, 5.0, 500.0);
	glMatrixMode(GL_MODELVIEW);
	glLoadIdentity();
	glTranslatef(0.0, 0.0, -10.0);
}

并在 *main* 中设置

int main(int argc, char** argv){

	glwSetResizeFunc(reshape);
	glwSetUpdateFunc(draw);
	return glwMainLoop();
}

如果现在运行同一个应用程序,你将看不到任何变化:)。这仅仅是因为我在这里粘贴了 glw 用于 3D 透视的默认 resize 函数。现在,如果你不打算自己设置透视,你就知道了默认值。

也有默认的 2D 设置。要切换 3D 和 2D 模式,请使用 glwSet2DMode() 和 glwSet3DMode()。要绘制相同的三角形,但以 2D 方式绘制,请尝试这样做

void draw(){
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

    glColor3f(0,1,0);	
    glBegin(GL_TRIANGLES);
      glVertex2f(320,0);
      glVertex2f(0,480);
      glVertex2f(640,480);
    glEnd();
	 
}
int main(int argc, char** argv){

    glwSet2DMode();
    glwSetUpdateFunc(draw);
    return glwMainLoop();
}

同样的三角形又出现了。

获取窗口大小

如果调整窗口大小,三角形将保持在相同的位置和大小。原因是 OpenGL 的视口配置为以左上角为原点 (0,0),右下角为最大值,但三角形被配置为在固定坐标中绘制,并且不考虑窗口大小的变化。要更新窗口大小更改时的三角形,你需要窗口的宽度和高度。你可以使用以下方法获取这些值:

glwGetWindowSize(int* w, int* h);

它会将窗口的宽度和高度放入你提供的参数中

void draw(){
    int w, h;
    glClearColor(1,1,1,0);
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    glwGetWindowSize(&w,&h);
    glColor3f(0,1,0);	
    glBegin(GL_TRIANGLES);
      glVertex2f((float)w*0.5f,0);
      glVertex2i(0,h);
      glVertex2i(w,h);
    glEnd();}
}

现在三角形随着窗口一起缩放(这次是白色背景)。

还有一个相应的 glwSetWindowSize(int width, int height) 用于设置窗口的宽度和高度。但是,它仅在创建的上下文中才有意义。这意味着你可以在进入主循环之前使用它来请求窗口的初始大小。之后你不能再用它来更改窗口的大小(用鼠标)。同样,没有定位函数。我只是觉得这些对于粗略而简单的“快速绘制并关闭”式的工作不是很实用。我想保持简单,而不是让它成为万能工具。

GLW 动画

让我们回到我们的 3D 三角形并添加绕 Y 轴的旋转

void draw(){
    
    glClearColor(1,1,1,0);
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    
    glRotatef(1,0,1,0);

    glColor3f(0,1,0);	
    glBegin(GL_TRIANGLES);
      glVertex3f(0,1,0);
      glVertex3f(-1,-1,0);
      glVertex3f(1,-1,0);
    glEnd();		 
}

如果你运行这个,你通常会期望看到三角形以恒定的速度绕 Y 轴旋转,但它并没有发生。默认情况下,glw 是一个事件驱动的框架。这意味着在有来自操作系统的事件之前,它不会做任何事情。我认为对于简单的应用程序,你不总是希望进行动画。如果你只想渲染一帧 OpenGL 内容,就没有必要占用 CPU 进行持续重绘。要启用持续重绘,请告诉 glw 使用轮询(异步)循环而不是事件驱动(阻塞)循环。切换到轮询循环是通过以下方式完成的:

glwSetUpdateMode(GLW_CONTINOUS_LOOP);

要切换回事件驱动循环,请使用

glwSetUpdateMode(GLW_EVENT_LOOP);

为了在行动中看到它,请使用下面的 main 函数

int main(int argc, char** argv){
    glwSetUpdateMode(GLW_CONTINOUS_LOOP);
    glwSetUpdateFunc(draw);
    return glwMainLoop();
}

现在你应该看到一个绿色的三角形持续绕 Y 轴旋转

 

Triangle rotating around Y-axis.

三角形绕 Y 轴旋转。

按需渲染

在事件模式下触发渲染,请使用

glwRedisplay()

你可能猜到,这是 glw 相当于 glutPostRedisplay()(对于习惯 GLUT 的人来说)。如果你运行轮询(连续)循环,你不必管这个,但如果你运行事件驱动循环,你需要它来告诉 glw 何时渲染。GLW 在标准的系统触发器上进行渲染,例如调整大小,但它不会在按键或鼠标移动时渲染。通常你不想在每次鼠标移动时都渲染,但有时你会使用鼠标来执行诸如旋转或在场景中拾取和移动对象等操作。例如,旋转后,你会调用 redisplay 回调来触发场景的新渲染。无法提前知道鼠标将如何使用,3D 建模师使用鼠标的方式与 3D 射击游戏不同,这个选择最好留给应用程序。

 

当你不在事件驱动模式下时,此函数不起任何作用,所以可以将其放在同时在两种模式下工作的代码中。

要测试 gears 应用程序中的更新模式,请按 'E' 将 GLW 置于事件模式,或按 'C' 返回连续渲染。

总而言之,这些是所有可用的窗口函数(有关更多详细信息,请参阅头文件)

glwMainLoop
glwSetResizeFunc
glwSetUpdateMode
glwSetUpdateFunc
glwSet2DMode
glwSet3DMode
glwSetWindowSize 
glwGetWindowSize

默认情况下,你只需要设置你的渲染回调并在 main 循环中调用它。

 

演示中的 GL Gears 示例

嗯,我可以用一个绿色的三角形继续下去,但这更有趣看到更复杂的东西。如前所述,我改编了 GLUT 3.6 代码中的 Gears 应用程序。解决方案中的 gears1 和 gears2 项目彼此之间是 100% 相同的。我之所以有两个项目,仅仅是为了测试将 dll 链接与源代码静态链接时的可执行文件大小。所以你可以查看其中任何一个,但不必查看两个。

Triangle rotating around Y-axis.

OpenGL 齿轮在 GLW 窗口中运行

输入

我也试图尽可能简化输入。我只偶尔使用输入来操作场景中的对象,这反映了 GLW 的功能。键盘处理仅限于物理按键。这意味着你可以监听按下的键,如 Enter、PageUp、PageDown、A、B、C 等。但是你无法获得“@、$、a-z”(ASCII 字符)之类的内容。我只是觉得用“@”之类的键在 3D 场景中移动东西不是很有用。如果你确实希望能够使用 glw 编写高级文本,恐怕你必须自己添加代码来处理 WM_CHAR。所以,例如,glw *不是* 你用来制作文本编辑器的应用程序(但也许是射击游戏?)。

鼠标处理得相当好;你可以监听左、中、右按钮、鼠标移动和滚轮滚动。

键盘

键盘回调的签名是

void glwKeyboardFunc(short key, short event)

Key 会告诉你哪个键被按下,而 event 会告诉你它是被按下还是释放。

 

通常你会忽略 key up,并将所有键盘处理变成“keypress”之类的模型。但是,如果你想做一些更高级的事情,比如在按住一个键时做某事,这是有可能的。

我使用 short 而不是 int,希望这样可以减少堆栈的推入和弹出(2 个 short 是 4 字节,2 个 int 是 8 字节)。嗯,我认为这节省不了多少,但我总是试图尽可能少地占用资源。

在 gears 中,我的键盘处理如下:

void key(short k, short event){

        if(event == GLW_KEYUP) // act only on keydown
          return;

	switch (k) {
	  case 'A':  showfps = ~showfps; break;
          case 'X':  view_rotz += 5.0;  break;
          case 'Z':  view_rotz -= 5.0;  break;
  	  case 'E':  glwSetUpdateMode(GLW_EVENT_LOOP);  break;
  	  case 'C':  glwSetUpdateMode(GLW_CONTINOUS_LOOP);  break;
  	  case 'F':  glwToggleFullscreen();  break;
  	  case 'V':
	    vsync = !vsync;
	    glwSetVSync(vsync);
	    break;
  	  case 'H':
	    view_movx = 0;
	    view_movy = 0;
	    view_movz = 0;
	  break;
  	  case 27:  /* Escape */
  	  case 'Q':
	    glwExit();
	  break;
        }
}

键被抽象成平台无关的;它们通常像普通键一样被调用,但都是大写字母,并带有 GLW_ 前缀,例如:

GLW_DELETE
GLW_INSERT
GLW_ESCAPE
GLW_SHIFT
GLW_ALT
GLW_CTRL

( ... )

实际上,“正常”取决于个人,所以你必须查看头文件以获取完整的键名列表,但你懂这个意思。

 

Key-up 事件称为 GLW_KEYUP,key-down 事件称为 GLW_KEYDOWN。GLW 中几乎没有按键处理;按键几乎是 1:1 映射到 Win32 中的对应键(VK_)。Windows 中的 Alt 和“扩展键”处理有点麻烦,需要一些特殊的处理,但除此之外,按键基本上会直接传递给应用程序。嗯,最快的处理就是你不需要做的事情:)。

就是这样;你不能做 case 'q' 或 case 'a' 之类的事情——那不会起作用。ASCII A-Z(如示例所示)和 0-9 有效,仅仅是因为操作系统为相应的虚拟键分配了与 ASCII 相同的值。

鼠标按钮

鼠标比键盘的功能稍多一些。首先,左、右、中鼠标按钮的处理方式与键盘类似。回调的签名是:

void glwMouseFunc(short x, short y, short event, short modifiers)

X 和 Y 自然地存储了光标的坐标。坐标是相对于窗口的客户端空间的,也就是说,窗口中 OpenGL 绘图的区域,而不是该区域周围的框架、边框或其他装饰。

 

通常,人们更关心的是最后一次读取和当前读取之间的差值(移动量)。因此,它们是屏幕坐标还是窗口坐标并不重要。如果你出于某种原因需要屏幕空间坐标,提供转换很简单;你可以自己计算,或者通过调用 Win32 API 中的 ScreenToClient 和 ClientToScreen。这两种解决方案都需要你修改 glw 本身(代码在 sglw.c 中)。

另一个不同之处在于,Windows 以左上角为原点计算坐标,而 GLW 以左下角为原点。所以,如果你需要将窗口中的鼠标位置转换到 GLW,你需要考虑到这一点。另外还要注意,光标坐标在窗口的 2D 空间中,而 OpenGL 则以你 3D 场景(世界)的原点进行思考。

*Event* 的含义与键盘相同,要么是按钮被抬起,要么是被按下。要找出哪个按钮被按下,你必须查看修饰符。可能的选项有:

GLW_LBUTTON 
GLW_RBUTTON
GLW_MBUTTON

除了这些按键之外,了解用户是否按下了 shift、ctrl 或 alt 键可能也有用。你可以从 *modifiers* 参数中获取该信息。修饰符被打包为位标志,所以要测试哪个被按下,你可以这样做:

if(modifiers & GLW_KEY)
  /* do something here */

代码中的 gears 有一个示例,它会将各种按下和修饰符打印到 stdout

void mpdbg(short x, short y, short e, short m){
	
	if(e == GLW_KEYDOWN)
	    puts("Event: mouse key down");
	if(e == GLW_KEYUP)
	    puts("Event: mouse key up");
	printf("Modifiers: ");

	if(m & GLW_ALT)
	    printf("alt ");
	if(m & GLW_CTRL)
	    printf("ctrl ");
	if(m & GLW_SHIFT)
	    printf("shift ");

	printf("\nButtons: ");

	if(m & GLW_LBUTTON)
	    printf("left ");
	if(m & GLW_RBUTTON)
	    printf("right ");
	if(m & GLW_MBUTTON)
	    printf("middle ");

	puts("");
        }

请注意,此函数中的 *event* 仅与左、右、中鼠标按钮有关,与修饰键(control、alt 和 shift)无关。

鼠标移动

当鼠标在窗口内移动时,会调用鼠标移动函数。签名比前面的函数要简单一些:

void glwMouseMoveFunc(short x,short y, short modifiers);

只有 x 和 y 坐标,以及修饰符列表。列表与上一个示例相同,但没有 event(按键弹起或按下)。如果按下了按钮,则会调用 glwMouseFunc,而不是这个。

 

以下是我在 gears 中为实现简单的鼠标平移和旋转所做的:

void onmousemove(short x, short y, short m){

	int dx, dy;
	
	if(oldx == 0) oldx = x;
	if(oldy == 0) oldy = y;

	dx = (x-oldx), dy = (y-oldy);
	
	if(m & GLW_ALT){
	    if(m & GLW_LBUTTON){
	        view_rotx += dx;
	        view_roty += dy;
	    }
	    else if(m & GLW_RBUTTON){
	        view_movx += dx*0.01f;
	        view_movy -= dy*0.01f;
	    }else if(m & GLW_MBUTTON){		
	        view_movz += (dx+dy)*0.5f*0.1f;
	    }
  	    glwRedisplay();
	}
	oldx = x; oldy = y;
        }

我使用 Alt 键作为修饰键。按住 Alt 键并按下左鼠标按钮,然后移动鼠标以旋转相机(或模型,随意)。按住右按钮和 Alt 键并移动鼠标以在 x 和 y 方向上平移,这将产生类似于平移场景的效果。最后,如果你按下中键和 Alt 键并移动鼠标,你将在 z 方向上平移,这将产生相机缩放进出的效果。

 

不要纠结于那段代码,它只是为了说明如何读取鼠标按钮,而不是展示如何在 OpenGL 中实现相机。

鼠标滚轮

当你转动鼠标滚轮时,会调用鼠标滚轮函数。与光标的 x 和 y 坐标不同,滚轮不发送坐标。它发送刻度。所以当你转动滚轮一点点时,你会得到一个刻度和一个窗口事件。当你再转动一点时,你会得到另一个刻度,依此类推。滚轮的连续移动也不会产生连续坐标,而是在每次事件时产生一个离散值。刻度有大小和名称。它的名字是 WHEEL_DELTA,你可以搜索这个术语来了解更多关于滚轮事件是如何处理的。

大小为 +120 或 -120,取决于移动方向(+ 是远离你,- 是朝向你)。所以你永远不会得到像 240、360 这样的值。而不是 360;你会得到 3 次更新,每次 120。这意味着我们实际上只对移动的符号感兴趣,而不是数量。为此,我们的 z 总是 +1 或 -1。下面是一个 gears 中的使用示例:

void onmousewheel(short x, short y, short z, short m){

    float amount = 10*(float)z;

    if(m & GLW_CTRL)	
        view_rotz += amount;
    if(m & GLW_SHIFT)
        view_roty += amount;
    if(m & GLW_ALT)
        view_rotx += amount;

    glwRedisplay();
}

我使用滚轮来旋转视图,但这次我根据按下的修饰键将旋转限制在 x、y 和 z 轴上。

初始化和清理

通常你需要加载纹理和其他一些需要 OpenGL 上下文的资源。嗯,在主循环开始之前没有上下文。相反,你可以注册一个函数,使其在 OpenGL 创建并准备好使用时被调用。函数签名是:

void glwInit()

设置器是:

void glwSetInitFunc(glwInitFunc cb)

不要将其与 glw 的初始化混淆;glw 初始化的任何东西都不需要!Init 函数是放置你自己的初始化任务的方便场所,至少是那些需要 GL 上下文的任务。在 init 中,你可以准备纹理、缓冲区、着色器、修改状态等等。

 

与初始化类似,当你完成后需要执行清理。为此,有 glwExitFunc,它使用 glwSetExitFunc 设置。就像 init 是初始化 gl 东西的安全场所一样,exit 也是清理它的安全场所。Exit 函数在 OpenGL 上下文仍然激活时被调用。在主循环退出后执行任何清理很可能会导致崩溃,因为此时 GL 上下文将被销毁。

最后是:

glwExit()

用于终止 GLW 应用程序。它是一个内部函数,用于替换标准的 *exit()*

 

不应将其与 glwExitFunc 混淆。*glwExit* 是 GLW 的 API 函数,它会反过来调用你的回调 glwExitFunc 来清理应用程序分配的资源。

这也会带来终止 glw 应用程序的问题:不要调用 exit(0),请调用 glwExit 以确保 OpenGL 资源得到正确释放。出于同样的原因,不要依赖 atexit() 来清理任何 OpenGL。而是使用 glwSetExitFunc() 注册你的回调,GLW 会在适当的时候调用它。

现代 OpenGL

众所周知,OpenGL 板块已经大大改变了 OpenGL API 的工作方式。我并不真正同意他们引入的所有更改,尤其是那些关于弃用的更改,但又能如何呢?:) 我已经加入了对现代上下文(3.xx 及更高版本)初始化的支持。如果你认为新方法更好,你可以这样做:

    int main(int argc, char** argv) {

	int attribs[] =
	{
            WGL_CONTEXT_MAJOR_VERSION_ARB, 3,
	    WGL_CONTEXT_MINOR_VERSION_ARB, 2,
	    WGL_CONTEXT_FLAGS_ARB, 
	    WGL_CONTEXT_FORWARD_COMPATIBLE_BIT_ARB,
	    WGL_CONTEXT_PROFILE_MASK_ARB, 
	    //WGL_CONTEXT_CORE_PROFILE_BIT_ARB,
	    WGL_CONTEXT_COMPATIBILITY_PROFILE_BIT_ARB,
	    0
	};

	glwSetGLAttribs(attribs);
	glwSetInitFunc(init);	
	glwSetExitFunc(clean);
        glwSetKeyboardFunc(key);
	glwSetUpdateFunc(render);
	
	return glwMainLoop();
    }

将你的 attribs 数组作为参数传递给 glwSetGLAttribs,GLW 将尽力满足你的要求。

 

作为注释,如果我从驱动程序请求 GL 版本,我总能在我的显卡(Nvidia 560ti)上获得最新版本,无论我将什么属性传递给 OpenGL。即使使用“旧方法”我也能获得最新版本,那我为什么还要费劲呢?好吧,仅仅因为在我显卡上有效并不意味着在其他驱动程序上也会同样有效。

杂项

这基本上就是整个框架了:),但还有一些我还没有讲到的额外功能。

可以通过调用以下函数将 GLW 窗口设置为全屏或退出全屏模式:

glwToggleFullscreen()

在 gears 应用程序中,如果你按下 'F',你就可以测试它。

 

请注意,它不会触发真正的视频模式更改。它只是将窗口置于最前面并隐藏所有窗口装饰。是的——我知道,这是旧的 GLUT‘游戏模式’,但我对此很满意。切换速度很快,代码成本几乎为零。添加真正的视频模式处理需要更多的代码,对于像 GLW 这样简单的东西来说,这会是臃肿。

全屏存在一个问题。有时,但只是有时,当应用程序首次切换到全屏时,屏幕可能会变黑(窗口未更新)。我只见过几次,并且实际上无法按需重现它,所以我无法查明原因。切换出去再切换回来可以解决问题;也许其他的屏幕更新也会解决它,但如前所述,我无法重现它,所以很难说。

最后一个函数是:

glwSetVsync()

它接受 1 或 0 的值来启用或禁用显卡的垂直同步。默认情况下,显卡设置为与显示器的垂直刷新率同步渲染。它只渲染显示器能显示的帧数。而且确实没有更多需求,除非你想进行基准测试。我不确定 gears 演示中实际测量的是什么;我认为我们必须向显卡发送大量多边形,然后它才能开始测量显卡的性能。而且我们从 gears 演示中得到的数字可能只是说明系统切换缓冲区有多快。不过,如果你想在屏幕上看到撕裂,请在 gears 演示中按 'V'。

 

有趣的是——你知道你可以将显卡设置为显示器刷新率的一半吗?我不知道,但我偶然发现了。将 vsync 设置为 ~1,帧率将减半(至少在我身上是这样,Gf560 ti,24'' 宽屏,60Hz 刷新率)。好吧,我知道 vsync 意味着显卡只能以显示器刷新率的倍数刷新,但我不知道我可以在这些倍数上手动开启或关闭它。

内部

我不会写太多关于内部机制的内容;它是一个如此小而简单的框架,讨论可能会变成 Win32 入门教程:)。但是,对于那些习惯于 Windows 编程的人来说,有几点值得一提。

首先,GLW 摆脱了 WinMain。在 Windows 应用程序中不需要 WinMain;它只是一个约定,并且这种约定很有用是一个迷思。真的:);winmain 只是让你的代码不那么容易移植,并增加了额外的习惯要记住,同时并没有给你任何特殊的价值。如果你需要,你可以通过 Win32 API 获取传递给 WinMain 的所有参数,并且你可以配置你的链接器来生成窗口应用程序。也许 WinMain 有什么更深层次的东西我错过了,如果是这样,请告诉我。

主循环的实现方式与常规方式略有不同。通常框架会实现一种或另一种循环(GetMessage 或 PeekMessage);但我的代码方式使得可以在它们之间切换。

关注点

此框架包含一个我从 Intel 网站上的文章中找到并改编的高分辨率计时器代码。它位于 etimer.h 和 etimer.c 中。我相信这段代码是免费的,但我会在 etimer.h 中包含原始文章和许可证的链接。它也是一个你可以独立于此库使用的*即插即用*代码。

历史

  • 2012-04-23 文章和源代码发布
  • 2012-04-25 修复了 glw.c 中初始窗口大小的错误。
© . All rights reserved.