OpenGL学习笔记:矩阵变换


接上篇 OpenGL学习笔记:数学基础和常用矩阵总结(一)

缩放

前面说了一大堆的理论,现在终于可以来点实际应用了
对一个向量进行缩放(Scaling)就是对向量的长度进行缩放,而保持它的方向不变。
我们先来尝试缩放向量 v ⃗ = ( 3 , 2 ) \vec{v} =(3,2) v =(3,2)。我们可以把向量沿着x轴缩放0.5,使它的宽度缩小为原来的二分之一;我们将沿着y轴把向量的高度缩放为原来的两倍。我们看看把向量缩放(0.5, 2)倍所获得的 s ⃗ \vec{s} s 是什么样的
向量缩放
这是我们想要的结果,但我们怎么计算出这个结果呢?如果是这种简单的缩放,我们可以把 v ⃗ \vec{v} v 的x分量乘以0.5,y分量乘以2来得到这个结果。但这种方法不适合用在计算机的计算中,为了加快运算速度,简化计算,往往使用矩阵。因此我们需要用构建一个缩放矩阵来实现缩放功能
回顾刚才单位矩阵的计算,第一个结果元素是矩阵的第一行的每个元素乘以向量的每个对应元素。因为每行的元素除了第一个都是0,可得: 1 ⋅ 1 + 0 ⋅ 2 + 0 ⋅ 3 + 0 ⋅ 4 = 1 1\cdot 1+0\cdot 2+0\cdot 3+0\cdot 4=1 11+02+03+04=1,向量的其他3个元素同理。
如果我们把对角线上的1改变一下呢?比如说第一行的对角线是0.5,第二行的对角线是2构建一个矩阵,再和 v ⃗ \vec{v} v 相乘,得到下面的计算过程
( 0.5 0 0 2 ) × ( 3 2 ) = ( 0.5 × 3 + 0 × 2 0 × 3 + 2 × 2 ) = ( 0.5 × 3 2 × 2 ) = ( 1.5 4 ) \left( \begin{array}{ccc} 0.5 & 0 \\ 0 & 2 \end{array} \right) \times \left( \begin{array}{ccc} 3 \\ 2 \end{array} \right) = \left( \begin{array}{ccc} 0.5\times3+0\times2 \\ 0\times3+2\times2 \end{array} \right)= \left( \begin{array}{ccc} 0.5\times3 \\ 2\times2 \end{array} \right) = \left( \begin{array}{ccc} 1.5 \\ 4 \end{array} \right) (0.5002)×(32)=(0.5×3+0×20×3+2×2)=(0.5×32×2)=(1.54)
这样,我们就得到了我们想要的结果, ( 0.5 0 0 2 ) \left( \begin{array}{ccc}0.5 & 0 \\0 & 2 \end{array} \right) (0.5002)就是我们的变换矩阵,下面,我们把这个变换矩阵推广一下,如果我们把缩放变量表示为 ( S 1 , S 2 , S 3 ) (S_1,S_2,S_3) (S1,S2,S3)我们可以为任意向量 ( x , y , z ) (x,y,z) (x,y,z)定义一个缩放矩阵:
( S 1 0 0 0 0 S 2 0 0 0 0 S 3 0 0 0 0 1 ) ⋅ ( x y z 1 ) = ( S 1 ⋅ x S 2 ⋅ y S 3 ⋅ z 1 ) \left( \begin{array}{ccc} S_1 & 0 &0 &0 \\ 0 & S_2 &0 &0 \\ 0 & 0 &S_3 &0 \\ 0 & 0 & 0 &1 \end{array} \right) \cdot \left( \begin{array}{ccc} x \\ y \\ z \\ 1 \end{array} \right) = \left( \begin{array}{ccc} S_1\cdot x \\ S_2\cdot y \\ S_3\cdot z \\ 1 \end{array} \right) S10000S20000S300001 xyz1 = S1xS2yS3z1
注意,第四个缩放向量仍然是1,因为在3D空间中缩放w分量是无意义的。w分量另有其他用途,在后面我们会看到。

glm矩阵表示

好,所有的理论知识都已经准备好了,下面我们来看一下程序中是怎么应用的,还记得OpenGL学习笔记:矩阵变换中我们用来做矩阵缩放的代码吗?
首先是创建缩放矩阵

