Windows 下的 OpenGL 开发环境配置(GLFW+GLAD)

引言

最近在读 Unity 4.3 的源码,研究到 Unity 集成的物理引擎 PhysX 和 Box2D 部分,接下来还要研究渲染引擎方面的东西,想着直接倒腾一下,自己集成一遍写个小 demo 。当然这都依赖于 OpenGL 这个东西来渲染(因为物理引擎本身都是一些计算,没有可视化的东西,需要借助渲染层将其可视化),这里先搭一个可以在 Windows 下编写 OpenGL 程序的架子,然后再往里面塞物理引擎和 Shader 相关的东西。

 

OpenGL 基础概念

OpenGL 函数库相关的 API 包括:核心库( gl ),实用库( glu ),辅助库( aux )、实用工具库( glut ),窗口库( glx 、 agl 、 wgl )和扩展函数库等。gl 是核心,glu 是对 gl 的部分封装。glx 、 agl 、wgl 是针对不同窗口系统的函数。扩展函数库是硬件厂商为实现硬件更新利用OpenGL的扩展机制开发的函数。

  • gult (OpenGL Utility Toolkit)

    是 OpenGL 跨平台的实用工具库,主要用于做窗口界面。大部分函数以 glut 开头,其 API 包括:窗口操作函数,窗口初始化、窗口大小、窗口位置等函数;回调函数:响应刷新消息、键盘消息、鼠标消息、定时器函数等;创建复杂的三维物体;菜单函数;程序运行函数。

    对应的开源实现是 freegult

  • glew

    glut 或 freeglut 主要是 1.0 的基本函数功能,glew 是使用OpenGL 2.0 之后接口的一个扩展库,能自动识别当前平台支持的全部 OpenGL 高级扩展函数。

    在程序中只要引入 glew.h 头文件,便可使用 gl, glu, glext, wgl 和 glx 的全部函数。

  • glfw

    是一个跨平台的 OpenGL 应用框架,支持 OpenGL 和 OpenGL ES ,支持窗口创建、读取输入和处理事件等功能。特点:轻量级、开源和跨平台。由于 glut 已经太老了,现在基本都是用 glfw 来替代 glut 。

  • glad

    glad 可以说是 glew 的升级版 。

 

IDE 选择

在 Windows 平台下开发 C++ 程序,可以使用 VS 2017 作为 IDE ,傻瓜式且继承了编译调试等,显然是更加简单的选择。但我还是倾向于折腾一点,也有助于了解底层的一些东西,windows 下开发 C++ 程序我还是更习惯于使用 MinGW-w64CMake 来完成编译,IDE 直接使用比较轻巧的 VS Code 。

这是我之前写的配置过程: Windows下的C++编译工具—— MinGW-w64 和 CMake

 

资源下载

这里直接使用 glad + glfw 组合的方式

glfw

打开 glfw 官网下载页 ,根据当前使用 MinGW 支持的编译位数选择下载 32 位或 64 位的包,使用 MinGW-w64 的直接下载 64-bit Windows binaries 即可,得到:glfw-3.3.bin.WIN64.zip

glad

glad 有一个在线服务 https://glad.dav1d.de/ ,设置如下:

  • 语言(Language)设为 C/C++

  • API 中 gl 版本指的是 OpenGL 的版本,选择 3.3 以上(因为 3.3 及之后的版本是纯可编程管线,去掉了固定管线),这里我选择最新的 4.6;

  • 模式(Profile)设为 Core

    这里有两种选择 :Compatibility 和 Core ,其中 Compatibility 兼容旧版本,包含低版本中的 API ,而 Core 是只包含当前版本必须支持的 API ,不考虑向下兼容旧版本,更为轻巧。

  • 确保勾选了 Generate a Loader ,然后点击 GENERATE

在生成页面中下载 glad.zip 压缩包。

 

创建工程

  • 创建一个空文件夹,并其下创建 include 、lib 和 src 目录

  • 将 glad.zip 解压后的文件:

    • include 中的 KHRglad 两个文件夹复制到 include 目录;

    • src/glad.c 复制到 src 目录。

    为了方便起见,还是把 glad.c 编译成静态库:

$ gcc .\src\glad.c -c -I.\include\
$ ar -rc libglad.a glad.o

