OpenGL开发环境配置
OpenGL的实际情况
在我们使用OpenGL画出绚丽的3D效果之前,首先要做的就是搭建OpenGL开发环境。然而,这些操作在每个操作系统上都是不一样的。因为OpenGL只是一个规范,它严格的定义了每个函数的实现逻辑和返回值,但具体的实现代码并没有给出(这有点类似ECMAScript)。这意味着,只要能实现OpenGL规范所定义的功能,代码的实现过程并不固定。这样一来,OpenGL代码的实际编写者大多是显卡开发商。
也就是说,由于OpenGL本身并没有提供针对不同操作系统而实现的实际代码,我们必须自己处理创建窗口和定义OpenGL上下文以及用户输入。这显然是不可取的。不过,有一些库已经提供了我们所需的功能,其中一部分是特别针对OpenGL的。这些库节省了我们书写操作系统相关代码的时间,提供给我们一个窗口和上下文用来渲染。最流行的几个库有GLUT,SDL,SFML和GLFW。这里我们使用GLFW。
利用GLFW库搭建OpenGL上下文
GLFW是一个专门针对OpenGL的C语言库,它提供了一些渲染物体所需的最低限度的接口。它允许用户创建OpenGL上下文,定义窗口参数以及处理用户输入,而这正是我们需要的。
注:glut是旧版的opengl上下文构建工具,这里不推荐。
首先从GLFW的官网上下载它,这里为了保证文件的完整性,我们下载源代码(source package),自己使用CMake工具来进行编译。下载完毕后将它解压。
使用CMake生成目标文件
首先从CMake官网上下载合适版本的CMake工具,然后傻瓜式安装在电脑上即可。为了简化操作,我们使用cmake-gui来进行操作,可见,这个界面是基于Qt的:
打开之后,在"where is the source code"输入框中填入解压好了的glfw文件夹的位置,在"where to build the binaries"输入框中填入存放目标文件的地方(这个随意)。使用左下角的Configure
按钮配置要使用的IDE工具(用来运行OpenGL代码的编译器)。配置完成后,点'generate'按钮在指定目录生成目标文件。
下图中打了勾的就是定义在glfw/CMakeLists.txt文件中的经CMake生成的创建OpenGL环境所需要的目标文件
点击CMake-gui上的'Open Project`按钮,使用VS 2017打开目录下的GLFW.sln文件:
经VS 2017编译运行这个项目后,会在项目所在的目录下的src/Debug文件夹中生成glfw3.lib库文件,
上面做的都是准备工作,现在开始正式配置OpenGL环境,首先创建一个OpenGL文件夹(位置任意),再在其中创建一个文件夹lib,把刚才编译得到的glfw3.lib放到里面。再到glfw解压的文件夹中把其中的include文件夹拷贝到OpenGL文件夹中。
使用GLAD库来管理OpenGL函数指针
到了这一步,OpenGL环境的搭建基本差不多了,但我们仍然还有一件事要做。
因为OpenGL只是一个标准/规范,具体的实现是由驱动开发商针对特定显卡实现的。但由于OpenGL驱动版本众多,它大多数函数的位置都无法在编译时确定下来,需要在运行时查询。这样一来,找出OpenGL函数的运行地址的任务就落在了开发者身上。也就是说,开发者需要在运行时获取函数地址并将其保存在一个函数指针中供以后使用,我们需要对每个可能使用的函数重复这个过程。这显然是不可取的。幸运的是,有些库能简化此过程,其中GLAD是目前最新,也是最流行的库。
首先我们要去官网下载,然后解压。
将解压得到的两个文件夹glad/include/glad和glad/include/KHR拷贝到之前创建好了的OpenGL文件夹下的include文件夹下。
创建第一个OpenGL项目
使用VS 2017打开之前提到过的GLFW.sln项目,将glad/src/glad.c文件添加到项目源文件中:
最后,就是使先前所有的操作生效的时候了——配置VS 2017的包含文件和链接库路径。
打开项目的"属性"面板,
配置VC++目录选项卡中的"包含目录",弹出文件选择框,选择刚才创建好了的OpenGL文件夹中的include文件夹。
再向"库目录"中选中OpenGL/lib文件夹。
再切换到"链接器"=>"输入"选项卡,向其中添加附加依赖项:
opengl32.lib
glfw3.lib
确认后关闭属性面板。
向项目的源文件中添加一个C++文件,test.cpp。
第一个OpenGL窗口
漫长的OpenGL环境搭建终于完成了,只有将下面的代码拷贝到test.cpp文件中,就可在VS 2017中看到效果。
// GLAD的头文件必须放在其它所有依赖于OpenGL库的头文件之前,因为GLAD的头文件中包含了正确的OpenGL头文件(例如GL/gl.h)
#include <glad/glad.h>
#include <GLFW/glfw3.h>
#include <iostream>
void framebuffer_size_callback(GLFWwindow* window, int width, int height);
void processInput(GLFWwindow *window);
// 接下来要创建的窗口的大小
const unsigned int SCR_WIDTH = 800;
const unsigned int SCR_HEIGHT = 600;
// 实例化GLFW窗口(由于我们使用GLFW来构建OpenGL上下文,所以实例化GLFW窗口就是创建OpenGL窗口对象)
// main()函数就是一个OpenGL应用程序,它是OpenGL程序中的一切核心逻辑的安放处
int main() {
glfwInit(); // 初始化GLFW
/* 使用glfwWindowHint()函数来配置GLFW */
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3); // 将使用的OpenGL的主版本号设为3(OpenGL 3)
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3); // 次版本号也设为3
glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE); // 使用OpenGL的核心模式(Core-profile)
// 在 Mac OS系统上,还需要加上下面这句才能生效
#ifdef __APPLE__
glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE); // uncomment this statement to fix compilation on OS X
#endif
// 创建一个窗口对象,glfwCreateWindow(window_Width, window_Height, window_Title, )
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;
}
// 通知GLFW将我们窗口的上下文设置为当前线程的主上下文
glfwMakeContextCurrent(window);
// 对GLFWwindow窗口对象注册一个回调函数framebuffer_size_callback()
// window窗口尺寸发生变化时,会将窗口句柄和当前窗口的宽高作为参数传给framebuffer_size_callback(GLFWwindow*, int, int),以方便framebuffer_size_callback()及时更正视口
glfwSetFramebufferSizeCallback(window, framebuffer_size_callback);
// GLAD是用来管理OpenGL的函数指针的,在调用任何OpenGL的函数之前我们都需要先初始化GLAD(使用gladLoadGLLoader())
// glfwGetProcAddress()是一个用来加载系统相关的OpenGL函数指针地址的函数,它由glfw提供,它会根据我们编译项目的系统做相应调整
if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress))
{
std::cout << "Failed to initialize GLAD" << std::endl;
return -1;
}
// 渲染循环(为了避免只绘制出一个图像后窗口就关闭,我们要给窗口加一个渲染循环,这样,在我们主动关闭它之前程序都会不断地绘制图像并能接受用户输入)
// glfwWindowShouldClose()函数在我们每次循环的开始前都要检查一遍GLFW是否被要求退出,如果是的话该函数就返回true,然后渲染循环便结束了
while (!glfwWindowShouldClose(window))
{
// 接收来自窗口的输入,下面有它的定义
processInput(window);
// 设置清屏颜色,每一次渲染循环开始前都会使用这个颜色刷新整个屏幕像素
glClearColor(0.2f, 0.3f, 0.3f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT); // 清空颜色缓冲区中的内容
// glfwSwapBuffers()函数会交换颜色缓冲(它是一个储存着GLFW窗口的每一个像素颜色值的大缓冲区),它在这个循环中被用来绘制图像,缓冲区中的像素值将会作为输出显示在屏幕上
glfwSwapBuffers(window);
// glfwPollEvents()函数检查有没有什么事件被触发(如键盘输入、鼠标移动等),它会更新窗口状态,并调用对应的回调函数
glfwPollEvents();
}
// 在使用return 0 退出应用程序之前,释放之前glfw分配出的所有资源
glfwTerminate();
return 0;
}
void processInput(GLFWwindow *window)
{
if(glfwGetKey(window, GLFW_KEY_ESCAPE) == GLFW_PRESS) // 按下回车键则关闭渲染循环
glfwSetWindowShouldClose(window, true);
}
void framebuffer_size_callback(GLFWwindow* window, int width, int height)
{
// 定义视口(视界范围),glViewport(left_x, left_y, width, height)
glViewport(0, 0, width, height);
}
双缓冲(Double Buffer) |
应用程序使用单缓冲绘图时可能会存在图像闪烁的问题,这是因为生成的图像不是一下子被绘制出来的,而是按照从左到右,由上而下逐像素地绘制而成的。最终图像不是瞬间显示给用户,而是通过一步一步生成的,这会导致渲染的结果很不真实。为了规避这些问题,我们应用双缓冲渲染窗口应用程序。前缓冲保存着最终输出的图像,它会在屏幕上显示;而所有的的渲染指令都会在后缓冲上绘制。当所有的渲染指令执行完毕后,我们交换(Swap)前缓冲和后缓冲,这样图像就立即呈显出来,之前提到的不真实感就消除了。这也就是上述代码要使用glfwSwapBuffers()的缘故了。 |