learnOpenGL网站是学习openGL非常有用的网站,作为一个小白,为了方便后续回顾同时给大家提供借鉴,在此记录学习的过程。
本文参考:你好,窗口
在上文中我们通过复制粘贴代码验证了搭建环境的成功与否。在这里,对该代码进行学习。 本文将一步步增加代码,实验每段代码的作用,对每段代码有个更好的了解
1. 程序第0步,引用头文件
#include <glad/glad.h> #include <GLFW/glfw3.h>
请确认是在包含GLFW的头文件之前包含了GLAD的头文件。GLAD的头文件包含了正确的OpenGL头文件(例如GL/gl.h),所以需要在其它依赖于OpenGL的头文件之前包含GLAD。
2. 从main函数开始,7步显示窗口
1. 调用glfwInit()
函数来初始化GLFW
2. 调用glfwWindowHint()
函数来配置GLFW
glfwWindowHint
函数的第一个参数代表选项的名称,我们可以从很多以GLFW_
开头的枚举值中选择;第二个参数接受一个整型,用来设置这个选项的值。该函数的所有的选项以及对应的值都可以在 GLFW’s window handling 这篇文档中找到。
int main() { glfwInit(); //初始化GLFW //配置GLFW glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3); //告知系统我们使用的opengl版本是3.3 glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3); glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE); //同样明确告诉GLFW我们使用的是核心模式(Core-profile) glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE); //MAC环境下需加这一句才能使以上配置生效 return 0; }
3. 创建一个窗口对象
GLFWwindow *window = glfwCreateWindow(800, 600, "LearnOpenGL", nullptr, nullptr); if (window == nullptr) { std::cout << "Failed to create GLFW window" << std::endl; glfwTerminate(); return -1; } glfwMakeContextCurrent(window);
glfwCreateWindow
函数前两个参数为窗口的宽和高。第三个参数表示这个窗口的名称。返回一个GLFWwindow对象。glfwTerminate
函数终止glfw窗口。glfwMakeContextCurrent
函数用来将创建出来的窗口上下文设置为当前线程的主上下文
4. 初始化GLAD
GLAD是用来管理OpenGL的函数指针的,所以在调用任何OpenGL的函数之前我们需要初始化GLAD。
我们给GLAD传入了用来加载系统相关的OpenGL函数指针地址的函数。GLFW给我们的是glfwGetProcAddress
,它根据我们编译的系统定义了正确的函数。
if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress)) { std::cout << "Failed to initialize GLAD" << std::endl; return -1; }
5. 设置视口
在我们开始渲染之前还有一件重要的事情要做,我们必须告诉OpenGL渲染窗口的尺寸大小,即视口(Viewport),这样OpenGL才只能知道怎样根据窗口大小显示数据和坐标。我们可以通过调用glViewport
函数来设置窗口的维度(Dimension):
glViewport(0, 0, 800, 600);
glViewport
函数前两个参数控制窗口左下角的位置。第三个和第四个参数控制渲染窗口的宽度和高度(像素)。
6. 渲染循环,让程序一直渲染输出图像,知道我们关闭窗口
我们希望程序在我们主动关闭它之前不断绘制图像并能够接受用户输入。因此,我们需要在程序中添加一个while循环,我们可以把它称之为渲染循环(Render Loop),它能在我们让GLFW退出前一直保持运行。
while(!glfwWindowShouldClose(window)) { glfwSwapBuffers(window); glfwPollEvents(); }
glfwWindowShouldClose
函数在我们每次循环的开始前检查一次GLFW是否被要求退出,如果是的话该函数返回true然后渲染循环便结束了。glfwPollEvents
函数检查有没有触发什么事件(比如键盘输入、鼠标移动等)、更新窗口状态,并调用对应的回调函数(可以通过回调方法手动设置)。glfwSwapBuffers
函数会交换颜色缓冲(它是一个储存着GLFW窗口每一个像素颜色值的大缓冲),它在这一迭代中被用来绘制,并且将会作为输出显示在屏幕上。
双缓冲(Double Buffer)
应用程序使用单缓冲绘图时可能会存在图像闪烁的问题。 这是因为生成的图像不是一下子被绘制出来的,而是按照从左到右,由上而下逐像素地绘制而成的。最终图像不是在瞬间显示给用户,而是通过一步一步生成的,这会导致渲染的结果很不真实。为了规避这些问题,我们应用双缓冲渲染窗口应用程序。前缓冲保存着最终输出的图像,它会在屏幕上显示;而所有的的渲染指令都会在后缓冲上绘制。当所有的渲染指令执行完毕后,我们交换(Swap)前缓冲和后缓冲,这样图像就立即呈显出来,之前提到的不真实感就消除了。
7. 正确释放/删除之前的分配的所有资源
当渲染循环结束后我们需要正确释放/删除之前的分配的所有资源。我们可以在main函数的最后调用glfwTerminate
函数来完成。
glfwTerminate(); return 0;
至此,通过编译运行程序,你可以在屏幕中看到一个窗口
3. 给窗口增加一些操作
前面7步让我们能够在屏幕中显示一个没有任何操作和意义的窗口,下面我们可以在这个窗口上加一些操作。
3.1. 通过callback进行视口大小调整
当用户改变窗口的大小的时候,视口也应该被调整。
我们可以对窗口注册一个回调函数(Callback Function),它会在每次窗口大小被调整的时候被调用。函数原型如下:
void framebuffer_size_callback(GLFWwindow* window, int width, int height); void framebuffer_size_callback(GLFWwindow* window, int width, int height) { glViewport(0, 0, width, height); }
这个帧缓冲大小函数需要一个GLFWwindow作为它的第一个参数,以及两个整数表示窗口的新维度。每当窗口改变大小,GLFW会调用这个函数并填充相应的参数供你处理。
需要注册这个函数,告诉GLFW我们希望每当窗口调整大小的时候调用这个函数:
glfwSetFramebufferSizeCallback(window, framebuffer_size_callback);
当窗口被第一次显示的时候framebuffer_size_callback也会被调用。
在创建窗口之后,渲染循环初始化之前注册这些回调函数。
3.2. 输入控制
void processInput(GLFWwindow *window) { if(glfwGetKey(window, GLFW_KEY_ESCAPE) == GLFW_PRESS) glfwSetWindowShouldClose(window, true); }
glfwGetKey
函数用来获取输入事件,第一个参数为创建的window,第二个参数为检测的按键,例如代码中的GLFW_KEY_ESCAPE
为检测ESC键是否按下。如果没有按下,函数返回GLFW_RELEASE
,如果按下,函数返回GLFW_PRESS
。
该代码应在每次循环中检测按键是否按下,如果按下,glfwSetWindowShouldClose
将被置为true,下次循环时将结束循环条件。因此该段代码在循环中使用。
while (!glfwWindowShouldClose(window)) { processInput(window); glfwSwapBuffers(window); glfwPollEvents(); }
程序效果:按下esc键,窗口关闭
3.3. 渲染
我们要把 所有的渲染(Rendering)操作放到渲染循环中,因为我们想让这些渲染指令在每次渲染循环迭代的时候都能被执行。
可以通过调用glClear
函数来清空屏幕的颜色缓冲,它接受一个缓冲位(Buffer Bit)来指定要清空的缓冲,可能的缓冲位有GL_COLOR_BUFFER_BIT
,GL_DEPTH_BUFFER_BIT
和GL_STENCIL_BUFFER_BIT
。由于现在我们只关心颜色值,所以我们只清空颜色缓冲。
void draw(GLFWwindow *window) { glClearColor(0.2f, 0.3f, 0.3f, 1.0f); glClear(GL_COLOR_BUFFER_BIT); }
glClearColor
函数用来设置清空屏幕所用的颜色。当调用glClear
函数,清除颜色缓冲之后,整个颜色缓冲都会被填充为glClearColor
里所设置的颜色。
将该段代码添加到渲染循环中:
//循环渲染 while(!glfwWindowShouldClose(window)) { //检测输入事件 processInput(window); //渲染指令 draw(window); glfwSwapBuffers(window); glfwPollEvents(); }
程序最终效果如下:
4. 全部源码
#include <glad/glad.h> #include <GLFW/glfw3.h> #include <iostream> void framebuffer_size_callback(GLFWwindow* window, int width, int height); void processInput(GLFWwindow *window); void draw(GLFWwindow *window); int main() { glfwInit(); //初始化GLFW //配置GLFW glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3); //告知系统我们使用的opengl版本是3.3 glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3); glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE); //同样明确告诉GLFW我们使用的是核心模式(Core-profile) glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE); //MAC环境下需加这一句才能使以上配置生效 GLFWwindow *window = glfwCreateWindow(800, 600, "LearnOpenGL", nullptr, nullptr); if (window == nullptr) { std::cout << "Failed to create GLFW window" << std::endl; glfwTerminate(); return -1; } glfwMakeContextCurrent(window); //初始化glad 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); //渲染指令 draw(window); 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); } void draw(GLFWwindow *window) { glClearColor(0.2f, 0.3f, 0.3f, 1.0f); glClear(GL_COLOR_BUFFER_BIT); }