three.js 數學方法之Matrix4

Vadim發表於2020-07-25

今天郭先生說一說three.js中的Matrix4,相較於Matrix3來說,Matrix4和three.js聯絡的更緊密,因為在4x4矩陣最常用的用法是作為一個變換矩陣。這使得表示三維空間中的一個點的向量Vector3通過乘以矩陣來進行轉換,如平移、旋轉、剪下、縮放、反射、正交或透視投影等。這就是把矩陣應用到向量上。

1. Object3D矩陣

任何3D物體Object3D都有三個關聯的矩陣:

  • Object3D.matrix: 儲存物體的本地變換。 這是物件相對於其父物件的變換。
  • Object3D.matrixWorld: 物件的全域性或世界變換。如果物件沒有父物件,那麼這與儲存在矩陣matrix中的本地變換相同。
  • Object3D.modelViewMatrix: 表示物件相座標相對於攝像機空間座標轉換, 一個物件的 modelViewMatrix 是物體世界變換矩陣乘以攝像機相對於世界空間變換矩陣的逆矩陣。

攝像機Cameras 有兩個額外的四維矩陣:

  • Camera.matrixWorldInverse: 檢視矩陣 - 攝像機世界座標變換的逆矩陣。
  • Camera.projectionMatrix: 投影矩陣 - 表示將場景中的資訊投影到裁剪空間。

注意:物體的正規矩陣 Object3D.normalMatrix 並不是一個4維矩陣,而是一個三維矩陣Matrix3。下面舉例子說明一下這幾個矩陣。

var geom = new THREE.BoxGeometry(); //建立一個幾何體
var mate = new THREE.MeshNormalMaterial(); //建立一個材質
var mesh = new THREE.Mesh(geom, mate);  //建立一個網格
var group = new THREE.Group(); //建立一個組
mesh.position.setX(9); //設定網格的位置
group.add(mesh); //將網格新增到組裡
group.position.setX(8); //設定組的位置
scene.add(group); //將組新增到場景中
scene.position.setX(7); //設定場景的位置

下面我們來分析一下這幾個矩陣,mesh、group和scene的Object3D.matrix矩陣分別是

  • mesh – elements: (16) [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 9, 0, 0, 1]
  • group – elements: (16) [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 8, 0, 0, 1]
  • scene – elements: (16) [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 7, 0, 0, 1]

可以看出他們本地變換矩陣的x方向位置分量分別是9、8和7,很明顯,Object3D.matrix是相對於父元素的變換,我們再來看看mesh、group和scene的Object3D.matrixWorld矩陣

  • mesh – elements: (16) [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 24, 0, 0, 1]
  • group – elements: (16) [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 15, 0, 0, 1]
  • scene – elements: (16) [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 7, 0, 0, 1]

其中scene世界變換矩陣x方向的位置分量是7,group的是15(7+8),mesh的是24(7+8+9),也就是說世界變換矩陣是該元素相對於世界座標系的變換,也是該元素的本地變換和該元素父級世界變換的乘積。
說Object3D.modelViewMatrix之前,先說一下Camera.matrixWorldInverse矩陣,這個矩陣有很重要的意義,

camera = new THREE.PerspectiveCamera(60,window.innerWidth / window.innerHeight,0.1,10000); 
camera.position.set(0, 0, 60); 
scene.add(camera);

由於相機是加到場景裡面的所以相機的本地變換矩陣是elements: (16) [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 60, 1],相機的世界變換矩陣是elements: (16) [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 7, 0, 60, 1],相機的檢視矩陣是elements: (16) [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, -7, 0, -60, 1],Camera.projectionMatrix是投影矩陣,它和相機的方向,視角等等有關。

Object3D.modelViewMatrix是物件相座標相對於攝像機空間座標轉換,如上mesh的世界變換是elements: (16) [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 24, 0, 0, 1],攝像機檢視矩陣陣elements: (16) [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, -7, 0, -60, 1],那麼將二者相乘,既是mesh的Object3D.modelViewMatrix的值elements: (16) [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 17, 0, -60, 1]。
以上是我自己在矩陣上推到出的結論。

2. Matrix4的屬性

1. elements

矩陣列優先column-major列表。

2. isMatrix4

用於判定此物件或者此類的派生物件是否是三維矩陣。預設值為 true。

3. Matrix4的方法

1. set(number, number, number …): Matrix4

設定set()方法引數採用行優先row-major, 而它們在內部是用列優先column-major順序儲存在陣列當中。方法同三維矩陣

2. identity(): Matrix4

建立並初始化一個4X4的單位矩陣identity matrix。方法同三維矩陣。

3. clone(): this

建立一個新的矩陣,元素elements與該矩陣相同。方法同三維矩陣。

4. copy( m: Matrix4 ): this

將矩陣m的元素elements複製到當前矩陣中。方法同三維矩陣。

5. copyPosition( m: Matrix4 ): Matrix4

