glm 库的一些知识
glm 库
- 文档中引用的代码仓库 ogl-intro
OpenGL 数据排布
- OpenGL 的数据排布是列优先的,例如如下的矩阵
\[ M=\begin{bmatrix}a & b \\c & d \end{bmatrix} \]
- 在内存中的数据排布是 \(a,c,b,d\),也就是说具体如下
\[ \begin{array}{c} M[0][0]=a\\ M[0][1]=c\\ M[1][0]=b\\ M[1][1]=d\\ \end{array} \]
- 第一个下标表示第几列,第二个下标表示第几行
- 这和我们平常 C++ 的数据排布是不一样的,我们平常使用的数组是行优先的
平移变换的例子
- 平移变换的矩阵如下
\[ T=\begin{bmatrix} 1 & 0 & 0 & a\\ 0 & 1 & 0 & b\\ 0 & 0 & 1 & c\\ 0 & 0 & 0 & 1\\ \end{bmatrix} \]
- \(T\cdot x\) 表示将点 \(x\) 平移 \((a,b,c)\)
- 以 ogl-intro 中的代码为例,我们在 01-ebo 中进行修改
- 原始效果如下
如何将它向右平移 0.5 个单位
回忆 OpenGL 的 NDC 坐标系(左手系),正方向
x:朝右
y:朝上
z:朝屏幕里面
构造的矩阵应该如下
\[ T=\begin{bmatrix} 1 & 0 & 0 & 0.5\\ 0 & 1 & 0 & 0\\ 0 & 0 & 1 & 0\\ 0 & 0 & 0 & 1\\ \end{bmatrix} \]
- 修改 vertex shader,左乘变换矩阵
1 | // 修改 vertex shader |
1 | // 关键代码如下,注意是列优先的 |
- 结果如下
- 如果我们每次都需要自己去进行行优先转为列优先的操作的话,就会很麻烦
- 不用担心,glm 库为我们做了这个操作
glm 库
(1) 列优先排布
- glm 库是表示的矩阵是列优先的
- 也就是说,如果使用下标索引方式的话
- 第一个下标表示列索引
- 第二个下标表示行索引
- 和 OpenGL 适配的
- 也就是说,如果使用下标索引方式的话
1 | // 定义一个输出函数 |
- 例如平移变换,结果如下
1 | glm::fmat4 m3 = glm::translate( |
- 因此他表达的实际矩阵如下
1 | print_mat4(m3, true); // true 表示将其理解为列优先表示方式 |
(2) glm 乘法
- 首先需要理解,矩阵表示是列优先的,然后就是普通的矩阵乘法了
1 | // 初始化为全 0 的矩阵 |
- 输出结果如下
1 | [Log: print_mat4()] |
(3) 变换顺序
- 我们在 ogl-intro 中的 02-uniform 中进行修改,用作示例
- 修改如下函数
1 | glm::mat4 calculate_transform(float aspect); |
- 我们不进行模型变换,修改代码如下,得到结果图
- 设置模型变换为单位矩阵 \(I\)
1 | glm::mat4 calculate_transform(float aspect) { |
- 现在我们想得到如下的结果,将这个头像顺时针旋转 90 度,然后向右平移 0.5 个单位长度,如下图所示
方法1:直接构建
1 | glm::mat4 rotation = glm::identity<glm::mat4>(); |
注意点:第一个索引下标表示第几列,第二个索引下标表示第几行
旋转矩阵:具体形式见上课 PPT《几何变换》中的三维几何变换章节
- 角度是弧度制
- 课程中推导的矩阵是右手系下的矩阵变换
- OpenGL 在 NDC 之后是左手系,之前实现为左手系和右手系都可以
- glm 库中默认为右手系
- 顺时针旋转 90 度
- 注意这里的顺时针/逆时针和左右手系相关,四指转向的方向为逆时针
- 因此 rotate 矩阵的构建不需要判断是左手系还是右手系(可以查看源码)
- 抵消了
- 课上推导的是以逆时针为正,因此最后的 \(\theta=-90\)
右手系:xyz 轴满足右手定则
- 右手 4 指从 x 轴正方向转向 y 轴正方向(4指张开到合上)
- z 轴正方向刚好是大拇指的方向
方法2:利用内置函数构建
1 | // 初始矩阵 |
- 我们会发现,这样的结果是不正确的
- 查看源码发现,glm 库的变换矩阵都是在原有矩阵的基础上右乘当前变换
例如 rotate()
文件:glm/extmatrix_transform.inl
因此相当于是先做了当前变换,再进行原来的变换矩阵
因此,如果调用内置函数的话,后进行的变换应该先调用
1 | glm::mat4 model = glm::identity<glm::mat4>(); |
(4) 细节
- 注意齐次坐标需要除以 \(w\),否则可能没有归一化