計算機圖形學二:觀測變換 Viewing transformation

本章內容:

  1. 三維變換 3D transformations

  2. 羅德里格斯旋轉公式 Rodrigues' rotation formula

  3. 模型變換 modeling tranformation

  4. 視圖變換 camera/view transformation

  5. 投影變換(正交、透視)projection tranformation

  6. 視口變換 viewport tranformation

  7. 變換模型代碼(c++) C++ Code

第二章 視圖、投影變換

上一章節內容:

  • 什麼是變換矩陣

  • 二維空間的變換:旋轉rotation、縮放scale、剪切shear

  • 齊次座標系Homogeneous coordinates

上一章的內容比較基礎,理解難度不大。本章相較於上一章節,難度較大。

這一章內容:

  • 三維變換 3D transformations

    • 羅德里格斯旋转公式 Rodrigues' rotation formula(難點)

  • 觀測變換(視圖變換) Viewing transformation

    • 模型變換 modeling tranformation

    • 攝像機/視圖變換 camera/view transformation

    • 投影變換 projection tranformation

      • 正交變換 Orthographic projection

      • 透視變換 Perspective projection(難點)

    • 視口變換 viewport tranformation

    • 變換模型代碼(c++) C++ Code

2.1 3D變換 3D Transformations

  • 3D座標 = $(x,y,z,1)^T$

  • 3D向量 = $(x,y,z,0)^T$

  • 在齊次座標系中, $\left(\begin{array}{}x\y\z\w\end{array}\right)$代表三維空間中的一個點$\left(\begin{array}{}x/w\y/w\z/w\1\end{array}\right),w\ne0$ .

  • 用4x4的矩陣表示三位仿射變換:

2.1.1 縮放與平移 Scale&Translation

基本與二維的旋轉類似。

  • 縮放 Scale

  • 平移 Translation

2.1.2 拉伸 Shear

可以僅僅進行二維的拉伸:

與2D變換一樣,任何3D變換矩陣都可以用SVD分解為旋轉、縮放和旋轉。任何對稱的3D矩陣都具有旋轉、縮放和逆旋轉的特征值分解。最後,三維旋轉可分解為三維拉伸(Shear)矩陣的乘積。

2.1.3 旋轉 Rotate

比二維旋轉複雜得多,因為可能旋轉的軸更多。但是,我們如果只想繞著某一個軸旋轉,則只會改變另外兩個軸。

2.1.3.1 繞一個軸旋轉

  • 比方說,我們繞z軸旋轉,則只需要改變x和y軸,z軸保持不動(右側是基於齊次座標下的,下面同理):

  • 類似的,繞x軸則只改變y和z:

  • 繞y軸同理:

但是我們發現,y軸 (9),(10) 這裡的有一些奇怪

  • 思考:為什麼繞著y軸旋轉時,方向是負的呢?

  • 提示:x軸叉乘z軸,等於-y。

2.1.3.2 復合旋轉

  • 將 $\mathbf{R}_x(\alpha),\mathbf{R}_y(\alpha),\mathbf{R}_z(\alpha)$ 復合:

  • 即歐拉角 Euler angles。

  • 四元數不在本章節討論範圍。

2.1.3.3 羅德里格斯旋转公式 Rodrigues' rotation formula

  • 公式的作用:把任意的旋轉都寫成矩陣的形式。

  • 繞$n$軸旋轉角度$\alpha$:

  • 其中,$n$軸默認是過圓點$(0,0)$的且是單位向量;$N=n^*$,即$N$是$n$的矩陣形式(需要可以回顧上一章節 1.2.7 的內容)。

  • 推導思路:假設是向量$a$繞著n軸旋轉至**$b$,則將$a$**分解為平行於n軸的分量(發現是不變的)加上垂直於n軸的分量。

  • 詳細推導過程:https://www.bilibili.com/video/BV1Eu411r7GC/

2.2 觀測變換 View Transformation

前面的內容,我們學習了如何使用矩陣變換在2D或3D空間中對物體進行旋轉、平移、縮放和裁切等一系列幾何變換(geo- metric transformations)操作。幾何變換的第二個用途就是用於觀測變換,也就是一種3D到2D的映射。下圖展示了將對象從原始座標轉換到屏幕空間的空間和變換序列:

