OpenGL 程式設計/3D/數學
許多關於我們做事方式的約定是任意的。例如,在道路的左側行駛並不比在右側行駛好或壞。但是,如果同一條道路的使用者不同意都在同一邊行駛,那麼結果就會很糟糕。
計算機圖形也是如此。例如,座標系可以是右手或左手;如果你想象將你的眼睛放在 (0, 0, 0) 點並依次沿著正 X 軸、正 Y 軸和正 Z 軸的方向看,如果你的視線描述了一個順時針旋轉,那麼座標系就是右手座標系,而逆時針則意味著它是左手座標系。記住哪一個是哪一個的方法是將你的拇指、食指和中指伸出成直角,將拇指方向稱為正 X,食指指向正 Y,中指指向正 Z;如果你用右手這樣做,那麼座標系(自然地)就是右手座標系;而你的左手定義了一個左手座標系。
建議的約定(在大多數 3D 軟體中使用)是定義你的模型/場景為右手座標系。
在計算機圖形中,通常會使用多個不同的座標系。例如,汽車模型是根據其自己的模型座標系定義的。要將汽車放置在場景中,也許是在道路上移動,需要將這些模型座標轉換為場景的世界座標。然後,“相機”(實際上是觀看場景的人的眼睛位置的表示)的放置就涉及到將這些世界座標(透過檢視變換)轉換為眼睛座標,這些座標相對於觀察者定義,使得 X 軸是水平的並且向右增加,Y 軸是垂直的並且向上增加,Z 軸是水平的並且直接向觀察者增加。最後,它們被轉換為規範化裝置座標並對映到使用者顯示器上的畫素。
並且只是為了增加樂趣,汽車模型本身可能有多個座標系。例如,每個車輪都可以在它自己的子座標系中定義,在該座標系中,它相對於其父級(即汽車的車身)旋轉。因為車輪相對於汽車進行變換,所以它會自動獲得汽車的變換,因此透過場景移動汽車就足以使車輪一起移動,它們不需要單獨重新定位。
因此,變換流水線(就眼睛座標而言)看起來像這樣(其中方括號中的步驟是變換,而沒有表示座標值)
model coords → [parent xform 1] → ... → [parent xform n] → [viewing xform] → eye coords
然後眼睛座標進一步轉換如下
eye coords → [projection xform] → clip coords → [÷ w] → normalized dev coords → [viewport xform] → window coords
我之前說過,建議使用右手座標系。這在眼睛座標階段之前是正確的。投影變換通常會翻轉 Z 值以使座標系成為左手座標系,這樣 Z 值現在就會遠離觀察者增加,而不是朝向他們增加。這樣做是為了使 Z 值能夠轉換為深度緩衝區中儲存的值,其中越來越大的值表示越來越大的深度。
眼睛座標的標準範圍是每個軸上的 [-1 .. +1] 區間。當然,你可以使用計算機浮點表示形式能夠處理的任何數字定義你的模型和場景,並且你可以隨意命名單位 - 米、英尺、微米、光年,無論什麼。你所要做的就是確保所有模型和檢視變換的組合將你想要看到的場景部分帶入那個 [-1 .. +1] 範圍的眼睛座標值內。
然後,規範化裝置座標只是將這些眼睛座標對映到 (x, y) = (-1, -1) 在檢視區域(視窗、全屏,無論什麼)的左下角,而 (x, y) = (+1, +1) 在右上角(z 值,當然,最終會進入深度緩衝區)。最終的視口變換將這些重新對映到實際畫素的單位中,這些畫素對應於你的檢視區域的實際大小。
應用於計算機圖形的普通變換型別(也是 OpenGL 支援的唯一型別)稱為線性變換。這是因為在變換之前是直線的直線在變換後仍然是直線。這種變換可以方便地用矩陣來表示。一個 4×4 矩陣可以表示三維空間中任何可能的線性變換。
多個變換可以透過乘法它們各自的矩陣來組合(“連線”)。與傳統標量數字的乘法不同,變換的順序很重要:縮放後進行平移(重新定位)與先進行平移不同,因為縮放是在不同的中心點進行的。
你會經常看到三維位置向量寫成 (x, y, z) 而不是 (x, y, z, w)。相應的變換矩陣是 4×4 而不是 3×3。為什麼呢?
這是為了使所有線性變換都可以在矩陣乘法的形式下統一表示。否則,平移將不得不作為加法步驟單獨分離出來
(x', y', z') = [scaling+rotation+shear] × (x, y, z) + [translation]
使用齊次座標,所有內容都可以合併到一個矩陣中
(x', y', z', w') = [scaling+rotation+shear+translation] × (x, y, z, w)
通常將w 設定為 1.0,至少在開始時是這樣。大多數變換將產生w = 1.0 的向量,如果它們一開始就是這樣給出的。但是,注意產生w ≠ 1.0 的向量的變換(特別是透視變換),如果你想對它們分別進行任何計算,你必須歸一化x、y 和z 值(用w 除它們),否則你可能會得到錯誤的結果!
這裡我們又遇到了需要選擇一個約定並堅持下去的情況。用變換M 將向量V 變換為向量V' 可以寫成列向量的前乘
V' = M × V
或行向量的後乘
V' = V × M
建議你堅持使用前乘約定,因為這與 OpenGL 的工作方式更一致。例如,在(OpenGL 3.0 之前的)固定功能流水線中,呼叫的順序(在虛擬碼中)將是
apply matrix M; draw vector V
這對應於前乘公式中的從左到右的順序。如果M 分解為單獨的組成部分,例如M1 × M2,那麼前乘公式變為
V' = M1 × M2 × V
相應的固定功能呼叫是
apply matrix M1; concatenate matrix M2; draw vector V