Three.js中矩阵和向量的使用教程


Posted in Javascript onMarch 19, 2019

前言

提起矩阵,很容易让人想起我们曾经学不会的线性代数和离散数学,但是作为图形开发中的核心部分,它代表着每一次的运动和变换,就像鱼不能脱离水一样,矩阵并不是一个可以避之不谈的话题。

好消息是,Three.js帮助我们把许多矩阵运算封装成了一些顶层的方法,并提供了一个优秀的数学库,我们不太需要知道HowToCalc,只需要知道HowToUse,就可以得到绝大部分我们想要的东西。

这篇文章将要介绍的就是,如何在不了解内部结构的情况下在Three.js中使用矩阵和向量。

从一个例子开始

在讲解一些枯燥的概念前先举一个小例子,来简要说明一下为什么我们要使用矩阵方法。

Three.js中矩阵和向量的使用教程

这是我们最终要完成的效果。

首先,我们要创建三个几何体:

var box_geometry = new THREE.BoxGeometry();
var sphere_geometry = new THREE.SphereGeometry(0.5, 32, 32);
var cylinder_geometry = new THREE.CylinderGeometry(0.1, 0.1, 0.5);

var material = new THREE.MeshLambertMaterial({color: new THREE.Color(0.9, 0.55, 0.4)})

这三个几何体分别是盒子、球和圆柱体。

然后去创建三个网格,并将它们置入场景。

var box = new THREE.Mesh(box_geometry, material);
var sphere = new THREE.Mesh(sphere_geometry, material);
sphere.position.y += 1;
var cylinder = new THREE.Mesh(cylinder_geometry, material);
cylinder.position.y += 1.75;
scene.add(box);
scene.add(sphere);
scene.add(cylinder);

这段代码将生成如下场景:

Three.js中矩阵和向量的使用教程

虽然不那么美观,但作为示例已经足够了,现在我希望这堆物体尺寸减半。通常我会把物体的scale属性减半,像这样:

box.scale.multiplyScalar(0.5); 
sphere.scale.multiplyScalar(0.5);
cylinder.scale.multiplyScalar(0.5);

Three.js中矩阵和向量的使用教程

和想象中的有些偏差。我的本意是让这一组物体进行一个整体的缩放,并不想让它们彼此偏离,为了修正这件事,我需要根据其他对象的缩放重新计算每个对象的位置。但这并不是一件很难解决的问题,three.js提供了一种优雅的方式,来处理这个问题。我们可以定义一个空对象,然后将三个对象放在其中,然后将比例应用于父对象。

var pile = new THREE.Object3D();
pile.scale.multiplyScalar(0.5);
pile.add(box);
pile.add(sphere);
pile.add(cylinder);
scene.add(pile);

Three.js中矩阵和向量的使用教程

接下来我们做一点更有趣的事。

我将在这个物体组合里添加旋转,让我们尝试围绕球体表面旋转的那个圆柱体,就像他将要滑落一样。

Three.js中矩阵和向量的使用教程

它变成了这样,很明显,这不是我想要的东西。我们在这里有两个做法可供选择:第一,通过数学计算算出圆柱相对于球体的正确位置;第二,创建另一个Object3D,将圆柱和球放进去并旋转。这听上去挺复杂的,而且也很不酷。

所以,我们可以尝试自己去计算矩阵。

首先,我需要将属性maxtrixAutoUpdate设置为false,然后我就不能再通过position,scale和rotation去修改矩阵。

box.matrixAutoUpdate = false;
sphere.matrixAutoUpdate = false;
cylinder.matrixAutoUpdate = false;

现在,我将用applyMatrix方法来解决这个问题。具体做法是:为每个对象创建一个Matrix4,然后我们将矩阵与该矩阵相乘以应用后续操作。

var sphere_matrix = new THREE.Matrix4().makeTranslation(0.0, 1.0, 0.0); 
sphere.applyMatrix(sphere_matrix);
var cylinder_matrix = sphere_matrix.clone(); cylinder_matrix.multiply(new THREE.Matrix4().makeTranslation(0.0, 0.75, 0.0)); 
cylinder.applyMatrix(cylinder_matrix);

这几步下来,可以让我们解锁很多知识,来看看这里发生了什么。

首先,我们把盒子单独留下,因为它不需要动。

接着,我创建了一个平移矩阵并把它应用到了球对象上。

最后,在圆柱体上,我clone了球的矩阵信息,并在此基础上又创建了一个新的平移矩阵,圆柱体将移动1.75。

Three.js中矩阵和向量的使用教程

理解了上面几步,你就会知道最后一步该做什么了。

只需要一行代码,作用在球上:

sphere_matrix.multiply(new THREE.Matrix4().makeRotationZ(-Math.PI * 0.25));

