OpenGL(一) 渲染循环创建窗口

OpenGL(一) 渲染循环创建窗口

注:本文仅供自己以及OPENGL初学者共同学习进步,并无实际教学意义,不过会对自己学习中遇到的关键点加上个人解释。应该会对初学者有所帮助,如有错误请指正!

上次我们已经配置好了glfw + glad环境,接下来就是我们实践使用的时候了。


首先,我们先将要使用的头文件加进来,注意,这里先后顺序不能错:

1
2
#include <glad/glad.h>
#include <GLFW/glfw3.h>

然后我们开始进入main()函数,初始化glfw,并且声明版本号:

1
2
3
4
5
6
int main()
{
glfwInit(); //初始化glfw
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3); //版本号
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3); //代表glfw3.3
}

我这里使用的是3.3版本。
然后我们用glfwCreateWindow()函数创建一个窗口对象:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
int main()
{
glfwInit(); //初始化glfw
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3); //版本号
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3); //代表glfw3.3

//创建窗口
GLFWwindow * window = glfwCreateWindow(800, 600, "LearnOpenGL", NULL, NULL);
//判断是否创建成功
if (window == NULL)
{
std::cout << "Failed to create GLFW window" << std::endl;
glfwTerminate();
return -1;
}
glfwMakeContextCurrent(window); //设置当前线程上下文 意思就是在切换下一个状态之前,进行的所有操作都是对 window这个对象进行的
}

在源码注释里,GLFWwindow是"brief Opaque window object",也就是简易窗口对象的意思。
glfwCreateWindow()函数显而易见,第一个参数是宽度,第二个参数是高度,第三个是title,也就是窗口名称,后面两个参数现在用不到。当窗口创建失败时,window指针会指向NULL,我们可以利用这个来判断窗口是否创建成功。

glfwTerminate()是释放资源的函数,当一切结束的时候,我们就要用到这个函数。
glfwMakeContextCurrent(window)就如注释所说,OPENGL是讲状态的,只要我们不在当前线程设置其他上下文,它就会一直绑定当前设置的window窗口,之后的所有操作,就都是在这个window里进行.


接下来我们初始化glad:

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
int main()
{
glfwInit(); //初始化glfw
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3); //版本号
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3); //代表glfw3.3

//创建窗口
GLFWwindow * window = glfwCreateWindow(800, 600, "LearnOpenGL", NULL, NULL);
//判断是否创建成功
if (window == NULL)
{
std::cout << "Failed to create GLFW window" << std::endl;
glfwTerminate();
return -1;
}
glfwMakeContextCurrent(window); //设置当前线程上下文 意思就是在切换下一个状态之前,进行的所有操作都是对 window这个对象进行的

if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress))
{
std::cout << "Failed to initialize GLAD" << std::endl;
return -1;
}

//开始渲染之前先告诉opengl渲染窗口的尺寸大小,以及坐标
//注意,opengl坐标范围为-1到1 ,与屏幕坐标之间存 映射关系
glViewport(0, 0, 800, 600);
//注册该函数,告诉opengl每次window调整大小时都调用该函数
glfwSetFramebufferSizeCallback(window, framebuffer_size_callback);
}

glad是用来管理opengl的函数指针的,所以启用opengl任何函数之前需要初始化glad
glViewport()是设置视口的,具体作用大概就是设置窗口的起始坐标,以及告诉opengl我们要渲染的窗口大小为800x600.
glfwSetFramebufferSizeCallback()目的是为了当我们拖动窗口,造成窗口大小变化时,能够及时改变渲染的窗口尺寸,里面传入了我们自己设置的函数,可以看到函数内;我们在实时更改视口。

1
2
3
4
void framebuffer_size_callback(GLFWwindow * window, int width, int height) //随用户调整窗口大小而变化视口大小
{
glViewport(0, 0, width, height);
}

该准备的准备完了,可以开始渲染循环了。

1
2
3
4
5
while (!glfwWindowShouldClose(window)) // 检查glfw是否被要求退出
{
glfwSwapBuffers(window); //交换前后缓冲
glfwPollEvents(); //监视事件
}

