three.js 之 Matrix

wangfulin

基础知识

欧拉角

欧拉角Eulerian angles用来确定定点转动刚体位置的3个一组独立角参量,由章动角 θ、旋进角(即进动角)ψ和自转角j组成。为欧拉首先提出而得名。

四元素(quaternion)

四元数是简单的超复数。 复数是由实数加上虚数单位 i 组成,其中i^2 = -1。 相似地,四元数都是由实数加上三个虚数单位 i、j、k 组成,而且它们有如下的关系: i^2 = j^2 = k^2 = -1, i^0 = j^0 = k^0 = 1 , 每个四元数都是 1、i、j 和 k 的线性组合,即是四元数一般可表示为a + bk+ cj + di,其中a、b、c 、d是实数。

矩阵

介绍

简单来说,矩阵就是有确定的行数和列数的一组数。例如,一个 2x3 的矩阵:

2x3矩阵

在 3D 图形中,我们最经常使用 4x4 的矩阵。这能够让我们变换 (x,y,z,w)的向量。通过将向量和矩阵相乘的方式完成。
矩阵 x 向量(位置不要变)= 变换后的向量

矩阵x向量

平移矩阵

下面这种是最简单易懂的变换矩阵。如下:

平移矩阵

X,Y,Z就是你想要给你位置添加的值。
所以如果你想将向量 (10,10,10,1) 在X轴移动10个单位,我们得到:

平移矩阵示例

并且我们得到了(20,10,10,1)齐次向量!记住,这个 1 指的是位置,不是方向。所以我们的转换并没有修改我们处理的是位置的事实。
让我们来看下如果对这个指向-z轴方向的向量(0,0,-1,0)沿X轴平移10个单位会发生什么:

方向平移

结果还是 (0,0,-1,0)方向,这很好,因为移动一个方向是没有意义的。

单位矩阵

这个矩阵很特殊,它什么也不做。但是我还是要提一下它,就好像你要知道 A X 1.0 = A 一样是很重要的。

单位矩阵运算

伸缩矩阵

伸缩矩阵也很容易:

伸缩矩阵

所以如果你想对向量的每一个方向都放大2倍:

放大两倍

并且 w 同样没有变化,你获取会问:”伸缩方向“是什么意思呢?当然,大部分情况下你不会做这种事情,但是在非常特殊的情况下,还是有用的。

旋转矩阵

这个矩阵比较复杂,可以参考Matrices and Quaternions FAQRotation tutorials

复合变换

所以现在我们知道如何旋转、平移、伸缩我们的向量。如果能够结合这些变换,将是很好的。这是通过矩阵的乘法完成的。比如:

TransformedVector = TranslationMatrix * RotationMatrix * ScaleMatrix * OriginalVector;

注意:这行代码事实上是伸缩,然后旋转,最后平移的。这就是矩阵乘法的使用方式。顺序改变是会影响最终的结果的。比如:
点(x0,y0)经过矩阵变换后得到(x,y),那这个变换顺序是怎么样的呢?

复合变换

变换顺序:T(-10, 10) -> R(theta) -> T(10, 10)。这一种,把运算式子写出来如M' = T(10,10) x R(θ) x T(-10,-10) x M,然后在按照从右边到左边的顺序(T(-10,-10)->R(θ)->T(10,10))去理解,改变的是坐标位置,坐标系不变。
右往左

变换顺序:T(10, 10) -> R(theta) -> T(-10, 10)。第二种方式去理解矩阵变换,就得改变变换的空间想象,这个时候改变得是坐标系,不变的是坐标位置,即坐标位置相对于它所在的坐标系里一直是不变的
左往右

矩阵类型

模型矩阵

这种模型,它们的顶点的 X,Y,Z坐标是相对对象的中心点确定的:也就是说,如果一个顶点的坐标是(0,0,0),那么它就是对象的中心点.

模型矩阵

我们想要移动这个模型,或许是因为玩家想通过键盘或者鼠标来控制它。很简单,上面已经讲过:translate*rotation*scale,就这样。你在每一帧都对这个模型上的每一个顶点做矩阵变换,那么所有的东西都会移动。不会移动的点就是世界的中心点。

世界坐标

现在你所有的顶点都在世界空间上。这就是下图黑色箭头的含义:我们从模型空间(所有顶点都是相对模型的中心点定义的)转化为了世界空间(所有的顶点都是相对世界的中心点确定的)

模型到世界

我们可以总结为下图:

模型坐标到世界坐标

视图矩阵

