目錄
- 1. 概述
- 2. 基本變換
- 2.1. 矩陣運算
- 2.2. 模型變換矩陣
- 2.2.1. 平移矩陣
- 2.2.2. 旋轉矩陣
- 2.2.2.1. 繞X軸旋轉矩陣
- 2.2.2.2. 繞Y軸旋轉矩陣
- 2.2.2.3. 繞Z軸旋轉矩陣
- 2.3. 投影變換矩陣
- 2.4. 視圖變換矩陣
- 3. 著色器變換
- 3.1. 代碼
- 3.2. 決議
- 4. 其他
1. 概述
我在《WebGL簡易教程(五):圖形變換(模型、視圖、投影變換)》這篇博文里詳細講解了OpenGL\WebGL關于繪制場景的圖形變換程序,并推導了相應的模型變換矩陣、視圖變換矩陣以及投影變換矩陣,這里我就通過three.js這個圖形引擎,驗證一下其推導是否正確,順便學習下three.js是如何進行圖形變換的,
2. 基本變換
2.1. 矩陣運算
three.js已經提供了向量類和矩陣類,定義并且查看一個4階矩陣類:
var m = new THREE.Matrix4();
m.set(11, 12, 13, 14,
21, 22, 23, 24,
31, 32, 33, 34,
41, 42, 43, 44);
console.log(m);
輸出結果:

說明THREE.Matrix4內部是列主序存盤的,而我們理論描述的矩陣都為行主序,
2.2. 模型變換矩陣
在場景中新建一個平面:
// create the ground plane
var planeGeometry = new THREE.PlaneGeometry(60, 20);
var planeMaterial = new THREE.MeshBasicMaterial({
color: 0xAAAAAA
});
var plane = new THREE.Mesh(planeGeometry, planeMaterial);
// add the plane to the scene
scene.add(plane);
three.js中場景節點的基類都是Object3D,Object3D包含了3種矩陣物件:
- Object3D.matrix: 相對于其父物件的區域模型變換矩陣,
- Object3D.matrixWorld: 物件的全域模型變換矩陣,如果物件沒有父物件,則與Object3D.matrix相同,
- Object3D.modelViewMatrix: 表示物件相對于相機坐標系的變換,也就是matrixWorld左乘相機的matrixWorldInverse,
2.2.1. 平移矩陣
平移這個mesh:
plane.position.set(15, 8, -10);
根據推導得到平移矩陣為:
\[\left[ \begin{matrix} 1 & 0 & 0 & Tx\\ 0 & 1 & 0 & Ty\\ 0 & 0 & 1 & Tz\\ 0 & 0 & 0 & 1 \end{matrix} \right] \]
輸出這個Mesh:

2.2.2. 旋轉矩陣
2.2.2.1. 繞X軸旋轉矩陣
繞X軸旋轉:
plane.rotation.x = THREE.Math.degToRad(30);
對應的旋轉矩陣:
\[\left[ \begin{matrix} 1 & 0 & 0 & 0\\ 0 & cosβ & -sinβ & 0\\ 0 & sinβ & cosβ & 0\\ 0 & 0 & 0 & 1 \end{matrix} \right] \]
輸出資訊:

2.2.2.2. 繞Y軸旋轉矩陣
繞Y軸旋轉:
plane.rotation.y = THREE.Math.degToRad(30);
對應的旋轉矩陣:
\[\left[ \begin{matrix} cosβ & 0 & sinβ & 0\\ 0 & 1 & 0 & 0\\ -sinβ & 0 & cosβ & 0\\ 0 & 0 & 0 & 1 \end{matrix} \right] \]
輸出資訊:

2.2.2.3. 繞Z軸旋轉矩陣
繞Z軸旋轉:
plane.rotation.z = THREE.Math.degToRad(30);
對應的旋轉矩陣:
\[\left[ \begin{matrix} cosβ & -sinβ & 0 & 0\\ sinβ & cosβ & 0 & 0\\ 0 & 0 & 1 & 0\\ 0 & 0 & 0 & 1 \end{matrix} \right] \]
輸出資訊:

