圖形學 旋轉與投影矩陣-3

隨遇丿而安發表於2021-12-28

圖形學 旋轉與投影矩陣-3

game101 第二次作業; webgl 實現

使用 THREEJS 作為基礎框架,構建各類矩陣,自定義矩陣運算,最終完成

  1. 正確構建模型矩陣
  2. 正確構建透視投影矩陣
  3. 看到變換後的三角形
  4. 按 A 和 D 三角形能夠進行旋轉
  5. 按 Q 和 E 三角形能夠繞任意過原點的向量進行旋轉

最終效果

第1次作業-最終效果

前文簡介

在旋轉與投影矩陣第二篇中,詳細解釋了三維變換的原理,詳細解釋了檢視矩陣,規範立方體,投影矩陣,透視投影矩陣引數的概念。三維變換分為縮放變換,平移變換和旋轉變換;檢視矩陣描述了當前相機的位置和旋轉角度得分復原;規範立方體描述了在預設情況下,元素應該處於 [-1. 1]^3 立方體內;投影矩陣描述了將人眼視角轉換為標準相機視角,位於原點,lookAt 為 (0, 0, -1),up 為 (0, 1, 0);透視投影引數描述了已知 fov,aspct,near,far 從而推導其他的引數。

理論基礎均已經講解完畢,本章主要講程式碼實現,列出過程量

構建透視投影矩陣

構建透視投影矩陣需要四個引數,fov,aspct,near,far,根據四個引數推導 top,right,bottom,left。將透視相機轉換為標準相機需要兩步:

  1. 將透視投影轉換為正交投影,得到轉換矩陣
  2. 將正交投影轉換為標準相機,得到正交相機矩陣

\[M_{persp} =M_{ortho} \times M_{persp \rightarrow ortho} \]

程式碼實現

// 構建一個透視投影矩陣,
function perspMatrix(fov, aspect, near, far){
    fov = Math.PI*fov/180;
    const top = Math.tan(fov/2) * Math.abs(near);
    const right = aspect * top;
    const bottom = -top;
    const left = -right;

    const ortho = new THREE.Matrix4()
        .scale(new THREE.Vector3(2/(right-left), 2/(top-bottom), 2/(near-far)))
        .multiply(new THREE.Matrix4().setPosition(-(right+left)/2, -(top+bottom)/2, -(near+far)/2));

    const perseToOrtho = new THREE.Matrix4().set(
        near, 0, 0, 0,
        0, near, 0, 0,
        0, 0, near+far, -1*near*far,
        0, 0,1, 0
    )
    return ortho.clone().multiply(perseToOrtho);
}

設定 fov 為 90,aspect 為 1,near 為 -3,far 為 -9 的結果

const per = perspMatrix(90, 1, -3, -9);

\[M_{persp}= \left[ \begin{matrix} -1 & 0 & 0 & 0 \\ 0 & -1 & 0 & 0 \\ 0 & 0 & -2 & -9 \\ 0 & 0 & 1 & 0 \end{matrix} \right] \]

繞任意向量的旋轉變換矩陣

繞任意過原點的軸的旋轉變換矩陣理論基礎如下,該表示繞過原點的 n 向量逆時針旋轉 θ 角度的變換矩陣

\[R(n, \theta) = \cos(\theta) \times I + (1-\cos(\theta)) \times n \times n^T + \sin(\theta) \times \left[ \begin{matrix} 0 & -n_z & n_y \\ n_z & 0 & -n_x \\ -n_y & n_x & 0 \end{matrix} \right] \\ n_x, n_y, n_z \: 分別是向量\: \overrightarrow{n} \: 的x,y,z值 \]

繞過任意點的向量如何計算呢?上篇也介紹過,先移動到原點計算再逆移動回去

程式碼如下

// 繞任意過原點的軸的旋轉變換矩陣
function getAnyRotation(axis, angle){
    const cosTheta = Math.cos(angle);
    const matrix1 = new THREE.Matrix3().multiplyScalar(cosTheta);
    const matrix2 = getSelfMul(axis).multiplyScalar(1-cosTheta);
    const matrix3 = new THREE.Matrix3();
    matrix3.set(
        0, -axis.z, axis.y,
        axis.z, 0, -axis.x,
        -axis.y, axis.x, 0
    );
    matrix3.multiplyScalar(Math.sin(angle));

    const result = matrix3Add( matrix3Add(matrix1,matrix2), matrix3);
    return new THREE.Matrix4().setFromMatrix3(result);
}

設定模型矩陣

三角形的三個頂點座標初始值已經固定,若想讓其旋轉,首要方法就是通過矩陣運算改變頂點座標,改變模型的位置資訊的矩陣叫做模型矩陣,理論基礎如下

\[R_x(\theta)= \left[ \begin{matrix} 1 & 0 & 0 & 0 \\ 0 & \cos(\theta) & -\sin(\theta) & 0 \\ 0 & \sin(\theta) & \cos(\theta) & 0 \\ 0 & 0 & 0 & 1 \end{matrix} \right] \\ \\ R_z(\theta)= \left[ \begin{matrix} \cos(\theta) & -\sin(\theta) & 0 & 0 \\ \sin(\theta) & \cos(\theta) & 0 & 0 \\ 0 & 0 & 1 & 0 \\ 0 & 0 & 0 & 1 \end{matrix} \right] \\ \\ R_y(\theta)= \left[ \begin{matrix} \cos(\theta) & 0 & \sin(\theta) & 0 \\ 0 & 1 & 0 & 0 \\ -\sin(\theta) & 0 & \cos(\theta) & 0 \\ 0 & 0 & 0 & 1 \end{matrix} \right] \]

這裡構造一個繞 z 軸逆時針旋轉的模型矩陣

// 等價於 makeRotationZ  model.makeRotationZ(rotation);
function rotateZ(model, rotate){
    model.set(
        Math.cos(rotate), -Math.sin(rotate), 0, 0,
        Math.sin(rotate), Math.cos(rotate), 0, 0,
        0, 0,1, 0,
        0, 0, 0, 1
    )
}

新增互動事件

呼叫相應函式即可

window.addEventListener('keydown', ev => {
    if(ev.key === "a" ){
        rotation += 0.2;
        rotateZ(model, rotation)
        return;
    }
    if ( ev.key === 'd'){
        rotation -= 0.2;
        rotateZ(model, rotation);
        return;
    }
    if ( ev.key === 'q'){
        angle -= 0.2;
        uniforms.anyRotate.value = getAnyRotation(vec, angle);
        return;
    }
    if ( ev.key === 'e'){
        angle += 0.2;
        uniforms.anyRotate.value = getAnyRotation(vec, angle);
    }
})

總結

至此完成了 games101 的第二次作業,並以 web 端的形式展示出來,算是做了部分創新,有興趣的夥伴可以一起探討,加油

相關文章