问题来源

尝试在UE5中使用本地编译的aws-sdk-cpp,并需要在UE的windows编辑器里,交叉编译linux版本。

编译aws-sdk-cpp的方法参照了Build the AWS SDK for C++ on Linux/macOS,在UE项目中引入库的方法,参照了How to Integrate the AWS C++ SDK with Unreal Engine

在Windows编辑器中编译正常,打包Windows也正常,但交叉编译linux版本时出现链接错误,提示undefined symbol: Aws::Utils::DateTime::DateTime,如下

1
2
[2023.09.14-01.59.44:568][655]UATHelper: Packaging (Linux): ld.lld: error: undefined symbol: Aws::Utils::DateTime::DateTime(std::__1::chrono::time_point<std::__1::chrono::system_clock, std::__1::chrono::duration<long long, std::__1::ratio<1l, 1000000l> > > const&)
[2023.09.14-01.59.44:568][655]UATHelper: Packaging (Linux): >>> referenced by AWSCredentials.h:36 (D:/Projects/DT/MapEditorUE/ThirdParty/AWS/Include\aws/core/auth\AWSCredentials.h:36)

解决方案

尝试了各种动态库,静态库,均报同样错误。

最终发现,编译aws-sdk-cpp时,需要使用clang,而非gcc,因为Unreal的交叉编译使用的就是clang(似乎还是定制过的),一些c++标准的函数符号似乎存在差异,导致了这个问题。

使用clang编译可在cmake前用环境变量指定,并需要设定-DCMAKE_CXX_FLAGS="-stdlib=libc++",否则找不到libc++库

1
2
3
4
5
6
7
8
9
# 指定使用clang作为编译器
export CXX=/path/to/clang++
export CC=/path/to/clang

# cmake时指定-DCMAKE_CXX_FLAGS="-stdlib=libc++"
cmake ../../aws-sdk-cpp -DCMAKE_BUILD_TYPE=Release -DCMAKE_PREFIX_PATH=/usr/local/ -DCMAKE_INSTALL_PREFIX=/usr/local/ -DBUILD_SHARED_LIBS=ON -DCMAKE_CXX_FLAGS="-stdlib=libc++"

# make
make -j 8

TIP

如果不设定-DCMAKE_CXX_FLAGS="-stdlib=libc++"cmake可能会报如下错误,因为clang默认不会取找gcc的库

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
CMake Error at /usr/local/share/cmake-3.22/Modules/CMakeTestCXXCompiler.cmake:62 (message):
The C++ compiler

"/usr/bin/clang++"

is not able to compile a simple test program.

It fails with the following output:

Change Dir: /home/SENSETIME/sunxia/Develop/Programs/aws-sdk-cpp-build/test/CMakeFiles/CMakeTmp

Run Build Command(s):/bin/make -f Makefile cmTC_fba77/fast && /bin/make -f CMakeFiles/cmTC_fba77.dir/build.make CMakeFiles/cmTC_fba77.dir/build
make[1]: Entering directory '/home/SENSETIME/sunxia/Develop/Programs/aws-sdk-cpp-build/test/CMakeFiles/CMakeTmp'
Building CXX object CMakeFiles/cmTC_fba77.dir/testCXXCompiler.cxx.o
/usr/bin/clang++ -MD -MT CMakeFiles/cmTC_fba77.dir/testCXXCompiler.cxx.o -MF CMakeFiles/cmTC_fba77.dir/testCXXCompiler.cxx.o.d -o CMakeFiles/cmTC_fba77.dir/testCXXCompiler.cxx.o -c /home/SENSETIME/sunxia/Develop/Programs/aws-sdk-cpp-build/test/CMakeFiles/CMakeTmp/testCXXCompiler.cxx
Linking CXX executable cmTC_fba77
/usr/local/bin/cmake -E cmake_link_script CMakeFiles/cmTC_fba77.dir/link.txt --verbose=1
/usr/bin/clang++ -rdynamic CMakeFiles/cmTC_fba77.dir/testCXXCompiler.cxx.o -o cmTC_fba77
/usr/bin/ld: cannot find -lstdc++
clang: error: linker command failed with exit code 1 (use -v to see invocation)
make[1]: *** [CMakeFiles/cmTC_fba77.dir/build.make:100: cmTC_fba77] Error 1
make[1]: Leaving directory '/home/SENSETIME/sunxia/Develop/Programs/aws-sdk-cpp-build/test/CMakeFiles/CMakeTmp'
make: *** [Makefile:127: cmTC_fba77/fast] Error 2





