学习3D特效前的准备阶段:使用mac系统,基于cmake搭建OpenGL环境

工作中越来越多的需求是要将数据以3D的形式展示给用户,但是在实现3D特效之前,都需要有一个准备阶段。OpenGL规范描述了绘制2D和3D图形的抽象API,正所谓“工欲善其事必先利其器”,所以,学习OpenGL之前,当然是需要搭建好运行的环境。

今年的目标就是熟练掌握好OpenGL的入门知识,之前由于工作的原因,迟迟无法开展,今天终于抽空搭建好了OpenGL环境。

搭建OpenGL环境过程中,也是花费了不少时间,特别是基于cmake来搭建OpenGL的环境,网络上的资料很少,并且也没找到成功的例子,因此,这里将搭建的过程分享出来。希望能够对大家有一定的参考价值。

本文首先会介绍搭建OpenGL环境之前的准备条件,然后再总体说明搭建的流程,接着再针对每个流程进行详细的说明。这里不会涉及太多专业术语,本文的目的就是要快速搭建好环境,方便后续的学习和工作的开展。

一、准备条件

本文OpenGL环境是在mac系统搭建,并且采用cmake来组织编译环境,使用的IDE工具是QtCreator 4.9.1, mac操作系统的版本是10.13.6。

搭建opengl环境的大致步骤为,构建glfw、构建glad, 链接系统自带的OpenGL库。

glfw的作用是创建OpenGL上下文、定义窗口参数以及处理用户输入;

glad根据版本加载所有相关的OpenGL函数。

OpenGL库则是实现了实现了规范的抽象API的集合。

二、创建工程

使用qtcreator工具来创建C++工程,工程名称为StudyOpengl

然后选择cmake来编译系统,其他按照默认进行操作即可

三、构建GLFW

官方网站上下载glfw的源代码包,其代码的目录结构如下图所示

然后使用qtcreator来打开glfw源代码包中的CMakeLists.txt, 目的就是使用qtcreator来编译glfw代码,并生成静态库

加载成功glfw的工程目录

选中工程根目录,然后右键弹出的菜单,点击“构建”选项

如果编译成功,那么在编译目录下能够看到名称为libglfw3的静态库

最后将glfw的头文件和静态库拷贝到工程StudyOpengl下,到这里,glfw的构建工作已经完成,后面还需要添加glfw的头文件和库的搜索路径,这个等到整体编译的时候,再统一进行说明。

四、构建GLAD

构建glad, 为了方便,我们这里直接使用网络上提供的在线服务来进行创建,具体的在线服务可以网络上进行搜索。

进入glad的在线服务页面如下所示,语言选择C/C++, API版本选择最新的为4.6, Profile下选择Core

拖动页面到最底部,勾选“Generate a loader”选项,然后点击“GENERATE”

等待片刻,即可生成glad的压缩包,将压缩包里面的include文件夹和glad.c文件拷贝到工程StudyOpengl, 至此,glad的构建工作也完成,添加头文件和库的搜索路径也等到整体编译的时候,再统一说明。

五、链接OpenGL

链接OpenGL,我觉得是最重要的一步,这是自己摸索出来的解决方法。这里链接是mac系统自带的opengl库。

项目工程StudyOpengl中的CMakeLists.txt文件中,添加搜索OpenGL库的路径,创建变量来保存Cocoa、CoreVideo、IOKit的库路径

find_package(OpenGL REQUIRED)
set(SYS_FRAMEWORKS_DIR /System/Library/Frameworks)
set(COCOA_LIBS ${SYS_FRAMEWORKS_DIR}/Cocoa.framework)
set(COREVIDEO_LIBS ${SYS_FRAMEWORKS_DIR}/CoreVideo.framework)
set(IOKIT_LIBS ${SYS_FRAMEWORKS_DIR}/IOKit.framework)

接着添加opengl的头文件搜索路径

include_directories(${OPENGL_INCLUDE_DIR})

最后将OpenGL、Cocoa、CoreVideo、IOKit的库链接到工程。

target_link_libraries(${PROJECT_NAME}
        ${OPENGL_LIBRARIES}
        ${COCOA_LIBS}
        ${COREVIDEO_LIBS}
        ${IOKIT_LIBS})

六、整体编译

cmake文件增加glad和glfw的头文件搜索路径,首次创建函数include_sub_directories_recursively,该函数的功能是递归的将输入路径下的所有目录添加到头文件的搜索路径

