简介
GLM 库主要用于代数运算,与 Eigen、Blas 库类似。但由于 GLM 库主要应用场景是坐标变换和摄像机,因此维数受限,功能弱于 Eigen、Blas。glm 的主要优势在于 GLM 提供了使用与 GLSL(OpenGL着色语言)相同的命名约定和功能设计和实现的类和函数,因此任何了解 GLSL 的人都可以在 C++ 中使用 GLM。根据官方文档介绍, GLM 全称是 OpenGL Mathematics,是一个基于 OpenGL 着色语言的用于图形软件的纯 C++的数学库。但是就算脱离 OpenGL 环境,也可以确保能够正常使用。这篇文章首先主要详解 GLM 库的两个数据结构 vec 和 mat。
vec
vec 表示向量 , glm 库中有四个头文件定义了 vec ,分别是 type_vec1.hpp , type_vec2.hpp , type_vec3.hpp 和 type_vec4.hpp。分别表示一到四维的向量。
首先是一维向量的定义。 注意到这段关于 vec1 的构造函数的声明代码:
template<typename T,qualifier Q>
struct vec<1,T,Q>{
...
// -- Implicit basic constructors --
GLM_FUNC_DECL GLM_CONSTEXPR vec(vec const& v) GLM_DEFAULT;
template<qualifier P>
GLM_FUNC_DECL GLM_CONSTEXPR vec(vec<1, T, P> const& v);
// -- Explicit basic constructors --
GLM_FUNC_DECL GLM_CONSTEXPR explicit vec(T scalar);
...
template<typename U, qualifier P>
GLM_FUNC_DECL GLM_CONSTEXPR GLM_EXPLICIT vec(vec<1, U, P> const& v);
...
}
这里发现模板里面有个 qualifier Q,查看 qualifier.hpp 文件可以查看其定义:
enum qualifier
{
packed_highp, ///< Typed data is tightly packed in memory and operations are executed with high precision in term of ULPs
packed_mediump, ///< Typed data is tightly packed in memory and operations are executed with medium precision in term of ULPs for higher performance
packed_lowp, ///< Typed data is tightly packed in memory and operations are executed with low precision in term of ULPs to maximize performance
# if GLM_CONFIG_ALIGNED_GENTYPES == GLM_ENABLE
aligned_highp, ///< Typed data is aligned in memory allowing SIMD optimizations and operations are executed with high precision in term of ULPs
aligned_mediump, ///< Typed data is aligned in memory allowing SIMD optimizations and operations are executed with high precision in term of ULPs for higher performance
aligned_lowp, // ///< Typed data is aligned in memory allowing SIMD optimizations and operations are executed with high precision in term of ULPs to maximize performance
aligned = aligned_highp, ///< By default aligned qualifier is also high precision
# endif
highp = packed_highp, ///< By default highp qualifier is also packed
mediump = packed_mediump, ///< By default mediump qualifier is also packed
lowp = packed_lowp, ///< By default lowp qualifier is also packed
packed = packed_highp, ///< By default packed qualifier is also high precision
# if GLM_CONFIG_ALIGNED_GENTYPES == GLM_ENABLE && defined(GLM_FORCE_DEFAULT_ALIGNED_GENTYPES)
defaultp = aligned_highp
# else
defaultp = highp
# endif
};
根据其注释发现是与精度有关,一般使用defaultp就行。
查看构造函数的定义:
template<typename T, qualifier Q>
template<qualifier P>
GLM_FUNC_QUALIFIER GLM_CONSTEXPR vec<1, T, Q>::vec(vec<1, T, P> const& v)
: x(v.x)
{}
// -- Explicit basic constructors --
template<typename T, qualifier Q>
GLM_FUNC_QUALIFIER GLM_CONSTEXPR vec<1, T, Q>::vec(T scalar)
: x(scalar)
{}
template<typename T, qualifier Q>
template<typename U, qualifier P>
GLM_FUNC_QUALIFIER GLM_CONSTEXPR vec<1, T, Q>::vec(vec<1, U, P> const& v)
: x(static_cast<T>(v.x))
{}
那么根据构造函数的声明和定义,一般我们可以按照两种方式初始化,要么直接传入一个标量,要么直接传入入另外一个 vec,但是数据类型也就是 typename 要相同:
vec<1,int,defaultp> a1(1);
vec<1,int,defaultp> a2(a1);
也可以依靠static_cast来进行类型转换:
vec<1,int,defaultp> a1(1);
vec<1,float,defaultp> a2(a1);
接着看 vec 这个类有哪些属性和方法:
struct vec<1,T,Q>{
union {T x, r, s;};
GLM_FUNC_DECL static GLM_CONSTEXPR length_type length(){return 1;}
GLM_FUNC_DECL GLM_CONSTEXPR T & operator[](length_type i);
GLM_FUNC_DECL GLM_CONSTEXPR T const& operator[](length_type i) const;
}
....
还有很多运算符的重载,大家可以去看 type_vec1.hpp 和 type_vec1.inl。这里主要说关于属性的访问,可以看到有一个匿名 union ,可以通过 x,r,s 三个属性去访问。 []的重载定义如下,无论输入任何数字都是返回 x ,这个定义很符合一维向量的特性,比较省事:
template<typename T, qualifier Q>
GLM_FUNC_QUALIFIER GLM_CONSTEXPR T & vec<1, T, Q>::operator[](typename vec<1, T, Q>::length_type)
{
return x;
}
template<typename T, qualifier Q>
GLM_FUNC_QUALIFIER GLM_CONSTEXPR T const& vec<1, T, Q>::operator[](typename vec<1, T, Q>::length_type) const
{
return x;
}
可以通过以下形式访问属性:
vec<1,int,defaultp> vec1(2);
cout<<vec1.x<<endl; //2
cout<<vec1.r<<endl;
cout<<vec1.s<<endl;
cout<<vec1[0]<<endl;
cout<<vec1[10]<<endl; //2,不会报错!
cout<<vec1.length(); //1,访问vec1的维度,固定返回1
现在铺垫说完了,因为我们一般用的头文件是 glm.hpp ,而不是 vec1.hpp,glm.hpp 用头文件 fwd.hpp 包装了 vec1.hpp。
在 fwd.hpp 中做了如下的声明:
typedef vec<1, float, defaultp> vec1;
typedef vec<1, int, defaultp> ivec1;
typedef vec<1, uint, defaultp> uvec1;
...
因此我们一般按照如下使用:
ivec1 c(1);
vec1 d(1.55);
vec1 a(1.2);
ivec1 b(a);
cout << b[1] << endl; //打印1
底层调用构造函数 vec<1, T, Q>::vec(vec<1, U, P> const& v) 来进行强制类型转换。
再来看四维向量,也就是 type_vec4.hpp 头文件。
分别看构造函数的声明代码( type_vec4.hpp ):
template<typename T, qualifier Q>
struct vec<4, T, Q>
{
// -- Implicit basic constructors --
GLM_FUNC_DECL GLM_CONSTEXPR vec(vec<4, T, Q> const& v) GLM_DEFAULT;
template<qualifier P>
GLM_FUNC_DECL GLM_CONSTEXPR vec(vec<4, T, P> const& v);
// -- Explicit basic constructors --
GLM_FUNC_DECL GLM_CONSTEXPR explicit vec(T scalar);
GLM_FUNC_DECL GLM_CONSTEXPR vec(T x, T y, T z, T w);
// -- Conversion scalar constructors --
template<typename U, qualifier P>
GLM_FUNC_DECL GLM_CONSTEXPR explicit vec(vec<1, U, P> const& v);
}
再来看看 vec4 构造函数的定义:
template<typename T, qualifier Q>
GLM_FUNC_QUALIFIER GLM_CONSTEXPR vec<4, T, Q>::vec(vec<4, T, Q> const& v)
: x(v.x), y(v.y), z(v.z), w(v.w)
{}
template<typename T, qualifier Q>
template<qualifier P>
GLM_FUNC_QUALIFIER GLM_CONSTEXPR vec<4, T, Q>::vec(vec<4, T, P> const& v)
: x(v.x), y(v.y), z(v.z), w(v.w)
{}
template<typename T, qualifier Q>
GLM_FUNC_QUALIFIER GLM_CONSTEXPR vec<4, T, Q>::vec(T scalar)
: x(scalar), y(scalar), z(scalar), w(scalar)
{}
template <typename T, qualifier Q>
GLM_FUNC_QUALIFIER GLM_CONSTEXPR vec<4, T, Q>::vec(T _x, T _y, T _z, T _w)
: x(_x), y(_y), z(_z), w(_w)
{}
template<typename T, qualifier Q>
template<typename U, qualifier P>
GLM_FUNC_QUALIFIER GLM_CONSTEXPR vec<4, T, Q>::vec(vec<1, U, P> const& v)
: x(static_cast<T>(v.x))
, y(static_cast<T>(v.x))
, z(static_cast<T>(v.x))
, w(static_cast<T>(v.x))
....
{}
根据以上 vec4 的声明和定义可以按照以下方式构造:
vec<4,int,defaultp> a(10); //传入一个整型标量
vec<4,int,defaultp> b(10,20,30,40); //传入四个整型变量
vec<1,int,defaultp> c(1);
vec<4,int,defaultp> d(c); //传入一个vec<1,U,P>
vec<4,int,defaultp> e(d);
同理 fwd.hpp 也对 vec4 做了相应的包装,只要使用头文件 <glm/glm.hpp> 就可以了。
再来看看 vec4 的属性和方法。
union { T x, r, s; };
union { T y, g, t; };
union { T z, b, p; };
union { T w, a, q; };
/// Return the count of components of the vector
GLM_FUNC_DECL static GLM_CONSTEXPR length_type length(){return 4;}
GLM_FUNC_DECL GLM_CONSTEXPR T & operator[](length_type i);
GLM_FUNC_DECL GLM_CONSTEXPR T const& operator[](length_type i) const;
再看 vec4.inl 文件:
template<typename T, qualifier Q>
GLM_FUNC_QUALIFIER GLM_CONSTEXPR T& vec<4, T, Q>::operator[](typename vec<4, T, Q>::length_type i)
{
assert(i >= 0 && i < this->length());
switch(i)
{
default:
case 0:
return x;
case 1:
return y;
case 2:
return z;
case 3:
return w;
}
}
template<typename T, qualifier Q>
GLM_FUNC_QUALIFIER GLM_CONSTEXPR T const& vec<4, T, Q>::operator[](typename vec<4, T, Q>::length_type i) const
{
assert(i >= 0 && i < this->length());
switch(i)
{
default:
case 0:
return x;
case 1:
return y;
case 2:
return z;
case 3:
return w;
}
}
其他更多运算符的重载可以查看头文件。
那么我们可以访问通过 [] 或者 属性访问符访问 xyzw 、rgba 、 stpq。
mat
mat 表示矩阵。关于 mat ,glm 支持行列分别为1到4维。为了一定的广义性, 我们这里分析 type_mat4x3.hpp ,其他的头文件也是以 type_matrxk.hpp(r,k都为1,2,3或者4)。
首先定义了 col_type 、 row_type 、type 、transpose_type 和 value_type 五种数据类型。
template<typename T, qualifier Q>
struct mat<4, 3, T, Q>
{
typedef vec<3, T, Q> col_type;
typedef vec<4, T, Q> row_type;
typedef mat<4, 3, T, Q> type;
typedef mat<3, 4, T, Q> transpose_type;
typedef T value_type;
}
定义 col_type 表示行向量的数据类型, row_type 表示列向量的数据类型, type 表示这个矩阵的数据类型。
除了数据类型,mat 还定义了一些属性:
col_type value[4];
typedef length_t length_type;
GLM_FUNC_DECL static GLM_CONSTEXPR length_type length() { return 4; }
GLM_FUNC_DECL col_type & operator[](length_type i);
GLM_FUNC_DECL GLM_CONSTEXPR col_type const& operator[](length_type i) const;
重载[]运算符来进行访问:
// -- Accesses --
template<typename T, qualifier Q>
GLM_FUNC_QUALIFIER typename mat<4, 3, T, Q>::col_type & mat<4, 3, T, Q>::operator[](typename mat<4, 3, T, Q>::length_type i)
{
assert(i < this->length());
return this->value[i];
}
template<typename T, qualifier Q>
GLM_FUNC_QUALIFIER GLM_CONSTEXPR typename mat<4, 3, T, Q>::col_type const& mat<4, 3, T, Q>::operator[](typename mat<4, 3, T, Q>::length_type i) const
{
assert(i < this->length());
return this->value[i];
}
通过源码发现[]可以访问一行数据,也就是返回 col_type 类型的数据,因此[]可以代入0,1,2,3分别为第一到四行。再根据vec的[]访问每个分量。
再来看看构造函数的声明:
<typename T, qualifier Q>
struct mat<4, 3, T, Q>{
// -- Constructors --
GLM_FUNC_DECL GLM_CONSTEXPR mat() GLM_DEFAULT;
template<qualifier P>
GLM_FUNC_DECL GLM_CONSTEXPR mat(mat<4, 3, T, P> const& m);
GLM_FUNC_DECL explicit GLM_CONSTEXPR mat(T const& x);
GLM_FUNC_DECL GLM_CONSTEXPR mat(
T const& x0, T const& y0, T const& z0,
T const& x1, T const& y1, T const& z1,
T const& x2, T const& y2, T const& z2,
T const& x3, T const& y3, T const& z3);
GLM_FUNC_DECL GLM_CONSTEXPR mat(
col_type const& v0,
col_type const& v1,
col_type const& v2,
col_type const& v3);
}
接下来看构造函数的定义:
template<typename T, qualifier Q>
template<qualifier P>
GLM_FUNC_QUALIFIER GLM_CONSTEXPR mat<4, 3, T, Q>::mat(mat<4, 3, T, P> const& m)
#if GLM_HAS_INITIALIZER_LISTS
:value{col_type(m[0]), col_type(m[1]), col_type(m[2]), col_type(m[3])}
#endif
{
#if !GLM_HAS_INITIALIZER_LISTS
this->value[0] = m[0];
this->value[1] = m[1];
this->value[2] = m[2];
this->value[3] = m[3];
#endif
}
template<typename T, qualifier Q>
GLM_FUNC_QUALIFIER GLM_CONSTEXPR mat<4, 3, T, Q>::mat(T const& s)
#if GLM_HAS_INITIALIZER_LISTS
: value{col_type(s, 0, 0), col_type(0, s, 0), col_type(0, 0, s), col_type(0, 0, 0)}
#endif
{
#if !GLM_HAS_INITIALIZER_LISTS
this->value[0] = col_type(s, 0, 0);
this->value[1] = col_type(0, s, 0);
this->value[2] = col_type(0, 0, s);
this->value[3] = col_type(0, 0, 0);
#endif
}
template<typename T, qualifier Q>
GLM_FUNC_QUALIFIER GLM_CONSTEXPR mat<4, 3, T, Q>::mat
(
T const& x0, T const& y0, T const& z0,
T const& x1, T const& y1, T const& z1,
T const& x2, T const& y2, T const& z2,
T const& x3, T const& y3, T const& z3
)
#if GLM_HAS_INITIALIZER_LISTS
: value{col_type(x0, y0, z0), col_type(x1, y1, z1), col_type(x2, y2, z2), col_type(x3, y3, z3)}
#endif
{
#if !GLM_HAS_INITIALIZER_LISTS
this->value[0] = col_type(x0, y0, z0);
this->value[1] = col_type(x1, y1, z1);
this->value[2] = col_type(x2, y2, z2);
this->value[3] = col_type(x3, y3, z3);
#endif
}
template<typename T, qualifier Q>
GLM_FUNC_QUALIFIER GLM_CONSTEXPR mat<4, 3, T, Q>::mat(col_type const& v0, col_type const& v1, col_type const& v2, col_type const& v3)
#if GLM_HAS_INITIALIZER_LISTS
: value{col_type(v0), col_type(v1), col_type(v2), col_type(v3)}
#endif
{
#if !GLM_HAS_INITIALIZER_LISTS
this->value[0] = v0;
this->value[1] = v1;
this->value[2] = v2;
this->value[3] = v3;
#endif
}
再由于 <glm/glm.hpp> 对type_mat4x3.hpp 的包装,因此有以下的构造 mat:
vec3 a(1, 2, 3);
vec3 b(4, 5, 6);
vec3 c(7, 8, 9);
vec3 d(10, 11, 12);
mat4x3 mat1(a, b, c, d); //利用四个 vec3 ,也就是 col_type 来构造
mat4x3 mat2(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12);
mat4x3 mat3(2);
mat4x3 mat4(mat3);
如果要观察这个矩阵,可以用两层循环,外层是从0循环到length(),内层是col_type的length()。以下以构造函数只传入一个基本数据类型为例来演示打印:
mat4x3 mat(2);
for (int i = 0; i < mat.length(); ++i) {
for (int j = 0; j < mat[0].length(); ++j) {
cout << mat[i][j] << " ";
}
cout << endl;
}
// 2 0 0
// 0 2 0
// 0 0 2
// 0 0 0
其他的运算符重载涉及一些矩阵运算,打算在后面文章中详细讨论,有些定义有点反人类。