最近在读 Unity 4.3 的源码,研究到 Unity 集成的物理引擎 PhysX 和 Box2D 部分,接下来还要研究渲染引擎方面的东西,想着直接倒腾一下,自己集成一遍写个小 demo 。当然这都依赖于 OpenGL 这个东西来渲染(因为物理引擎本身都是一些计算,没有可视化的东西,需要借助渲染层将其可视化),这里先搭一个可以在 Windows 下编写 OpenGL 程序的架子,然后再往里面塞物理引擎和 Shader 相关的东西。
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 的升级版 。
在 Windows 平台下开发 C++ 程序,可以使用 VS 2017 作为 IDE ,傻瓜式且继承了编译调试等,显然是更加简单的选择。但我还是倾向于折腾一点,也有助于了解底层的一些东西,windows 下开发 C++ 程序我还是更习惯于使用 MinGW-w64
和 CMake
来完成编译,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 中的 KHR
和 glad
两个文件夹复制到 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.dll
和 libglfw3dll.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
可以看到如下结果:
假如 .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 配置不一致,因此导致了结果不同。
解决方案:关闭资源管理器,重新打开资源管理器再双击可执行程序就可以了。
终于知道为什么修改环境变量配置要重启电脑了,其实就是最简单粗暴避开这种隐含规则的方法。
本工程的源码以提交到 github :/linshuhe/OpenGLWinDemo
cmake 添加头文件目录,链接动态、静态库
如何将DLL文件复制到与使用CMake的可执行文件相同的文件夹中?