在学习OpenGL的时候,很多项目都使用了glfw库。
下面一段代码就使用GLFW
创建了一个窗口,并设置了清屏颜色
,如下所示:
#include
#include
#include
void framebuffer_size_callback(GLFWwindow* window, int width, int height);
void processInput(GLFWwindow *window);
// settings
const unsigned int SCR_WIDTH = 800;
const unsigned int SCR_HEIGHT = 600;
int main()
{
// glfw: initialize and configure
// ------------------------------
glfwInit();
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
#ifdef __APPLE__
glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE);
#endif
// glfw window creation
// --------------------
GLFWwindow* window = glfwCreateWindow(SCR_WIDTH, SCR_HEIGHT, "LearnOpenGL", NULL, NULL);
if (window == NULL)
{
std::cout << "Failed to create GLFW window" << std::endl;
glfwTerminate();
return -1;
}
glfwMakeContextCurrent(window);
glfwSetFramebufferSizeCallback(window, framebuffer_size_callback);
// glad: load all OpenGL function pointers
// ---------------------------------------
if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress))
{
std::cout << "Failed to initialize GLAD" << std::endl;
return -1;
}
// render loop
// -----------
while (!glfwWindowShouldClose(window))
{
// input
// -----
processInput(window);
// render
// ------
glClearColor(0.2f, 0.3f, 0.3f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT);
// glfw: swap buffers and poll IO events (keys pressed/released, mouse moved etc.)
// -------------------------------------------------------------------------------
glfwSwapBuffers(window);
glfwPollEvents();
}
// glfw: terminate, clearing all previously allocated GLFW resources.
// ------------------------------------------------------------------
glfwTerminate();
return 0;
}
// process all input: query GLFW whether relevant keys are pressed/released this frame and react accordingly
// ---------------------------------------------------------------------------------------------------------
void processInput(GLFWwindow *window)
{
if(glfwGetKey(window, GLFW_KEY_ESCAPE) == GLFW_PRESS)
glfwSetWindowShouldClose(window, true);
}
// glfw: whenever the window size changed (by OS or user resize) this callback function executes
// ---------------------------------------------------------------------------------------------
void framebuffer_size_callback(GLFWwindow* window, int width, int height)
{
// make sure the viewport matches the new window dimensions; note that width and
// height will be significantly larger than specified on retina displays.
glViewport(0, 0, width, height);
}
上面代码很快就创建了一个窗口,为了看得懂代码,我们就得去学一下什么是glfw。
GLFW
是一个开源的多平台库,用于桌面上的 OpenGL
、OpenGL ES
和 Vulkan
开发。它提供了一个简单的 API
,用于创建窗口、上下文和表面,接收输入和事件。
GLFW
用 C 语言
编写,支持 Windows、macOS、X11 和 Wayland
。
GLFW
在 zlib/libpng 许可证下获得许可。
在使用 GLFW 的应用程序的源文件中,您需要包含它的头文件。
#include
他的头文件提供了 GLFW API
的所有常量、类型和函数原型。
默认情况下,它还包括来自您的开发环境的 OpenGL
标头。在某些平台上,此标头仅支持旧版本的 OpenGL
。最极端的情况是 Windows
,它通常只支持 OpenGL 1.2
。
大多数程序将改为使用扩展加载程序库并包含其标头。此示例使用由 glad 生成的文件。 GLFW
标头可以检测到大多数此类标头,如果它们首先包含在内,然后将不包含来自您的开发环境的标头。
#include
#include
为确保不存在标头冲突,您可以在 GLFW 标头之前定义 GLFW_INCLUDE_NONE
以显式禁用包含开发环境标头。这也允许以任何顺序包含两个标头。
#define GLFW_INCLUDE_NONE
#include
#include
在您可以使用大多数 GLFW
函数之前,必须先初始化该库。初始化成功后,返回 GLFW_TRUE
。如果发生错误,则返回 GLFW_FALSE
。
if (!glfwInit())
{
// Initialization failed
}
请注意,GLFW_TRUE
和 GLFW_FALSE
始终是 1
和 0
。 当您使用完 GLFW
后,通常就在应用程序退出之前,您需要终止 GLFW
。
glfwTerminate();
这会销毁任何剩余的窗口并释放 GLFW
分配的任何其他资源。在这个调用之后,您必须在使用任何需要它的 GLFW
函数之前再次初始化 GLFW
。
大多数事件都是通过回调报告的,无论是按键被按下、GLFW
窗口被移动还是发生错误。回调是 GLFW
使用描述事件的参数调用的 C
函数(或 C++
静态方法)。
如果 GLFW
函数失败,则会向 GLFW
错误回调报告错误。您可以通过错误回调接收这些报告。此函数必须具有以下签名,但可以执行其他回调中允许的任何操作。
void error_callback(int error, const char* description)
{
fprintf(stderr, "Error: %s\n", description);
}
必须设置回调函数,以便 GLFW
知道调用它们。设置错误回调的函数是为数不多的可以在初始化之前调用的 GLFW
函数之一,它可以让您在初始化期间和之后收到错误通知。
glfwSetErrorCallback(error_callback);
窗口及其 OpenGL
上下文是通过一次调用 glfwCreateWindow 来创建的,它会返回创建的组合窗口和上下文对象的句柄
GLFWwindow* window = glfwCreateWindow(640, 480, "My Title", NULL, NULL);
if (!window)
{
// Window or OpenGL context creation failed
}
这将创建一个带有 OpenGL
上下文的 640 x 480
窗口模式窗口。如果窗口或 OpenGL
上下文创建失败,将返回 NULL
。您应该始终检查返回值。虽然窗口创建很少会失败,但上下文创建取决于正确安装的驱动程序,甚至在具有必要硬件的机器上也可能会失败。
默认情况下,OpenGL
上下文GLFW
创建可能具有任何版本。您可以通过设置GLFW_CONTEXT_VERSION_MAJOR
和GLFW_CONTEXT_VERSION_MINOR
提示创建之前,您可以使用最小的OpenGL
版本。如果在机器上不支持所需的最小版本,则上下文(和窗口)的创建失败。
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 2);
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 0);
GLFWwindow* window = glfwCreateWindow(640, 480, "My Title", NULL, NULL);
if (!window)
{
// Window or context creation failed
}
窗口句柄被传递给所有与窗口相关的函数,并提供给所有与窗口相关的回调函数,因此它们可以判断哪个窗口接收到事件。
当不再需要窗口和上下文时,将其销毁。
glfwDestroyWindow(window);
一旦调用此函数,将不再为该窗口传递任何事件,并且其句柄将变为无效。
在使用 OpenGL API 之前,您必须拥有当前的 OpenGL 上下文。
glfwMakeContextCurrent(window);
上下文将保持当前,直到您使另一个上下文成为当前上下文或直到拥有当前上下文的窗口被销毁。
如果您使用扩展加载器库来访问现代 OpenGL,那么这是初始化它的时候,因为加载器需要从当前上下文加载。此示例使用 glad,但同样的规则适用于所有此类库。
gladLoadGL(glfwGetProcAddress);
每个窗口都有一个标志,指示该窗口是否应该关闭。
当用户尝试关闭窗口时,通过按标题栏中的关闭小部件或使用 Alt+F4 等组合键,此标志设置为 1。请注意,窗口实际上并未关闭,因此您应该监视此标志并销毁窗口或向用户提供某种反馈。
while (!glfwWindowShouldClose(window))
{
// Keep running
}
当用户试图通过使用 glfwSetWindowCloseCallback
设置关闭回调来关闭窗口时,您会收到通知。设置关闭标志后将立即调用回调。
您也可以使用 glfwSetWindowShouldClose
自行设置。如果您想将其他类型的输入解释为关闭窗口,这可能很有用,例如按下 Escape
键。
每个窗口都有大量的回调,可以设置为接收各种事件。要接收按键按下和释放事件,请创建按键回调函数。
static void key_callback(GLFWwindow* window, int key, int scancode, int action, int mods)
{
if (key == GLFW_KEY_ESCAPE && action == GLFW_PRESS)
glfwSetWindowShouldClose(window, GLFW_TRUE);
}
与其他窗口相关的回调一样,键回调是为每个窗口设置的。
glfwSetKeyCallback(window, key_callback);
为了在事件发生时调用事件回调,您需要按如下所述处理事件。
一旦有了当前的 OpenGL
上下文,就可以正常使用 OpenGL
。在本教程中,将渲染一个多色旋转三角形。需要为 glViewport
检索帧缓冲区大小。
int width, height;
glfwGetFramebufferSize(window, &width, &height);
glViewport(0, 0, width, height);
您还可以使用 glfwSetFramebufferSizeCallback
设置帧缓冲区大小回调,并在大小更改时收到通知。
如何使用 OpenGL
进行渲染的详细信息不在本教程的讨论范围内,但是有许多学习现代 OpenGL 的优秀资源。这里有几个:
这些都碰巧使用了 GLFW,但是无论您使用什么 API 来创建窗口和上下文,OpenGL 本身的工作方式都是一样的。
要创建流畅的动画,需要一个时间源。 GLFW 提供了一个计时器,它返回自初始化以来的秒数。使用的时间源在每个平台上都是最准确的,通常具有微秒级或纳秒级分辨率。
double time = glfwGetTime();
GLFW 窗口默认使用双缓冲。这意味着每个窗口都有两个渲染缓冲区;一个前台缓冲区和一个后台缓冲区。前台缓冲区是正在显示的缓冲区,后台缓冲区是您渲染到的缓冲区。
渲染完整个帧后,缓冲区需要相互交换,因此后台缓冲区变为前台缓冲区,反之亦然
glfwSwapBuffers(window);
交换间隔指示在交换缓冲区之前要等待多少帧,通常称为 vsync。默认情况下,交换间隔为零,这意味着缓冲区交换将立即发生。在快速机器上,许多帧永远不会被看到,因为屏幕仍然通常每秒更新 60-75 次,因此这会浪费大量 CPU 和 GPU 周期。
此外,由于缓冲区将在屏幕更新中间交换,导致屏幕撕裂。
由于这些原因,应用程序通常希望将交换间隔设置为 1。它可以设置为更高的值,但通常不推荐这样做,因为它会导致输入延迟。
glfwSwapInterval(1);
此函数作用于当前上下文,除非上下文是当前的,否则将失败。
GLFW 需要定期与窗口系统通信,以便接收事件并显示应用程序尚未锁定。当您有可见窗口时,必须定期进行事件处理,并且通常在缓冲区交换后每帧进行一次。
有两种处理未决事件的方法;轮询和等待。这个例子将使用事件轮询,它只处理那些已经收到的事件,然后立即返回。
glfwPollEvents();
这是连续渲染时的最佳选择,就像大多数游戏一样。相反,如果您只需要在收到新输入后更新渲染,glfwWaitEvents
是更好的选择。它会等到至少接收到一个事件,同时让线程休眠,然后处理所有接收到的事件。这节省了大量的 CPU 周期,并且对许多类型的编辑工具都很有用.
既然您知道了如何初始化 GLFW、创建窗口并轮询键盘输入,就可以创建一个简单的程序了。
该程序创建一个 640 x 480
的窗口模式窗口,并启动一个循环来清除屏幕、呈现三角形并处理事件,直到用户按下 Escape
或关闭窗口。
#include
#define GLFW_INCLUDE_NONE
#include
#include "linmath.h"
#include
#include
static const struct {
float x, y;
float r, g, b;
} vertices[3] =
{
{-0.6f, -0.4f, 1.f, 0.f, 0.f},
{0.6f, -0.4f, 0.f, 1.f, 0.f},
{0.f, 0.6f, 0.f, 0.f, 1.f}
};
static const char *vertex_shader_text =
"#version 110\n"
"uniform mat4 MVP;\n"
"attribute vec3 vCol;\n"
"attribute vec2 vPos;\n"
"varying vec3 color;\n"
"void main()\n"
"{\n"
" gl_Position = MVP * vec4(vPos, 0.0, 1.0);\n"
" color = vCol;\n"
"}\n";
static const char *fragment_shader_text =
"#version 110\n"
"varying vec3 color;\n"
"void main()\n"
"{\n"
" gl_FragColor = vec4(color, 1.0);\n"
"}\n";
static void error_callback(int error, const char *description) {
fprintf(stderr, "Error: %s\n", description);
}
static void key_callback(GLFWwindow *window, int key, int scancode, int action, int mods) {
if (key == GLFW_KEY_ESCAPE && action == GLFW_PRESS)
glfwSetWindowShouldClose(window, GLFW_TRUE);
}
int main(void) {
GLFWwindow *window;
GLuint vertex_buffer, vertex_shader, fragment_shader, program;
GLint mvp_location, vpos_location, vcol_location;
glfwSetErrorCallback(error_callback);
if (!glfwInit())
exit(EXIT_FAILURE);
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 2);
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 0);
window = glfwCreateWindow(640, 480, "Simple example", NULL, NULL);
if (!window) {
glfwTerminate();
exit(EXIT_FAILURE);
}
glfwSetKeyCallback(window, key_callback);
glfwMakeContextCurrent(window);
gladLoadGL(glfwGetProcAddress);
glfwSwapInterval(1);
// NOTE: OpenGL error checks have been omitted for brevity
glGenBuffers(1, &vertex_buffer);
glBindBuffer(GL_ARRAY_BUFFER, vertex_buffer);
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
vertex_shader = glCreateShader(GL_VERTEX_SHADER);
glShaderSource(vertex_shader, 1, &vertex_shader_text, NULL);
glCompileShader(vertex_shader);
fragment_shader = glCreateShader(GL_FRAGMENT_SHADER);
glShaderSource(fragment_shader, 1, &fragment_shader_text, NULL);
glCompileShader(fragment_shader);
program = glCreateProgram();
glAttachShader(program, vertex_shader);
glAttachShader(program, fragment_shader);
glLinkProgram(program);
mvp_location = glGetUniformLocation(program, "MVP");
vpos_location = glGetAttribLocation(program, "vPos");
vcol_location = glGetAttribLocation(program, "vCol");
glEnableVertexAttribArray(vpos_location);
glVertexAttribPointer(vpos_location, 2, GL_FLOAT, GL_FALSE,
sizeof(vertices[0]), (void *) 0);
glEnableVertexAttribArray(vcol_location);
glVertexAttribPointer(vcol_location, 3, GL_FLOAT, GL_FALSE,
sizeof(vertices[0]), (void *) (sizeof(float) * 2));
while (!glfwWindowShouldClose(window)) {
float ratio;
int width, height;
mat4x4 m, p, mvp;
glfwGetFramebufferSize(window, &width, &height);
ratio = width / (float) height;
glViewport(0, 0, width, height);
glClear(GL_COLOR_BUFFER_BIT);
mat4x4_identity(m);
mat4x4_rotate_Z(m, m, (float) glfwGetTime());
mat4x4_ortho(p, -ratio, ratio, -1.f, 1.f, 1.f, -1.f);
mat4x4_mul(mvp, p, m);
glUseProgram(program);
glUniformMatrix4fv(mvp_location, 1, GL_FALSE, (const GLfloat *) mvp);
glDrawArrays(GL_TRIANGLES, 0, 3);
glfwSwapBuffers(window);
glfwPollEvents();
}
glfwDestroyWindow(window);
glfwTerminate();
exit(EXIT_SUCCESS);
}
本教程仅使用了 GLFW 提供的众多功能中的一小部分。 GLFW 涵盖的每个领域都有指南。每个指南将介绍该类别的所有功能。
您可以通过单击访问任何 GLFW 功能的参考文档,每个功能的参考链接到相关功能和指南部分。