如圖所示,觀測變換分為四個步驟:

  1. 模型變換 modeling tranformation

  2. 攝像機/視圖變換 camera/view transformation

  3. 投影變換 projection transformation

  4. 視口變換 viewport transformation

提取上面三個步驟的首字母,MVP,也就很容易記住觀測變換的三大步驟了。

接下來,我們依次講解。

2.2.1 模型變換 modeling tranformation

  • 調整場景中模型的位置、大小、旋轉至合適的形態

2.2.2 攝像機/視圖變換 camera/view transformation

  • 我們把計算機圖形想像成一個攝影的過程,攝像機就是鏡頭的中心。

  • 攝像機/視圖變換(下文簡稱攝像機變換)的目的是,獲取所有在攝像機視線範圍的物體與攝像機之間的相對位置。非常直觀的做法就是以攝像機為中心,換一句話說,就是把攝像機調整至世界座標的原點上。怎麼讓攝像機與世界座標原點重合呢?

2.2.2.1 攝像機定義

  • 攝像機的位置 $e$

  • 注視的角度 $g$

  • 攝像機上方向 $t$

  • 通過上面三個定義,即可建立以$e$為原點,基底為 $u,v,w$ 的攝像機座標系(如下圖所示)。

  • $u,v,w$ 分別對應笛卡爾座標系中的 $x,y,z$

  • $g$ 向量對應 $z$ 軸( $w$ 軸)的負方向

  • $e$ 向量對應座標原點

2.2.2.2 將相機座標系移動至原世界座標系的原點

分為兩個步驟:

  1. 將相機移動至原點

  2. 通過旋轉矩陣將座標系重合

第一步非常簡單,只需要將當前相機座標減去相機座標,即: $\left[\begin{array}{cccc}1 & 0 & 0 & -x_e \0 & 1 & 0 & -y_e \0 & 0 & 1 & -z_e \0 & 0 & 0 & 1\end{array}\right]$ .

第二步,由於旋轉變換矩陣是一種正則基矩陣(正交矩陣 The orthogonal matrix),矩陣 $M^T=M^{-1}$ ,所以將單位矩陣$I$旋轉至當前相機狀態的矩陣的逆矩陣則是: $\left[\begin{array}{cccc}x_u & y_u & z_u &0 \x_v & y_v & z_v & 0 \x_w & y_w & z_w & 0 \0 & 0 & 0 & 1\end{array}\right]$ .

所以,定義相機變換矩陣 $M_{cam}$ :

2.2.3 投影變換 projection transformation

https://stackoverflow.com/questions/36573283/from-perspective-picture-to-orthographic-picture


  • 投影變換分為正交投影變換透視投影變換

  • 投影變換,簡單的說就是3D->2D的變換。

2.2.3.1 正交投影變換 The Orthographic Projection Transformation

  • 攝像機在世界座標原點,看向 $-Z$ 軸, 正上方是 $Y$ 軸。

  • 將某可視區域長方體壓縮至 $[-1,1]^3$

  • 定義這個長方體,即攝像機可視空間:

    • x = l ≡ left plane,

    • x = r ≡ right plane,

    • y = b ≡ bottom plane,

    • y = t ≡ top plane,

    • z = n ≡ near plane,

    • z = f ≡ far plane.

  • 則很容易推倒出將長方體變換到中心是$(0,0,0)$ 且所在區域是 $[-1,1]^3$ 的變換矩陣$M_{orth}$:

2.2.3.2 透視投影變換 Perspective Projection

  • 透視和正交的區別(左透視、右正交):

  • 透視投影過程很簡單,我們先將Frustum“壓縮”成Cuboid,也就是先將透視投影轉換為正交投影,然後就可以利用2.2.3.1的知識做投影變換了。

  • 壓縮如下圖所示。我們需要找出“壓縮”這一步的變換矩陣$M_{\text {persp } \rightarrow \text { ortho }}^{(4 \times 4)}$。

