Maine

纵有疾风起,人生不言弃

菜鸟 OpenGL 入门 - 窗口(一)

我们已经配置好了 GLADGLFW ,下面我们来创建一个窗口来验证一下我们的所有工作是否正常。

这里,你可以使用 XCode 或其他 IDE 创建项目,但我使用手动编译的方式来测试。

1. 初始化项目:

创建任何一个空目录作为项目目录,在项目目录下创建一个 main.cpp,并将之前在配置 GLAD 时下载的 glad.c 复制到项目目录中。

2. 包含相应头文件

然后在 main.cpp 中包含必要的头文件:

#include <glad/glad.h>
#include <GLFW/glfw3.h>

3. 基本编译设置

这里我是手动编译,因此写一个 Makefile 方便开发,如果是使用 IDE 则略过这一步骤,不过要注意在 IDE 中引入 OpenGL 和 GLFW 的库,以及将 glad.c 加入项目当中。

在这里可以先创建一个空的 main 函数,完整文件如下:

#include <iostream>
using namespace std;

#include <glad/glad.h>
#include <GLFW/glfw3.h>

int main(int argc, char *argv[])
{
    cout << "Hello OpenGL" << endl;
    return 0;
}

然后创建一个 Makefile,内容如下:

# 创建一些基本变量
CC = gcc
C++ = g++
LINK = g++
LIBS = -lGLFW
FRAMEWORK = -framework OpenGL
TARGET = ogldemo

BUILD_DIR = ./build/
INC_PATH = ./include/
SRC_PATH = ./

CPP_FILES = ${wildcard ${SRC_PATH}*.cpp}
C_FILES = ${wildcard ${SRC_PATH}*.c}

OBJS = ${patsubst ${SRC_PATH}%.cpp, ${BUILD_DIR}%.o, ${CPP_FILES}} \
    ${patsubst ${SRC_PATH}%.c, ${BUILD_DIR}%.o, ${C_FILES}}


# 规则
${TARGET}:init ${OBJS}
    ${LINK} ${OBJS} ${FRAMEWORK} ${LIBS} -o [email protected]

${BUILD_DIR}%.o:${SRC_PATH}%.c
    ${CC} -I ${INC_PATH} -c $< -o [email protected]

${BUILD_DIR}%.o:${SRC_PATH}%.cpp
    ${C++} -I ${INC_PATH} -c $< -o [email protected]

run:${TARGET}
    @echo "###########################"
    @./${TARGET}

# 创建 build 目录
.PHONY:init
init:
    -mkdir ${BUILD_DIR}

# 自动清理
.PHONY:clean
clean:
    rm -rf ${TARGET} ${OBJS} ${BUILD_DIR}

接下来我们就可以在项目目录执行 make 来编译项目,make run 来编译并且运行项目,make clean 清理编译文件。

4. 实例化 Glfw 窗口

GLFW

接下来需要将 main 函数的代码修改为如下内容:

int main(int argc, char *argv[])
{
    glfwInit();
    glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 4);
    glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 1);
    glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
    glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE);

    return 0;
}
  • glfwInit 函数用来初始化 GLFW
  • glfwWindowHint 函数用来配置 GLFW

glfwWindowHint 函数的第一个参数定义了配置选项的名称,第二个参数是配置选项的值。该函数所有配置选项的值可以看 GLFW’s window handling 这篇文章。

glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 4); 告诉 GLFW 我们使用的 OpenGL 主版本号是 4。

glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 1); 告诉 GLFW 我们使用的 OpenGL 次版本号是 1。

glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE); 告诉 GLFW 我们使用的 OpenGL 是核心模式而非兼容模式。

glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE); 是 Mac 下必须要加的,如果没有的话,上面的配置项则不起作用,其他平台可以略过。

如果你在编译上面的代码的时候出现了很多 undefined reference(未定义的引用)错误就证明你的 GLEW 库链接出错了。

接下来我们创建一个窗口对象,这个窗口对象存放了所有和窗口相关的数据,而且会被GLFW的其他函数频繁地用到。

GLFWwindow* window = glfwCreateWindow(800, 600, "Hello OpenGL", NULL, NULL);
if(window == NULL)
{
        cout << "Failed to create window" << endl;
        glfwTerminate();
        return -1;
}
glfwMakeContextCurrent(window);

glfwCreateWindow 函数需要窗口的宽和高作为它的前两个参数。第三个参数表示这个窗口的名称(标题),这里我们使用”Hello OpenGL”,最后两个参数我们暂时忽略。这个函数将会返回一个GLFWwindow对象,我们会在其它的GLFW操作中使用到。创建完窗口我们就可以告诉 GLFW 将我们窗口的上下文设置为当前线程的主上下文了。

GLAD

if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress))
{
        cout << "Failed to initialize GLAD" << endl;
        return -1;
}

我们给GLAD传入了用来加载系统相关的OpenGL函数指针地址的函数。GLFW中,我们使用glfwGetProcAddress来获取,它根据我们编译的系统定义了正确的函数。

渲染循环

这个时候,如果你编译并允许代码,会有一个窗口一闪而过,好吧,事实上你根本看不到窗口。

因为我们需要在程序中添加一个渲染循环,保证在我们手动关闭 GLFW 窗口前他不会自己消失。下面几行代码实现了简单的渲染循环:

while(!glfwWindowShouldClose(window))
{
    glfwSwapBuffers(window);
    glfwPollEvents();    
}
  • glfwWindowShouldClose 函数在我们每次循环的开始前检查一次GLFW是否被要求退出,如果是的话该函数返回true然后渲染循环便结束了,之后为我们就可以关闭应用程序了。
  • glfwPollEvents 函数检查有没有触发什么事件(比如键盘输入、鼠标移动等)、更新窗口状态,并调用对应的回调函数(可以通过回调方法手动设置)。
  • glfwSwapBuffers 函数会交换颜色缓冲(它是一个储存着GLFW窗口每一个像素颜色值的大缓冲),它在这一迭代中被用来绘制,并且将会作为输出显示在屏幕上。

双缓冲(Double Buffer)
应用程序使用单缓冲绘图时可能会存在图像闪烁的问题。 这是因为生成的图像不是一下子被绘制出来的,而是按照从左到右,由上而下逐像素地绘制而成的。最终图像不是在瞬间显示给用户,而是通过一步一步生成的,这会导致渲染的结果很不真实。为了规避这些问题,我们应用双缓冲渲染窗口应用程序。前缓冲保存着最终输出的图像,它会在屏幕上显示;而所有的的渲染指令都会在后缓冲上绘制。当所有的渲染指令执行完毕后,我们交换(Swap)前缓冲和后缓冲,这样图像就立即呈显出来,之前提到的不真实感就消除了。

最后,记得释放资源:

glfwTerminate();
return 0;

如果这个时候你可以编译成功,并出现一个黑乎乎的窗口,那恭喜你,第一步已经成功迈出。当然关于窗口还有一些细节,下次再说,先消化一下。

点赞

发表评论

电子邮件地址不会被公开。 必填项已用*标注