开始
目录表
- 一步一步地
- 包括GLFW头文件
- 初始化和终止GLFW
- 设置错误回调
- 创建窗口和上下文
- 使当前是OpenGL上下文
- 检查窗口关闭标志
- 接收输入事件
- 使用OpenGL渲染
- 读取定时器
- 交换缓冲区
- 处理事件
- 把它们放在一起
本指南将介绍如何使用GLFW 3编写一个简单的应用程序。应用程序将创建一个窗口和OpenGL上下文,呈现一个旋转三角形,并在用户关闭窗口或按Escape键时退出。本指南将介绍一些最常用的函数,但还有更多。
本指南假定没有使用早期版本GLFW的经验。如果您过去使用过GLFW 2,请阅读从GLFW 2迁移到GLFW 3,因为一些函数在GLFW 3中的行为不同。
一步一步地
包含GLFW头文件
在使用GLFW的应用程序的源文件中,需要包含它的头文件。
# include < GLFW / glfw3.h >
这个头文件提供了GLFW API的所有常量、类型和函数原型。
默认情况下,它还包括来自开发环境的OpenGL头文件。在某些平台上,这个头文件只支持旧版本的OpenGL。最极端的例子是Windows,它通常只支持OpenGL 1.2。
大多数程序将使用扩展加载器库并包含其头文件。本例使用由glad生成的文件。GLFW头文件可以检测到大多数这样的头文件,如果它们首先被包含,然后将不包括来自开发环境的头文件。
#include <glad/gl.h>
#include <GLFW/glfw3.h>
为了确保没有头文件冲突,您可以在GLFW头之前定义GLFW_INCLUDE_NONE,以显式禁用开发环境包含头文件。这也允许以任何顺序包含两个头。
#define GLFW_INCLUDE_NONE
#include <GLFW/glfw3.h>
#include <glad/gl.h>
初始化和终止GLFW
在使用大多数GLFW函数之前,必须初始化库。初始化成功时,返回GLFW_TRUE。如果发生错误,则返回GLFW_FALSE。
if(!glfwInit ())
{
//初始化失败
}
请注意,GLFW_TRUE和GLFW_FALSE现在和将来都是1和0。
当您使用完GLFW后,通常在应用程序退出之前,您需要终止GLFW。
glfwTerminate ();
这将破坏所有剩余的窗口并释放由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上下文是通过调用glfwSetWindowCloseCallback.来创建的,它返回一个句柄到创建的窗口和上下文对象的组合
GLFWwindow* window = glfwCreateWindow(640,480,"My Title", NULL, NULL);
if(!window)
{
// 窗口或OpenGL上下文创建失败
}
这将创建一个带有OpenGL上下文的640 * 480窗口模式窗口。如果窗口或OpenGL上下文创建失败,将返回NULL。您应该始终检查返回值。虽然窗口创建很少失败,但上下文创建取决于正确安装的驱动程序,甚至在具有必要硬件的机器上也可能失败。
默认情况下,GLFW创建的OpenGL上下文可以有任何版本。您可以通过在创建之前设置GLFW_CONTEXT_VERSION_MAJOR和GLFW_CONTEXT_VERSION_MINOR提示来要求最小OpenGL版本。如果机器上不支持所需的最低版本,则上下文(和窗口)创建失败。
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR,2);
glfwWindowHint(GLFW_CONTEXT_VERSION_MINJOR,0);
GLFWwindow* window = glfwCreateWindow(640,480,"My Title",NULL,NULL);
if(!window)
{
// 窗口或上下文创建失败
}
窗口句柄传递给所有与窗口相关的函数,并提供给所有与窗口相关的回调函数,因此它们可以告诉哪个窗口接收了事件。
当不再需要一个窗口和上下文时,销毁它。
glfwDestroyWindow(window);
一旦调用这个函数,就不会再为该窗口传递事件,并且它的句柄无效。
将OpenGL上下文置为当前
在你可以使用OpenGL API之前,你必须有一个当前的OpenGL上下文。
glfwMakeContextCurrent(window);
该上下文将保持当前状态,直到您将另一个上下文设置为当前状态,或者直到拥有当前上下文的窗口被销毁。
如果你正在使用扩展加载器库来访问现代OpenGL,那么这就是初始化它的时候,因为加载器需要一个当前上下文来加载。这个例子使用了glad,但是同样的规则适用于所有这样的库。
gladLoadGL (glfwGetProcAddress);
检查窗口关闭标志
每个窗口都有一个标志,指示该窗口是否应该关闭。
当用户试图关闭窗口时,通过按标题栏中的关闭小部件或使用像Alt+F4这样的组合键,该标志被设置为1。请注意,该窗口实际上并没有关闭,因此您应该监视该标志,并销毁该窗口或向用户提供某种反馈。
while(!glfwWindowShouldClose(window))
{
//保持运行
}
当用户试图关闭窗口时,你可以通过使用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(窗口,GLFW_TRUE);
}
按键回调和其他与窗口相关的回调一样,是按窗口设置的。
glfwSetKeyCallback(window,key_callback);
为了在事件发生时调用事件回调,您需要按照下面描述的方式处理事件。
使用OpenGL渲染
一旦你有一个当前的OpenGL上下文,你可以正常使用OpenGL。在本教程中,将渲染一个多色旋转三角形。帧缓冲区大小需要为glViewport检索。
int width,height;
glfwGetFramebufferSize(window,&width, &height);
glViewport(0,0, width, height);
您还可以使用glfwSetFramebufferSizeCallback设置帧缓冲区大小回调,并在大小更改时收到通知。
如何用OpenGL渲染的细节超出了本教程的范围,但是有许多学习现代OpenGL的优秀资源。以下是其中的一些:
这些碰巧都使用GLFW,但OpenGL本身的工作原理是相同的,无论你使用什么API来创建窗口和上下文。
读取定时器
为了创建流畅的动画,需要一个时间源。GLFW提供了一个计时器,返回自初始化以来的秒数。所使用的时间源在每个平台上都是最精确的,通常具有微秒或纳秒分辨率。
double time = glfwGetTime();
交换缓冲区
默认情况下,GLFW窗口使用双缓冲。这意味着每个窗口都有两个渲染缓冲区;一个前缓冲,一个后缓冲。前缓冲区是要显示的缓冲区,后缓冲区是要渲染的缓冲区。
当整个帧被渲染后,缓冲区需要相互交换,所以后缓冲区变成前缓冲区,反之亦然。
glfwSwapBuffers(window);
交换间隔表示在交换缓冲区之前需要等待多少帧,通常称为vsync。默认情况下,交换间隔为零,这意味着缓冲区交换将立即发生。在快速的机器上,许多这些帧永远不会被看到,因为屏幕通常每秒只更新60-75次,所以这浪费了大量的CPU和GPU周期。
另外,由于缓冲区会在屏幕更新的中间进行交换,导致屏幕撕裂。
由于这些原因,应用程序通常希望将交换间隔设置为1。它可以设置为更高的值,但通常不建议这样做,因为它会导致输入延迟。
glfwSwapInterval (1);
此函数作用于当前上下文,除非上下文是当前的,否则将失败。
处理事件
GLFW需要定期与窗口系统通信,以便接收事件并显示应用程序尚未锁定。事件处理必须在有可见窗口时定期执行,通常在缓冲区交换后的每一帧执行。
处理挂起事件有两种方法;轮询和等待。这个例子将使用事件轮询,它只处理那些已经接收到的事件,然后立即返回。
glfwPollEvents ();
当像大多数游戏一样持续渲染时,这是最好的选择。如果您只需要在接收到新输入后更新呈现,那么glfwWaitEvents是更好的选择。它将等待,直到至少接收到一个事件,在此期间将线程置于睡眠状态,然后处理所有接收到的事件。这节省了大量的CPU周期,并且对于许多类型的编辑工具都很有用。
把它们放在一起
现在您已经知道了如何初始化GLFW、创建窗口和轮询键盘输入,那么就可以创建一个简单的程序了。
该程序创建一个640 * 480的窗口模式窗口,并启动一个循环,清除屏幕,呈现三角形并处理事件,直到用户按下Escape或关闭窗口。
#include <glad/gl.h>
#define GLFW_INCLUDE_NONE
#include <GLFW/glfw3.h>
#include "linmath.h"
#include <stdlib.h>
#include <stdio.h>
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);
// 注意:为简洁起见,省略了OpenGL错误检查
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);
}
上面的程序可以在源代码包的examples/simple.c中找到,并且在构建GLFW时与所有其他示例一起编译。如果您从源包构建GLFW,那么您已经在Windows上有simple.exe,在Linux上有simple或simple.app在macOS。
本教程只使用了GLFW提供的许多函数中的几个。GLFW涵盖的每个领域都有指南。每个指南将介绍该类别的所有功能。
本教程到此结束。一旦你编写了一个使用GLFW的程序,你就需要编译和链接它。如何做到这一点取决于您正在使用的开发环境,该环境的文档最好地解释了这一点。要了解特定于GLFW的详细信息,请参阅构建应用程序.。