OpenGL教程(2)——第一个窗口

注:本文可转载,转载请注明出处:http://www.cnblogs.com/collectionne/p/6618419.html

OpenGL环境终于配置好了,现在我们可以开始学习OpenGL了。

首先,创建一个.cpp文件,然后打上几行#include指令:

#include <iostream>
using std::cout;
using std::endl; #include <GL/glew.h>
#include <GLFW/glfw3.h>

先从<iostream>说起。其实<iostream>不是必须的,这里包含它,是因为后面可能需要在控制台上打印信息,提醒用户。glew.h和glfw3.h是我们上一篇教程里所下载的头文件,注意一定要先包含glew.h,再包含glfw3.h(因为glfw3.h会包含gl.h,而这是glew.h不允许的)。最后,如果你使用的是静态版的GLEW,需要在包含glew.h前加上下面的#define指令:

#define GLEW_STATIC

然后我们进入main()函数:

int main()
{

GLFW

首先我们需要初始化GLFW:

if (!glfwInit())
{
cout << "Failed to initialize GLFW!\n";
return -;
}

glfwInit()函数用于初始化GLFW,在调用大部分其它GLFW函数前,都需要初始化GLFW。glfwInit()如果成功,将会返回GLFW_TRUE,否则返回GLFW_FALSE(GLFW_TRUE和GLFW_FALSE是GLFW定义的常量,被定义为于1和0)。也就是说,如果返回GLFW_FALSE,就说明初始化失败,这时我们将打印一条信息告知用户GLFW初始化失败,并且返回(注意,你可能无法看到打印的信息,这时可以在return -1前加上一句cin.get()或system("pause")暂停程序)。不过,检查返回值也不是必须的。

然后我们需要使用glfwWindowHint()函数设置hint:

glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, );
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, );
glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
glfwWindowHint(GLFW_RESIZABLE, GLFW_FALSE);

Hint直接翻译成中文是“线索”,这里大致指“选项”。使用glfwWindowHint()可以设置一些关于窗口的选项。glfwWindowHint()接受两个参数,第一个是我们要设置的hint的名字,使用GLFW常量(以GLFW_开头)指定;第二个是我们要把该hint设置成的值,该值随要设置的hint而异。

Hint也有很多种,这里我们只设置了4种,分别是OpenGL主版本号(GLFW_CONTEXT_VERSION_MAJOR)、OpenGL副版本号(GLFW_CONTEXT_VERSION_MINOR)、OpenGL模式(GLFW_OPENGL_PROFILE)、窗口是否可调整大小(GLFW_RESIZABLE)。这4个hint,我们分别设置为3、3、GLFW_CORE_PROFILE、GLFW_FALSE。

因为我们要使用OpenGL 3.3,所以我们把GLFW_CONTEXT_VERSION_MAJOR和GLFW_CONTEXT_VERSION_MINOR对应的hint都设置为3。因为我们要使用OpenGL核心模式(这个后面会提到更多),所以我们把GLFW_OPENGL_PROFILE对应的hint设置为GLFW_OPENGL_CORE_PROFILE,表示使用OpenGL核心模式。最后,把GLFW_RESIZABLE对应的hint设置为GLFW_FALSE,表示窗口不允许用户调整大小。之所以这样做是因为如果允许用户调整大小,大小发生变化后,窗口的绘制区域默认不变(依然是原来窗口的区域),也就是说窗口上绘制的图像的大小、位置不会发生改变。为了避免这种现象发生,我们就简单地不让用户调整窗口大小(当然也有更好的方法,就是用GLFW设置一个窗口大小的回调函数,但这样比较简单)。

注意,如果你使用Mac OS X,你需要再加上下面这句调用:

glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GLFW_TRUE);

然后就该创建窗口了:

GLFWwindow * window = glfwCreateWindow(, , "First window", nullptr, nullptr);
if (window == nullptr)
{
cout << "Failed to create window using GLFW!\n";
return -;
}

glfwCreateWindow()接受5个参数。第一个、第二个是窗口的宽和高,以像素为单位,这里分别是800和600;第三个是窗口标题,这里是"First window";第四个和第五个参数可以忽略,直接传入nullptr。

glfwCreateWindow()将会使用前面glfwWindowHint()所设置的hint创建窗口,返回一个GLFWwindow指针,我们把这个指针叫做窗口句柄(window handle)。简单说来,就是从此以后我们都用它来代表我们的窗口,后面会需要对窗口进行设置、询问一些关于窗口的信息,就需要传入这个指针。如果创建窗口出现问题,将返回NULL(C++里就是nullptr),因此如果window为nullptr,说明创建窗口失败,打印错误信息,返回-1。

创建完毕之后,需要让当前窗口的环境在当前线程上成为当前环境(说法有点复杂,不太严谨地说,就是接下来的画图都会画在我们刚刚创建的窗口上):

glfwMakeContextCurrent(window);

GLEW

GLFW设置完毕后,还需要初始化GLEW:

glewExperimental = GL_TRUE;
if (glewInit())
{
cout << "Failed to init GLEW!\n";
return -;
}

首先我们把glewExperimental设置为GL_TRUE,这样GLEW就会使用更加新的方法管理OpenGL,减少错误。然后调用glewInit()初始化GLEW。注意glewInit()成功返回0,失败返回1,与GLFW相反。如果失败则输出错误信息,并返回-1。

游戏循环

和控制台程序不同,我们希望这个程序可以一直运行,直到用户关闭窗口。这样我们就需要创建一个循环,叫做游戏循环(game loop)。

while (!glfwWindowShouldClose(window))
{
glfwSwapBuffers(window);
glfwPollEvents();
}

