06 | 可视化中你必须要掌握的向量乘法知识

你好,我是月影。

上一节课,我们学习了Canvas实现坐标系转换的方法,以及利用向量描述点和线段来绘制基本图形的方法。接下来,我们继续学习和向量有关的绘图知识,教你用数学语言描述曲线。

不过,在讨论如何描述曲线之前,还有一些关于向量乘法的前置知识需要你掌握。虽然它们和如何描述曲线并没有直接的关系,但是使用它们可以计算曲线的控制点、判断曲线的方向以及对曲线进行变换。

因此,向量的乘法在可视化中也是非常重要并且实用的内容。比如,上节课的思考题3:给你一个任意点,让你判断这个点在不在这个扫描器范围内。这道题可以用我们前面学过的知识解决,但用向量乘法可以更轻松解决。接下来就让我们从这一道题开始今天的学习吧!

如果利用我们前面学过的知识来解题,你可能是直接使用向量的方向定义来做的,代码如下所示。

v.dir = function() {return Math.atan2(this.y, this.x)}

没错,这道题我们可以使用向量的方向来解。因为这里的dir是由向量与x轴夹角决定的,所以判断点是否在扫描器范围内,我们只需要计算点坐标对应的向量的dir值,是否在扫描器的范围内就可以了。代码如下:

const isInRange = v0.dir > Math.PI / 3 && v0.dir < 2 * Math.PI / 3;

这是一个很简单、直观的解法,但是它不够完美,因为这个判断和扫描器的方向有关。什么意思呢?从上面的图中你可以看到,现在它正对着y轴正方向,所以角度在π/3和2π/3之间。但如果将它的方向旋转,或者允许它朝向任意的方向,我们就必须要修改对应的角度值了。这个时候就会非常麻烦。

因此,我们会使用一个更通用的解法,也就是利用向量的乘法来解。那具体怎么做呢?别着急,我先带你来复习一下我们高中学过的向量乘法的知识,如果你记得不是特别清楚,正好可以借着这个机会来加深印象。

我们知道,向量乘法有两种,一种是点乘,一种是叉乘,它们有着不同的几何和物理含义。下面,我们一一来看。

向量的点乘

首先,我们来看向量的点乘。

假设,现在有两个N维向量a和b,a = [a1, a2, ...an],b = [b1, b2, ...bn],那向量的点积代码如下:

a•b = a1*b1 + a2*b2 + ... + an*bn

在N维线性空间中,a、b向量点积的几何含义,是a向量乘以b向量在a向量上的投影分量。它的物理含义相当于a力作用于物体,产生b位移所做的功。点积公式如下图所示:

好了,现在你已经知道a、b向量点积的定义了。那关于还有两个比较特殊的情况,你需要掌握。第一种是,当a、b两个向量平行时,它们的夹角就是0°,那么a·b=|a|*|b|,用JavaScript代码表示就是:

a.x * b.x + a.y * b.y === a.length * b.length;

第二种是,当a、b两个向量垂直时,它们的夹角就是90°,那么a·b=0,用JavaScript代码表示就是:

a.x * b.x + a.y * b.y === 0;

向量的叉乘

叉乘和点乘有两点不同:首先,向量叉乘运算的结果不是标量,而是一个向量;其次,两个向量的叉积与两个向量组成的坐标平面垂直。怎么理解呢?我们接着往下看。

以二维空间为例,向量a和b的叉积,就相当于向量a(蓝色带箭头线段)与向量b沿垂直方向的投影(红色带箭头线段)的乘积。那如下图所示,二维向量叉积的几何意义就是向量a、b组成的平行四边形的面积

那叉乘在数学上该怎么计算呢?假设,现在有两个三维向量a(x1, y1, z1)和b(x2, y2, z2),那么,a与b的叉积可以表示为一个如下图的行列式:

其中i、j、k分别是x、y、z轴的单位向量。我们把这个行列式展开,就能得到如下公式:

a X b = [y1 * z2 - y2 * z1, - (x1 * z2 - x2 * z1), x1 * y2 - x2 * y1]

我们计算这个公式,得到的值还是一个三维向量,它的方向垂直于a、b所在平面。因此,我们刚才说的二维空间中,向量a、b的叉积方向就是垂直纸面朝向我们的。那有什么办法可以很容易,就确定出a、b的叉积方向呢?