Three.js中矩阵和向量的使用教程

达成了想要的效果,很酷。

示例中用到的方法

在上面的示例中,我将球和圆柱体分别沿y轴移动了一定的距离,并使用了makeTranslation这个方法。这个方法的作用是创建了一个平移矩阵。紧接着,我又使用到了applyMatrix的方法。这个方法的作用是把平移矩阵作用在球和圆柱体上。

那么什么是平移矩阵?它又是如何完成一次平移呢?

Three.js中最常见的一种4x4的矩阵,被称为变换矩阵,它所表示的变换类型包括平移、旋转和缩放。

用一个简单的数学题来说明变换矩阵:

Three.js中矩阵和向量的使用教程

有一个起始点,用向量来表示即为Vector3(20,20,0);现在,我要把它移动到另一个位置,Vector3(30,60,0)。

接下来,我设置一个平移矩阵,来表示向量依照什么方式去移动。

t = |1 0 0 10|
 |0 1 0 40|
 |0 0 1 0 |
 |0 0 0 1 |

最后,用起始的向量去乘以变换矩阵的向量。

|20| |1 0 0 10| |30|
|20| x |0 1 0 40| = |60|
|0 | |0 0 1 0 | |0 |
|1 | |0 0 0 1 | |1 |

变换公式如下:

transformedVector = vector * transformationMatrix

最终的变换向量 = 原始向量 * 变换矩阵

用我们上面例子中的方法来还原这个公式,即:

var vector = new THREE.Vector3(20, 20, 0);
var matrix = new THREE.Matrix4();
matrix.makeTranslation(10, 40, 0);
vector.applyMatrix4(matrix);

除了平移,Three的API中还提供了rotation和scale,scale变化很简单,它将使用makeScale(x, y, z)这个方法来表示缩放。

而旋转则相对复杂许多,Three.js提供以下旋转方法:

matrix.makeRotationX(angle);
matrix.makeRotationY(angle);
matrix.makeRotationZ(angle);
matrix.makeRotationAxis(axis, angle);
matrix.makeRotationFromEuler(euler);
matrix.makeRotationFromQuaternion(quaternion);

前三个方法分别代表的是绕X、Y、Z三个轴旋转,无需赘述。

第四个方法是前三个方法的整合版,第一个参数表示的是代表xyz的THREE.Vector3,第二个参数是旋转的弧度。下面两行代码是等价的:

matrix.makeRotationX(Math.PI);
matrix.makeRotationAxis(new THREE.Vector3(1, 0, 0), Math.PI);

第五个方法表示围绕x、y和z轴的旋转,这是表示旋转最常用的方式;第六个方法是一种基于轴和角度表示旋转的替代方法。

最后,Three.js api提供了一种方法来创建表示平移,旋转和缩放的组合的矩阵 -- matrix.compose:

var translation = new THREE.Vector3();
var rotation = new THREE.Quaternion();
var scale = new THREE.Vector3();
var matrix = new THREE.Matrix4();
matrix.compose(translation, rotation, scale);

矩阵相乘

矩阵乘法的意义在于叠加。

Three.js中矩阵和向量的使用教程

上图表示了三个变化:旋转、缩放和移动。

通过按次序相乘,三个变化矩阵可以得出一个最终的变化矩阵:

combinedMatrix = rotationMatrix * scaleMatrix * translationMatrix;

Three.js里提供了两种矩阵相乘的方法:

  • matrix.multiply(otherMatrix)
  • matrix.multiplyMatrices(matrixA, matrixB)

第一种方法表示将矩阵乘以另一个矩阵;而第二种方法代表的是将矩阵设置为matrixA * matrixB的结果。

我们在示例中也使用到了第一个方法:将圆柱体的矩阵乘以新的平移矩阵,和将球的矩阵乘以一个旋转矩阵。

需要注意的是,乘法交换律不适用于矩阵乘法,矩阵乘法是具有次序的,先旋转再移动和先移动再旋转的结果是完全不同的。

矩阵的逆

在数字的运算里,除法相当于是乘法的“撤销”操作:

4 x 5 = 20
20 / 5 = 4

但是在矩阵计算里,这个守则同样是不适用的。我们不能用向量去除一个矩阵,我们只能用向量去乘以一个矩阵的逆矩阵,来完成“撤销”的操作。

变化后的向量 = 原始向量 * 变化矩阵;
逆矩阵 = 变化矩阵.inverse();
原始向量 = 变化后的向量 * 逆矩阵;

逆矩阵表示的是相反的变换。

Three.js里提供了一种计算逆矩阵的方法:

var matrix = new THREE.Matrix4();
var inverseMatrix = new THREE.Matrix4();
matrix.getInverse(inverseMatrix);