CMake will not be able to correctly generate this project.
Call Stack (most recent call first):
CMakeLists.txt:152 (project)

Unreal对C++代码有一套构建系统UBT(Unreal Build Tools),从引擎自身到用户的代码,都由UBT以Module为单位进行构建。一个Module相当于一个库,被UBT构建后,在运行时被加载和使用。UBT相当于UE的一套CMake+编译系统,一来隔离平台差异,二来用统一的方式来管理依赖比较优雅。下面罗列一些Unreal Module的特点(后面称Module为模块)。

结构和构建

模块的根目录有一个配置文件,名为YourModuleName.Build.cs,基本内容如下,声明了

1
2
3
4
5
6
7
8
9
10
// ...

// 声明预编译头的用法
PCHUsage = PCHUsageMode.UseExplicitOrSharedPCHs;
// 声明公开依赖的其它模块,这里依赖了一些引擎的基本模块
PublicDependencyModuleNames.AddRange(new string[] { "Core", "CoreUObject", "Engine", "InputCore" });
// 声明私有依赖的其它模块
PrivateDependencyModuleNames.AddRange(new string[] { });

// ...

构建时,UBT基于这些依赖关系去引用其它模块的头文件,并链接它们的代码,进行编译。Unreal也提供了工具(右键uproject文件)来生成对应IDE的工程文件(比如.sln或.xcodeproj),方便用IDE打开。

预编译头(PCHs)

预编译头是为了编译性能而设计的机制,将常用的、通用的、不经常变的头预先编译一次,后续只要不改变就不重新编译,从而提升编译性能。

用户定义模块时,可以选择创建自己的PCH,使用依赖模块的PCH,或不使用PCH。

上面的Build.cs文件中有定义PCHUsage,创建一个模块时的默认选项是UseExplicitOrSharedPCHs,绝大部分模块都使用UseExplicitOrSharedPCHsNoPCHs即可。

所谓使用SharedPCHs,就是由Engine在依赖的诸多模块中,选择一个最合适的模块所定义的预编译头。而Explicit的,就是使用本模块显式定义的预编译头

用户可以在一个模块的Build.cs中定义PrivatePCHHeaderFile = "xxx.h"SharedPCHHeaderFile = xxx.h

  • PrivatePCHHeaderFile是为当前模块指定的预编译头
  • SharedPCHHeaderFile则是为其它依赖本模块的其它模块提供的可选预编译头,不是给本模块用的

暴露性(给其它模块调用)

模块的根目录通常有Private和Public两个路径,想要暴露给其它模块使用的代码,要放在Public中,但还不够,需要对代码做进一步定义。

在UCLASS声明中添加MinimalAPI,可以允许该类在其它模块代码中被用于类型转换,或调用inline的函数

1
UCLASS(Blueprintable, MinimalAPI)

在成员变量/函数前声明本模块API字样,可以允许其它模块代码使用此成员变量/函数

1
HEHE_API void DoSomething();

在类名前声明本模块API字样,可以允许整个类被其它模块的代码使用

1
class HEHE_API AHeheActor : public AActor

可见性(public/private)