还记得吗?第2节课我们提到过左手系和右手系,其中x轴向右、y轴向下的坐标系是右手系。在右手系中求向量a、b叉积的方向时,我们可以把右手食指的方向朝向a,把右手中指的方向朝向b,那么大拇指所指的方向就是a、b叉积的方向,这个方向是垂直纸面向外(即朝向我们)。因此,右手系中向量叉乘的方向就是右手拇指的方向,那左手系中向量叉乘的方向自然就是左手拇指的方向了。

在二维空间里,由于z的值为0,因此我们得到的向量a X b的数值,就等于x1 * y2 - x2 * y1。那它的物理意义是什么呢?二维空间中向量叉乘的物理意义就是a和b的力矩(力矩你可以理解为一个物体在力的作用下,绕着一个轴转动的趋向。它是一个向量,等于力臂L和力F的叉乘。这个概念你记住就好了,我们今天不会用到,后面用到的时候我会再详细来讲)。

还记得上一节课的思考题2,求点到线段的距离吗?在了解了向量叉积的几何意义之后,我们通过向量叉积得到平行四边形面积,再除以底边长,就能得到点到向量所在直线的距离了。是不是很简单?

同样,向量叉积也可以解决思考题3。那具体该怎么做呢?

首先,对于任意一点v0来说,我们都要先将它归一化。简单来说,归一化就是让向量v0除以它的长度(或者说是模)。归一化后的向量方向不变,长度为1。归一化是向量运算中一个非常重要的操作,用处也非常多。比如说,在向量乘法里,如果a、b都是长度为1的归一化向量,那么|a X b| 的结果就是a、b夹角的正弦值,而|a • b|的结果就是a、b夹角的余弦值。这个特性在图形学里用处非常大,你一定要记住它。

好了,再说回来,我们把归一化的向量a叉乘扫描器中线上的v(0,1),由于扫描器关于y轴对称,所以扫描器边缘与y轴的夹角是正负30度。那么在与单位向量求叉积的时候,就会出现2种情况:

  1. 点在扫描范围内,如向量a,就一定满足: |a X v| <= ||a||v|sin(30°)| = |sin(30°)| = 0.5;
  2. 点不在扫描范围内,如向量b,就一定满足:|b X v| > ||b||v|sin(30°)| = |sin(30°)| = 0.5。

因此,只要任意一点所在的向量与单位向量的叉积结果的绝对值不大于0.5(即sin30°),就说明这个点在扫描范围内。所以我们可以用如下代码来判断:

const isInRange = Math.abs(new Vec2(0, 1).cross(v0.normalize())) <= 0.5; // v0.normalize()即将v0归一化

好了,关于向量乘法的内容,我们就全部复习完了,相信你已经很好地掌握它们了。不过,我还想再多说几句 ,对于图形学来说,向量运算是基础中的基础,非常重要。所以,我们不仅要熟练掌握,还要学会用向量的思路去解决问题。只有这样,你才能学好图形学,从而成为优秀的可视化工程师。

要点总结

这一节课,我们学习了向量的乘法,包括点乘与叉乘。

其中点乘的几何意义是向量a与它在向量b所在的轴的投影向量的乘积,物理意义是力向量产生的位移向量所做的功。叉乘的几何意义是向量a和向量b构成的平行四边形的面积,物理意义是力产生的力矩。

最后,我们还要记住,把向量归一化以后,我们就可以通过向量的点乘与叉乘快速求出向量夹角的正弦和余弦值。

小试牛刀

让我们延续上一节课的第2道思考题。已知,平面上有一点P,它的坐标是(x0, y0),还有一条直线,直线上有两个点Q(x1, y1)和R(x2, y2)。你能求出点P到直线的距离,以及点P到线段QR的距离吗?这里面,P、Q、R这三个值可以是任意的。你可以要试着用一段JavaScript代码把计算的过程写出来,最好还能把直线、点和距离在Canvas上给直观地绘制出来。

欢迎在留言区和我讨论,分享你的答案和思考,也欢迎你把这节课分享给你的朋友,我们下节课见!

