原文链接
译者:ktxiaok

开始入门

本指南将会带你熟悉使用GLFW 3编写一个简单的应用。这个应用会创建一个窗口、OpenGL上下文,渲染一个旋转的三角形,并且实现当用户关闭窗口或者按下Escape键时程序退出的功能。本指南会介绍一些最常用的函数,但是实际上有更多。
本指南假定你没有使用GLFW早期版本的经验。如果你在过去使用过GLFW 2, 请阅读GLFW 2到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()) { // Initialization failed }

注意到 GLFW_TRUE 和 GLFW_FALSE 通常会是1和0。
在你完成对GLFW的使用后,特别是在应用退出之前,你需要终止GLFW。

glfwTerminate();

这会销毁任何残留的窗口以及释放由GLFW分配的其他任何资源。在此调用之后,你必须在使用任何需要的GLFW函数之前再一次初始化GLFW。

设置一个错误回调

大多数事件都会通过回调来报告, 不管它是否一个按键被按下、一个GLFW窗口被移动或者一个发生一个错误。回调是被GLFW以描述事件所用的参数来调用的C函数(或者C++静态函数)。
如果有一个GLFW函数崩溃,一个错误会被报告给GLFW的错误回调函数。你可以通过错误回调来接受这些报告。这个函数必须拥有下面这样的签名(signature),但也可以在其他回调中做任何事情。

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上下文的,分辨率为640x480的窗口化模式窗口。如果窗口或者OpenGL上下文创建过程失败,将会返回 NULL。
你应该总是检查返回的值。如果窗口创建罕见地失败了,那么依赖正确安装的驱动程序的上下文创建在即使安装了必要的硬件的机器上也会失败。
默认情况下,GLFW创建的OpenGL上下文可能拥有任意的版本。你可以请求最小的OpenGL版本,通过在创建之前设置 GLFW_CONTEXT_VERSION_MAJOR 和 GLFW_CONTEXT_VERSION_MINOR 提示(hints)。
如何请求的最小版本在机器上不受到支持,上下文(和窗口)创建将会失败。

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上下文

在你能够使用OpenGL API之前,你必须要拥有一个当前的OpenGL上下文。

glfwMakeContextCurrent(window);

这个上下文会持续保持在当前直到你设置其他的上下文在当前或者拥有这个上下文的窗口被销毁。
如果你使用一个扩展加载器程序库(extension loader library)来访问现代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上下文,你就可以正常地使用OpenGL了。在本教程中,将会渲染一个多颜色的旋转的三角形。需要为了 glViewport 检索帧缓存大小。

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

你也能使用 glfwSetFramebufferSizeCallback 来设置一个帧缓存大小回调,这样你会在大小被更改时被告知。
如何使用OpenGL来渲染的细节超出了本教程的范围,但是这里有许多杰出的资源来学习现代OpenGL。这里将会列举一些:

  • Anton’s OpenGL 4 Tutorials
  • Learn OpenGL
  • Open.GL
    这些都使用了GLFW,但是无论你使用的创建窗口和上下文的API是什么,OpenGL它自身的工作方式都是相同的。

读取计时器

为了创建一个平滑的动画,需要一个时间源。GLFW提供了一个可以返回从初始化开始的秒数的计时器。这个使用的时间源在任何平台上大部分是精确的并且一般有着微秒级或者纳秒级的精度。

double time = glfwGetTime();

交换缓冲

GLFW默认使用了双缓冲技术。这意味着每个窗口会有两个渲染缓冲区,一个前置缓冲区和一个后置缓冲区。前置缓冲区会在屏幕上显示而后置缓冲区是你渲染的目标。
当整个帧已经渲染完毕时,两个缓冲区需要进行交换,所以后置缓冲区会变成前置缓冲区,反之亦然。

glfwSwapBuffers(window);

交换间隔指示了直到交换缓冲区前需要等待多少帧,通常被理解为垂直同步。默认情况下,交换间隔为0,意味着缓冲区交换会立即发生。在一些快速的机器上,因为屏幕保持以典型的60-75次每秒的速度更新,许多帧会永远看不到,所以这会浪费许多CPU和GPU周期。
而且,因为缓冲区可能会在屏幕更新的中途被交换,导致画面撕裂。
因此,应用需要代表性地设置交换间隔为1。也可以设置成更高的值,但是通常情况下这不会被推荐,因为这会导致输入延迟。

glfwSwapInterval(1);

这个函数会在当前上下文生效并且会在没有当前上下文的情况下失败。

处理事件

GLFW需要定期地与窗口系统进行交流,不仅是为了接收事件,还是为了让整个应用看起来没有卡住。当你有着可见的窗口时,事件处理必须定期执行,正常情况下它会在每帧的缓冲区交换之后进行。
这里有两种方式来处理挂起的事件。轮询(polling)和等待(waiting)。这个例子将会使用事件轮询,它只会处理已经接收到的事件并且会立即返回。

glfwPollEvents();

当连续渲染时这是最好的选择,像大多数的游戏那样。如果你只需要在收到新的输入后更新一次渲染,glfwWaitEvents 会是更好的选择。
它会一直等待直到收到至少一个事件,与此同时它会将线程进入休眠状态, 然后处理所有收到的事件。这将会节省大量的CPU周期,比如对于一些修改器界面来说是非常有用的。

把它们放在一起

现在你已经直到怎么去初始化GLFW,创建一个窗口和轮询(poll)键盘输入了。这可以让我们创建一个简单的程序。
这个程序会创建一个640x480分辨率的窗口化模式窗口,开启一个清理屏幕、渲染三角形和处理事件的渲染,直到用户按下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);// NOTE: OpenGL error checks have been omitted for brevityglGenBuffers(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);
}

