为了显示图形,首先要做的就是创建一个OpenGL上下文(Context)和一个用于显示的窗口。然而,这些操作在每个系统上都是不一样的,OpenGL有目的地从这些操作抽象(Abstract)出去。这意味着我们必须自己处理创建窗口,定义OpenGL上下文以及处理用户输入。
我们可以使用一部分库来提供给我们一个窗口和上下文用来渲染。最流行的几个库有GLUT,SDL,SFML和GLFW。这里使用GLFW,并使用Microsoft Visual Studio 2022作为IDE。当然,使用别的IDE或者老版本也是类似的操作。
GLFW 下载
GLFW是一个专门针对OpenGL的C语言库,它提供了一些渲染物体所需的最低限度的接口。它允许用户创建OpenGL上下文,定义窗口参数以及处理用户输入。
GLFW可以从它官方网站的下载页上获取。GLFW已经有针对Visual Studio 的预编译的二进制版本和相应的头文件,也可以下载源代码包自己编译。如果是为了完成一个完整的大项目,应该下载源代码包编译源码获得。但是,这里仅仅只介绍最基本的入门,为了简单起见,直接下载预编译的二进制版本和相应的头文件即可。
创建工程
首先,打开Visual Studio,创建一个新的项目。如果VS提供了多个选项,选择Visual C++,然后选择Empty Project(空项目)
为了使程序使用GLFW,我们需要把GLFW库链接(Link)进工程。这可以通过在链接器的设置里指定我们要使用glfw3.lib
来完成,但是由于我们将第三方库放在另外的目录中,我们的工程还不知道在哪寻找这个文件。于是我们还需要将放第三方库的目录添加进设置。
要添加这些目录(需要VS搜索库和include文件的地方),我们首先进入Project Properties(工程属性,在解决方案窗口里右键项目),然后选择VC++ Directories(VC++ 目录)选项卡。分别在Include Directories
与Library Directories
最后需要在Linker(链接器)选项卡里的Input(输入)选项卡里添加glfw3.lib
这个文件,加到Additional Dependencies(附加依赖项)字段中。
如果是Windows平台,opengl32.lib已经包含在Microsoft SDK里了,它在Visual Studio安装的时候就默认安装了。由于这里用的是VS编译器,并且是在Windows操作系统上,我们只需将opengl32.lib添加进连接器设置里就行了。
如果是在Linux下需要链接libGL.so库文件,这需要添加-lGL
到链接器设置中。如果找不到这个库可能需要安装Mesa,NVidia或AMD的开发包,这部分因平台而异。
因为OpenGL只是一个标准/规范,具体的实现是由驱动开发商针对特定显卡实现的。由于OpenGL驱动版本众多,它大多数函数的位置都无法在编译时确定下来,需要在运行时查询。任务就落在了开发者身上,开发者需要在运行时获取函数地址并将其保存在一个函数指针中供以后使用。可以通过使用GLEW来获得这些函数地址。
GLEW是OpenGL Extension Wrangler Library的缩写,类似地,GLEW可以从这里下载,同样可以选择下载二进制版本,如果目标平台列在上面的话,或者下载源码编译。接着,使用GLEW的静态版本glew32s.lib(注意这里的“s”),将库文件添加到你的库目录,将include内容添加到你的include目录。接下来,在VS的链接器选项里加上glew32s.lib。注意GLFW3(默认)也是编译成了一个静态库。
为了静态链接GLEW,必须在包含GLEW头文件之前定义预处理器宏GLEW_STATIC
:
#define GLEW_STATIC
#include <GL/glew.h>
如果使用动态链接,那么可以省略这个宏。但是记住使用动态链接的话你需要拷贝一份.DLL文件到应用程序目录。
简单测试用例
测试下能不能让GLFW正常工作。
#define GLEW_STATIC
#include <GL/glew.h>
// GLFW
#include <GLFW/glfw3.h>
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);
GLFWwindow* window = glfwCreateWindow(800, 600, "FirstOpenGL", nullptr, nullptr);
if (window == nullptr)
{
std::cout << "Failed to create GLFW window" << std::endl;
glfwTerminate();
return -1;
}
glfwMakeContextCurrent(window);
glewExperimental = GL_TRUE;
if (glewInit() != GLEW_OK)
{
std::cout << "Failed to initialize GLEW" << std::endl;
return -1;
}
while(!glfwWindowShouldClose(window))
{
glfwPollEvents();
glfwSwapBuffers(window);
}
glfwTerminate();
return 0;
}
首先,新建一个.cpp
文件,然后定义GLEW_STATIC
宏,这是因为使用的是GLEW静态的链接库。
接下来创建main函数,在这个函数中将会实例化GLFW窗口。
在main函数中调用glfwInit
函数来初始化GLFW,然后使用glfwWindowHint
函数来配置GLFW。
glfwWindowHint
函数的第一个参数代表选项的名称,可以从很多以GLFW_
开头的枚举值中选择;
第二个参数接受一个整形,用来设置这个选项的值。该函数的所有的选项以及对应的值都可以在 GLFW’s window handling 这篇文档中找到。如果现在编译你的cpp文件得到了大量的 undefined reference
(未定义的引用)错误,这说明你并未顺利地链接GLFW库。
接下来我们创建一个窗口对象,这个窗口对象存放了所有和窗口相关的数据
glfwCreateWindow
函数需要窗口的宽和高作为它的前两个参数;第三个参数表示这个窗口的名称(标题);最后两个参数暂时忽略,先设置为空指针就行。它的返回值GLFWwindow
对象的指针会在其他的GLFW操作中使用到。创建完窗口就可以通知GLFW将窗口的上下文设置为当前线程的主上下文了。
然后初始化GLEW用于获取OpenGL的函数指针。注意,在初始化GLEW之前设置glewExperimental
变量的值为GL_TRUE
,这样做能让GLEW在管理OpenGL的函数指针时更多地使用现代化的技术,如果把它设置为GL_FALSE
的话可能会在使用OpenGL的核心模式时出现一些问题。
为了循环绘制图形,需要在程序中添加一个while循环,可以把它称之为游戏循环(Game Loop),它能在我们让GLFW退出前一直保持运行。
glfwWindowShouldClose
函数在我们每次循环的开始前检查一次GLFW是否被要求退出,如果是的话该函数返回true然后游戏循环便结束了,之后为我们就可以关闭应用程序了。glfwPollEvents
函数检查有没有触发什么事件(比如键盘输入、鼠标移动等),然后调用对应的回调函数(可以通过回调方法手动设置)。我们一般在游戏循环的开始调用事件处理函数。glfwSwapBuffers
函数会交换颜色缓冲(它是一个储存着GLFW窗口每一个像素颜色的大缓冲),它在这一迭代中被用来绘制,并且将会作为输出显示在屏幕上。
应用程序使用单缓冲绘图时可能会存在图像闪烁的问题。 这是因为生成的图像不是一下子被绘制出来的,而是按照从左到右,由上而下逐像素地绘制而成的。最终图像不是在瞬间显示给用户,而是通过一步一步生成的,这会导致渲染的结果很不真实。为了规避这些问题,我们应用双缓冲渲染窗口应用程序。
前缓冲保存着最终输出的图像,它会在屏幕上显示;而所有的的渲染指令都会在后缓冲上绘制。当所有的渲染指令执行完毕后,我们交换(Swap)前缓冲和后缓冲,这样图像就立即呈显出来,之前提到的不真实感就消除了。
最后,调用glfwTerminate
清理所有的资源并正确地退出应用程序。
如果没有问题的话,运行程序应该可以看到一个黑色的窗口显示。
参考资料
[1] https://learnopengl-cn.readthedocs.io/zh/latest/01%20Getting%20started/03%20Hello%20Window/
[2] http://bit.ly/2lt7ccM