- Published on
glm:我踩过的坑
- Authors
- Name
使用 glm 时经常出现「de 了很久 bug 才发现其实是个 feature」的情况。用这篇记录一下我用 glm 踩过的坑。
左乘 v.s. 右乘
根据基本的线性代数常识我们知道,矩阵 能够左乘矩阵 的充分必要条件是 ,此时有
在另一个被广泛使用的的线性代数库 Eigen 中,想求这样两个矩阵的乘积时可以方便地写为:
auto C = A * B;
然而在 glm 中不是这样(没想到吧!)。在 glm 中,要求矩阵 左乘矩阵 的结果,应该写成:
auto C = B * A;
结论
也就是左乘写在右边,右乘写在左边。
维度 v.s. 长度 v.s. 模长
维度,长度,模长,这三个概念对于一个 的向量来讲,我的第一感觉是后两者 (长度和模长) 表示向量在空间中的长度,而向量的维度指的是向量分量个数。于是为了判断一个颜色是否是黑色,我用了向量的成员函数 .length()
:
if ( sign(this->material->emission.length()) == 0 ) { ... }
结论
这样写不仅是错的,而且是可以编译通过的:向量的 .length()
方法返回的是向量的维度。正确的写法是用全局函数 glm::length(vec const&)
求模长。
四元数的构造函数与 operator[]
结论
目前开发版本(未发布)的 glm 中四元数的构造函数的默认行为与 operator[]
的默认行为已经一致。一些相关 pull request: #1069
/ #1074
/ #1076
/ #1084
(其中 #1069
是我提的)。
glm 的四元数实现中有一个宏定义 GLM_FORCE_QUAT_DATA_WXYZ
,顾名思义,有这个宏定义时,glm 将把四元数的数据用 wxyz
的布局来存,glm 默认是不定义这个宏的,此时四元数的默认数据布局是 xyzw
。
那么重点来了:
#include <glm/glm.hpp>
#include <glm/gtx/quaternion.hpp>
#include <cstdio>
using quat = glm::qua<double>;
int main() {
quat q1(0, 1, 2, 3);
quat q2;
q2[0] = 0;
q2[1] = 1;
q2[2] = 2;
q2[3] = 3;
printf("q1: (%f %f %f %f)\n", q1.x, q1.y, q1.z, q1.w);
// Outputs:
// q1: (1.000000 2.000000 3.000000 0.000000)
printf("q2: (%f %f %f %f)\n", q2.x, q2.y, q2.z, q2.w);
// Outputs (without `g++ -DGLM_FORCE_QUAT_DATA_WXYZ`):
// q2: (0.000000 1.000000 2.000000 3.000000)
// Outputs (with `g++ -DGLM_FORCE_QUAT_DATA_WXYZ`):
// q2: (1.000000 2.000000 3.000000 0.000000)
return 0;
}
注意分别在编译时是否定义宏 GLM_FORCE_QUAT_DATA_WXYZ
的情况下,四元数 q2
的不同行为。这段代码说明在默认情况下(编译时未定义宏 GLM_FORCE_QUAT_DATA_WXYZ
)你以为的相同的初始化,其实是不同的(如果不能叫做相反的话)。从 glm 的源码里可以看到,无论前面提到的宏 GLM_FORCE_QUAT_DATA_WXYZ
是否被定义,四元数的构造函数接受的参数都是 (T _w, T _x, T _y, T _z)
, 并且内部赋值也总是把第一个参数赋值给四元数的 w
, 以此类推:
template <typename T, qualifier Q>
GLM_FUNC_QUALIFIER GLM_CONSTEXPR qua<T, Q>::qua(T _w, T _x, T _y, T _z)
# ifdef GLM_FORCE_QUAT_DATA_WXYZ
: w(_w), x(_x), y(_y), z(_z)
# else
: x(_x), y(_y), z(_z), w(_w)
# endif
{}
而四元数的 []
运算符的行为根据 GLM_FORCE_QUAT_DATA_WXYZ
宏是否被定义有不同的实现:
template<typename T, qualifier Q>
GLM_FUNC_QUALIFIER GLM_CONSTEXPR T & qua<T, Q>::operator[](typename qua<T, Q>::length_type i)
{
assert(i >= 0 && i < this->length());
# ifdef GLM_FORCE_QUAT_DATA_WXYZ
return (&w)[i];
# else
return (&x)[i];
# endif
这就造成了上面的代码中生成了两个不同的四元数的情况。