上面的程序可以在source package的examples/simple.c处找到,并且会在你构建GLFW时连同其他所有例子一起被编译。如果你从source package构建GLFW,你就已经拥有了在Windows上的simple.exe,在Linux上的simple,或者在macOS上的simple.app。
本教程只使用了其中少数的GLFW提供的大量函数。

GLFW官方文档中文翻译——开始入门相关推荐

  1. ElasticSearch Java High level Rest Client 官方文档中文翻译(一)

    ElasticSearch Java High level Rest Client 官方文档中文翻译 一 纯粹记录自己在看官网的es rest high level api 时的翻译笔记,可以对照着官 ...

  2. 【开源项目推荐】Android Jetpack 官方文档 中文翻译

    Jetpack 是 Android 软件组件的集合,使您可以更轻松地开发出色的 Android 应用.这些组件可帮助您遵循最佳做法.让您摆脱编写样板代码的工作并简化复杂任务,以便您将精力集中放在所需的 ...

  3. Spring官方文档中文翻译

    准备做个Spring官方文档全翻译专栏以下是大目录, 本翻译是基于Spring5 Core Technologies

  4. EdgeX Foundary 2.1Jakarta版官方文档中文翻译

    基于机器翻译,部分翻译有修订,不清楚的地方请参阅官方文档. 引言 EdgeX Foundry是一个开源的.供应商中立的.灵活的.可互操作的.位于网络边缘的软件平台,与设备.传感器.执行器和其他物联网对 ...

  5. Sencha Cmd 6 和 Ext JS 6 指南文档(部分官方文档中文翻译)

    近期组织了几个程序员网友,正在翻译一部分官方的Sencha Cmd 6 和 Ext JS 6 指南文档. 眼下还没翻译完,大家能够先看看 Sencha Cmd 6 和 Ext JS 6 指南文档  ( ...

  6. OAuth2.0官方文档中文翻译

    http://page.renren.com/699032478/note/708597990 (一)背景知识 OAuth 2.0很可能是下一代的"用户验证和授权"标准,目前在国内 ...

  7. python3官方文档 中文-Python 3.7 官方文档中文翻译召集

    geelaw 2018-08-07 00:59:47 +08:00 @LudwigWS #22 在你给出的第一个链接里面的第一个例子,前后分句的主语是相同的,只是重复了一次.注意 > 小明虽然考 ...

  8. kafka官方文档中文翻译(kafka参数解释)

    目录 入门 1.1简介 kafka™是一个分布式流媒体平台.这到底意味着什么? 1.2使用案例 1.3快速入门 1.4生态系统 1.5从以前的版本升级 2. API 2.1生产者API 2.2消费者A ...

  9. MediaCodec 官方文档中文翻译

    MediaCodec MediaCodec 类可以用来访问底层媒体编解码器,如编码器/解码器组件.是Android底层多媒体支持框架的一部分(通常和MediaExtractor,MediaSync,M ...

最新文章

  1. 顶会paper越来越多,我该怎么看?
  2. Linking Containers Together
  3. java多线程售票例子
  4. jquery 学习之一 对象访问
  5. Oracle在rownum使用结果集排序
  6. 更换pip源,解决pip install安装包慢的问题
  7. java android 五子棋游戏_基于Android平台五子棋游戏最终版.doc
  8. winscp普通用户向linux根目录中上传文件无法建立远程文件
  9. 在PhpStorm9中与Pi的xdebug进行调试
  10. iZotope RX 9 Advanced for Mac - 专业音频修复软件
  11. Gensim进阶教程
  12. session和cookie的内部原理
  13. HTML5超炫3D雷达扫描地图玩法
  14. 竞拍系统c语言,C++版扫拍卖源代码,非程序,这下不会删帖吧?
  15. 美国苹果股价走势图(抢先看美股三大指数新动态)
  16. 银行争夺又一万亿市场:汽车金融
  17. 【leetcode】189.旋转数组 (四种方法开阔思路,java实现!)
  18. PC端浏览器如何访问微信小程序
  19. AutoCAD Civil 3D-加宽与超高
  20. 面试|详细分析ScheduledThreadPoolExecutor(周期性线程池)的原理

热门文章

  1. 产品运营怎么做规划?
  2. MySQL安装版安装与配置图解
  3. MATLAB代码:基于V2G技术的电动汽车实时调度策略
  4. oracle主产品清单位置不可写,真正卸载oracle 11g
  5. 记一次阿里前端面试的具体经历(资料已更新)
  6. 微信小程序中的VUEX
  7. FL Studio 编辑软件入门讲解
  8. mysql8 servertime_MySql的时区(serverTimezone)引发的血案
  9. 在线重建分区表的分区索引
  10. java 将图片分割成9张_将多张图片合并成一张,代码(可垂直和水平方向合并)...