让我们再一次引用 Futurama:“引擎并不会移动船。船一直呆着不动,引擎让整个世界绕着船动。”

相机视角

当我们思考这个的时候,同样的道理也适用于照相机。如果你想换个角度看山,你既可以移动相机,也可以移动山。虽然在现实生活中不太实际,但是在图形学中是相当简单和方便的。

所以最开始的时候你的照相机是在世界空间的中心。为了移动世界,你其实引入了另一个矩阵。假设说你沿着X轴往右把你的相机移动了3个单位,就等价于你将整个世界(包括里面的网格)向左移动了3个单位。

我们还是用图片来描述一下这个:我们从世界空间(所有的顶点都是相对世界中心定义的)到相机空间(所有的顶点都是相对相机定义的)。

模型到世界到视图

下面就是复合图:

模型矩阵到视图矩阵

还没完,别着急,哈哈

投影矩阵

我们现在到了相机世界。这意味着经过所有的这些变换,如果一个顶点恰好是 x = 0 并且 y = 0,它应该在屏幕中间渲染。但是我们不能够只使用x,y坐标来决定一个对象应该放置在屏幕的位置。它离相机的距离(z)也是有用的。对于x,y坐标类似的顶点,拥有最大的z坐标的顶点会比其他的在更中间。

下面这个叫做透视投影:

投影

最后一次:

我们从相机空间(所有的顶点都是相对相机定义的)到齐次空间(所有的顶点都是定义在一个小方块里面,所有在小方块里面的东西都是在屏幕上的)

最后一个图:

投影变换

下面的另一张图可以让我们更好地了解投影过程中到底发生了什么。在投影之前,我们有蓝色的立方体,在相机空间,我们用红色的图形来代表相机的视景体:相机真正能够看到的那部分场景。

未变形前

将所有的一切乘以投影矩阵,我们有了如下的效果:

齐次空间

在这张图中,视景体是一个完整的立方体(所有的轴都在-1到1之间,有点难看出来),并且所有的蓝色对象都同样变形啦。离照相机近的对象会大一些,远一点的会小一些。就跟现实生活一样!

让我们看看从视景体的后面看是什么效果:

投影图

上面看到的是正方形的图片,所以需要执行另一个数学变换(这个是自动计算的,不需要自己手动执行)去适应屏幕的正式宽高。

最后一张图

矩阵变换

Three.js使用矩阵来表达 3D 变换---平移(位置)、渲染、伸缩。每一个 Object3D 的实例都有一个矩阵来存储对象的位置、旋转、伸缩。接下来我们看下如何对一个对象进行转换。

常用属性和 matrixAutoUpdate

有两种方式来更新对象的变换

  1. 更改对象的位置,四元数,和伸缩属性,three.js 会根据这些属性重新计算对象的矩阵:

    object.position.copy(start_position);
    object.quaternion.copy(quaternion);

    默认情况下,matrixAutoUpdate 属性是设置为 true 的,矩阵会自动重新计算。如果对象是静态的,或者你希望自己手动控制什么时候重新计算,可以通过将属性设置为 false 来获取更好的性能。

    object.matrixAutoUpdate = false

    同时在改变任何属性之后,手动更新矩阵:

    object.updateMatrix();
  2. 直接修改对象的矩阵。Matrix4提供了各种修改矩阵的方法:

    object.matrix.setRotationFromQuaternion(quaternion);
    object.matrix.setPosition(start_position);
    object.matrixAutoUpdate = false;

    注意在这种情况下 matrixAutoUpdate 必须设置成 false。并且你要确定不要调用 updateMatrix 方法。调用 updateMatrix 会阻断对矩阵的手动更改,会根据位置、伸缩等属性重新计算矩阵。

对象和世界矩阵

一个对象的矩阵存储着对象相对父对象的变换。要获得对象在世界坐标的变换,你需要访问 Object3D.matrixWorld

当无论是父对象或者是子对象的变换改变的时候,你都可以通过调用 updateMatrixWorld来更新子对象的 matrixWorld

旋转和四元数

Three.js提供了两种方式来表示 3D 旋转:欧拉角四元数,当然也包括用于两者之间转换的方法。欧拉角会有“万向节锁”的问题,导致某些配置失去一定的自由度(阻止物体绕一个轴旋转)。因为这个原因,对象旋转总是存储在对象的四元数里。

参考

  1. three.js matrix transformation
  2. matrices
阅读 5.5k

记录技术的点滴
记录一些自己正在学习的知识
6.1k 声望
103 粉丝
0 条评论
你知道吗?

6.1k 声望
103 粉丝
宣传栏