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 相关文章推荐
jquery 简单的进度条实现代码
Mar 11 Javascript
JS函数验证总结(方便js客户端输入验证)
Oct 29 Javascript
网页前端优化之滚动延时加载图片示例
Jul 13 Javascript
jQuery学习笔记之Ajax用法实例详解
Dec 01 Javascript
利用jquery实现瀑布流3种案例
Sep 18 Javascript
Angularjs通过指令监听ng-repeat渲染完成后执行脚本的方法
Dec 31 Javascript
彻底学会Angular.js中的transclusion
Mar 12 Javascript
JS判断时间段的实现代码
Jun 14 Javascript
解决Vue使用mint-ui loadmore实现上拉加载与下拉刷新出现一个页面使用多个上拉加载后冲突问题
Nov 07 Javascript
微信小程序 功能函数小结(手机号验证*、密码验证*、获取验证码*)
Dec 08 Javascript
vue 微信授权登录解决方案
Apr 10 Javascript
VUE 全局变量的几种实现方式
Aug 22 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压缩多个CSS为一个css的代码并缓存
2011/04/21 PHP
php摘要生成函数(无乱码)
2012/02/04 PHP
CodeIgniter删除和设置Cookie的方法
2015/04/07 PHP
PHP中的类型约束介绍
2015/05/11 PHP
PHP中Closure类的使用方法及详解
2015/10/09 PHP
Yii实现简单分页的方法
2016/04/29 PHP
PHP长网址与短网址的实现方法
2017/10/13 PHP
在thinkphp5.0路径中实现去除index.php的方式
2019/10/16 PHP
laravel框架之数据库查出来的对象实现转化为数组
2019/10/23 PHP
javascript实现的使用方向键控制光标在table单元格中切换
2010/11/17 Javascript
js正则表达式匹配数字字母下划线等
2015/04/14 Javascript
jQuery动画显示和隐藏效果实例演示(附demo源码下载)
2015/12/31 Javascript
图文详解Javascript中的上下文和作用域
2017/02/15 Javascript
Angular 4.x中表单Reactive Forms详解
2017/04/25 Javascript
Vue封装Swiper实现图片轮播效果
2018/02/06 Javascript
JavaScript中严格判断NaN的方法
2018/02/16 Javascript
微信小程序中button去除默认的边框实例代码
2019/08/01 Javascript
[05:56]第十六期——新进3大C之小兔基
2014/06/24 DOTA
[05:42]DOTA2英雄梦之声_第10期_蝙蝠骑士
2014/06/21 DOTA
Python在不同目录下导入模块的实现方法
2017/10/27 Python
浅谈Scrapy框架普通反爬虫机制的应对策略
2017/12/28 Python
Python实现PS图像抽象画风效果的方法
2018/01/23 Python
浅谈Python3 numpy.ptp()最大值与最小值的差
2019/08/24 Python
python matplotlib如何给图中的点加标签
2019/11/14 Python
Python多线程正确用法实例解析
2020/05/30 Python
localStorage、sessionStorage使用总结
2017/11/17 HTML / CSS
英国最全面的橄榄球联盟门票网站:Live Rugby Tickets
2018/10/06 全球购物
c语言常见笔试题总结
2016/09/05 面试题
七年级政治教学反思
2014/02/03 职场文书
工程承包协议书
2014/04/22 职场文书
大学运动会加油稿200字(5篇)
2014/09/27 职场文书
2014年学习委员工作总结
2014/11/14 职场文书
2019年暑期安全广播稿!
2019/07/03 职场文书
WinServer2012搭建DNS服务器的方法步骤
2022/06/10 Servers
JS轻量级函数式编程实现XDM二
2022/06/16 Javascript
Zabbix6通过ODBC方式监控Oracle 19C的详细过程
2022/09/23 Servers