libglad.a 复制到 lib 目录。

  • 解压 glfw-3.3.bin.WIN64.zip ,然后将其目录下的文件:

    • include 目录下的 GLFW 文件夹复制到 include 目录;

    • lib-mingw-w64 目录下的 glfw3.dlllibglfw3dll.a 复制到 lib 目录。

  • 在 src 目录下创建一个测试脚本 test.cpp ,内容如下:

    直接从网上抄的一段代码,参考:vscode OpenGL 环境搭建

    #include 
    #include 
    #include 
    // 设置窗口尺寸
    const unsigned int SCR_WIDTH = 400;
    const unsigned int SCR_HEIGHT = 300;
    int main()
    {
        // glfw: 初始化
        glfwInit();
        glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
        glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
        glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
    #ifdef __APPLE__
        // uncomment this statement to fix compilation on OS X
        glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE); 
    #endif
        // glfw 创建窗口
        GLFWwindow *window = glfwCreateWindow(SCR_WIDTH, SCR_HEIGHT, "Test", NULL, NULL);
        if (window == NULL)
        {
            std::cout << "Failed to create GLFW window" << std::endl;
            glfwTerminate();
            return -1;
        }
        glfwMakeContextCurrent(window);
        // 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))
        {
            glClearColor(0.0f, 1.f, 0.0f, 1.0f);
            glClear(GL_COLOR_BUFFER_BIT);
            // glfw: swap buffers and poll IO events (keyspressed/released, mouse moved etc.)
            glfwSwapBuffers(window);
            glfwPollEvents();
        }
        // glfw: terminate, clearing all previously allocated GLFWresources.
        glfwTerminate();
        return 0;
    }

 

编译工程