如果跟着前面打没有打错的话,至此一个完整的黑色背景窗口就能出现了。


来解释一下glfwSwapBuffers(),因为OPENGL是双缓冲,那么双缓冲的作用是什么呢?假如是单缓冲,由于屏幕上的像素绘制是由左到右,从上到下一个一个绘制的,如果缓冲量过大,那么可能会出现图像闪烁,但是如果是双缓冲,它是由前缓冲全部绘制好,然后再跟后缓冲互换,然后前后工作交替进行,这样子就解决了闪烁问题。所以也能理解这个函数为什么叫这么个名字了吧。

那么现在我们想给窗口背景改个色,该怎么弄呢,我们需要设置一个颜色缓冲。

1
2
glClearColor(0.2f, 0.3f, 0.3f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT);

glClearColor()是设置清空屏幕所使用的颜色,意思就是使用这个函数后,之后每次清屏,默认颜色都是我们这个函数所设置的颜色。然后glClear()就是清除颜色缓冲,也就是清屏,那么我们为什么每次循环都要glClear()呢,因为有些人可能认为,我都画上去了,为什么要清除屏幕,再绘制一次呢,因为在渲染循环里,你是一个在不断绘制图像的过程,如果不清屏,那么每次画的图像就会叠加,导致跟我们预想的绘制内容会出现很大差别,比如你只是要画一只鸭子,但你如果不使用glClear(),那可能就是成千上万只鸭子绘制在同一个地方,内容不断叠加。

最后我们再加入一个退出事件的函数:

1
2
3
4
5
void processInput(GLFWwindow * window)
{
if (glfwGetKey(window, GLFW_KEY_ESCAPE) == GLFW_PRESS)
glfwSetWindowShouldClose(window, true);
}

可以看到一旦检测到我们按下ESC键,那么就会利用glfwSetWindowShouldClose()函数,传入true,使window窗口关闭,可以看到我们循环的条件是while (!glfwWindowShouldClose(window))

接下来是完整源码:

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
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
#include <glad/glad.h>
#include <GLFW/glfw3.h>
#include <iostream>
void framebuffer_size_callback(GLFWwindow * window, int width, int height);
void processInput(GLFWwindow * window);

int main()
{
glfwInit(); //初始化glfw
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3); //版本号
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3); //代表glfw3.3

//创建窗口
GLFWwindow * window = glfwCreateWindow(800, 600, "LearnOpenGL", NULL, NULL);
//判断是否创建成功
if (window == NULL)
{
std::cout << "Failed to create GLFW window" << std::endl;
glfwTerminate();
return -1;
}
glfwMakeContextCurrent(window); //设置当前线程上下文 意思就是在切换下一个状态之前,进行的所有操作都是对 window这个对象进行的

if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress))
{
std::cout << "Failed to initialize GLAD" << std::endl;
return -1;
}

//开始渲染之前先告诉opengl渲染窗口的尺寸大小,以及坐标
//注意,opengl坐标范围为-1到1 ,与屏幕坐标之间存 映射关系
glViewport(0, 0, 800, 600);
//注册该函数,告诉opengl每次window调整大小时都调用该函数
glfwSetFramebufferSizeCallback(window, framebuffer_size_callback);

while (!glfwWindowShouldClose(window)) // 检查glfw是否被要求退出
{
processInput(window);

glClearColor(0.2f, 0.3f, 0.3f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT);

glfwSwapBuffers(window); //交换前后缓冲
glfwPollEvents(); //监视事件
}

//释放内存
glfwTerminate();
return 0;
}

void framebuffer_size_callback(GLFWwindow * window, int width, int height) //随用户调整窗口大小而变化视口大小
{
glViewport(0, 0, width, height);
}

void processInput(GLFWwindow * window)
{
if (glfwGetKey(window, GLFW_KEY_ESCAPE) == GLFW_PRESS)
glfwSetWindowShouldClose(window, true);
}

这样,一个基本完整的窗口就完成啦。