OpenGL(三)
以下内容均来自此网站:https://learnopengl-cn.github.io/01%20Getting%20started/03%20Hello%20Window/
1、GLFW的初始化与配置
初始化GLFW,利用 glfwInit函数对GLFW进行初始化,
然后使用glfwWindowHint函数对GLFW进行配置
函数原型是这个样子的:
void glfwWindowHint(int hint, int value)
这个函数无返回值,有两个参数,第一个参数是一些选项,第二个参数是配合第一个参数这些选项的值。例如:
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3); glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3); glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3) 这句中参数的意思就是设置使用的OpenGL主版本号为3,第一个参数就是上下文主版本号的意思,第二个参数说明要设置的上下文主版本号为3。 同理第二句代码设置上下文的次版本号为3,第三句设置OpenGL的模式为核心模式。
当然还可以设置的参数还有好多,如GLFW_RESIZABLE设置是否可以让用户改变窗口大小,默认值为GLFW_TRUE;GLFW_MAXIMIZED设置是否窗口最大化,默认值是GLFW_FALSE,当然还可以设置的值还有很多,可以看下表:三列值分别为选项、默认值和可选的值。详细信息看这里https://www.glfw.org/docs/latest/window_guide.html#window_hints
另外说一句:可以在glfw3.h头文件中看到,Window hint 和 Supported values 这些值都是使用宏定义(#define)的,且宏定义的值是用16进制表示的。
Window hint | Default value | Supported values |
---|---|---|
GLFW_RESIZABLE | GLFW_TRUE | GLFW_TRUE or GLFW_FALSE |
GLFW_VISIBLE | GLFW_TRUE | GLFW_TRUE or GLFW_FALSE |
GLFW_DECORATED | GLFW_TRUE | GLFW_TRUE or GLFW_FALSE |
GLFW_FOCUSED | GLFW_TRUE | GLFW_TRUE or GLFW_FALSE |
GLFW_AUTO_ICONIFY | GLFW_TRUE | GLFW_TRUE or GLFW_FALSE |
GLFW_FLOATING | GLFW_FALSE | GLFW_TRUE or GLFW_FALSE |
GLFW_MAXIMIZED | GLFW_FALSE | GLFW_TRUE or GLFW_FALSE |
GLFW_CENTER_CURSOR | GLFW_TRUE | GLFW_TRUE or GLFW_FALSE |
GLFW_TRANSPARENT_FRAMEBUFFER | GLFW_FALSE | GLFW_TRUE or GLFW_FALSE |
GLFW_FOCUS_ON_SHOW | GLFW_TRUE | GLFW_TRUE or GLFW_FALSE |
GLFW_SCALE_TO_MONITOR | GLFW_FALSE | GLFW_TRUE or GLFW_FALSE |
GLFW_RED_BITS | 8 | 0 to INT_MAX or GLFW_DONT_CARE |
GLFW_GREEN_BITS | 8 | 0 to INT_MAX or GLFW_DONT_CARE |
GLFW_BLUE_BITS | 8 | 0 to INT_MAX or GLFW_DONT_CARE |
GLFW_ALPHA_BITS | 8 | 0 to INT_MAX or GLFW_DONT_CARE |
GLFW_DEPTH_BITS | 24 | 0 to INT_MAX or GLFW_DONT_CARE |
GLFW_STENCIL_BITS | 8 | 0 to INT_MAX or GLFW_DONT_CARE |
GLFW_ACCUM_RED_BITS | 0 | 0 to INT_MAX or GLFW_DONT_CARE |
GLFW_ACCUM_GREEN_BITS | 0 | 0 to INT_MAX or GLFW_DONT_CARE |
GLFW_ACCUM_BLUE_BITS | 0 | 0 to INT_MAX or GLFW_DONT_CARE |
GLFW_ACCUM_ALPHA_BITS | 0 | 0 to INT_MAX or GLFW_DONT_CARE |
GLFW_AUX_BUFFERS | 0 | 0 to INT_MAX or GLFW_DONT_CARE |
GLFW_SAMPLES | 0 | 0 to INT_MAX or GLFW_DONT_CARE |
GLFW_REFRESH_RATE | GLFW_DONT_CARE | 0 to INT_MAX or GLFW_DONT_CARE |
GLFW_STEREO | GLFW_FALSE | GLFW_TRUE or GLFW_FALSE |
GLFW_SRGB_CAPABLE | GLFW_FALSE | GLFW_TRUE or GLFW_FALSE |
GLFW_DOUBLEBUFFER | GLFW_TRUE | GLFW_TRUE or GLFW_FALSE |
GLFW_CLIENT_API | GLFW_OPENGL_API | GLFW_OPENGL_API , GLFW_OPENGL_ES_API or GLFW_NO_API |
GLFW_CONTEXT_CREATION_API | GLFW_NATIVE_CONTEXT_API | GLFW_NATIVE_CONTEXT_API , GLFW_EGL_CONTEXT_API or GLFW_OSMESA_CONTEXT_API |
GLFW_CONTEXT_VERSION_MAJOR | 1 | Any valid major version number of the chosen client API |
GLFW_CONTEXT_VERSION_MINOR | 0 | Any valid minor version number of the chosen client API |
GLFW_CONTEXT_ROBUSTNESS | GLFW_NO_ROBUSTNESS | GLFW_NO_ROBUSTNESS , GLFW_NO_RESET_NOTIFICATION or GLFW_LOSE_CONTEXT_ON_RESET |
GLFW_CONTEXT_RELEASE_BEHAVIOR | GLFW_ANY_RELEASE_BEHAVIOR | GLFW_ANY_RELEASE_BEHAVIOR , GLFW_RELEASE_BEHAVIOR_FLUSH or GLFW_RELEASE_BEHAVIOR_NONE |
GLFW_OPENGL_FORWARD_COMPAT | GLFW_FALSE | GLFW_TRUE or GLFW_FALSE |
GLFW_OPENGL_DEBUG_CONTEXT | GLFW_FALSE | GLFW_TRUE or GLFW_FALSE |
GLFW_OPENGL_PROFILE | GLFW_OPENGL_ANY_PROFILE | GLFW_OPENGL_ANY_PROFILE , GLFW_OPENGL_COMPAT_PROFILE or GLFW_OPENGL_CORE_PROFILE |
GLFW_COCOA_RETINA_FRAMEBUFFER | GLFW_TRUE | GLFW_TRUE or GLFW_FALSE |
GLFW_COCOA_FRAME_NAME | "" | A UTF-8 encoded frame autosave name |
GLFW_COCOA_GRAPHICS_SWITCHING | GLFW_FALSE | GLFW_TRUE or GLFW_FALSE |
GLFW_X11_CLASS_NAME | "" | An ASCII encoded WM_CLASS class name |
GLFW_X11_INSTANCE_NAME | "" | An ASCII encoded WM_CLASS instance name |
2、创建窗口对象
初始化glfw且设置完一些选项后,就可以创建窗口对象了,用的是glfwCreateWindow这个函数,函数原型如下:
GLFWwindow* glfwCreateWindow(int width, int height, const char* title, GLFWmonitor* monitor, GLFWwindow* share)
函数有4个参数,第一个参数是窗口宽度。第二个参数是窗口高度,第三个是窗口的标题,后面两个参数都是窗口对象(暂时设置为null),函数的返回值为GLFWwindow对象。
如果创建成功,则返回一个window对象,如果创建失败,则返回一个空指针(null)。可用下面的方式来判断:
GLFWwindow* window = glfwCreateWindow(800, 600, "LearnOpenGL", NULL, NULL); if (window == NULL) { std::cout << "Failed to create GLFW window" << std::endl; glfwTerminate(); return -1; }
如果返回空指针,说明创建窗口对象失败,输出一句创建失败的提示,同时需要调用一下glfwTerminate函数终止并释放所有分配的资源,最后返回 -1 。
如果没问题,接下来用glfwMakeContextCurrent函数来将我们窗口的上下文设置为当前线程的主上下文。如下:
glfwMakeContextCurrent(window);
3、初始化GLAD
之前提到,在运行时查找OpenGL的函数并存储在一个函数指针中使用是很麻烦的,所以需要借助GLAD库来简化这个过程,通过以下方式来初始化GLAD获取OpenGL函数指针:
if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress)) { std::cout << "Failed to initialize GLAD" << std::endl; return -1; }
4、视口大小
教程中提到了要设置视口大小,视口是什么,可以认为是实际上OpenGL渲染的窗口的大小,这个大小是小于等于窗口大小的。教程中的这个位置没有写设置饰扣大小的代码,也并未有什么影响,只是好像这样视口大小和窗口大小是一样的,这里暂且不管。呃,后面提到了,当窗口被第一次显示的时候framebuffer_size_callback(马上写到这个函数)也会被调用。
正常情况下用户可能会改变窗口大小,这样的话视口大小也是要随着改变的,所以要对窗口注册一个回调函数,每当窗口大小发生变化时调用这个函数使相应的视口调整相应的大小。
为窗口大小调整时注册回调函数的函数是:
这个函数有两个参数,第一个是要注册函数的窗口,第二个参数是回调函数,其实就是一个函数指针。好,这里我们已经有了窗口对象,那么就只需要写回调函数,通过查看函数的定义,可以看到回调函数的原型(因为本质上回调函数这个参数是个函数指针,调用的函数与函数指针要匹配)是:
该函数原型无返回值,需要三个参数,第一个参数是窗口对象,第二三个参数分别是调整后视口的宽和搞,所以如下设计回调函数:
void framebuffer_size_callback(GLFWwindow* window, int width, int height) { glViewport(0, 0, width, height); }
函数名叫framebuffer_size_callback,参数和返回值与要求的一样,当然第一个参数这里没有用到,到是没有什么影响,暂且忽略。函数内部用到了一个OpenGL的函数glViewport,这个函数用于调整视口的大小,四个参数,第一二个参数是视口左下角的坐标,这里设置为(0,0),第三四个参数是视口的宽和高。
于是为窗口注册回调函数就这样写:
glfwSetFramebufferSizeCallback(window, framebuffer_size_callback);
5、循环渲染(绘制)
上面的几步走完后,如果运行会发现窗口一闪而过就结束了,我们需要循环绘制画面,称为渲染循环,需要一个while循环一直进行,而循环的判断条件是使用glfwWindowShouldClose这个函数,这个函数检查当前窗口是否要求被关闭,返回值是int类型,代表是否关闭的标志(flag),只有一个参数,是要检查是否关闭的窗口。每次循环该函数都检查一下是否要求关闭,是的话循环结束,程序继续后面内容然后就结束了。完整代码如下:
while(!glfwWindowShouldClose(window)) { glfwSwapBuffers(window); glfwPollEvents(); }
当然循环里面还有两个函数,glfwSwapBuffers和glfwPollEvents,这里依次说一下:
glfwSwapBuffers是交换前后的颜色缓冲,这是因为这里用的是双缓冲模式,使用单缓冲的时候,生成的图像是从左到右从上到下依次绘制出来的,不是一下绘制出来,所以可能存在图像闪烁的问题。这里使用双缓冲(Double Buffer),前缓冲保存着最终输出的图像,后缓冲进行着绘制,当所有渲染指令完成后,交换前后缓冲,这样图像就立即显示出来,不会出现单缓冲时出现的闪烁不真实感等问题。这个函数无返回值,只有一个传入的窗口参数。
glfwPollEvents检查有没有触发什么事件(键盘输入、鼠标移动等),更新窗口状态,并调用对应的回调函数(如果不加这句,之前写的回调函数就用不到了)。函数无参无返回值。
最后如果渲染结束的话应该调用glfwTERminate函数来释放/删除之前分配的所有资源:
glfwTerminate(); return 0;
到这里整个流程就基本结束了,如果运行的话,会出现一个黑色的窗口,事实上,整个看似什么都没有的窗口一直在刷新,而且刷新速率还很高,我这UHD630的核显都能达到六七百帧的速度。
6、处理输入
我们也希望GLFW能够处理一些输入控制,可以通过glfwGetKey函数来完成,两个参数,返回值是int,参数分别是需要检测按键的窗口和需要检测按下的键,检测到返回1,没检测到返回0。
为了简洁好看,将检测过程写到函数里面,每次循环的时候调用,如下:
void processInput(GLFWwindow *window) { if(glfwGetKey(window, GLFW_KEY_ESCAPE) == GLFW_PRESS) glfwSetWindowShouldClose(window, true); }
以下是完整代码:
#include <glad/glad.h> #include <GLFW/glfw3.h> #include <iostream> void framebuffer_size_callback(GLFWwindow* window, int width, int height); void processInput(GLFWwindow *window); // settings const unsigned int SCR_WIDTH = 800; const unsigned int SCR_HEIGHT = 600; int main() { // glfw: 初始化和配置 // ------------------------------ glfwInit(); glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3); glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3); glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE); //如果是苹果系统,应有以下配置 #ifdef __APPLE__ glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE); #endif // 创建glfw窗口 // -------------------- GLFWwindow* window = glfwCreateWindow(SCR_WIDTH, SCR_HEIGHT, "LearnOpenGL", NULL, NULL); if (window == NULL) { std::cout << "Failed to create GLFW window" << std::endl; glfwTerminate(); return -1; } glfwMakeContextCurrent(window); glfwSetFramebufferSizeCallback(window, framebuffer_size_callback); // glad: 加载所有的OpenGL函数指针 // --------------------------------------- if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress)) { std::cout << "Failed to initialize GLAD" << std::endl; return -1; } // 循环渲染 // ----------- while (!glfwWindowShouldClose(window)) { // 处理输入 // ----- processInput(window); // glfw: 交换缓冲区和处理IO事件 (按下释放按键,鼠标移动等等) // ------------------------------------------------------------------------------- glfwSwapBuffers(window); glfwPollEvents(); } // glfw: 终止,释放清空之前分配的资源 // ------------------------------------------------------------------ glfwTerminate(); return 0; } // 处理所有输入: 查询GLFW在当前帧是否有相关的键按下/释放,并且做出相应的反应 // --------------------------------------------------------------------------------------------------------- void processInput(GLFWwindow *window) { if(glfwGetKey(window, GLFW_KEY_ESCAPE) == GLFW_PRESS) glfwSetWindowShouldClose(window, true); } // glfw: 无论何时窗口的大小发生改变(有操作系统或者用户导致)这个回调函数都会被执行 // --------------------------------------------------------------------------------------------- void framebuffer_size_callback(GLFWwindow* window, int width, int height) { // 确保视口与新窗口维度相匹配; 注意对于视网膜(Retina)显示屏,width和height都会明显比原输入值更高一点。 glViewport(0, 0, width, height); }