glfwWindowShouldClose()检查窗口是否需要关闭。如果是,游戏循环就结束了,接下来我们将会清理资源,结束程序。

glfwSwapBuffers()用来交换窗口的两个颜色缓冲(color buffer)。这个概念叫做双缓冲(double buffer)。如果不使用双缓冲,就可能会出现闪屏现象,因为绘制一般不是一下子就绘制完毕的,而是从左到右、从上到下地绘制。为了避免这个问题,一般会使用双缓冲,前缓冲(front buffer)是最终的图像,而程序会在后缓冲(back buffer)上绘制。后缓冲绘制完毕后,就交换两个缓冲,这样就不会有闪屏的问题了。

glfwPollEvents()用来检查是否有事件被触发,例如点击关闭按钮、点击鼠标、按下键盘,等等。如果有,GLFW将会对这些事件进行处理。更严谨地说(以键盘、鼠标键为例),GLFW自己会记录每个键、鼠标键的状态(按下/没有按下),但当某个按键松开或被按下时,GLFW不会自动更新状态,必须调用glfwPollEvents()才能更新。调用glfwPollEvents()时会检查状态是否有变化(如按下的是否松开,没有按下的是否被按下),如果有就会更新该状态。如果设置了回调函数(这个将在以后讲),还会调用相应的回调函数。如果不调用这个函数,不仅无法检测输入(后文会需要这样),我们在点击窗口右上角的X时,GLFW也不会知道需要关闭窗口。所以必须在每一轮游戏循环中调用这个函数。

最后,当循环执行完毕后,我们需要释放前面所申请的资源:

glfwTerminate();
return ;
}

glfwTerminate()将会释放所有GLFW资源,关闭当前窗口。然后程序就成功退出了。

现在运行你的程序,如果你得到了一个黑色窗口,恭喜你,成功了!如果没有,请参考结尾的代码,对照自己的代码。

额外的东西

现在我们已经成功创建窗口,并且在用户点击右上角的X时退出。但是还不够——你可能会问,可不可以让用户按ESC键时退出,或者换一个窗口背景颜色?这里我们就来实现这两项。

关于第一个功能,可以这样写(在游戏循环里):

if (glfwGetKey(window, GLFW_KEY_ESCAPE) == GLFW_PRESS)
glfwSetWindowShouldClose(window, GLFW_TRUE);

glfwGetKey()用来判断一个键是否按下。第一个参数是GLFW窗口句柄,第二个参数是一个GLFW常量,代表一个键。GLFW_KEY_ESCAPE表示Esc键。如果Esc键按下了,glfwGetKey将返回GLFW_PRESS(值为1),否则返回GLFW_RELEASE(值为0)。

如果Esc被按下了,我们就会调用glfwSetWindowShouldClose()函数,为窗口设置关闭标志。第一个参数是窗口句柄,第二个参数表示是否关闭,这里为GLFW_TRUE,表示关闭该窗口。注意,这时窗口不会立即被关闭,但是glfwWindowShouldClose()将返回GLFW_TRUE,到了glfwTerminate()就会关闭窗口。

关于第二个功能,可以在进入游戏循环之前,加一句:

glClearColor(0.2f, 0.3f, 0.3f, 1.0f);

与其它glfw开头的函数(这些函数由GLFW提供)不同,这个函数是以gl开头的,是OpenGL的函数。glClearColor用来设置窗口被清除时的颜色,也就是背景颜色。前3个参数分别是背景颜色的R、G、B分量,范围是0~1;第4个是alpha值表示透明度,这里只是设为1.0f,表示不透明。(0.2, 0.3, 0.3)这一颜色大致是较暗的蓝绿色。

然后在游戏循环中加一句:

glClear(GL_COLOR_BUFFER_BIT);

这将会清除当前窗口,把所有像素的颜色都设置为前面所设置的清除颜色。GL_COLOR_BUFFER_BIT是OpenGL定义的常量,表示清除颜色缓存。后面会学到,还有GL_DEPTH_BUFFER_BIT和GL_STENCIL_BUFFER_BIT。

现在运行程序,你应该会得到下面的画面,并且按Esc时会关闭窗口:

OpenGL教程(2)——第一个窗口

最终代码

#include <iostream>
using std::cout;
using std::endl; #include <GL/glew.h>
#include <GLFW/glfw3.h> int main()
{
// init GLFW
if (!glfwInit())
{
cout << "Failed to init GLFW!\n";
return -;
} // set window hints
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, );
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, );
glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
glfwWindowHint(GLFW_RESIZABLE, GLFW_FALSE);
// create the window
GLFWwindow * window = glfwCreateWindow(, , "First window", nullptr, nullptr);
if (window == nullptr)
{
cout << "Failed to create window using GLFW!\n";
return -;
}
glfwMakeContextCurrent(window); // init GLEW
glewExperimental = GL_TRUE;
if (glewInit())
{
cout << "Failed to init GLEW!\n";
return -;
} // set clear color
glClearColor(0.2f, 0.3f, 0.3f, 1.0f); // game loop
while (!glfwWindowShouldClose(window))
{
// if Esc is pressed, close the window
if (glfwGetKey(window, GLFW_KEY_ESCAPE) == GLFW_PRESS)
glfwSetWindowShouldClose(window, GLFW_TRUE); // clear the window
glClear(GL_COLOR_BUFFER_BIT); // poll and swap
glfwSwapBuffers(window);
glfwPollEvents();
}
// cleanup
glfwTerminate();
return ;
}

好啦,你已经学会了创建窗口的基本技巧,下一课,我们将会学习如何画我们的第一个三角形!

上一篇:android 中targetSdkVersion和与target属性的区别


下一篇:一文说透WordPress的自定义文章类型