对glm库源码的理解(二)

1,395 阅读6分钟
简介

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 

其他的运算符重载涉及一些矩阵运算,打算在后面文章中详细讨论,有些定义有点反人类。