將給定矩陣m : Matrix4 的平移分量拷貝到當前矩陣中。

var matrix1 = new THREE.Matrix4().makeTranslation(3,3,3);
var matrix2 = new THREE.Matrix4().makeScale(2,2,2);
matrix2.copyPosition(matrix1);
console.log(matrix2); //返回elements: (16) [2, 0, 0, 0, 0, 2, 0, 0, 0, 0, 2, 0, 3, 3, 3, 1]

6. extractBasis( xAxis: Vector3, yAxis: Vector3, zAxis: Vector3 ): Matrix4

將矩陣的基向量basis提取到指定的3個軸向量中。方法同三維矩陣。

7. makeBasis( xAxis: Vector3, yAxis: Vector3, zAxis: Vector3 ): Matrix4

通過給定的三個向量設定該矩陣為基矩陣basis:
xAxis.x, yAxis.x, zAxis.x, 0,
xAxis.y, yAxis.y, zAxis.y, 0,
xAxis.z, yAxis.z, zAxis.z, 0,
0, 0, 0, 1

8. extractRotation( m: Matrix4 ): Matrix4

將給定矩陣m的旋轉分量提取到該矩陣的旋轉分量中。

var matrix1 = new THREE.Matrix4().makeRotationZ(Math.PI/6);
var matrix2 = new THREE.Matrix4().makeScale(2,2,2);
matrix2.extractRotation(matrix1)
console.log(matrix2); //返回elements: (16) [0.8660254037844387, 0.49999999999999994, 0, 0, -0.49999999999999994, 0.8660254037844387, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1]

9. makeRotationFromEuler( euler: Euler ): Matrix4

將傳入的尤拉角轉換為該矩陣的旋轉分量(左上角的3x3矩陣)。 矩陣的其餘部分被設為單位矩陣。根據尤拉角euler的旋轉順序order,總共有六種可能的結果。

var euler = new THREE.Euler(0,0,Math.PI/6, 'XYZ');
var matrix = new THREE.Matrix4().makeScale(2,2,2);
matrix.makeRotationFromEuler(euler)
console.log(matrix); //返回elements: (16) [0.8660254037844387, 0.49999999999999994, 0, 0, -0.49999999999999994, 0.8660254037844387, 0, 0, 0, -0, 1, 0, 0, 0, 0, 1]

因為都是繞z軸旋轉30度,所以返回結果和上面一樣

10. makeRotationFromQuaternion( q: Quaternion ): Matrix4

將這個矩陣的旋轉分量設定為四元數q指定的旋轉,使用方法同makeRotationFromEuler。

11. lookAt( eye: Vector3, target: Vector3, up: Vector3 ): Matrix4

構造一個旋轉矩陣,從eye 指向 center,由向量 up : Vector3 定向。

var eye = new THREE.Vector3(0,0,0);
var target = new THREE.Vector3(1,0,0);
var up = new THREE.Vector3(0,0,1);
var matrix = new THREE.Matrix4();
matrix.lookAt(eye,target,up);
console.log(matrix); //返回elements: (16) [0, -0, 1, 0, 0, 1, 0, 0, -1, 0, 0, 0, 0, 0, 0, 1]

12. multiply( m: Matrix4 ): Matrix4

將當前矩陣乘以矩陣m。方法同三維矩陣。

13. premultiply( m: Matrix4 ): Matrix4

將矩陣m乘以當前矩陣。方法同三維矩陣。

14. multiplyMatrices( a: Matrix4, b: Matrix4 ): Matrix4

設定當前矩陣為矩陣a x 矩陣b。方法同三維矩陣。

15. multiplyScalar( s: number ): Matrix4

當前矩陣所有的元素乘以該縮放值s。方法同三維矩陣。

16. determinant(): number;

計算並返回矩陣的行列式determinant。方法同三維矩陣。

17. transpose(): Matrix4

將該矩陣轉置Transposes。方法同三維矩陣。

18. setPosition( v: Vector3 | number, y?: number, z?: number ): Matrix4

取傳入引數v : Vector3中值設定該矩陣的位置分量,不影響該矩陣的其餘部分——即。或者直接傳遞向量的分量值(v,y,z),下面是原始碼:

setPosition: function ( x, y, z ) {
    var te = this.elements;
    if ( x.isVector3 ) {
        te[ 12 ] = x.x;
        te[ 13 ] = x.y;
        te[ 14 ] = x.z;
    } else {
        te[ 12 ] = x;
        te[ 13 ] = y;
        te[ 14 ] = z;
    }
    return this;
}

19. getInverse( m: Matrix4 ): Matrix4

使用逆矩陣計算方法analytic method, 將當前矩陣設定為給定矩陣的逆矩陣inverse,如果throwOnDegenerate 引數沒有設定且給定矩陣不可逆,那麼將當前矩陣設定為3X3單位矩陣。逆矩陣求法可以是用伴隨矩陣的方法,同三維矩陣。

20. scale( v: Vector3 ): Matrix4