精选留言

  • 2020-07-04 12:36:24

    今天终于弄明白了二维向量叉积的几何意义了,以前一直没想明白,谢谢月影老师指点
    不过对于那道扫描器的题,老师介绍的方法似乎有个漏洞:比如(0,-1)这个向量,显然是个单位向量,它与v(0,1)的叉积是0,符合课中给出的判断条件,但它显然不在扫描范围内,而是反向延长线所形成范围内。
    原因嘛,因为正弦函数有一条对称轴是x=Math.PI/2。
    所以我认为更严谨的方法应该用余弦。炮制老师的方法就是用点积:
    const isInRange = new Vec2(0, 1).dot(v0.normalize()) >= Math.cos(Math.PI/6);
    正好规避了[0,Math.PI]范围内,正弦的轴对称特性
    作者回复

    非常好👍,没错,用叉积会有两个方向,反向的也会被判定,需要处理

    2020-07-05 09:28:00

  • 老孟

    2020-12-01 17:37:33

    分享私货https://www.bilibili.com/video/BV1ys411472E,网络条件好的小朋友可以翻墙去看英文原版
    作者回复

    赞赞

    2021-02-09 21:13:39

  • gltjk

    2020-07-03 17:29:27

    今天的小试牛刀:https://codepen.io/gltjk/pen/gOPozZK
    在下面选好要动哪个点,然后鼠标移动到 canvas 上就能看到那个点动了
    专门做了一个 Vector 类和一个 Canvas 类,不过代码应该还可以更加模块化些,下次考虑做成带 ES Module 的……
    作者回复

    赞👍

    2020-07-04 08:27:55

  • Tom

    2020-07-03 11:46:07

    得去补数学了
    作者回复

    数学基础可以捡起来~

    2020-07-04 08:40:54

  • Cailven

    2020-07-03 11:27:23

    一般在webgl计算三维空间坐标中,叉乘的几何意义是计算平面的法线,因为叉乘能计算出一根同时垂直于原向量的新向量,而叉乘的数量积的几何意义可以用来计算三角面的面积;点乘的几何意义是计算夹角。我工作中用的最多的地方就是计算光源入射折射对物体表面的影响,不过向量知识的数学基础是理解PBR物理渲染的关键。
    作者回复

    2020-07-03 12:11:03

  • 白夜

    2020-08-06 15:08:09

    假设AB为一线段,P为线段外一点,求P点距离AB线段最短距离。

    设C点位P点再AB线段上的投影点,那么AC向量 = AP向量 * AB向量 / |AB向量| * AB向量/|AB向量|
    其中,AB向量/|AB向量|表示AB向量的单位向量,赋予AC向量方向; AP向量 * AB向量 / |AB向量|表示AC向量的长度。
    继续推导可知:AC向量 = AP向量 * AB向量 / |AB向量| * AB向量/|AB向量| = AP向量 * AB向量 / |AB向量| ^2 * AB向量,假设 r = AP向量 * AB向量 / |AB向量| ^2 ,由向量的方向性可知,当 r < 0时,表示,投影C位于BA的延长线上,最短距离为|AP|;当0<r<1时,投影点C位于线段AB上,最短距离为|CP|;当r>1时,投影点C位于AB的延长线上,最短距离为|BP|。
    作者回复

    是的~

    2020-08-10 09:20:47

  • 量子蔷薇

    2022-09-24 11:35:49

    点到线段的距离与点到直线的距离不同,但逻辑是一样的,即依次比较线上所有点与目标点的距离,然后取最小值,对于直线就是投影点或者说垂足到目标点的距离;对于线段,如果投影点不在线段上,最小值其实是线段其中一个端点到目标点的距离
    所以课后题这样做:通过点积求投影的长度,保留正负号,再除以线段的长度得到 lerp 值,如果 0 < lerp < 1,则投影在线段上,再利用叉积求得以线段为底的高就是距离;如果 lerp > 1,计算线段终点与目标点的距离;如果 lerp < 0,计算线段起点与目标点的距离
  • 樊瑞

    2021-03-05 18:10:35

    老师,扫描器的计算我感觉是错的,用cross是利用正弦判断,会在0~30度,150~180度都适用。用余弦是正确的,控制度数在-30 ~ 30度,所以该用dot,我测试了结果是对的。

    vec3 st = vec3(vUv - 0.5, 0);
    float d = dot(normalize(st), vec3(0, 1, 0));
    gl_FragColor.rgb = vec3(
    step(cos(3.1415926/6.0), d)
    );
    gl_FragColor.a = 1.0 * uTime;

    如果用cross加绝对值来判断,会得到一个类似宝马图案的。
  • Geek_3469f6

    2020-07-03 15:26:52

    https://codepen.io/maslke/pen/RjrJew?editors=1000

    想要标识点的名称,不过画上去是倒置的,想了一下是scale了的原因。需要研究下api。
    作者回复

    很棒,的确text倒置是因为scale(1, -1)的原因,有text的话坐标就不能这么变换了

    2020-07-04 08:38:00

  • 阿不晕

    2022-07-20 21:38:30

    点乘的几何意义应该是向量 a 在向量 b 上的投影长度与向量 b 长度的乘积,可以判断两个向量是否在同一个方向。
  • 剑走偏锋

    2020-07-08 19:00:16

    老师能不能说一下可视化大致要用到哪些数学基础知识,有很多知识都已经忘得差不多了
    作者回复

    我在数学篇最后列出了一个脑图,应该很快就会更新到了的~

    2020-07-09 10:15:59

  • becky

    2023-09-22 15:59:25

    作业:https://codepen.io/yeying0827/pen/vYvWMNL,数学基础有点差,自己实现有点复杂,根据楼里其他同学的思路稍微优化了一下
  • 李小燕

    2022-12-29 11:32:28

    看不明白还能继续往下学么
  • ht

    2021-11-05 05:53:21

    老师,请问有没有关于学习这方面数学的书可以推荐?
  • 哈哈哈[吃瓜]

    2021-10-11 17:34:56

    向量加法 平移 也可以求中心点向量
    向量减法 指向被减数 可得方向和两点之间距离
    向量乘标量 放大缩小 标量的正负代表方向
    向量叉乘 得法线向量

    实际应用 三维模型加载到网页上之后 先用包围盒包裹起来 然后可以用盒模型的上线确界向量相加乘0.5得中心点坐标

    中心点和三围模型的上确界的面的法线放大 就可以用这两个向量叉乘得法线再乘放大倍数

    最近刚开始看一个叫拓扑的公司的代码 里面有一段没看明白 看了向量的知识 感觉是这么回事
  • 刘洪林

    2021-04-14 21:05:44

    /**
    * 核心逻辑
    * 先计算扫描器左右两边的向量
    * 只要同时满足:
    * 1. 右边界向量叉乘目标向量大于0 (表示右边界在目标右边)
    * 2. 左边界向量叉乘目标向量小于0 (表示左边界在目标左边)
    * @param v 需要扫描的变量
    * @param angle 扫描器的角度
    * @return {boolean|boolean}
    */
    function canScan(v, angle) {
    const y = new Vector2D(0, 1);
    const right = y.rotate(- angle / 2).copy();
    const left = y.rotate(angle / 2).copy();
    return right.cross(v) > 0 && left.cross(v) < 0;
    }
  • wuscarecrow

    2021-03-10 16:41:18

    请问一下老师,示例当中的图像和数学公式是通过什么方式画的?
  • nico@佳

    2021-03-08 07:20:01

    之前学习数学都是记忆公式,看了3b1b才发现是井底之蛙。
    时不时探究下事物的本质和基础原理,站在基础之上才能看得更高,走得更远
  • 番薯

    2021-02-06 21:53:55

    https://codepen.io/sfyan-the-styleful/pen/eYBZNJq
    终于搞定本章牛刀!画垂直线的时候,在投影标量转向量上卡了好久
    且y轴向上也有text颠倒的问题,不确定怎么处理。
    作者回复

    嗯绘制text不能颠倒y轴

    2021-02-09 20:50:20

  • LiSkysunCHN

    2020-07-17 11:33:44

    老师点 P 到直线的距离不正是P 到线段 QR 的距离吗?
    作者回复

    不是,如果是线段,还要考虑P在线段上的投影,如果不在线段内的话,就不一样了,在线段内才等于到线段所在直线的距离。因为P可能离线段端点很远,但是离直线很近。

    2020-07-21 08:54:05