【OpenGL】创建窗口(二)

这篇文章 中我们介绍了使用 glut 库来创建和管理窗口,这里我们再介绍另外两个可用于 OpenGL 窗口管理的库 glfw 和 SFML。

glfw

glfw 是一个专门针对 OpenGL 编写的 C 语言库,它提供了渲染物体所需的最低限度的接口。

配置环境

下载 glfw,可以下载编译好的二进制包,也可以下载源代码;这里我们下载源代码,然后手动进行编译。

源码中没有工程文件,需要手动创建一个工程,然后进行配置。这个过程比较笨重,可以使用一个工程文件生成工具 CMake,它可以使用预定义好的 CMake 脚本自动生成工程文件。

  • 第一步,点击 Browse Source 按钮,选择源路径,这里选择 glfw 的根目录;
  • 第二步,点击 Browse Build 按钮,选择生成的项目文件存放位置,可以在 glfw 根目录下新建一个 build 目录;
  • 第三步,点击 Configure 按钮,选择要生成的项目类型,如果是 Windows 平台则选择电脑上安装的 vs 版本即可;
  • 第四步,Generate 按钮,则会在 build 目录下生成一个 vs 解决方案文件;
  • 第五步,打开 glfw.sln,生成项目 glfw,会生成一个 glfw3.lib,这是一个静态链接库文件;如果要生成动态链接库,把项目属性改成动态链接库就行了。

使用的方法就很简单了,和之前使用 glew 一样,三步曲操作:

  • 把 glfw3.h 和 glfw3native.h 文件拷贝到 vs 目录下或项目目录下;
  • 把 glfw3.lib 拷贝到 vs 目录下或项目目录下;
  • 如果是使用动态链接库的形式,把 glfw3.dll 拷贝到系统目录下或项目可执行文件目录下。

之前使用 glut 的时候不需要配置环境,只需要引入头文件 glut.h,那是因为这个头文件帮我们做了很多事情。打开 glut.h,可以看到下面几行代码。

1
2
3
4
5
6
7
#pragma comment (lib, "winmm.lib")     /* link with Windows MultiMedia lib */
#pragma comment (lib, "opengl32.lib") /* link with Microsoft OpenGL lib */
#pragma comment (lib, "glu32.lib") /* link with OpenGL Utility lib */
#pragma comment (lib, "glut32.lib") /* link with Win32 GLUT lib */

#include <GL/gl.h>
#include <GL/glu.h>

#pragma comment 命令用于添加要使用的静态库,其作用和在 链接器->输入 添加相应的库是一样的。在 glut.h 中添加了对 OpenGL 核心库 opengl32.lib,实用库 glu32.lib 和工具库 glut32.lib 的引用。还 include 了 gl.hglu.h 这两个头文件,所以在我们的项目中只需要 incldue glut.h 即可。

现在我们使用 glfw 库来代替 glut 库,则需要手动添加对 opengl32.lib, glu32.lib, glfw3.lib 的引用,还要在 #include <glfw3.h> 之前 include gl.hglu.h 这两个头文件。如果想省略这些工作,可以模仿 glut.h,在 glfw3.h 中添加下面的代码。

1
2
3
4
5
6
#pragma comment (lib, "opengl32.lib")  /* link with Microsoft OpenGL lib */
#pragma comment (lib, "glu32.lib") /* link with OpenGL Utility lib */
#pragma comment(lib, "glfw3.lib")

#include <GL/gl.h>
#include <GL/glu.h>

创建窗口

先上完整的代码。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
#include <GL\glew.h>
#include <GLFW\glfw3.h>
#include <iostream>

int main(int argc, char** argv)
{
glfwInit();
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 4);
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 4);
glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
glfwWindowHint(GLFW_RESIZABLE, GL_FALSE);

GLFWwindow* window = glfwCreateWindow(400, 400, "Shader", nullptr, nullptr);
if (window == nullptr)
{
return 1;
}
glfwMakeContextCurrent(window);

glewExperimental = true;
if (GLEW_OK != glewInit())
{
return 2;
}

GLint width, height;
glfwGetFramebufferSize(window, &width, &height);
glViewport(0, 0, width, height);

glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
while (!glfwWindowShouldClose(window))
{
glfwPollEvents();
glfwSwapBuffers(window);
}