glm::mat4 trans(1.0f);
trans = glm::scale(trans, glm::vec3(0.5, 0.5, 0.5));

然后是将缩放矩阵传递给着色器

unsigned int transformLoc = glGetUniformLocation(shaderProgram, "transform");
glUniformMatrix4fv(transformLoc, 1, GL_FALSE, glm::value_ptr(trans));

最后是在着色器中对顶点向量进行缩放

gl_Position = transform * vec4(aPos, 1.0f);

接下来我们来扒一下这些代码都干了什么
首先是glm::mat4 trans(1.0f);创建一个单位矩阵,怎么创建的呢?我们来看看glm::mat4的构造函数

	template<typename T, qualifier Q>
	GLM_FUNC_QUALIFIER GLM_CONSTEXPR mat<4, 4, T, Q>::mat(T const& s)
	{
		// 每个元素都是矩阵的第一列,不是第一行
		this->value[0] = col_type(s, 0, 0, 0);	
		this->value[1] = col_type(0, s, 0, 0);
		this->value[2] = col_type(0, 0, s, 0);
		this->value[3] = col_type(0, 0, 0, s);
	}

简单目测一下,this->value应该是用来表示矩阵的,数组的每个元素都是一组向量(注意:这个数组的每一个元素是一个向量,这个向量对应的是矩阵中的一列,不是一行),并用变量s初始化,我们再来看一下this->value和col_type都是什么鬼

	template<typename T, qualifier Q>
	struct mat<4, 4, T, Q>
	{
		typedef vec<4, T, Q> col_type;
		typedef vec<4, T, Q> row_type;
		typedef mat<4, 4, T, Q> type;
		typedef mat<4, 4, T, Q> transpose_type;
		typedef T value_type;

	private:
		col_type value[4];
		// 下面代码省略
	}

确定了,value就是一个具有四个元素的col_type类型的数组,而col_type又是一个vec<4, T, Q>类型,从名字上猜测应该是一个向量类型,我们看一下vec的数据定义

// 删除无关代码
template<typename T, qualifier Q>
	struct vec<4, T, Q>
	{
		// -- Data --
		union { T x, r,s; };
		union { T y, g, t; };
		union { T z, b, p; };
		union { T w, a, q; };
	}

可以看到vec中存放了四个联合体,每个联合体就是一个向量分量,而模版T就是数据类型。
到这里我们就清楚glm中是怎么表示一个矩阵的了
我们给glm::mat4的构造函数传进来一个参数1,然后glm用这个参数1分别构造了四个向量,其中第一个向量的第一个分量是1,其他分量是0,而第二个向量的第二个分量是1,其他分量是0,第三、四个向量同样构造,然后在把这四个向量存放在一个具有四个元素的四维向量数组中,这个数组就是我们所构建的 4 × 4 4\times4 4×4的单位矩阵了。

glm缩放矩阵实现

接下来我们再来看一下trans = glm::scale(trans, glm::vec3(0.5, 0.5, 0.5));都干了些什么事

template<typename T, qualifier Q>
GLM_FUNC_QUALIFIER mat<4, 4, T, Q> scale(mat<4, 4, T, Q> const& m, vec<3, T, Q> const& v)
{
	mat<4, 4, T, Q> Result;
	Result[0] = m[0] * v[0];
	Result[1] = m[1] * v[1];
	Result[2] = m[2] * v[2];
	Result[3] = m[3];
	return Result;
}

在scale函数中,形参m是我们传进来的单位矩阵,v是我们传进来的缩放倍数的向量。
v[0]很好理解,就是取出v的第一个分量,这里是0.5,m[0]也很好理解,就是取出m的第一个向量,前面我们已经分析过了,m是一个具有四个元素的四维向量数组,本例中应该是 ( 1 , 0 , 0 , 0 ) (1,0,0,0) (1,0,0,0),m[0] * v[0]就是用向量 ( 1 , 0 , 0 , 0 ) (1,0,0,0) (1,0,0,0)乘以0.5,结果应该是 ( 0.5 , 0 , 0 , 0 ) (0.5,0,0,0) (0.5,0,0,0)。虽然我们看懂了这句代码,但还是想看一看glm是怎么计算的。下面这段代码是vec<3, T, Q>重载的[]运算符,根据下标取出响应的xyz,对应的是scale函数中的v[0]。

