一篇文章弄懂THREE.js中的各類矩陣關係

這兩天正在從新實現maptalks.js三維變換邏輯, 須要從底層從新實現一遍三維投影轉換的算法。 好在三維投影算法已有很成熟的實現範式, 我選擇了THREE.js做爲參考對象, 這篇文章也是我對THREE.js中矩陣轉換關係的總結。javascript

本文面向有必定webgl開發基礎的讀者,THREE的版本爲寫做時的最新版本r88html

三維投影算法

什麼是三維投影轉換? 簡而言之,就是將三維空間中的物體,投射在相機視平面的轉換算法, 如圖:java

(圖片截取自webglfundamentals):git

在這個場景中,如圖擺放5個F字母和相機github

相機看到的景象以下:web

根據實際生活經驗,相機看到的景象和如下因素有關,任何改變都會讓相機眼中的世界發生變化:算法

  • 相機投影類型(正射投影仍是透視投影), 透視投影最符合現實世界, 也是最經常使用的投影方式
  • 相機的位置和方向
  • 物體的位置和形變(旋轉/縮放/平移)

三維投影算法就是將上訴因素抽象爲數學算法,用來計算三維物體在相機視平面上的位置app

實際應用中咱們是經過矩陣計算來實現的。簡而言之,咱們將相機的位置方向, 相機的類型, 物體的位置和形變能轉換爲 矩陣, 將這些矩陣進行一系列計算後, 最終獲得三維投影矩陣:less

u_matrix

基於它, 任意給定三維座標[x, y, z], 咱們都能算出相機視平面上的位置:
[x, y] = u_matrix * [x, y, z, 1]

固然,實際應用中狀況會更復雜一些,例如三維圖形引擎爲了簡化計算,通常將三維物體組織爲層級結構,經過物體的本地位置和對上層的相對位置來計算出其在世界中的絕對位置,但歸根到底,咱們須要的只是最終的位置矩陣。webgl

THREE中的矩陣

讓咱們回到THREE.js,來看看THREE是怎麼組織定義組織投影矩陣的。

咱們知道,THREE定義了場景(Scene)和相機(Camera), Scene用來添加管理三維物體, Camera用來控制相機的位置, 角度等,代碼大概以下:

const scene = new THREE.Scene();
const mesh = new THREE.Mesh(new THREE.Cube());
scene.add(mesh);
const camera = new THREE.PerspectiveCamera(fov, aspect, near, far);
scene.add(camera);
renderer.render(scene, camera);複製代碼

咱們根據上面的總結按圖索驥,THREE中定義了下述三個矩陣:

  • 相機投影類型:投影矩陣(ProjectMatrix
  • 相機的位置和方向: 視圖矩陣 (CameraMatrixWorldInverseViewMatrix)
  • 物體的位置和形變: 物體位置矩陣(ObjectWorldMatrix)

三維投影矩陣(u_matrix)計算公式

三維投影矩陣計算公式以下:

const uMatrix = ProjectMatrix * CameraMatrixWorldInverse* ObjectMatrixWorld複製代碼

是否是很簡單?

若是你有興趣,能夠寫一段最簡單的THREE程序,跟蹤一下THREE的繪製邏輯,看看THREE是怎麼生成和運用這些矩陣的。

接下來咱們來解釋一下怎麼在THREE中獲得上述三個矩陣:

相機投影矩陣(ProjectMatrix)

相機投影矩陣決定了相機是透視投影相機仍是正射投影相機,現實世界都是透視投影,因此透視投影也是最經常使用的。
在THREE中,經過用不一樣的相機類實例化,獲得不一樣類型的相機,例如定義一個透視投影相機:

const camera = new THREE.PerspectiveCamera(fov, aspect, near, far);複製代碼
  • 得到ProjectionMatrix
    camera.projectMatrix複製代碼

相機視圖矩陣(CameraMatrixWorldInverse)

有的三維引擎或教程,會把視圖矩陣稱爲ViewMatrix(例如webglfundamentals)
視圖矩陣的含義是,固定其餘因素,咱們改變了相機的位置和角度後,它眼中的世界也會發生變化,這種變化就是視圖矩陣。

前面提到,相機在三維空間中的位置是camera.matrixWorld,而它的視圖矩陣是相機位置矩陣的逆矩陣CameraMatrixWorldInverse,它也符合了咱們的生活經驗:

  • 固定相機,人向左移動
  • 固定人,向右移動相機

這兩種狀況在相機眼中是同樣的。

在THREE中,咱們通常經過設置camera的position和up,調用lookAt來改變相機的視圖矩陣

camera.position.set(x, y, z);
camera.up.set(x, y, z);
camera.lookAt(x, y, z);複製代碼
  • 得到CameraMatrixInverse
    camera.matrixWorldInverse複製代碼

咱們知道,最終的投影是在GLSL頂點着色器中計算的。在一次繪製中,ProjectionMatrixCameraMatrixWorldInverse通常不會發生變化,而ObjectMatrixWorld每一個物體均可能不一樣, 因此爲了減小頂點着色器中的計算量,有些三維引擎會在javascript程序中提早計算出ProjectionMatrix * CameraMatrixWorldInverse的值傳遞給頂點着色器,這個矩陣通常稱爲ViewProjectionMatrix

物體位置矩陣(ObjectWorldMatrix)

ObjectWorldMatrix描述了物體在三維場景中的位置。

  • 得到ObjectWorldMatrix
    object.matrixWorld複製代碼

前面提到,THREE中的物體是有層級關係的,因此THREE中物體的matrixWorld是經過local matrix(object.matrix)與父親的matrixWorld遞歸相乘獲得的, 其中的原理能夠查閱webglfundamentals中的這篇教程

一些應用

獲取屏幕二維座標

給定三維座標[x, y, z],怎麼獲取它在屏幕上的二維座標呢?計算公式以下:

const [x, y] = ProjectionMatrix * CameraWorldMatrixInverse * [x, y, z]複製代碼

THREE在Vector3上封裝了方法:

const v = new THREE.Vector3(x, y, z);
const xy = v.project(camera);複製代碼

源代碼以下:

project: function () {

    var matrix = new Matrix4();

    return function project(camera) {

        matrix.multiplyMatrices(camera.projectionMatrix, matrix.getInverse(camera.matrixWorld));
        return this.applyMatrix4(matrix);

    };
}(),複製代碼

屏幕座標轉化爲三維座標

給定屏幕二維座標[x, y],怎麼獲取它在三維空間中三維座標呢?計算公式以下:

const [x, y, z] = CameraWorldMatrix * ProjectionMatrixInverse * [x, y, z]複製代碼

THREE在Vector3上封裝了方法:

const v = new THREE.Vector3(x, y, z);
const xyz = v.unproject(camera);複製代碼

源代碼以下:

unproject: function () {

    var matrix = new Matrix4();

    return function unproject(camera) {

        matrix.multiplyMatrices(camera.matrixWorld, matrix.getInverse(camera.projectionMatrix));
        return this.applyMatrix4(matrix);

    };

}(),複製代碼

不過屏幕座標轉化爲三維座標不是這麼簡單,由於屏幕上的二維座標在三維空間中其實對應的是一條射線,其能夠對應了無限個三維座標點,更深刻的原理能夠閱讀這篇stackoverflow上的問題, THREE的做者mroob和一位網友給了精彩的回答。

相關文章
相關標籤/搜索