第一章 OpenGL简介
本章主要编程实现一个最基础的OpenGL程序,目前大家可能对于OpenGL几乎不怎么了解,最开始学习的时候大家都是这样,因此以编程实现作为一种方法慢慢的学习OpenGL是一种非常不错的方法,然后再回过头来看就会加深对OpenGL的理解,因此目前大家不要担心看不懂,只要开始上手就会很好的学习OpenGL。
首先需要导入所需要的库glfw和glad,glfw主要在程序中创建窗口,获取键盘输入等,glad是当前最新的用来访问OpenGL规范接口的第三方库。
#include <glad/glad.h> #include <GLFW/glfw3.h>
请确认是在包含GLFW的头文件之前包含了GLAD的头文件。GLAD的头文件包含了正确的OpenGL头文件(例如GL/gl.h),所以需要在其它依赖于OpenGL的头文件之前包含GLAD。
由于OpenGL只是一个规范接口,所以想要创建OpenGL程序首先要初始化glfw和glad。
glfw初试化使用glfwInit()函数,然后配置OpenGL的版本以及渲染模式。GLAD是用来管理OpenGL的函数指针的,所以在调用任何OpenGL的函数之前我们需要初始化GLAD。
我们给GLAD传入了用来加载系统相关的OpenGL函数指针地址的函数。
int gladLoadGLLoader(GLADloadproc load):
任何的OpenGL接口调用都必须在初始化GLAD库后才可以正常访问。如果成功的话,该接口将返回GL_TRUE,否则就会返回GL_FALSE。
其中GLADloadproc函数声明如下: void* (*GLADloadproc)(const char* name)
GLFW给我们的是glfwGetProcAddress,它根据我们编译的系统定义了正确的函数。即GLADloadProc函数中的参数。
glfwInit(); //初始化glfw /* 我们用glfwWindowHint配置OpenGL, glfwWindowHint函数的第一个参数代表选项的名称,我们可以从很多以GLFW_开头的枚举值中选择;第二个参数接受一个整型,用来设置这个选项的值。 下面两行配置OpenGL的版本号即OpenGL4.3 */ glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 4); glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3); glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE); //这行代码配置OpenGL的渲染模式,我们使用OpenGL核心模式 if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress)) { cout << "Failed to initialize glad" << endl; return -1; }
下面我们将创建第一个OpenGL窗口,创建窗口我们使用GLFW提供的glfwCreateWindow函数,函数原型如下:
GLFWwindow* glfwCreateWindow(int width, int height, const char* title, GLFWmonitor* monitor, GLFWwindow* share);
width: 创建的OpenGL窗口的宽 height:创建的OpenGL窗口的高 title:创建OpenGL窗口的标题 monitor:显示器用于全屏模式,设为NULL是为窗口 share:窗口的上下文为共享资源,NULL为不共享资源
后面两个参数在使用的过程中设为BULL即可,glfwCreateWindow函数的返回类型为GLFWwindow*,返回值为窗口的指针。即
GLFWwindow* window = GLFWCreateWindow(800,600,"我们的第一个OpenGL窗口"),NULL,NULL);
创建完窗口我们就可以通知GLFW将我们窗口的上下文设置为当前线程的主上下文。
glfwMakeContextCurrent(window);
完整的初始化代码如下:
glfwInit(); //初始化glfw /* 我们用glfwWindowHint配置OpenGL, glfwWindowHint函数的第一个参数代表选项的名称,我们可以从很多以GLFW_开头的枚举值中选择;第二个参数接受一个整型,用来设置这个选项的值。 下面两行配置OpenGL的版本号即OpenGL4.3 */ glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 4); glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3); glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE); //这行代码配置OpenGL的渲染模式,我们使用OpenGL核心模式 //创建OpenGL窗口需要在glad初始化之前创建,否则glad会初始化失败 GLFWwindow* window = glfwCreateWindow(800, 600, "First OpenGL Window", NULL, NULL); if (window == NULL) { std::cout << "Failed to create GLFW window" << std::endl; glfwTerminate(); return -1; } glfwMakeContextCurrent(window); if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress)) { cout << "Failed to initialize glad" << endl; return -1; }
下面我们将正式绘制窗口,其实窗口绘制非常简单,只需要在一个循环中不断刷新窗口就可以了。
在这个while循环的调剂安装我们的思维应该是窗口没有在销毁之前都应该在不停的绘制,应该这里需要调用glfwWindowShouldClose函数,函数原型如下:
int glfwWindowShouldClose(GLFWwindow* window);
在渲染循环中,主要使用两个函数:glfwSwapBuffers() 和 glfwPollEvents();
glfwWindowShouldClose函数在我们每次循环的开始前检查一次GLFW是否被要求退出,如果是的话该函数返回true然后渲染循环便结束了,之后为我们就可以关闭应用程序了。
glfwPollEvents函数检查有没有触发什么事件(比如键盘输入、鼠标移动等)、更新窗口状态,并调用对应的回调函数(可以通过回调方法手动设置)。
glfwSwapBuffers函数会交换颜色缓冲(它是一个储存着GLFW窗口每一个像素颜色值的大缓冲),它在这一迭代中被用来绘制,并且将会作为输出显示在屏幕上。
双缓冲(Double Buffer)
应用程序使用单缓冲绘图时可能会存在图像闪烁的问题。
这是因为生成的图像不是一下子被绘制出来的,而是按照从左到右,由上而下逐像素地绘制而成的。最终图像不是在瞬间显示给用户,而是通过一步一步生成的,这会导致渲染的结果很不真实。为了规避这些问题,我们应用双缓冲渲染窗口应用程序。前缓冲保存着最终输出的图像,它会在屏幕上显示;而所有的的渲染指令都会在后缓冲上绘制。当所有的渲染指令执行完毕后,我们交换(Swap)前缓冲和后缓冲,这样图像就立即呈显出来,之前提到的不真实感就消除了。
完整代码如下:
#include <glad/glad.h> #include <GLFW/glfw3.h> #include <iostream> using namespace std; int main() { glfwInit(); //初始化glfw /* 我们用glfwWindowHint配置OpenGL, glfwWindowHint函数的第一个参数代表选项的名称,我们可以从很多以GLFW_开头的枚举值中选择;第二个参数接受一个整型,用来设置这个选项的值。 下面两行配置OpenGL的版本号即OpenGL4.3 */ glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 4); glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3); glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE); //这行代码配置OpenGL的渲染模式,我们使用OpenGL核心模式 GLFWwindow* window = glfwCreateWindow(800, 600, "First OpenGL Window", NULL, NULL); if (window == NULL) { std::cout << "Failed to create GLFW window" << std::endl; glfwTerminate(); return -1; } glfwMakeContextCurrent(window); if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress)) { cout << "Failed to initialize glad" << endl; return -1; } while (!glfwWindowShouldClose(window)) { glfwSwapBuffers(window); glfwPollEvents(); } return 0; }
程序绘制的窗口如果没有错误的话应该和下面一样。
这个窗口看起来非常简陋,对这个窗口最简单的操作就是改变窗口的背景颜色。因为我们想每一帧都绘制这样的背影颜色,所以我们应该把这个操作放进渲染循环中,其实我们大部分对窗口内容的变化操作都应该放进这个渲染循环中。
在每个新的渲染迭代开始的时候我们总是希望清屏,否则我们仍能看见上一次迭代的渲染结果。我们可以通过调用glClear函数来清空屏幕的颜色缓冲,它接受一个缓冲位(Buffer Bit)来指定要清空的缓冲,可能的缓冲位有GL_COLOR_BUFFER_BIT,GL_DEPTH_BUFFER_BIT和GL_STENCIL_BUFFER_BIT。由于现在我们只关心颜色值,所以我们只清空颜色缓冲。之后调用glClearColor函数设置清空屏幕后显示的背景颜色。
void glCLearColor(GLfloat red,GLfloat green,GLfloat blue,Glfloat alpha);
上面两个函数可以使用glClearbufferfv()函数代替,其中,buffer可以传入GL_COLOR、GL_DEPTH或GL_STENCIL,以指明我们要清空哪种缓存;value指明了清空后的默认值,而drawbuffer用于多输出缓存的情况。
void glClearBufferfv(GLenum buffer, GLint drawbuffer, const GLfloat* value);
这里我们将屏幕清空为红色,则两种设置方式为:
第一种:
glClear(GL_COLOR_BUFFER_BIT); glClearColor(1,0,0,1);
第二种:
float color = {1,0,0,0}; glClearBuffefv(GL_COLOR,0,color);
部分代码如下:
while (!glfwWindowShouldClose(window)) { glClear(GL_COLOR_BUFFER_BIT); glClearColor(1, 0, 0, 1); /*float color[] = { 1,0,0,1 }; glClearBufferfv(GL_COLOR, 0, color);*/ glfwSwapBuffers(window); glfwPollEvents(); }
我们可以通过调用glViewport函数来设置窗口的维度,告诉OpenGL才只能知道怎样根据窗口大小显示数据和坐标。
glViewport(0, 0, 800, 600);
glViewport函数前两个参数控制窗口左下角的位置。第三个和第四个参数控制渲染窗口的宽度和高度(像素)。
我们实际上也可以将视口的维度设置为比GLFW的维度小,这样子之后所有的OpenGL渲染将会在一个更小的窗口中显示,这样子的话我们也可以将一些其它元素显示在OpenGL视口之外。
然而,当用户改变窗口的大小的时候,视口也应该被调整。我们可以对窗口注册一个回调函数(Callback Function),它会在每次窗口大小被调整的时候被调用。这个回调函数的原型如下:
void framebuffer_size_callback(GLFWwindow* window, int width, int height);
这个帧缓冲大小函数需要一个GLFWwindow作为它的第一个参数,以及两个整数表示窗口的新维度。每当窗口改变大小,GLFW会调用这个函数并填充相应的参数供你处理。
void framebuffer_size_callback(GLFWwindow* window, int width, int height) { glViewport(0, 0, width, height); }
我们还需要注册这个函数,告诉GLFW我们希望每当窗口调整大小的时候调用这个函数:
glfwSetFramebufferSizeCallback(window, framebuffer_size_callback);
完整程序如下:
#include <glad/glad.h> #include <GLFW/glfw3.h> #include <iostream> using namespace std; void framebuffer_size_callback(GLFWwindow* window, int width, int height); int main() { glfwInit(); //初始化glfw /* 我们用glfwWindowHint配置OpenGL, glfwWindowHint函数的第一个参数代表选项的名称,我们可以从很多以GLFW_开头的枚举值中选择;第二个参数接受一个整型,用来设置这个选项的值。 下面两行配置OpenGL的版本号即OpenGL4.3 */ glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 4); glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3); glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE); //这行代码配置OpenGL的渲染模式,我们使用OpenGL核心模式 GLFWwindow* window = glfwCreateWindow(800, 600, "First OpenGL Window", NULL, NULL); if (window == NULL) { std::cout << "Failed to create GLFW window" << std::endl; glfwTerminate(); return -1; } glfwMakeContextCurrent(window); if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress)) { cout << "Failed to initialize glad" << endl; return -1; } while (!glfwWindowShouldClose(window)) { glClear(GL_COLOR_BUFFER_BIT); glClearColor(1, 0, 0, 1); /*float color[] = { 1,0,0,1 }; glClearBufferfv(GL_COLOR, 0, color);*/ glfwSetFramebufferSizeCallback(window, framebuffer_size_callback); glfwSwapBuffers(window); glfwPollEvents(); } return 0; } void framebuffer_size_callback(GLFWwindow* window, int width, int height) { glViewport(0, 0, 800, 600); }