將該矩陣的列向量乘以對應向量v的分量。

var matrix = new THREE.Matrix4();
matrix.scale(new THREE.Vector3(1,2,3));
console.log('matrix', matrix); //返回elements: (16) [1, 0, 0, 0, 0, 2, 0, 0, 0, 0, 3, 0, 0, 0, 0, 1]

21. getMaxScaleOnAxis(): number

獲取3個軸方向的最大縮放值。

var matrix = new THREE.Matrix4().makeScale(2,3,4); 
matrix.getMaxScaleOnAxis(); //返回4

22. makeTranslation( x: number, y: number, z: number ): Matrix4

x - 在X軸上的平移量。y - 在Y軸上的平移量。z - 在Z軸上的平移量。不多說。

23. makeRotationX( theta: number ): Matrix4

把該矩陣設定為繞x軸旋轉弧度theta (θ)大小的矩陣。很簡單,不多說。

24. makeRotationY( theta: number ): Matrix4

把該矩陣設定為繞y軸旋轉弧度theta (θ)大小的矩陣。同上。

25. makeRotationZ( theta: number ): Matrix4

把該矩陣設定為繞z軸旋轉弧度theta (θ)大小的矩陣。同上。

26. makeRotationAxis( axis: Vector3, angle: number ): Matrix4

設定當前矩陣為圍繞軸 axis 旋轉量為 theta弧度。也不難。

var matrix = new THREE.Matrix4(); //一下兩個旋轉是相同的 matrix.makeRotationZ(Math.PI/4); 
matrix.makeRotationAxis(new THREE.Vector3(0,0,1), Math.PI/4);

27. makeScale( x: number, y: number, z: number ): Matrix4

x - 在X軸方向的縮放比。y - 在Y軸方向的縮放比。z - 在Z軸方向的縮放比。方法不難,就不多說了。

28. compose( translation: Vector3, rotation: Quaternion, scale: Vector3 ): Matrix4

設定將該物件由位置position,四元數quaternion 和 縮放scale 組合變換的矩陣。內部先呼叫makeRotationFromQuaternion( quaternion ) 再呼叫縮放scale( scale )最後是平移setPosition( position )。
這個方法整合了旋轉、縮放和平移。

//使用make系列的方法操作
Object3D.applyMatrix(new THREE.Matrix4().makeScale(2,1,1));
Object3D.applyMatrix(new THREE.Matrix4().makeTranslation(0,4,0));
Object3D.applyMatrix(new THREE.Matrix4().makeRotationZ(Math.PI/6));
//使用compose方法操作
var matrix = new THREE.Matrix4();
var trans = new THREE.Vector3(0,4,0);
var rotat = new THREE.Quaternion().setFromEuler(new THREE.Euler(0,0,Math.PI/6));
var scale = new THREE.Vector3(2,1,1);
Object3D.applyMatrix4(matrix.compose(trans, rotat, scale)); //效果同上

29. decompose(translation: Vector3, rotation: Quaternion, scale: Vector3): Matrix4;

將矩陣分解到給定的平移position ,旋轉 quaternion,縮放scale分量中。就是compose的逆過程。隨便舉個例子。

var matrix = new THREE.Matrix4().set(1,2,3,4,2,3,4,5,3,4,5,6,4,5,6,7);
var trans = new THREE.Vector3();
var rotat = new THREE.Quaternion();
var scale = new THREE.Vector3();
matrix.decompose(trans, rotat, scale);
console.log(trans); //返回Vector3 {x: 4, y: 5, z: 6} 因為是隨便寫的,所以只有平移變數不需計算就可以看出來的
console.log(rotat); //返回Quaternion {_x: 0.05565363763555474, _y: -0.11863820054057297, _z: 0.051265314875937947, _w: 0.7955271896092125}
console.log(scale); //返回Vector3 {x: 3.7416573867739413, y: 5.385164807134504, z: 7.0710678118654755}

30. makePerspective(fov: number,aspect: number,near: number,far: number): Matrix4

建立一個透視投影矩陣perspective projection。 在引擎內部由PerspectiveCamera.updateProjectionMatrix()使用。

31. makeOrthographic(left: number,right: number,top: number,bottom: number,near: number,far: number): Matrix4;

建立一個正交投影矩陣orthographic projection。 在引擎內部由OrthographicCamera.updateProjectionMatrix()使用。

32. equals( matrix: Matrix4 ): boolean

如果矩陣m 與當前矩陣所有對應元素相同則返回true。

33. fromArray( array: number[], offset?: number ): Matrix4

使用基於列優先格式column-major的陣列來設定該矩陣。方法同三維矩陣。

34. toArray( array?: number[], offset?: number ): number[]

使用列優先column-major格式將此矩陣的元素寫入陣列中。

四維矩陣就先說到這裡,我們以後將會經常看到他,無論是在變換中,還是在著色器中。

 

轉載請註明地址:郭先生的部落格

相關文章