glfwTerminate();
return 0;
}
  • 注意 glew.h 在所有 OpenGL 头文件中必须第一个 include。
  • glfwInit 用于初始化 glfw。
  • glfwWindowHint 用于初始化 OpenGL 参数。

    GLFW_CONTEXT_VERSION_MAJOR 指定 OpenGL Context 的主版本号。
    GLFW_CONTEXT_VERSION_MINOR 指定 OpenGL Context 的次版本号,这两个版本号均不能高于系统上的 OpenGL 版本。
    GLFW_OPENGL_PROFILE 指定使用的 OpenGL 模式为核心模式。
    GLFW_RESIZEABLE 指定窗口是否可以改变大小,如果设置成 false,则窗口大小不能改且最大化按钮不能用。

  • glfwCreateWindow 创建一个窗口,前两个参数设置窗口宽高,第三个参数设置窗口标题,最后两个参数设置为空即可。
  • glfwMakeContextCurrent 把创建的窗口设置成 OpenGL Context。

    前面讲过 Context 是 OpenGL 状态的合集,没有 Context,OpenGL 将不存在;之前使用 glut 只要创建出窗口就行了,使用 glfw 要手动把窗口设置成 OpenGL 的 Context。

  • glViewport 来设置视口大小,这一步暂时不设置也行,其默认的视口大小就是窗口初始的大小,后面讲到摄像机的时候会再讨论视口的知识。
  • glfwGetFramebufferSize 可以得到窗口的大小,这个函数中在窗口大小改变时很有用。
  • 最后我们使用一个 while 循环让程序阻塞,在 while 循环条件中调用 glfwWindowShouldClose 函数判断窗口是否关闭,如果关闭则跳出循环。

    glut 阻塞程序的方式是使用 glMainLoop 函数。

  • 在 while 循环中先调用 glfwPollEvents 处理窗口事件,然后开始渲染,glfwSwapBuffersglutSwapBuffers 的作用是一样的。
  • 最后窗口关闭时调用 glfwTerminate 来释放 glfw 分配的内存。

整个过程很简单,在前面的例子中我们都会使用一个 init 函数来初始化一些数据,然后使用一个 render 或 display 函数来进行渲染,在 glut 中是在 glutCreateWindow 之后调用 init 函数,然后使用 glutDisplayFunc(display) 来注册回调函数。在 glfw 中同样是在 glfwCreateWindow 之后调用 init 函数,然后在 while 循环中调用 display 函数即可。这里有一点不同的是在 glut 中,display 只有在窗口改变时才会调用,而在 glfw 中 display 函数是每一帧都会调用的。

键盘事件

glfw 中的键盘事件是通过回调函数设置的,函数原型如下。

1
void key_callback(GLFWwindow* window, int key, int scancode, int action, int mode);
  • 第二个参数是按下的键的 ascii 码;
  • 第三个参数暂没用到;
  • 第四个参数表示当前是按下动作 GL_PRESS 还是松开动作 GL_RELEASE
  • 第五个参数表示当前是否按下 Ctrl, Alt, Shift 等特殊键。

注册事件方法如下。

1
glfwSetKeyCallback(window, key_callback);

下面是一个实例。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
void key_callback(GLFWwindow* window, int key, int scancode, int action, int mode)
{
if (key == GLFW_KEY_ESCAPE && action == GLFW_RELEASE)
{
glfwSetWindowShouldClose(window, GL_TRUE);
}
else if (mode == GLFW_MOD_CONTROL && action == GLFW_RELEASE)
{
if (key == GLFW_KEY_1)
{
vertices[0] -= 0.1f;
glBindBuffer(GL_ARRAY_BUFFER, VBO);
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
}
else if (key == GLFW_KEY_2)
{
vertices[0] += 0.1f;
glBindBuffer(GL_ARRAY_BUFFER, VBO);
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
}
}
}

在这个回调函数里,首先检测到按下 esc 键然后松开,则把窗口关闭;然后检测按下 ctr+1 或 ctr+2 键松开,修改顶点数据,在下一帧绘制的时候就会生效。

鼠标事件

glfw 中鼠标事件共四种,分别是移动鼠标事件、鼠标进入离开事件、鼠标按键事件和鼠标滚轮事件。

移动鼠标事件

1
2
glfwSetCursorPosCallback(window, mouse_callback);
void mouse_callback(GLFWwindow* window, double x, double y);

x y 是当前鼠标相对于窗口的位置。

鼠标进入离开事件

1
2
glfwSetCursorEnterCallback(window, mouse_enter_callback);
void mouse_enter_callback(GLFWwindow* window, int enter);

enter 等于 0 表示鼠标离开,enter 等于 1 表示鼠标进入。

鼠标按键事件

1
2
glfwSetMouseButtonCallback(window, mouse_down_callback);
void mouse_down_callback(GLFWwindow* window, int button, int action, int mods);
  • 第二个参数指按下的是哪个键,取值范围有 GLFW_MOUSE_BUTTON_LEFT, GL_MOUSE_BUTTON_MIDDLE, GL_MOUSE_BUTTON_RIGHT
  • 第三个参数表示是按下动作还是松开动作;
  • 最后一个参数表示当前是否有按下 ctrl, shift, alt 键。

鼠标滚轮事件

1
2
glfwSetScrollCallback(window, mouse_scroll_callback);
void mouse_scroll_callback(GLFWwindow* window, double xoffset, double yoffset);

xoffset 和 yoffset 表示两个方向的滚动值,一般的鼠标滚动事件都只有垂直方向,即 xoffset 恒定为 0,yoffset = 1 表示向上滚动,yoffset = -1 表示向下滚动。

SFML

SFML,全称是简单快速的多媒体库,用于快速构建 PC 上的多媒体应用和游戏,其本身内嵌了 OpenGL,能够快速创建 OpenGL Context,而且无需包含其它库。SFML 比 freeglut 功能更强大,使用起来也简单,除了 OpenGL 环境,SFML 本身包括 系统、窗口、图形、网络、音频 五个模块,窗口模块用于创建 OpenGL 环境,其它模块可快速实现 OpenGL 之外的功能。

关于 SFML,有专门的 系列文章 介绍,这里就不展开讲了。