这是我参与8月更文挑战的第7天,活动详情查看:8月更文挑战
本文标题:WebGL第二十一课:矩阵乘法的非数学理解法(适合不想搞数学的小伙伴)| 8月更文挑战
引子
# 每个方程式都会使书的销量减少一半。
---- 霍金
前面几次课,不得不用这些严谨的数学推导来表达矩阵以及矩阵乘法。
不喜欢数学的小伙伴肯定是看不下去的,那么既想学习WebGL,又不想了解这些乏味的公式,那怎么办呢?
将矩阵看成一个函数
一个向量,经过左乘一个矩阵,变成另一个向量。这种形式跟我们的函数非常像,我们就直接这么理解矩阵就好了。
例如:
function 拉伸函数(输入向量) {
return 拉伸矩阵 * 输入向量;
}
上面的伪代码是不是清晰的代表了这个概念:
无论传入什么向量,你都会得到一个拉伸之后的向量。
我们再定义一个旋转函数:
function 旋转函数(输入向量) {
return 旋转矩阵 * 输入向量;
}
那么上面的旋转函数就是说:
无论传入什么向量,你都会得到一个旋转之后的向量。
一个向量经过多个矩阵函数
假如我们有一个需求,一个向量,先拉伸一下,然后再旋转一下,怎么办。
如果不熟悉矩阵数学表达法的小伙伴,肯定就懵了。
但是根据矩阵函数理解法,那么就很简单:
var A;// 一个初始的输入向量
A = 拉伸函数(A);
A = 旋转函数(A);
经过上面两次函数调用,我们就会得到一个先被拉伸,再被旋转的新向量。
多个矩阵合并
我们仔细观察一下上面的两个代码,把他们进行合并:
A' = 旋转矩阵 * (拉伸矩阵 * A)
A' = 旋转矩阵 * 拉伸矩阵 * A
A' = (先拉伸再旋转矩阵) * A
也就是说:
先拉伸再旋转矩阵 = 旋转矩阵 * 拉伸矩阵
根据上式, 我们可以定义如下代码:
function 先拉伸再旋转函数(输入向量){
return 先拉伸再旋转矩阵 * 输入向量;
}
也就是一句话:
如果你有一个向量,你想将这个向量翻过来,倒过去,把他跟一堆矩阵搞来搞去的话,你可以:
先把这一堆矩阵合并(就是乘起来),最后把这个向量扔给这个矩阵,最后得到你想要的向量。
综合例子
有一个向量A
我们想把它先拉伸,x变成 a倍, y变成 b倍;
然后向把他旋转 α弧度。
请写出一个函数,可以进行以上操作。
假设 A = [x,y]; // 就是一个数组
function 先拉伸再旋转函数(A){
/// 请给出具体实现代码
}
先解决矩阵的合并
我们把拉伸的矩阵拿出来:
我们然后把旋转的矩阵拿出来:
因为是先拉伸再旋转,所以合并之后的矩阵应该是:
*
上面两个矩阵相乘的话,有兴趣的小伙伴可以自己手算一下,精通矩阵乘法的小伙伴,其实可以口算出来:
* =
那最后的算法就是:
*
再解决矩阵乘以向量
矩阵乘以向量,可以化成,向量的线性组合,而向量的线性组合包含了:
- 向量数乘
- 向量相加
两个操作
我们先实现向量数乘的函数:
// A = [x, y]
// return [nx, ny]
function 向量数乘(A, n) {
return [A[0]*n, A[1]*n];
}
再实现向量相加:
// A = [x, y]
// B = [w, z]
// return [x + w, y + z]
function 向量相加(A, B) {
return [A[0] + B[0], A[1] + B[1]];
}
我们知道矩阵就是多个向量横着排在一起了,我们给出矩阵可以用向量的数组来给出,例如:
// A = [x, y];
// B = [w, z];
var 矩阵 = [A, B];
好了,我们可以给出矩阵乘以向量的函数了:
// 向量1 = [a, b];
// 向量2 = [c, d];
// D = [向量1, 向量2];
// A = [x, y]
function 矩阵乘以向量(D, A){
var res0 = 向量数乘(D[0], A[0]);
var res1 = 向量数乘(D[1], A[1]);
return 向量相加(res0, res1);
}
最终,给出先拉伸,再旋转的函数式:
// A 是一个向量
// 最后的结果是,A先被拉伸,拉伸系数是a b。
// 然后再旋转alpha弧度。
function 先拉伸再旋转(a, b, alpha, A){
var D = [ [a*Math.cos(alpha),a*Math.sin(alpha)], [-b*Math.sin(alpha), b*Math.cos(alpha)]
];
return 矩阵乘以向量(D, A);
}
以上包含了四个函数,都是直接可以运行在浏览器的console环境的,你如果介意中文函数名,自行修改就行,不过,中文函数名确实是可以直接运行的,以下给出整体代码例子:
function 向量数乘(A, n) {
return [A[0]*n, A[1]*n];
}
function 向量相加(A, B) {
return [A[0] + B[0], A[1] + B[1]];
}
function 矩阵乘以向量(D, A){
var res0 = 向量数乘(D[0], A[0]);
var res1 = 向量数乘(D[1], A[1]);
return 向量相加(res0, res1);
}
function 先拉伸再旋转(a, b, alpha, A){
var D = [ [a*Math.cos(alpha),a*Math.sin(alpha)], [-b*Math.sin(alpha), b*Math.cos(alpha)]
];
return 矩阵乘以向量(D, A);
}
var A = [1,1];
A = 先拉伸再旋转(1,1, 3.14/2, A); // 不拉伸,然后旋转大约90°。
// 得到的结果是 [-0.9992033562211013, 1.0007960096425679]
// 约等于 [-1, 1]。
A = 先拉伸再旋转(2,3, 0, A); // x坐标变成2倍,y坐标变成3倍,不旋转
// 结果是[-1.9984067124422027, 3.0023880289277036]
// 约等于 [-2, -3]
A = 先拉伸再旋转(0.5,1/3, -3.14/2, A); // 还原到最初的[1,1]
// 结果就是 [1,1]
总结
本次课讲了一个概念:
将矩阵看成函数,并且可以合并。
合并之后的矩阵,跟向量进行运算,可以一次性得到最终的向量。
正文结束,下面是答疑
小丫丫说:我后面不想记具体数学表达式了,我把函数一抄,直接用函数就好了。
-
答:这就是这次课的目的!