2.3. 投影變換矩陣
在場景中新建一個Camera:
var camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 0.1, 1000);
這里創建了一個透視投影的相機,一般建立的都是對稱的透視投影,推導的透視投影矩陣為:
\[P= \left[ \begin{matrix} \frac{1}{aspect*tan?(\frac{fovy}{2})} & 0 & 0 & 0 \\ 0 & \frac{1}{tan?(\frac{fovy}{2})} & 0 & 0 \\ 0 & 0 & \frac{f+n}{n-f} & \frac{2fn}{n-f} \\ 0 & 0 & -1 & 0 \\ \end{matrix} \right] \]
為了驗證其推導是否正確,輸出這個camera,查看projectionMatrix,也就是透視投影矩陣:

2.4. 視圖變換矩陣
通過Camera可以設定視圖矩陣:
camera.position.set(0, 0, 100); //相機的位置
camera.up.set(0, 1, 0); //相機以哪個方向為上方
camera.lookAt(new THREE.Vector3(1, 2, 3)); //相機看向哪個坐標
根據《WebGL簡易教程(五):圖形變換(模型、視圖、投影變換)》中的描述,可以通過three.js的矩陣運算來推導其視圖矩陣:
var eye = new THREE.Vector3(0, 0, 100);
var up = new THREE.Vector3(0, 1, 0);
var at = new THREE.Vector3(1, 2, 3);
var N = new THREE.Vector3();
N.subVectors(eye, at);
N.normalize();
var U = new THREE.Vector3();
U.crossVectors(up, N);
U.normalize();
var V = new THREE.Vector3();
V.crossVectors(N, U);
V.normalize();
var R = new THREE.Matrix4();
R.set(U.x, U.y, U.z, 0,
V.x, V.y, V.z, 0,
N.x, N.y, N.z, 0,
0, 0, 0, 1);
var T = new THREE.Matrix4();
T.set(1, 0, 0, -eye.x,
0, 1, 0, -eye.y,
0, 0, 1, -eye.z,
0, 0, 0, 1);
var V = new THREE.Matrix4();
V.multiplyMatrices(R, T);
console.log(V);
其推導公式如下:
\[V=R^{-1} T^{-1}= \left[ \begin{matrix} Ux & Uy & Uz & 0 \\ Vx & Vy & Vz & 0 \\ Nx & Ny & Nz & 0 \\ 0 & 0 & 0 & 1 \\ \end{matrix} \right] * \left[ \begin{matrix} 1 & 0 & 0 & -Tx \\ 0 & 1 & 0 & -Ty\\ 0 & 0 & 1 & -Tz\\ 0 & 0 & 0 & 1\\ \end{matrix} \right] = \left[ \begin{matrix} Ux & Uy & Uz & -U·T \\ Vx & Vy & Vz & -V·T \\ Nx & Ny & Nz & -N·T \\ 0 & 0 & 0 & 1 \\ \end{matrix} \right] \]
最后輸出它們的矩陣值:


兩者的計算結果基本時一致的,需要注意的是Camera中表達視圖矩陣的成員變數是Camera.matrixWorldInverse,它的邏輯應該是視圖矩陣與模型矩陣互為逆矩陣,模型矩陣也可以稱為世界矩陣,那么世界矩陣的逆矩陣就是視圖矩陣了,
3. 著色器變換
可以通過給著色器傳值來驗證計算的模型視圖投影矩陣(以下稱MVP矩陣)是否正確,對于一個任何事情都不做的著色器來說:
vertexShader: `
void main() {
gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );
}`
,
fragmentShader: `
void main() {
gl_FragColor = vec4(0.556, 0.0, 0.0, 1.0)
}`
projectionMatrix和modelViewMatrix分別是three.js中內置的投影矩陣和模型視圖矩陣,那么可以做一個簡單的驗證作業,將計算得到的MVP矩陣傳入到著色器中,代替這兩個矩陣,如果最終得到的值是正確的,那么就說明計算的MVP矩陣是正確的,
3.1. 代碼
實體代碼如下:
<!DOCTYPE html>
<html>
<head>
<title>Example 01.01 - Basic skeleton</title>
<meta charset="UTF-8" />
<script type="text/javascript" charset="UTF-8" src=https://www.cnblogs.com/charlee44/p/"../three/three.js"></script>
<script type="text/javascript" charset="UTF-8" src="../three/controls/TrackballControls.js"></script>
<script type="text/javascript" charset="UTF-8" src="../three/libs/stats.min.js"></script>
<script type="text/javascript" charset="UTF-8" src="../three/libs/util.js"></script>
<script type="text/javascript" src="MatrixDemo.js"></script>
<link rel="stylesheet" href="../css/default.css">
<body>
<script type="text/javascript">
(function () {
// contains the code for the example
init();
})();
</script>