一个模块要使用另一个模块,需要:

  • 引用其头文件
  • 在Build.cs文件中的依赖中添加该模块(PublicDependencyModuleNamesPrivateDependencyModuleNames

除了C++本身的可见性,UBT在处理依赖时,也有一些可见性规则。

  • 单层依赖,一个模块A依赖另一个模块B,UBT在构建A模块时会去包含B模块的头文件,并链接B模块的代码
  • 多层依赖,模块A以依赖模块B,模块B以private依赖模块C,A并不会直接包含C的头文件或链接C的代码
  • 多层依赖,模块A以依赖模块B,模块B以public依赖模块C,A会直接包含C的头文件,但不会链接C的代码

所以在实际设计模块代码时,将只有本模块才使用的依赖项,作为PrivateDependencyModuleNames依赖,如果依赖项可能被本模块的用户使用,就应该加入到PublicDependencyModuleNames

实现

要让UE引擎识别模块,需要在源码的任意处声明IMPLEMENT_MODULEIMPLEMENT_GAME_MODULEIMPLEMENT_PRIMARY_GAME_MODULE,例如默认的C++游戏工程会自动生成一个模块名.cpp,里面定义了IMPLEMENT_PRIMARY_GAME_MODULE。一个游戏工程里通常只有一个一个IMPLEMENT_PRIMARY_GAME_MODULE

模块可以定义一个实现类,通过IMPLEMENT_MODULE方法注册给引擎,这个类中可以实现一些有用的回调,比如StartupModuleStartupModule如下

1
2
3
4
5
6
7
8
9
// Hehe.h
class FHeheModule : public IModuleInterface
{
virtual void StartupModule() override;
virtual void StartupModule() override;
}

// Hehe.cpp
IMPLEMENT_MODULE(FHeheModule, ModuleTest);

其它模块也可以调用实现类的成员函数

1
FModuleManager::Get().LoadModuleChecked<FHeheModule>(TEXT("Hehe")).DoSomething();

一个模块如果依赖其它GameModule,它就应该被声明为GameModule,GameModule的好处是热更新。如果希望做一个功能相对独立的,可以发布给其它人使用的模块,则可以定义为普通Module。定义为GameModule的模块如果重载了FDefaultModuleImpl实现类,还需要定义IsGameModule()方法来返回true

重要属性

模块要么在.uproject中加载,要么在.uplugin中加载。在这两个文件中,可以定义一些模块的属性:

  • Type:默认是Runtime,除了独立运行都可以,可以进一步限制模块的使用场景,仅编辑器或者仅服务器/客户端等,详见EHostType::Type
  • LoadingPhase:模块的加载阶段,默认是Default是在引擎初始化期间,GamePlay模块加载完后加载,详见ELoadingPhase::Type
  • IncludelistPlatforms / ExcludelistPlatforms: Win32, Win64, Mac, Linux, Android, IOS等
  • IncludelistTargets / ExcludelistTargets:Game, Server, Client, Editor, Program等
  • IncludelistTargetConfigurations / ExcludelistTargetConfigurations:ebug, DebugGame, Development, Shipping, Test等

详细属性列表可见Unreal Engine Modules。上述的Include/Exclude是UE5的属性,在UE4中叫做Whitelist/Blacklist。

参考

在Windows上编译coturn,互联网上的绝大部分资料使用的是cgywin,存在性能问题,并与依赖项、coturn本身的代码版本强相关,实际编译困难。

coturn项目从4.6.2版本开始原生支持了MSVC编译,并可以使用vcpkg方便安装依赖项。此方法目前在互联网上几乎尚无关注,在此记录。

  1. 安装vcpkg

    1
    git clone https://github.com/Microsoft/vcpkg.git
  2. 初始化vcpkg

    1
    .\vcpkg\bootstrap-vcpkg.bat
  3. 设置vcpkg集成

    1
    .\vcpkg\vcpkg integrate install
  4. 将vcpkg路径加入系统PATH环境变量,方便使用命令

  5. 进入coturn项目目录,执行cmake操作(注意将E:\Programs\vcpkg替换为你本地的vcpkg路径)

    1
    2
    3
    mkdir build
    cd build
    cmake.exe -G "Visual Studio 16 2019" -A x64 -DVCPKG_TARGET_TRIPLET=x64-windows -DVCPKG_BUILD_TYPE=release -DCMAKE_TOOLCHAIN_FILE:STRING="E:\Programs\vcpkg\scripts\buildsystems\vcpkg.cmake" ..\
  6. 用VisualStudio打开生成的sln,一键build即可,编译生成的coturn.exe位于./bin路径下

此方式可以避免cgywin的复杂配置和性能损失(系统调用、文件、进程通信等方面),应作为coturn 4.6.2版本后在windows编译的首选项。

投影一般指高维向量到低维向量的转换,在绘制管线中投影矩阵是很重要的概念,它将相机坐标系下的三维点转换成二维坐标。主流绘制引擎和工具库中都提供了通过指定相机参数(fov, aspect, 视锥参数等)来控制三维投影的方法,所以投影的数学过程常常被忽略,本文我们来推导一下投影矩阵的公式。

太长不看

给定视锥参数$l,r,t,b,n,f$,则正交投影矩阵$P_{ortho}$和透视投影矩阵$P_{persp}$分别为

$$
P_{ortho}
=
\begin{bmatrix}
\frac {2}{r - l} & 0 & 0 & -\frac {r + l}{r - l}\\
0 & \frac {2}{t - b} & 0 & -\frac {t + b}{t - b}\\
0 & 0 & \frac {2}{n - f} & -\frac {f + n}{f - n}\\
0 & 0 & 0 & 1
\end{bmatrix}
$$
$$
P_{persp}
=
\begin{bmatrix}
\frac {2n}{r - l} & 0 & -\frac {(r + l)}{r - l} & 0\\
0 & \frac {2n}{t - b} & -\frac {(t + b)}{t - b} & 0\\
0 & 0 & \frac {f + n}{f - n} & -\frac {2nf}{f - n}\\
0 & 0 & 1 & 0
\end{bmatrix}
$$

投影矩阵的推导本文参考了闫令琪在GAMES101中的思路,先推正交,再由正交推透视。

正交投影推导

参考课程讲义p22

正交投影的视锥是一个立方体,定义其左右上下前后$l,r,t,b,n,f$参数如下图

将这个立方体内的点转换成标准立方体内的点需要做两部操作,首先平移到原点,然后在三个轴上进行缩放,易得

$$
P_{ortho} = \begin{bmatrix}
\frac {2}{r - l} & 0 & 0 & 0\\
0 & \frac {2}{t - b} & 0 & 0\\
0 & 0 & \frac {2}{f - n} & 0\\
0 & 0 & 0 & 1
\end{bmatrix}
\begin{bmatrix}
1 & 0 & 0 & -\frac {r + l}{2}\\
0 & 1 & 0 & -\frac {t + b}{2}\\
0 & 0 & 1 & -\frac {f + n}{2}\\
0 & 0 & 0 & 1
\end{bmatrix}
=
\begin{bmatrix}
\frac {2}{r - l} & 0 & 0 & -\frac {r + l}{r - l}\\
0 & \frac {2}{t - b} & 0 & -\frac {t + b}{t - b}\\
0 & 0 & \frac {2}{n - f} & -\frac {f + n}{f - n}\\
0 & 0 & 0 & 1
\end{bmatrix}
$$

特别的,当视锥正对原点左右对称时,即满足$r=-l$和$t=-b$时,假设$w=r-l$,$h=t-b$,则上面的投影矩阵可以简化为

$$
P_{ortho} = \begin{bmatrix}
\frac {2}{w} & 0 & 0 & 0\\
0 & \frac {2}{h} & 0 & 0\\
0 & 0 & \frac {2}{f - n} & -\frac {f + n}{f - n}\\
0 & 0 & 0 & 1
\end{bmatrix}
$$

透视投影推导

参考课程讲义p29

我们已经推导了正交投影的公式,这里我们尝试推导一个4x4的变换矩阵$M_{p2o}$,将透视投影视锥中的点转换到正交投影到视锥(立方体)中,透视视锥到透视视锥的变换如下图所示

我们总结四个重要的结论,用于后面的推导:

  • 结论1:在齐次坐标中有一点$P=\begin{bmatrix}x,y,z,1\end{bmatrix}^T$,n为任意数,$P^{‘}=\begin{bmatrix}nx,ny,nz,n\end{bmatrix}^T$和$P$和$P^{‘}$表示的是三维空间中的同一个点,可以说$P$和$P^{‘}$等价,记做$P==P^{‘}$
  • 结论2:透视视锥内的任意点变换前后z值不变
  • 结论3:透视视锥中近平面上的任意点变换前后不变
  • 结论4:远平面中心点变换前后不变

先看一个基本情况,对$P=\begin{bmatrix}x,y,z,1\end{bmatrix}^T$进行变换得到$P^{‘}=\begin{bmatrix}x^{‘},y^{‘},z^{‘},1\end{bmatrix}^T$

根据相似三角形原理,易得
$$
x^{‘} = \frac {nx}{z}, y^{‘} = \frac {ny}{z}
$$

根据结论1,则

$$
M_{p2o}
\begin{bmatrix}
x\\
y\\
z\\
1
\end{bmatrix}
=
\begin{bmatrix}
\frac {nx}{z}\\
\frac {ny}{z}\\
…\\
1
\end{bmatrix}
==
\begin{bmatrix}
nx\\
ny\\
…\\
z
\end{bmatrix}
$$

这一步足够求出矩阵$M_{p2o}$的第1、2、4行
$$
M_{p2o}=
\begin{bmatrix}
n & 0 & 0 & 0\\
0 & n & 0 & 0\\
… & … & … & …\\
0 & 0 & 1 & 0
\end{bmatrix}
$$

接下来我们尝试求解矩阵$M_{p2o}$的第三行,根据结论3我们可求得

$$
\begin{bmatrix}
n & 0 & 0 & 0\\
0 & n & 0 & 0\\
… & … & … & …\\
0 & 0 & 1 & 0
\end{bmatrix}
\begin{bmatrix}
x\\
y\\
n\\
1
\end{bmatrix}
=
\begin{bmatrix}
x\\
y\\
n\\
1
\end{bmatrix}
==
\begin{bmatrix}
nx\\
ny\\
n^2\\
n
\end{bmatrix}
$$

则第三行必为$[0,0,A,B]$,满足$An+B=n^2$,两个变量一个等式求不出来,我们再基于结论4,得出

$$
\begin{bmatrix}
n & 0 & 0 & 0\\
0 & n & 0 & 0\\
0 & 0 & A & B\\
0 & 0 & 1 & 0
\end{bmatrix}
\begin{bmatrix}
0\\
0\\
f\\
1
\end{bmatrix}
=
\begin{bmatrix}
0\\
0\\
f\\
1
\end{bmatrix}
==
\begin{bmatrix}
0\\
0\\
f^2\\
f
\end{bmatrix}
$$

我们可得$Af+B=f^2$,与$An+B=n^2$连立求解得$A=f+n,B=-nf$,则求解矩阵$M_{p2o}$为
$$
M_{p2o}
=
\begin{bmatrix}
n & 0 & 0 & 0\\
0 & n & 0 & 0\\
0 & 0 & f + n & -nf\\
0 & 0 & 1 & 0
\end{bmatrix}
$$

则可以求的最终的透视投影矩阵$P_{persp}$为

$$
P_{persp}
=
P_{ortho}
M_{p2o}
=
\begin{bmatrix}
\frac {2}{r - l} & 0 & 0 & -\frac {r + l}{r - l}\\
0 & \frac {2}{t - b} & 0 & -\frac {t + b}{t - b}\\
0 & 0 & \frac {2}{f - n} & -\frac {f + n}{f - n}\\
0 & 0 & 0 & 1
\end{bmatrix}
\begin{bmatrix}
n & 0 & 0 & 0\\
0 & n & 0 & 0\\
0 & 0 & f+n & -nf\\
0 & 0 & 1 & 0
\end{bmatrix}
\\=
\begin{bmatrix}
\frac {2n}{r - l} & 0 & -\frac {(r + l)}{r - l} & 0\\
0 & \frac {2n}{t - b} & -\frac {(t + b)}{t - b} & 0\\
0 & 0 & \frac {f + n}{f - n} & -\frac {2nf}{f - n}\\
0 & 0 & 1 & 0
\end{bmatrix}
$$

参考

太长不看

假设右(或左)手坐标系下的旋转矩阵和平移向量分别为$R$和$T$,左(或右)手坐标系下分别为$R^{’}$和$T^{’}$,假设$S = \begin{bmatrix}
-1 & 0 & 0 \\
0 & 1 & 0 \\
0 & 0 & 1 \\
\end{bmatrix}$,则有
$$
R = S \cdot R^{’} \cdot S \\
T = S \cdot T^{’}
$$

推导

右手食指为Y轴正方向,中指为Z轴正方向,大拇指为X轴正方向,这样的坐标系为右手坐标系。右手换成左手则为左手坐标系。将一个坐标系的一个轴取反向,则改变了手性;两个轴取反向,则等价于绕第三轴旋转180度;将三个轴都取反向,则是前面两者的叠加,改变手性+旋转。

左右手坐标系的转换在图形学开发中经常出现,例如OpenGL使用右手坐标系,Unity使用左手坐标系。下文所述右手坐标系即是OpenGL坐标系,左手坐标系即是Unity坐标系。

左手坐标系下有一个点$P_l=(x,y,z)^T$,则在右手坐标系下,该点应该表示为$P_r=(-x,y,z)^T$。

假设空间中有变换矩阵
$$S=\begin{bmatrix}
-1 & 0 & 0 \\
0 & 1 & 0 \\
0 & 0 & 1 \\
\end{bmatrix}=S^{-1}$$

则左右手坐标系下点的变换即可用$S$来表示,即
$$
P_l = S \cdot P_r \\
P_r = S \cdot P_l
$$

假设空间中有旋转矩阵$R$和平移向量$T$,世界坐标系下有点$P_w$,对应相机坐标系下有点$P_c$,则有
$$
P_c = R \cdot P_w + T
$$

假设$P_c$、$P_w$、$R$、$T$均定义在右手坐标系下,${P_c}^{’}$、${P_w}^{’}$、${R}^{’}$、${T}^{’}$分别为上述变量在左手坐标系下的定义,即
$$
P_c = R \cdot P_w + T \\
{P_c}^{’} = {R}^{’} \cdot {P_w}^{’} + {T}^{’}
$$

左右手坐标系下位置的变换我们已经知道,即${P}^{’}=S \cdot P$,则有
$$
{P_c}^{’} = {R}^{’} \cdot {P_w}^{’} + {T}^{’} \rightarrow \\
S \cdot {P_c} = {R}^{’} \cdot S \cdot {P_w} + {T}^{’} \rightarrow \\
{P_c} = {S}^{-1} \cdot {R}^{’} \cdot S \cdot {P_w} + {S}^{-1} \cdot {T}^{’}
$$

已知右手坐标系下$P_c = R \cdot P_w + T$,故有左右手坐标系下旋转矩阵和平移向量的的转换
$$
R = S^{-1} \cdot R^{’} \cdot S = S \cdot R^{’} \cdot S \\
T = S^{-1} \cdot {T}^{’} = S \cdot {T}^{’}
$$

以上

最近做一个跨平台的绘制项目,尝试在Ubuntu上搭建C++的OpenGL开发环境,在这里把一些过程记录一下。

  • OpenGL工具库:GLAD+GLFW
  • 项目构建:CMake
  • 编辑/调试:VSCode
  • 实现Demo:Hello Triangle - Learn OpenGL(中文/英文),完整代码

基本C++环境搭建

  1. 安装编译工具,build-essential包括gcc,g++,make在内的编译器和工具

    1
    2
    sudo apt update
    sudo apt install build-essential
  2. 安装CMake,跨平台构建工程比较方便

    1
    sudo apt install cmake
  3. 创建C++基本工程
    创建一个项目路径HelloTriangle,在其之下创建源文件路径src和build路径。在src下创建代码文件main.cpp,写个Hello World

    1
    2
    3
    4
    5
    #include <iostream>

    int main(void) {
    std::cout << "Hello Triangle" << std::endl;
    }
  4. 在HelloTriangle根目录下创建CMakeLists.txt,为方便补全,可以安装VSCode的CMake插件和CMake Tools插件

  5. CMakeLists.txt填入以下配置内容

    1
    2
    3
    4
    5
    cmake_minimum_required(VERSION 3.6)

    add_executable(HelloTriangle
    src/main.cpp
    )
  6. 此时,项目路径如下图所示

    1
    2
    3
    4
    ├── build
    ├── CMakeLists.txt
    └── src
    └── main.cpp
  7. 进入build路径,运行cmake,在build路径下生成Makefile,然后运行make,生成HelloTriangle可执行文件,运行即可

    1
    2
    3
    4
    cd build
    cmake ../ # 运行cmake生成Makefile
    make # 运行make生成可执行文件
    ./HelloTriangle # 运行

至此,基本C++环境已经配好,接下来开始配置OpenGL的开发环境。

基本OpenGL开发环境配置

  1. 安装GLFW,下载GLFW源码,解压至任意路径,进行构建和安装

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    # 视情况apt安装如下工具库,缺的话再补
    sudo apt install libglfw3 libglfw3-dev glibc-doc libxmu-dev libxrandr-dev doxygen libsdl2-dev

    # 构建工程 & 安装
    cd glfw-3.3.3 && mkdir build && cd build
    cmake ../
    sudo make
    sudo make install

    # 运行simple程序,若绘制出旋转的三角形则安装已完成
    ./examples/simple
  2. 查询设备OpenGL版本,以选择合适版本的GLAD库,这里输出的是4.6

1
2
glxinfo | grep "OpenGL version"
输出示例:OpenGL version string: 4.6 (Compatibility Profile) Mesa 20.0.8
  1. 安装GLAD,用于在运行时动态获取设备的OpenGL接口函数, 进入GLAD生成页面,选取Language、Specification、API - gl、Profile,勾选Generate a loader,然后点击右下角Generate,在新页面中下载glad.zip,解压至任意位置,后续在工程中配置

至此,基本OpenGL开发环境已经配置好,接下来开始配置Hello Triangle的工程代码。

Hello Triangle

  1. HelloTriangle示例代码贴入之前创建的main.cpp,如果编辑器提示有语法错误先暂时忽略。

  2. 修改CMakeLists.txt文件,增加glad.c为源文件,引用glad的include路径,并连接glfw3和OpenGL,完整CMakeLists.txt如下

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    cmake_minimum_required(VERSION 3.6)

    project(HelloTriangle)

    add_executable(HelloTriangle
    src/main.cpp
    /home/summersun/Projects/glad/src/glad.c # 新增源文件
    )

    include_directories(
    /home/summersun/Projects/glad/include # 新增include
    )

    # 链接glfw
    find_package(glfw3 3.3 REQUIRED)
    target_link_libraries(HelloTriangle
    glfw
    ${CMAKE_DL_LIBS}
    )

    # 链接opengl
    find_package(OpenGL REQUIRED)
    target_link_libraries(HelloTriangle OpenGL::GL)
  3. 回到HelloTriangle/build路径下,依次构建和编译工程,并运行即可

    1
    2
    cmake ../ && make
    ./HelloTriangle # 运行HelloTriangle

Debugging Tricks

  1. 代码补全、语法提示?

    在VSCode安装C/C++插件以获取此能力

  2. VSCode提示glad/glad.h头文件未找到,但可以正常编译运行?

    先在CMakeLists.txt中添加set(CMAKE_EXPORT_COMPILE_COMMANDS YES)定义,再次cmake时可以生成compile_commands.json文件,该文件记录了CMakeLists.txt中定义的头文件

    然后生成VSCode的配置文件c_cpp_properties.json文件,在其中加入compileCommands选项指向CMake刚生成的配置文件"compileCommands": "${workspaceFolder}/build/compile_commands.json"

  3. 如何调试?

    在CMakeLists.txt中添加set(CMAKE_BUILD_TYPE Debug)定义,编译时会加入调试flag,在launch.json的program中设置需要调试的程序位置,F5即可调试

参考