###
### 添加头文件搜索路径
###
function(include_sub_directories_recursively root_dir)
        if (IS_DIRECTORY ${root_dir})
                # include_directories包含头文件的搜索路径
                message("root_dir : ${root_dir}")
                include_directories(${root_dir})
        endif()
        # 如果 RELATIVE 标志位被设定,将返回指定路径的相对路径
        file (GLOB ALL_SUB RELATIVE ${root_dir} ${root_dir}/*)
        foreach(sub ${ALL_SUB})
                if (IS_DIRECTORY ${root_dir}/${sub} AND (NOT ("${sub}" STREQUAL ".svn")) AND (NOT ("${sub}" STREQUAL ".DS_Store")) )
                        include_sub_directories_recursively(${root_dir}/${sub})
                endif()
        endforeach()
endfunction()

include_sub_directories_recursively(${PROJECT_SOURCE_DIR}/code/third)

include_directories(
        ${CMAKE_CURRENT_BINARY_DIR}
        ${PROJECT_SOURCE_DIR}
        ${PROJECT_SOURCE_DIR}/code/third/glad/include
)

添加完成头文件的搜索路径之后,接下来就来添加库的搜索路径,同样的,实现函数link_sub_directories_recursively用于将输入路径下的所有目录都添加到库的搜索路径中

###
### 添加库的查找路径
###
function(link_sub_directories_recursively root_dir)
        if (IS_DIRECTORY ${root_dir})
                # link_directories(directory1 directory2 ...)
                # 指定查找库的目录
                link_directories(${root_dir})
        endif()

        # 如果 RELATIVE 标志位被设定,将返回指定路径的相对路径
        file (GLOB ALL_SUB RELATIVE ${root_dir} ${root_dir}/*)
        foreach(sub ${ALL_SUB})
                if (IS_DIRECTORY ${root_dir}/${sub} AND (NOT ("${sub}" STREQUAL ".svn")))
                        link_sub_directories_recursively(${root_dir}/${sub})
                endif()
        endforeach()
endfunction()

link_sub_directories_recursively(${PROJECT_SOURCE_DIR}/depend)

最后记得将libglfw3.a的静态库链接到工程中

target_link_libraries(${PROJECT_NAME}
        glfw3
        pthread
        ${OPENGL_LIBRARIES}
        ${COCOA_LIBS}
        ${COREVIDEO_LIBS}
        ${IOKIT_LIBS})

为了测试环境是否生效,使用网络上一位大神写的代码来进行测试,直接将以下代码拷贝到main.cpp文件

#include <iostream>
#include "glad/glad.h"
#include "glfw3.h"

using namespace std;

void framebuffer_size_callback(GLFWwindow* window, int width, int height);
void processInput(GLFWwindow *window);

// settings
const unsigned int SCR_WIDTH = 800;
const unsigned int SCR_HEIGHT = 600;

int main()
{
    // glfw: initialize and configure
    // ------------------------------
    glfwInit();
    glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
    glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
    glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);

    #ifdef __APPLE__
    glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE);
    #endif

    // glfw window creation
    // --------------------
    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;
    }
    glfwMakeContextCurrent(window);
    glfwSetFramebufferSizeCallback(window, framebuffer_size_callback);

    // glad: load all OpenGL function pointers
    // ---------------------------------------
    if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress))
    {
        std::cout << "Failed to initialize GLAD" << std::endl;
        return -1;
    }

    // render loop
    // -----------
    while (!glfwWindowShouldClose(window))
    {
        // input
        // -----
        processInput(window);

        // render
        // ------
        glClearColor(0.2f, 0.3f, 0.3f, 1.0f);
        glClear(GL_COLOR_BUFFER_BIT);

        // glfw: swap buffers and poll IO events (keys pressed/released, mouse moved etc.)
        // -------------------------------------------------------------------------------
        glfwSwapBuffers(window);
        glfwPollEvents();
    }

    // glfw: terminate, clearing all previously allocated GLFW resources.
    // ------------------------------------------------------------------
    glfwTerminate();
    return 0;
}

// process all input: query GLFW whether relevant keys are pressed/released this frame and react accordingly
// ---------------------------------------------------------------------------------------------------------
void processInput(GLFWwindow *window)
{
    if(glfwGetKey(window, GLFW_KEY_ESCAPE) == GLFW_PRESS)
        glfwSetWindowShouldClose(window, true);
}

// glfw: whenever the window size changed (by OS or user resize) this callback function executes
// ---------------------------------------------------------------------------------------------
void framebuffer_size_callback(GLFWwindow* window, int width, int height)
{
    // make sure the viewport matches the new window dimensions; note that width and
    // height will be significantly larger than specified on retina displays.
    glViewport(0, 0, width, height);
}

不出意外的话,编译运行之后,可以看到如下所示的效果

六、概括总结

至此,基于mac系统,通过cmake搭建OpenGL环境已经完成。我们再来梳理下,首先通过q tcreator创建工程,接着构建glfw和glad, 然后添加glfw、glad、opengl的头文件搜索路径,最后链接glfw库和opengl的相关库。最后一点需要注意下,就是要确定glad.c文件编译进工程,否则可能会出现奇怪的编译错误提示信息。

发表评论

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