template<typename T, qualifier Q>
GLM_FUNC_QUALIFIER GLM_CONSTEXPR T const& vec<3, T, Q>::operator[](typename vec<3, 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;
	}
}

下面这段代码是mat<4, 4, T, Q>重载的[],取出value中的第i个向量,对应的是scale函数中的m[0]。

template<typename T, qualifier Q>
GLM_FUNC_QUALIFIER GLM_CONSTEXPR typename mat<4, 4, T, Q>::col_type const& mat<4, 4, T, Q>::operator[](typename mat<4, 4, T, Q>::length_type i) const
{
	assert(i < this->length());
	return this->value[i];
}

下面的三个函数是glm对运算符的重载堆栈,第一层是mat<4, 4, T, Q>重载的,在这层重载中,用v重新构造了一个vec<4, T, Q>型的向量,再用这个向量乘以缩放比例,就是scale函数中的v[0]。然后再继续调用vec<4, T, Q>型的*重载,在这一层中先是用v[0]构造了一个四维向量,看下面的第四个函数,vec<4, T, Q>::vec(T scalar)构造方式是用scale给四维向量的四个分量赋值,得到了一个 ( s c a l e , s c a l e , s c a l e , s c a l e ) (scale,scale,scale,scale) (scale,scale,scale,scale)的四维向量,然后再调用了call函数,我们继续来到call函数中。在call函数中我们看到,对两个四维变量的四个分量依次相乘,再用四个结果构造了一个新的四维变量,最后返回到我们调用的scale函数中,将结果拷贝给Result[0]。

GLM_FUNC_QUALIFIER GLM_CONSTEXPR vec<4, T, Q> operator*(vec<4, T, Q> const& v, T const & scalar)
{
	return vec<4, T, Q>(v) *= scalar;
}

template<typename U>
GLM_FUNC_QUALIFIER GLM_CONSTEXPR vec<4, T, Q> & vec<4, T, Q>::operator*=(U scalar)
{
	return (*this = detail::compute_vec4_mul<T, Q, detail::is_aligned<Q>::value>::call(*this, vec<4, T, Q>(scalar)));
}

GLM_FUNC_QUALIFIER GLM_CONSTEXPR static vec<4, T, Q> call(vec<4, T, Q> const& a, vec<4, T, Q> const& b)
{
	return vec<4, T, Q>(a.x * b.x, a.y * b.y, a.z * b.z, a.w * b.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)
{}

我们再来理一下这个过程
首先是我们代码中,我们以一个单位矩阵和一个缩放向量为参数调用glm的scale函数,scale函数首先取出单位矩阵的第一行的数据和缩放向量的第一个分量做乘法,结果得到一个缩放后的向量。
而glm计算向量数乘的方法是,首先用这个标量构建一个所有分量都是该标量的向量,再用这个向量和要计算的向量的分量依次相乘,将得到的结果构造一个结果向量。
而我们传进去的是一个四阶单位向量和一个三维的缩放比例,我们想要的结果是单位的第一二三行进行缩放,第四行还是1,所以scale函数只计算了前三行的比例缩放,第四行原样输出,至此,我们得到了一个这样的矩阵
( 0.5 0 0 0 0 0.5 0 0 0 0 0.5 0 0 0 0 1 ) \left( \begin{array}{ccc} 0.5 & 0 &0 &0 \\ 0 & 0.5 &0 &0 \\ 0 & 0 &0.5 &0 \\ 0 & 0 & 0 &1 \end{array} \right) 0.500000.500000.500001
这就是我们的缩放矩阵了,再将这个缩放矩阵传到着色器中,与顶点数据相乘,就得到了缩放后的顶点向量。这一过程是在GPU中进行的,因此,我们无法看到矩阵乘法是怎么做的。
不过不用担心,glm应该会有矩阵乘法的代码,本次没有用到,就先不研究了,等以后用到了在研究一下矩阵乘法在计算机中是怎么实现的。

位移

位移(Translation)是在原始向量的基础上加上另一个向量从而获得一个在不同位置的新向量的过程,从而在位移向量基础上移动了原始向量。我们已经讨论了向量加法,所以这应该不会太陌生。
我们先来尝试位移向量 v ⃗ = ( 3 , 2 ) \vec{v} =(3,2) v

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

木千

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值