由于工程比较简单,这里有几种方式可以完成工程的编译

  • 直接 gcc 命令编译工程:

    假如是直接使用 glad.c 进行编译:

    $ g++ -g -std=c++17 -I ./include -L ./lib src/test.cpp src/glad.c -o main -lglfw3dll

    假如使用 libglad.a 进行编译:

    $ g++ -g -std=c++17 -I ./include -L ./lib src/test.cpp -o main -lglad -lglfw3dll

    当然更推荐使用 glfw3.dll 来编译程序:

    $ g++ -g -std=c++17 -I ./include -L ./lib src/test.cpp -o main -lglad .\glfw3.dll

    -lglfw3dll 使用的是 libglfw3dll.a 来进行编译,然而此库文件最终编译出来的 exe 文件也必须依赖 glfw3.dll 才能启动,既然如此,还不如直接使用 glfw3.dll 库文件就好了。

  • 手写 makefile ,在工程根目录下创建 makefile 文本文件,内容如下:

    CXX := g++
    CXX_FLAGS := -g -std=c++17
    ​
    SRC := src
    INCLUDE := ./include
    LIB := ./lib
    ​
    LIBRARIES := -lglad .\glfw3.dll
    EXECUTABLE := main
    ​
    all:./$(EXECUTABLE)
    ​
    run: all
        ./$(EXECUTABLE)
    ​
    $(EXECUTABLE):$(SRC)/*.cpp
        $(CXX) $(CXX_FLAGS) -I$(INCLUDE) -L$(LIB) $^ -o $@ $(LIBRARIES)

    在命令行执行 make run 则编译完成后会自动执行可执行程序。

  • 创建 CMakeLists.txt ,借助 CMake 来生成 Makefile ,然后再编译工程,内容如下:

    cmake_minimum_required(VERSION 3.0)
    ​
    project (TestGladGlfw)      # 工程名称
    ​
    link_directories(${PROJECT_SOURCE_DIR}/lib)         # 库目录, -L
    ​
    set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -g -std=c++17")
    ​
    set(SOURCE_FILES src/test.cpp src/glad.c)
    add_executable(main ${SOURCE_FILES})    # 源文件
    ​
    include_directories(${PROJECT_SOURCE_DIR}/include)   # 头文件目录, -I
    ​
    target_link_libraries(main glfw3)

    需要注意的是:原本以为 target_link_libraries 是从只从 MinGW-w64 安装目录中的 lib 中去获取库文件的,因为 lib 中明明有 libglfw3dll.a ,但编译时且出现 cannot find -lglfw3dll 的错误,如下:

    E:/C++/installs/x86_64-8.1.0-release-posix-seh-rt_v6-rev0/mingw64/bin/../lib/gcc/x86_64-w64-mingw32/8.1.0/../../../../x86_64-w64-mingw32/bin/ld.exe: cannot find -lglfw3dll
    collect2.exe: error: ld returned 1 exit status
    make[2]: *** [CMakeFiles/main.dir/build.make:104: main.exe] Error 1
    make[1]: *** [CMakeFiles/Makefile2:73: CMakeFiles/main.dir/all] Error 2
    make: *** [Makefile:84: all] Error 2

    而将该库复制到 MinGW-w64 安装目录中的 lib 中去又能正常编译。最后才发现原来 link_directories 必须在 add_executable 之前,应该该指令对其后续操作生效。假如不使用 link_directories 而只使用 target_link_libraries 的话,需要使用绝对地址,相对当前工程的地址似乎无效。

    在 build 目录下生成 Makefile

    $ mkdir build
    $ cd build
    $ cmake -G"Unix Makefiles" ../
    -- The C compiler identification is GNU 8.1.0
    -- The CXX compiler identification is GNU 8.1.0
    -- Check for working C compiler: E:/C++/installs/x86_64-8.1.0-release-posix-seh-rt_v6-rev0/mingw64/bin/gcc.exe
    -- Check for working C compiler: E:/C++/installs/x86_64-8.1.0-release-posix-seh-rt_v6-rev0/mingw64/bin/gcc.exe -- works
    -- Detecting C compiler ABI info
    -- Detecting C compiler ABI info - done
    -- Detecting C compile features
    -- Detecting C compile features - done
    -- Check for working CXX compiler: E:/C++/installs/x86_64-8.1.0-release-posix-seh-rt_v6-rev0/mingw64/bin/c++.exe
    -- Check for working CXX compiler: E:/C++/installs/x86_64-8.1.0-release-posix-seh-rt_v6-rev0/mingw64/bin/c++.exe -- works
    -- Detecting CXX compiler ABI info
    -- Detecting CXX compiler ABI info - done
    -- Detecting CXX compile features
    -- Detecting CXX compile features - done
    -- Configuring done
    -- Generating done
    -- Build files have been written to: E:/C++/projects/TestGladGlfw/build

    使用 Makefile 编译当前工程:

    $ make
    Scanning dependencies of target main
    [ 33%] Building CXX object CMakeFiles/main.dir/src/test.cpp.obj
    [ 66%] Building C object CMakeFiles/main.dir/src/glad.c.obj
    [100%] Linking CXX executable main.exe
    "copy dll files after make"
    [100%] Built target main

 

运行结果

上面编译命令执行完成之后,会生成 main.exe 可执行文件,直接在命令行运行或者双击打开:

$ .\main.exe

可以看到如下结果:

Windows 下的 OpenGL 开发环境配置(GLFW+GLAD)_第1张图片

 

DLL 库 丢失问题

假如 .dll 动态库文件与 .exe 不在同一目录下,常常会出现 无法启动此程序,因为计算机中丢失 xxx.dll 。... 这类的报错,有两个解决方案:

方案一

可以通过 CMakeLists.txt 实现在编译得到目标文件后,将依赖库复制到同级目录下,由于不同平台使用的库文件可能不同,这里只演示 Windows 平台,直接在上述 CMakeLists.txt 末尾加上如下内容:

# 平台区分
if (CMAKE_HOST_WIN32)
    set(WINDOWS 1)
elseif(CMAKE_HOST_APPLE)
    set(MACOS 1)
elseif(CMAKE_HOST_UNIX)
    set(LINUX 1)
endif()
​
# 复制 dll 到 build 目录
if(WINDOWS)
    add_custom_command(TARGET main 
        POST_BUILD 
        COMMAND echo "copy dll files after make"
        COMMAND ${CMAKE_COMMAND} -E copy_if_different
        "${PROJECT_SOURCE_DIR}/lib/glfw3.dll"              
        $)
endif()

方案二

参考 Qt5 编译 & 打包依赖dll发布 ,可以使用 EnigmaVirtual Box 工具将所有依赖的 .dll 都打进 .exe 中 。

 

环境变量更新的坑

编译出来的 .exe 文件在命令行使用 .\main.exe 可以打开,而直接在资源管理器中双击打开却提示:应用程序无法正常启动(0xc000007b)。请点击 “确定” 关闭程序

原本以为是程序编译或者依赖库的问题,后来才发现是环境变量的问题:

我们都知道 Windows 中设置变量有 系统变量用户变量 两种,其中系统变量是针对全部用户起作用的,而用户变量是针对当前用户起作用。而在命令行中输入 PATH 查询的结果其实是系统变量和当前用户变量合并后的结果。

然而,有个坑点,启动命令行的方式也会决定使用变量的不同:

  • win+R 快捷键输入 cmd 启动的命令行一般会获得最新的 PATH 配置;

  • 在资源管理器下通过 Shift + 鼠标右键,然后选择 在此处打开命令窗口 启动的命令行,获取的 PATH 配置是此资源管理器窗口打开时刻的 PATH 配置,假如更细了环境变量后没有重新打开窗口,则此窗口中的 PATH 不会更新。

我之前使用的是 MinGW ,这次改为 MinGW-w64 ,因此修改了环境变量的 PATH ,但我没有重启电脑,打开项目文件夹的资源管理器没有关闭重开,因此窗口中的 PATH 还是旧的,而我编译工程是在 VS Code 中进行的(PATH 更新时重启过,使用的是新的 PATH),因此双击启动时的 PATH 配置与 VS Code 中命令行启动时的 PATH 配置不一致,因此导致了结果不同。

解决方案:关闭资源管理器,重新打开资源管理器再双击可执行程序就可以了。

终于知道为什么修改环境变量配置要重启电脑了,其实就是最简单粗暴避开这种隐含规则的方法。

 

Demo 源码

本工程的源码以提交到 github :/linshuhe/OpenGLWinDemo

 

参考

  • cmake 添加头文件目录,链接动态、静态库

  • 如何将DLL文件复制到与使用CMake的可执行文件相同的文件夹中?

你可能感兴趣的:(OpenGL)