Three.js中矩陣和向量的使用教程
前言
提起矩陣,很容易讓人想起我們曾經(jīng)學(xué)不會的線性代數(shù)和離散數(shù)學(xué),但是作為圖形開發(fā)中的核心部分,它代表著每一次的運動和變換,就像魚不能脫離水一樣,矩陣并不是一個可以避之不談的話題。
好消息是,Three.js幫助我們把許多矩陣運算封裝成了一些頂層的方法,并提供了一個優(yōu)秀的數(shù)學(xué)庫,我們不太需要知道HowToCalc,只需要知道HowToUse,就可以得到絕大部分我們想要的東西。
這篇文章將要介紹的就是,如何在不了解內(nèi)部結(jié)構(gòu)的情況下在Three.js中使用矩陣和向量。
從一個例子開始
在講解一些枯燥的概念前先舉一個小例子,來簡要說明一下為什么我們要使用矩陣方法。
這是我們最終要完成的效果。
首先,我們要創(chuàng)建三個幾何體:
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)})
這三個幾何體分別是盒子、球和圓柱體。
然后去創(chuàng)建三個網(wǎng)格,并將它們置入場景。
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);
這段代碼將生成如下場景:
雖然不那么美觀,但作為示例已經(jīng)足夠了,現(xiàn)在我希望這堆物體尺寸減半。通常我會把物體的scale屬性減半,像這樣:
box.scale.multiplyScalar(0.5); sphere.scale.multiplyScalar(0.5); cylinder.scale.multiplyScalar(0.5);
和想象中的有些偏差。我的本意是讓這一組物體進行一個整體的縮放,并不想讓它們彼此偏離,為了修正這件事,我需要根據(jù)其他對象的縮放重新計算每個對象的位置。但這并不是一件很難解決的問題,three.js提供了一種優(yōu)雅的方式,來處理這個問題。我們可以定義一個空對象,然后將三個對象放在其中,然后將比例應(yīng)用于父對象。
var pile = new THREE.Object3D(); pile.scale.multiplyScalar(0.5); pile.add(box); pile.add(sphere); pile.add(cylinder); scene.add(pile);
接下來我們做一點更有趣的事。
我將在這個物體組合里添加旋轉(zhuǎn),讓我們嘗試圍繞球體表面旋轉(zhuǎn)的那個圓柱體,就像他將要滑落一樣。
它變成了這樣,很明顯,這不是我想要的東西。我們在這里有兩個做法可供選擇:第一,通過數(shù)學(xué)計算算出圓柱相對于球體的正確位置;第二,創(chuàng)建另一個Object3D,將圓柱和球放進去并旋轉(zhuǎn)。這聽上去挺復(fù)雜的,而且也很不酷。
所以,我們可以嘗試自己去計算矩陣。
首先,我需要將屬性maxtrixAutoUpdate設(shè)置為false,然后我就不能再通過position,scale和rotation去修改矩陣。
box.matrixAutoUpdate = false; sphere.matrixAutoUpdate = false; cylinder.matrixAutoUpdate = false;
現(xiàn)在,我將用applyMatrix方法來解決這個問題。具體做法是:為每個對象創(chuàng)建一個Matrix4,然后我們將矩陣與該矩陣相乘以應(yīng)用后續(xù)操作。
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);
這幾步下來,可以讓我們解鎖很多知識,來看看這里發(fā)生了什么。
首先,我們把盒子單獨留下,因為它不需要動。
接著,我創(chuàng)建了一個平移矩陣并把它應(yīng)用到了球?qū)ο笊稀?br />
最后,在圓柱體上,我clone了球的矩陣信息,并在此基礎(chǔ)上又創(chuàng)建了一個新的平移矩陣,圓柱體將移動1.75。
理解了上面幾步,你就會知道最后一步該做什么了。
只需要一行代碼,作用在球上:
sphere_matrix.multiply(new THREE.Matrix4().makeRotationZ(-Math.PI * 0.25));
達成了想要的效果,很酷。
示例中用到的方法
在上面的示例中,我將球和圓柱體分別沿y軸移動了一定的距離,并使用了makeTranslation這個方法。這個方法的作用是創(chuàng)建了一個平移矩陣。緊接著,我又使用到了applyMatrix的方法。這個方法的作用是把平移矩陣作用在球和圓柱體上。
那么什么是平移矩陣?它又是如何完成一次平移呢?
Three.js中最常見的一種4x4的矩陣,被稱為變換矩陣,它所表示的變換類型包括平移、旋轉(zhuǎn)和縮放。
用一個簡單的數(shù)學(xué)題來說明變換矩陣:
有一個起始點,用向量來表示即為Vector3(20,20,0);現(xiàn)在,我要把它移動到另一個位置,Vector3(30,60,0)。
接下來,我設(shè)置一個平移矩陣,來表示向量依照什么方式去移動。
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)這個方法來表示縮放。
而旋轉(zhuǎn)則相對復(fù)雜許多,Three.js提供以下旋轉(zhuǎn)方法:
matrix.makeRotationX(angle); matrix.makeRotationY(angle); matrix.makeRotationZ(angle); matrix.makeRotationAxis(axis, angle); matrix.makeRotationFromEuler(euler); matrix.makeRotationFromQuaternion(quaternion);
前三個方法分別代表的是繞X、Y、Z三個軸旋轉(zhuǎn),無需贅述。
第四個方法是前三個方法的整合版,第一個參數(shù)表示的是代表xyz的THREE.Vector3,第二個參數(shù)是旋轉(zhuǎn)的弧度。下面兩行代碼是等價的:
matrix.makeRotationX(Math.PI); matrix.makeRotationAxis(new THREE.Vector3(1, 0, 0), Math.PI);
第五個方法表示圍繞x、y和z軸的旋轉(zhuǎn),這是表示旋轉(zhuǎn)最常用的方式;第六個方法是一種基于軸和角度表示旋轉(zhuǎn)的替代方法。
最后,Three.js api提供了一種方法來創(chuàng)建表示平移,旋轉(zhuǎn)和縮放的組合的矩陣 -- 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);
矩陣相乘
矩陣乘法的意義在于疊加。
上圖表示了三個變化:旋轉(zhuǎn)、縮放和移動。
通過按次序相乘,三個變化矩陣可以得出一個最終的變化矩陣:
combinedMatrix = rotationMatrix * scaleMatrix * translationMatrix;
Three.js里提供了兩種矩陣相乘的方法:
- matrix.multiply(otherMatrix)
- matrix.multiplyMatrices(matrixA, matrixB)
第一種方法表示將矩陣乘以另一個矩陣;而第二種方法代表的是將矩陣設(shè)置為matrixA * matrixB的結(jié)果。
我們在示例中也使用到了第一個方法:將圓柱體的矩陣乘以新的平移矩陣,和將球的矩陣乘以一個旋轉(zhuǎn)矩陣。
需要注意的是,乘法交換律不適用于矩陣乘法,矩陣乘法是具有次序的,先旋轉(zhuǎn)再移動和先移動再旋轉(zhuǎn)的結(jié)果是完全不同的。
矩陣的逆
在數(shù)字的運算里,除法相當于是乘法的“撤銷”操作:
4 x 5 = 20 20 / 5 = 4
但是在矩陣計算里,這個守則同樣是不適用的。我們不能用向量去除一個矩陣,我們只能用向量去乘以一個矩陣的逆矩陣,來完成“撤銷”的操作。
變化后的向量 = 原始向量 * 變化矩陣;
逆矩陣 = 變化矩陣.inverse();
原始向量 = 變化后的向量 * 逆矩陣;
逆矩陣表示的是相反的變換。
Three.js里提供了一種計算逆矩陣的方法:
var matrix = new THREE.Matrix4(); var inverseMatrix = new THREE.Matrix4(); matrix.getInverse(inverseMatrix);
除此之外,逆矩陣還應(yīng)用在3D場景中處理相機對象的時候。
最后
矩陣在3D世界里是一種十分強大的工具,它能夠?qū)⑷我庾儞Q都表示為一種相似的結(jié)構(gòu),并采用相同的計算過程。而實際上,矩陣的世界遠遠比這里介紹的內(nèi)容更多,希望通過這些簡要的介紹,可以讓我們進入到一個更深的領(lǐng)域,并游刃有余的利用他處理圖形開發(fā)中更復(fù)雜的場景。
好了,以上就是這篇文章的全部內(nèi)容了,希望本文的內(nèi)容對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,謝謝大家對腳本之家的支持。
相關(guān)文章
D3.js的基礎(chǔ)部分之數(shù)組的處理數(shù)組的排序和求值(v3版本)
這篇文章主要介紹了D3.js的基礎(chǔ)部分之數(shù)組的處理數(shù)組的排序和求值(v3版本) ,非常不錯,具有一定的參考借鑒價值,需要的朋友可以參考下2019-05-05動態(tài)的綁定事件addEventListener方法的使用
本文為大家介紹下動態(tài)的綁定事件的方法addEventListener的使用示例,不了解的朋友可以參考下2014-01-01JavaScript 中如何實現(xiàn)并發(fā)控制
在日常開發(fā)過程中,你可能會遇到并發(fā)控制的場景,比如控制請求并發(fā)數(shù)。那么在 JavaScript 中如何實現(xiàn)并發(fā)控制呢?在回答這個問題之前,我們來簡單介紹一下并發(fā)控制。2021-05-05JavaScript數(shù)組類型Array相關(guān)的屬性與方法詳解
這篇文章主要給大家介紹了關(guān)于JavaScript數(shù)組類型Array相關(guān)的屬性與方法的相關(guān)資料,文中通過示例代碼介紹的非常詳細,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2020-09-09JavaScript代碼因逗號不規(guī)范導(dǎo)致IE不兼容的問題
這篇文章主要介紹了JavaScript代碼因逗號不規(guī)范導(dǎo)致IE不兼容的問題的相關(guān)資料,需要的朋友可以參考下2016-02-02