求解步驟如下:

    1. 將 $(x,y,z)$ 投影到屏幕上,就變成了 $(x',y',z')$

    2. 原點是視點, $z=-n$ 表示投影平面,利用相似三角形得出投影後的 $x,y$ 座標

  • 現在我們清楚的是,xOy平面的變換過程,但是z怎麼變化尚不清楚。

    1. $x'=\frac{n}{z}x$

    2. $y'=\frac{n}{z}y$

    3. $z'=??$

  • 回顧一個齊次座標的性質

    • $(x,y,z,1)=(kx,ky,kz,k\ne0)$

    • $(x,y,z,1)=(xz,yz,z2,z\ne0)$

    • 例子:$(2,0,0,2)=(1,0,0,1)$,都代表三維空間中的點$(1,0,0)$。

  • 在齊次座標中,我們可以根據上面的推倒,寫出:

  • 我們想要求處上面方程中的變換矩陣 $M_{\text {persp } \rightarrow \text { ortho }}^{(4 \times 4)}$ ,則只需解方程(16)

  • 由於等號右邊第三行未知,我們只能寫出“壓縮”變換矩陣的一部分:

  • 利用透視投影的性質:將Frustum“壓縮”成Cuboid後,n面和f面的$z$座標不變。

  • 我們將 $z=n$ 帶入方程(16)中,得到方程(18)

  • 方程(18),將變換矩陣$M$的第三行單獨拿出來,並且方程右邊的 $n^2$ 肯定與 $x,y$ 無關,所以得到方程(19)

  • 且我們欲求的變換矩陣 $M_{persp \rightarrow ortho}$ 第三行的前兩個數字也確定了

  • 又因為遠平面 $f$ 的中間點 $(0,0,f)$ 經過變換依然是 $(0,0,f)$ ,所以將 $(0,0,f)$ 帶入方程(16),得到方程(20)

  • 方程(19)(20),解得:

  • 則求出“壓縮”變換矩陣 $M_{persp \rightarrow ortho}$ :

  • 所以,透視投影 $M_{persp}=M_{ortho}M_{persp\rightarrow ortho}$

  • 計算最終結果:

2.2.3.3 關於透視矩陣的練習題

  • 問:在Frustum內部,任意一點經過了“壓縮”變換矩陣 $M_{persp \rightarrow ortho}$ 的變換後,會向哪個方向移動?

  • 答:對於Frustum中的任意一點 $\left(\begin{array}{l}x \y \z \1\end{array}\right)$ ,其中 $(n<x<f)$ ,經過“擠壓”變換都有:

  • $M_{persp \rightarrow ortho}\left(\begin{array}{l}x \y \z \1\end{array}\right)=\left(\begin{array}{l}nx \ny \(n+f)z-nf \z\end{array}\right)$

  • 由於齊次座標的性質,上面等價於:

  • $\left(\begin{array}{l}nx/z \ny/z \(n+f)-nf/z \1\end{array}\right)$

  • 現在問題轉換為判斷變化前後 $z$ 分量的大小關係。

  • $\text{let } z=\frac{n+f}{z}$ 經過變化得到點 $z'$

  • 易得 $z'=\frac{n^2+f^2}{n+f}$

  • 使 $z,z'$ 做差,得 $z'-z = \frac{(n-f)^2}{2(n+f)}$

  • 又因為 $0>n>f$

  • 所以 $z'-z<0$ ,所以變化後的點向遠平面靠近(距離屏幕更遠)。

2.2.4 視口變換 viewport transformation

  • 視口(viewport),其實就是攝像機畫面。

  • 做完模型、攝像機、投影變換之後,所有需要的物體都在$[-1,1]^3$這個立方體內了。

  • 我們將$x=-1$放在屏幕的左側,$x=+1$仿仔屏幕的右側,$y=-1$放在屏幕的底部,$y=+1$放在屏幕的頂部。

  • 屏幕上每一個像素都“擁有”1個以整數座標為中心的單位正方形。在渲染的時候,需要將 $[-1,1]^2$ 映射到 $[-0.5,n_x-0.5]*[-0.5,n_y-0.5]$ ,公式如下:

  • 如果需要將 $[-1,1]^2$ 映射到 $[0,n_x]*[0,n_y]$ ,則公式如下:

  • $z$ 方向沒有任何改變

2.2.5 觀測變換矩陣 $M$

  • 模型、相機、投影、視口

2.2.5 變換模型代碼(c++) C++ Code(附)

  1. 模型變換 Modeling Transformation

這裡的3D變換是繞 $z$ 軸旋轉,即變換矩陣(6)

直接通過3D變換就可以寫出get_model_matrix()模型變換函數。

需要注意:

  • 函數傳入的是角度制,需要先將角度轉換為弧度。

Eigen::Matrix4f get_model_matrix(float rotation_angle)
{
    Eigen::Matrix4f model = Eigen::Matrix4f::Identity();
    auto radian = (float)(rotation_angle/180.0*MY_PI);
    Eigen::Matrix4f translate;
    translate << cos(radian),-sin(radian),0,0,
                 sin(radian),cos(radian),0,0,
                 0,0,1,0,
                 0,0,0,1;
    model = translate * model;
    return model;
}
  1. 攝像機/視圖變換 camera/view transformation

默認相機的朝向一定正確,只需要調整當前攝像機的位置向量eye_pos至世界座標的原點即可。

Eigen::Matrix4f get_view_matrix(Eigen::Vector3f eye_pos)
{
    Eigen::Matrix4f view = Eigen::Matrix4f::Identity();
    Eigen::Matrix4f translate;
    translate << 1, 0, 0, -eye_pos[0],
                 0, 1, 0, -eye_pos[1],
                 0, 0, 1,  eye_pos[2],
                 0, 0, 0, 1;
    view = translate * view;
    return view;
}
  1. 投影透視變換 Perspective Projection

本函數需要寫出兩個矩陣:

Eigen::Matrix4f get_projection_matrix(float eye_fov, float aspect_ratio, float zNear, float zFar){
    Eigen::Matrix4f projection = Eigen::Matrix4f::Identity();
    Eigen::Matrix4f M_p2o = Eigen::Matrix4f ::Identity();
    M_p2o <<    zNear,0,0,0,
                0,zNear,0,0,
                0,0,zNear+zFar,-zNear*zFar,
                0,0,1,0;
    Eigen::Matrix4f M_orth = Eigen::Matrix4f ::Identity();
    float fov = 0.5*eye_fov*MY_PI/180;
    float top = tan(fov) * zNear;
    float bottom = -top;
    float right = top * aspect_ratio;
    float left = -right;
    M_orth <<   2/(right-left),0,0,-(right+left)/(right-left),
                0,2/(top-bottom),0,-(top+bottom)/(top-bottom),
                0,0,2/(zNear-zFar),-(zNear+zFar)/(zNear-zFar),
                0,0,0,1;
    projection = M_orth * M_p2o;
    return projection;
}
  1. 羅德里格斯旋轉公式代碼實現

實現以下公式即可。

Eigen::Matrix4f get_rotation(Vector3f axis, float angle){
    Eigen::Matrix4f M_rodrigues = Eigen::Matrix4f::Identity();
    Eigen::Matrix3f M_temp_rodrigues = Eigen::Matrix3f::Identity();
    Eigen::Matrix3f M_I = Eigen::Matrix3f::Identity();
    Eigen::Matrix3f M_N;
    M_N <<  0, -axis[2], axis[1],
            axis[2], 0, -axis[0],
            -axis[1], axis[0], 0;
    auto radian = (float)(angle/180.0*MY_PI);
    M_temp_rodrigues << cos(radian)*M_I+(1-cos(radian))*axis*axis.transpose()+sin(radian)*M_N;
    M_rodrigues <<  M_temp_rodrigues(0,0), M_temp_rodrigues(0,1), M_temp_rodrigues(0,2), 0,
            M_temp_rodrigues(1,0), M_temp_rodrigues(1,1), M_temp_rodrigues(1,2), 0,
            M_temp_rodrigues(2,0), M_temp_rodrigues(2,1), M_temp_rodrigues(2,2), 0,
            0, 0, 0, 1;
    return M_rodrigues;
}

Reference

[1] Fundamentals of Computer Graphics 4th

[2] GAMES101 Lingqi Yan

最后更新于