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 相关文章推荐
使用JS读秒使用示例
Sep 21 Javascript
form.submit()不能提交表单的原因分析
Oct 23 Javascript
鼠标事件的screenY,pageY,clientY,layerY,offsetY属性详解
Mar 12 Javascript
AngularJS 遇到的小坑与技巧小结
Jun 07 Javascript
AngularJS入门教程之XHR和依赖注入详解
Aug 18 Javascript
Vue中添加过渡效果的方法
Mar 16 Javascript
javascript基于牛顿迭代法实现求浮点数的平方根【递归原理】
Sep 28 Javascript
js中DOM事件绑定分析
Mar 18 Javascript
代码实例ajax实现点击加载更多数据图片
Oct 12 Javascript
深入解析vue 源码目录及构建过程分析
Apr 24 Javascript
es6中reduce的基本使用方法
Sep 10 Javascript
javascript使用canvas实现饼状图效果
Sep 08 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 不同编码下的字符串长度区分
2009/09/26 PHP
php feof用来识别文件末尾字符的方法
2010/08/01 PHP
解析PHP 5.5 新特性
2013/07/02 PHP
php使用pdo连接报错Connection failed SQLSTATE的解决方法
2014/12/15 PHP
PHP Smarty模版简单使用方法
2016/03/30 PHP
jquery ajax abort()的使用方法
2010/10/28 Javascript
javascript游戏开发之《三国志曹操传》零部件开发(二)人物行走的实现
2013/01/23 Javascript
javascript里模拟sleep(两种实现方式)
2013/01/25 Javascript
JavaScript控制按钮可用或不可用的方法
2015/04/03 Javascript
jQuery里filter()函数与find()函数用法分析
2015/06/24 Javascript
ES6所改良的javascript“缺陷”问题
2016/08/23 Javascript
JavaScript算法系列之快速排序(Quicksort)算法实例详解
2016/09/04 Javascript
JS多物体实现缓冲运动效果示例
2016/12/20 Javascript
使用UrlConnection实现后台模拟http请求的简单实例
2017/01/04 Javascript
EasyUI的TreeGrid的过滤功能的解决思路
2017/08/08 Javascript
js 发布订阅模式的实例讲解
2017/09/10 Javascript
jQuery实现的form转json经典示例
2017/10/10 jQuery
MUI 实现侧滑菜单及其主体部分上下滑动的方法
2018/01/25 Javascript
微信小程序如何实现全局重新加载
2019/06/05 Javascript
小谈angular ng deploy的实现
2020/04/07 Javascript
[01:07:11]Secret vs Newbee 2019国际邀请赛小组赛 BO2 第二场 8.15
2019/08/17 DOTA
python实现分析apache和nginx日志文件并输出访客ip列表的方法
2015/04/04 Python
Python 循环语句之 while,for语句详解
2018/04/23 Python
Python中return self的用法详解
2018/07/27 Python
Python基于read(size)方法读取超大文件
2020/03/12 Python
HTML5之SVG 2D入门9—蒙板及mask元素介绍与应用
2013/01/30 HTML / CSS
Bibloo匈牙利:女装、男装、童装及鞋子和配饰
2019/04/14 全球购物
饿了么订餐官网:外卖、网上订餐
2019/06/28 全球购物
通信工程专业毕业生推荐信
2013/12/25 职场文书
工地门卫岗位职责
2013/12/30 职场文书
自愿解除劳动合同协议书
2014/09/11 职场文书
小学生田径运动会广播稿
2014/09/11 职场文书
党员年度个人总结
2015/02/14 职场文书
找规律教学反思
2016/02/23 职场文书
好段摘抄大全(48句)
2019/08/08 职场文书
vue.js 使用原生js实现轮播图
2022/04/26 Vue.js