学习参考网站Learn OpenGL, extensive tutorial resource for learning Modern OpenGL
准备工作
在创建窗口之前,先进行一些必要的准备工作,这里我们采用的是OpenGL3.3版本,事实上这个版本已经可以基本满足初学者的要求,
// GLEW
#define GLEW_STATIC
#include <GL/glew.h>
// GLFW
#include <GLFW/glfw3.h>
首先先引入了glew和glfw两个库,在glew之前我们进行了静态链接,定义GLEW_STATIC宏,因为使用的是glew静态链接库。需要注意的是,有必要在glfw之前引入glew库,因为在包含glew时也同时会引入许多OpenGL必要的依赖文件。
接下来就是对于main中的准备:
int main()
{
glfwInit();
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
glfwWindowHint(GLFW_RESIZABLE, GL_FALSE);
return 0;
}
在main函数当中,首先,我们调用glfwInit进行初始化,接下来用glfwWindow来配置GLFW(glfw本身是一个开源的语言库,用来创建窗口并处理用户输入,在这里笔者配置好的glfw认为是一个抽象的对象,负责创建以及管理窗口,并针对用户输入进行窗口框架的操作)。对于glfwWindowHint函数,第一个参数代表当前设置选项的名称,很多是以GLFW_为开头的枚举值,第二个参数则是设置这些选项的状态,比如第一第二行我们设置OpenGL版本为3.3,第三个告诉了GLFW使用的是核心模式,第四个则告诉了我们创建的窗口之后不可以被手动放大或缩小。
创建窗口对象
接下来创建一个窗口对象,需要了解的是,这个窗口对象是一个框架的对象,它与后面所说的视口有所差异。
GLFWwindow* window = glfwCreateWindow(1920,1080,"learnOpenGL",glfwGetPrimaryMonitor(), nullptr);
if (window == nullptr)
{
std::cout << "Failed to create GLFW window" << std::endl;
glfwTerminate();
return -1;
}
首先用glfwCreateWindow创建了一个窗口对象,因为我想要一个全屏的效果和原屏幕保持一致,所以我在他的前两个参数设置为1920 * 1080,分别代表了窗口的宽和高占多少个像素。接下来为这个窗口的名称,第四个设置显示器,意思为当前主显示器,也可以设置为nullptr默认为当前显示器,最后一个暂且设置为nullptr,想要进一步了解的可以专门去文档中查看。返回的指针储存在新创建的window当中。
根据创建的窗口对象创建一个视口(viewport)
int width, height;
glfwGetFramebufferSize(window, &width, &height);
glViewport(0, 0, width, height);
通过调用glViewport函数,我们告诉了需要渲染的窗口的大小,其中前两个是窗口左下角的信息, 而对于后两个参数则设定了这个窗口的大小,可以直接设定为具体的数值,在这里通过调用函数glfwGetFramebufferSize获取指定窗口对象的尺寸,并把信息指定到width和height,需要注意的是这里的window是我们先前已经创建的GLFWwindow*类型的窗口对象。
我们不希望窗口在运行后就立刻结束,希望窗口能一直保持并持续响应用户输入,我们需要在程序中添加一个while循环,我们可以把它称之为游戏循环(Game Loop),它能在我们让GLFW退出前一直保持运行。下面几行的代码就实现了一个简单的游戏循环
while(!glfwWindowShouldClose(window))
{
glfwPollEvents();
glfwSwapBuffers(window);
}
- glfwWindowShouldClose函数在我们每次循环的开始前检查一次GLFW是否被要求退出,如果是的话该函数返回
true
然后游戏循环便结束了,之后为我们就可以关闭应用程序了。 - glfwPollEvents函数检查有没有触发什么事件(比如键盘输入、鼠标移动等),然后调用对应的回调函数(可以通过回调方法手动设置)。我们一般在游戏循环的开始调用事件处理函数。需要注意这个回调将会在后边得以使用。
-
glfwSwapBuffers函数会交换颜色缓冲(它是一个储存着GLFW窗口每一个像素颜色的大缓冲),它在这一迭代中被用来绘制,并且将会作为输出显示在屏幕上。
需要注意的是为了规避图像顺序渲染可能出现的闪烁问题,目前的应用程序采用的是双缓冲机制,前缓冲会显示在屏幕上,而所有的渲染指令在后缓冲进行,之后执行完毕后,前后交换(swap),而这个正是我们调用glfwSwapBuffers所实现的。
最后,在程序结束后,我们需要正确释放删除之前调用GLFW的所有资源,这个通过函数glfwTerminate来实现
-
输入
- 对于对窗口的键盘输入响应,可以通过glfw的回调函数来实现,当我们设置了按键回调之后,当我们有键盘输入时,会调用对应的回调函数进行处理,
void key_callback(GLFWwindow* window, int key, int scancode, int action, int mode) { // 当用户按下ESC键,我们设置window窗口的WindowShouldClose属性为true // 关闭应用程序 if(key == GLFW_KEY_ESCAPE && action == GLFW_PRESS) glfwSetWindowShouldClose(window, GL_TRUE); }
我们可以定义一个这样的按键回调函数,当用户进行按键输入时调用这个函数,检查如果键入为escape(esc)时,设置窗口WindowShouldClose属性为GL_TRUE,当进行下一次while更新时检测到此属性窗口关闭,之后通过glfwSetKeyCallback注册我们的函数至合适的回调,他接受两个参数,第一个是窗口对象,第二个是要调用的函数指针,当用户有键盘相应时调用指定的函数。
除了按键回调函数之外,我们还能我们自己的函数注册其它的回调。例如,我们可以注册一个回调函数来处理窗口尺寸变化、处理一些错误信息等。我们可以在创建窗口之后,开始游戏循环之前注册各种回调函数。
整体代码如下:// GLEW #define GLEW_STATIC #include <GL/glew.h> // GLFW #include <GLFW/glfw3.h> #include<iostream> //回调函数的声明 void call_back(GLFWwindow* window, int key, int scancode, int action, int mode); int main() { //Init GLFW glfwInit(); //Set all options for GLFW glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR,3); glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);//设置语言规范版本3.3 glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);//显示模式为核心模式 glfwWindowHint(GLFW_RESIZABLE,GL_FALSE);//We can not resize the window we have created. //Create a window object that we can use for GLFW's functions. GLFWwindow* window = glfwCreateWindow(1920,1080,"learnOpenGL",glfwGetPrimaryMonitor(), nullptr); if (window == nullptr) { std::cout << "Failed to create GLFW window" << std::endl; glfwTerminate(); return -1; } glfwMakeContextCurrent(window);//一直设置窗口为当前环境,直到更换当前环境或者窗口消失 //注册键盘回调函数,键盘相应时调用 glfwSetKeyCallback(window, call_back); // Set this to true so GLEW knows to use a modern approach to retrieving function pointers and extensions //glew让OpenGL能够实现一些扩展功能,但是和glfw一样,使用前需要先初始化 glewExperimental = GL_TRUE; if(glewInit() != GLEW_OK) { std::cout << "Failed to initialize GLEW" << std::endl; return -1; } //针对的是已经创建的窗口对象 int width, height; glfwGetFramebufferSize(window, &width, &height); glViewport(0, 0, width,height); while (!glfwWindowShouldClose(window)) { glfwPollEvents(); //在这里我们设置了一个屏幕显示的颜色为绿色, glClearColor(0.0,1.0f, 0.0,0.0);//起设置的作用,设置了清除屏幕时使用的颜色,四个参数相当于RGBA glClear(GL_COLOR_BUFFER_BIT);//当调用glClear函数,清除颜色缓冲之后,整个颜色缓冲都会被填充为glClearColor里所设置的颜色,在这里为用绿色来清除屏幕。 glfwSwapBuffers(window); } glfwTerminate(); return 0; } //实现 void call_back(GLFWwindow* window, int key, int scancode, int action, int mode) {//设置回调函数 if (key == GLFW_KEY_ESCAPE && action == GLFW_PRESS) glfwSetWindowShouldClose(window, GL_TRUE); }
最终显示结果为一个覆盖全屏的绿色。