除此之外,逆矩阵还应用在3D场景中处理相机对象的时候。

最后

矩阵在3D世界里是一种十分强大的工具,它能够将任意变换都表示为一种相似的结构,并采用相同的计算过程。而实际上,矩阵的世界远远比这里介绍的内容更多,希望通过这些简要的介绍,可以让我们进入到一个更深的领域,并游刃有余的利用他处理图形开发中更复杂的场景。

好了,以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,谢谢大家对三水点靠木的支持。

Javascript 相关文章推荐
javascript 遍历验证所有文本框的值
Aug 27 Javascript
jQuery使用数组编写图片无缝向左滚动
Dec 11 Javascript
如何屏蔽防止别的网站嵌入框架代码
Aug 24 Javascript
JavaScript实现带缓冲效果的随屏滚动漂浮广告代码
Nov 06 Javascript
javascript拖拽应用实例(二)
Mar 25 Javascript
基于JavaScript实现Tab选项卡切换效果
Nov 24 Javascript
Bootstrap Table使用整理(二)
Jun 09 Javascript
axios取消请求的实践记录分享
Sep 26 Javascript
微信小程序自定义tabBar组件开发详解
Sep 24 Javascript
浅谈Vue SSR中的Bundle的具有使用
Nov 21 Javascript
浅谈Vue3 Composition API如何替换Vue Mixins
Apr 29 Javascript
解决VUE项目localhost端口服务器拒绝连接,只能用127.0.0.1的问题
Aug 14 Javascript
vue+iview动态渲染表格详解
Mar 19 #Javascript
浅谈vue加载优化策略
Mar 19 #Javascript
Vue中Table组件Select的勾选和取消勾选事件详解
Mar 19 #Javascript
详解js加减乘除精确计算
Mar 19 #Javascript
jQuery添加新内容的四个常用方法分析【append,prepend,after,before】
Mar 19 #jQuery
浅谈JavaScript_DOM学习篇_图片切换小案例
Mar 19 #Javascript
vue多层嵌套路由实例分析
Mar 19 #Javascript
You might like
PHP的FTP学习(四)
2006/10/09 PHP
COM in PHP (winows only)
2006/10/09 PHP
echo(),print(),print_r()之间的区别?
2006/11/19 PHP
php获取目标函数执行时间示例
2014/03/04 PHP
利用javascript中的call实现继承
2007/01/22 Javascript
jQuery源码中的chunker 正则过滤符分析
2012/07/31 Javascript
图片动画横条广告带上下滚动可自定义图片、链接等等
2013/10/20 Javascript
javascript中数组的多种定义方法和常用函数简介
2014/05/09 Javascript
JS实现距离上次刷新已过多少秒示例
2014/05/23 Javascript
JavaScript用JQuery呼叫Server端方法示例代码
2014/09/03 Javascript
JavaScript中双叹号!!作用示例介绍
2014/09/21 Javascript
JsRender for object语法简介
2014/10/31 Javascript
jQuery中removeAttr()方法用法实例
2015/01/05 Javascript
jQuery右下角悬浮广告实例
2016/10/17 Javascript
jQuery中ztree 点击文本框弹出下拉框的实例代码
2017/02/05 Javascript
原生js简单实现放大镜特效
2017/05/16 Javascript
JavaScript内存泄漏的处理方式
2017/11/20 Javascript
基于vue-cli配置lib-flexible + rem实现移动端自适应
2017/12/26 Javascript
express启用https使用小记
2019/05/21 Javascript
nodejs文件夹深层复制功能
2019/09/03 NodeJs
用vue写一个日历
2020/11/02 Javascript
VUE异步更新DOM - 用$nextTick解决DOM视图的问题
2020/11/06 Javascript
Python Mysql数据库操作 Perl操作Mysql数据库
2009/01/12 Python
python写入中英文字符串到文件的方法
2015/05/06 Python
Python 实现数据库(SQL)更新脚本的生成方法
2017/07/09 Python
Python多重继承的方法解析执行顺序实例分析
2018/05/26 Python
详解使用PyInstaller将Pygame库编写的小游戏程序打包为exe文件
2019/08/23 Python
python属于哪种语言
2020/08/16 Python
学习Python需要哪些工具
2020/09/04 Python
selenium+python自动化78-autoit参数化与批量上传功能的实现
2021/03/04 Python
薇诺娜官方网上商城:专注敏感肌肤
2017/05/25 全球购物
IRO美国官网:法国服装品牌
2018/03/06 全球购物
《三峡》教学反思
2014/03/01 职场文书
英语教师自荐信
2014/05/26 职场文书
消防安全标语
2014/06/07 职场文书
计算机相关专业自荐信
2014/07/02 职场文书