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(); glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3); glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3); }
|
我这里使用的是3.3版本。
然后我们用glfwCreateWindow()函数创建一个窗口对象:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| int main() { glfwInit(); glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3); glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 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); }
|
在源码注释里,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(); glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3); glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 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);
if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress)) { std::cout << "Failed to initialize GLAD" << std::endl; return -1; }
glViewport(0, 0, 800, 600); 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)) { 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(); glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3); glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 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);
if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress)) { std::cout << "Failed to initialize GLAD" << std::endl; return -1; }
glViewport(0, 0, 800, 600); glfwSetFramebufferSizeCallback(window, framebuffer_size_callback);
while (!glfwWindowShouldClose(window)) { 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); }
|
这样,一个基本完整的窗口就完成啦。