1. 程式人生 > >投影矩陣的推導(Deriving Projection Matrices)

投影矩陣的推導(Deriving Projection Matrices)

  本文乃<投影矩陣的推導>譯文,原文地址為:

        譯者: 流星上的瀦

        如需轉載,請註明出處,感謝!

        在3D圖形程式的基本矩陣變換中,投影矩陣是其中比較複雜的。平移和縮放瀏覽一下就能理解,旋轉矩陣只要掌握了三角函式知識也可以理解,但投影矩陣有點棘手。如果你曾經看過投影矩陣,你會發現你的常識不足以告訴你它是怎麼來的。而且,我在網上還未看到許多關於如何推導投影矩陣的教程資源。本文的話題就是如何推導投影矩陣。

        對於剛剛開始接觸3D圖形的人,我應該指出,理解投影矩陣如何推導可能是我們對於數學的好奇心,它不是必須的。你可以只用公式,並且如果你用像Direct3D那樣的圖形API,你甚至都不需要使用公式,圖形API會為你構建一個投影矩陣。所以,如果本文看起來有點難,不要害怕。只要你理解了投影矩陣做了什麼,你沒必要在你不想的情況下關注它是怎麼做的。本文是給那些想了解更多的程式設計師的。

        概述: 什麼是投影?

        計算機顯示器是一個二維表面,所以如果你想顯示三維影象,你需要一種方法把3D幾何體轉換成一種可作為二維影象渲染的形式。那也正是投影做的。拿一個簡單的例子來說,一種把3D物件投影到2D表面的方法是簡單的把每個座標點的z座標丟棄。對立方體來說,看上去可能像圖1:

圖1: 通過丟棄Z座標投影到XY平面

        當然,這過於簡單,並且在大多數情況下不是特別有用。首先,根本不會投影到一個平面上;相反,投影公式將變換你的幾何體到一個新的空間體中,稱為規範視域體(canonical view volume),規範視域體的精確座標可能在不同的圖形API之間互不相同,但作為討論起見,把它認為是從(-1, -1, 0)延伸至(1, 1, 1)的盒子,這也是Direct3D中使用的。一旦所有頂點被對映到規範視域體,只有它們的x和y座標被用於對映到螢幕上。這並不代表z座標是無用的,它通常被深度緩衝用於可見度測試。這就是為什麼變換到一個新的空間體中,而不是投影到一個平面上。

        注意,圖1描述的是左手座標系,攝像機俯視z軸正方向,y軸朝上並且x軸朝右。這是Direct3D中使用的座標系,本文中我都將使用該座標系。對於右手座標系系統來說,在計算方面沒有明顯差異,在規範視域體方面有一點區別,所以一切討論仍將適用即使你的圖形API使用與Direct3D不同的規定。

現在,可以進入實際的投影變換了。有許多投影方法,我將介紹最常見的2種:正交和透視。

        正交投影(Orthographic Projection)

        正交投影,之所以這麼稱呼是因為所有的投影線都與最終的繪圖表面垂直,是一種相對簡單的投影技術。視域體,也就是包含所有你想顯示的幾何體的可視空間——是一個將被變換到規範視域體的軸對齊盒子,見圖2:

圖2: 正交投影

        正如你看見的,視域體由6個面定義:

        

        因為視域體和規範視域體都是軸對齊盒子,這種型別的投影沒有距離更正。最終的結果是,事實上,很像圖1那樣每個座標點只是丟棄了z座標。物件在3D空間中的大小和在投影中的大小相同,即使一個物件比另一個物件距離攝像機遠很多。在3D空間中平行的直線在最終的影象上也是平行的。使用這種型別的投影將出現一些問題像第一人稱射擊遊戲——試想一下在不知道任何東西有多遠的情況下玩!但它也有它的用處。你可能在格子游戲中使用它,例如,特別是攝像機被繫結在一個固定角度的一款格子游戲中,圖3顯示了1個簡單的例子:

圖3: 正交投影的一個簡單例子

        所以,事不宜遲,現在開始弄清楚它是如何工作的。最簡單的方法可能是3個座標軸分開考慮,並且計算如何沿著每個座標軸將點從視域體對映到規範視域體。從x軸開始,視域體中的點的x座標範圍在[l, r],想把它變換到範圍在[-1, 1]:

        

        現在,準備把範圍縮小到我們期望的,各項減去l,這樣,最左邊的項變為0。另一種可能考慮的做法是平移範圍使其以0為中心,而不是一端為0,但現在這種方式代數式更整潔,所以為了可讀性起見我將以現在這種方式做:

        

        現在,範圍的一端是0,你可以縮小到期望的大小。你期望x值的範圍是2個單位寬,從1到-1,所以把各項乘以2/(r-l)。注意r-l是視域體的寬度,因此始終是一個正數,所以不用擔心不等號會改變方向:

        

        下一步,各項減去1就產生了我們期望的範圍[-1,1]:

        

        基本代數允許我們將中間項寫成一個單一的分數:

        

        最後,把中間項分成兩部分使它形如px+q的形式,我們需要把項組織成這種形式這樣我們推導的公式就可以簡單的轉換成矩陣形式:

        

        這個不等式的中間項告訴了我們把x轉換到規範視域體的公式:

        

        獲取y的變換公式的步驟是完全一樣的——只要用y替代x,用t替代r,用b替代l——所以這裡不重複它們了,只是給出結果:

        

        最後,需要推倒z的變換公式。z的推導有點不同,因為需要把z對映到範圍[0, 1]而不是[-1, 1],但看上去很相似。z座標最開始在範圍[n,f]:

        

        把各項減去n,這樣的話範圍的下限就變為了0:

        

        現在剩餘要做的就是除以f-n,這樣就產生了最終的範圍[0,1]。和前面相同,注意f-n是視域體的深度所以絕對不會為負:

        

        最後,把它分成兩部分使它形如px+q的形式:

        

        這樣便給出了z的變換公式

        

        現在,可以準備寫正交投影矩陣了。總結到目前為止的工作,推導了3個投影公式:

        

        如果寫成矩陣形式,就得到了:

        

        就是這樣!Direct3D提供了D3DXMatrixOrthoOffCenterLH()(what a mouthful!)方法構造一個和這個公式相同的正交投影矩陣;你可以在DirectX文件中找到。方法名中的"LH"代表了你正在使用左手座標系。但是,究竟"OffCenter"的意思是什麼呢?

        這一問題的答案引導你到一個正交投影矩陣的簡化形式。考慮幾點: 首先,在可見空間中,攝像機定位在原點並且沿著z軸方向觀看。第二,你通常希望你的視野在左右方向上延伸的同樣遠,並且在z軸的上下方向上也延伸的同樣遠。如果是這樣的情況,那麼z軸正好直接穿過你視域體的的中心,所以得到了r = -l並且t = -b。換句話說,你可以把r, l, t和b一起忘掉,簡單的把視域體定義為1個寬度w和1個高度h,以及裁剪面f和n。如果你在正交投影矩陣中應用上面說的,那麼你將得到這個相當簡化的版本:

        

        這個公式是Direct3D中D3DXMatrixOrthoLH()方法的實現。你幾乎可以一直使用這個矩陣替代上面那個你推導的更通用的"OffCenter"版本,除非你用投影做些奇怪的事情。

        在完成這部分之前還有一點。它啟發我們注意到這個矩陣可以用兩個簡單的變換串聯替代:平移其次是縮放。如果你思考幾何的話這對你是有意義的,因為所有你在正交投影中做的就是從一個軸對齊盒子轉向另一個軸對齊盒子;視域體不改變它的形狀,只改變它的位置和大小。具體來說,有:

        

        這種投影方式可能更直觀一點因為它讓你更容易想象發生了什麼。首先,視域體沿著z軸平移使它的近平面和原點重合;然後,應用一個縮放把它縮小到規範視域體大小。很容易理解吧,對不對?一個偏離中心(OffCenter)的正交投影矩陣也可以用一個變換和一個縮放代替,它和上面的結果很相似所以我在這裡不列出了。

        上面就是正交投影,現在可以去接觸一些更有挑戰性的東西了。

        透視投影(Perspective Projection)

        透視投影是稍複雜的一種投影方法,並且用的越來越平凡,因為它創造了距離感,因此會生成更逼真的影象。從幾何上說,這種方法與正交投影不同的地方在於透視投影的視域體是一個平截頭體——也就是,一個截斷的金字塔,而不是一個軸對稱盒子。見圖4:

圖4: 透視投影

        正如你所看見的,視域體的近平面從(l,b, n)延伸至(r, t, n)。遠平面範圍是從原點發射穿過近平面四個點的射線直至與平面z=f相交。由於視域體從原點進一步延伸,它變得越來越寬大;同時你將這個形狀變換到規範視域體盒子;視域體的遠端比視域體的近端壓縮的更厲害。因此,視域體遠端的物體會變得更小,這就給了你距離感。

        由於空間體形狀的這種變換,透視投影不能像正交投影那樣簡單的表達為一個平移和一個縮放。你必須制定一些不同的東西。但是,這並不意味著你在正交投影上做的工作是無用的。一個方便的解決數學問題的方法是把問題減少到你已經知道怎麼解決的那一個。所以,這就是你在這裡可以做的。上一次,你一次檢查一個座標,但這次,你將把x和y座標合起來一起做,然後再考慮z座標。你對x和y的處理可以分2個步驟:

        第1步: 給定視域體中的點(x,y, z),把它投影到近平面z=n。由於投影點在近平面上,所以它的x座標範圍在[l, r],y座標範圍在[b, t]。

        第2步: 使用你在正交投影中學會推導的公式,把x座標從[l, r]對映到[-1, 1],把y座標範圍從[b, t]對映到[-1, 1]。

        聽上去很棒吧?看一看圖5:

圖5: 使用相似三角形投影一個點到z=n平面

        在這個圖中,你從點(x, y, z)到原點畫了條直線,注意直線與z=n平面相交的那個點——用黑色標記的那個。通過這些點,你畫了2條相對於z軸的垂線,突然你得到了一對相似三角形。如果你能夠回想起高中的幾何知識,相似三角形是擁有相同形狀但大小不一定相同的三角形。為了證明2個三角形是相似的,必須證明它們的同位角相等,在這裡不難做到。角1被兩個三角形共享,顯然它和自身相等。角2和角3是穿越兩條平行線形成的同位角,所以它們是相等的。同時,直角當然是彼此相等的,所以兩個三角形是相似的。

        對於相似三角形你應該感興趣的是它們的每對對應邊都是同比例的。你知道沿著z軸的邊的長度,它們是n和z。那意味著其他對應邊的比例也是n/z。所以,考慮下你知道了什麼。根據勾股定理,從(x, y, z)相對於z軸做的垂線具有以下長度:

        

        如果你知道了從你的投影點到z軸的垂線的長度,那麼你就可以計算出該點的x和y座標。長度怎麼求?那太簡單了!因為你有了相似三角形,所以長度就是簡單的L乘以n/z:

        

         因此,x座標是x * n/z,y座標是y * n/z。第一步做完了。

        第二步只是簡單的執行你上一部分做的同樣的對映,所以是時候回顧下你在正交投影中學習到的推導公式了。回想下把x和y座標對映到規範視域體,像這樣:

        

        現在你可以再次呼叫這些公式,除非你要考慮到投影;所以,把x用x * n/z代替,把y用y * n/z代替:

        

        現在,通過乘以z:

        

        這些結果有點奇怪。為了把這些等式寫進矩陣,你需要把它們寫成這種形式:

        

        但很明顯,現在還做不到,所以現在看起來進入了僵局。應該做什麼呢?如果你能找到個辦法獲得z'z的公式就像x'z和y'z那樣,你就可以寫一個變換矩陣把(x, y, z)對映到(x'z, y'z, z'z)。然後,你只需要把各部分除以點z,你就會得到你想要的(x', y', z')。

        因為你知道z到z'的轉換不依賴於x和y,你知道你想要一個公式形如z'z= pz + q,p和q是常量。並且,你可以很容易的找到那些常量,因為你知道在兩種特殊情況下如何得到z': 因為你要把[n, f]對映到[0, 1],你知道當z=n時z'=0,和z=f時z'=1。當你把第一組值代入z'z = pz + q,你可以解得:

        

        現在,把第二組值代入,得到:

        

        把q的值代入等式,你可以很容易的解得p:

        

        現在你有p的值了,並且剛剛你求得了q= –pn,所以你可以解得q:

        

        最後,把p和q的表示式代入最原始的公式中,得:

        

        你就快完成了,但是你處理這個問題的不尋常的性質需要你也處理齊次座標w。通常情況下,只是簡單的設定w' = 1 ——你可能已經注意到在一個基本的變換下最後一行總是[0, 0, 0, 1]---但是現在你在為點(x'z, y'z, z'z, w'z)寫一個變換。所以取而代之的,把w' = 1寫成w'z = z。因此最後用於透視投影的等式如下:

        

        現在,當你把這個等式寫成矩陣的形式,得到:

        當你把這個矩陣用於點(x, y, z,1),它將產生(x'z, y'z, z'z, w'z)。然後,你應用通常的步驟去除以齊次座標,得到(x', y', z', 1)。那就是透視投影。Direct3D的D3DXMatrixPerspectiveOffCenterLH()方法也實現了上述公式。正如正交投影,如果你假設視域體是對稱的並且中心是z軸(也就是r = -l,t = -b),你可以簡單的用視域體的寬w和高h改寫矩陣中的各項:

        Direct3D的D3DXMatrixPerspectiveLH()方法也生成這個矩陣。

        最後,還有個經常用的上的透視投影的表示。在這種表示中,你根據攝像機的可視範圍定義視域體,而不用去擔心視域體的尺寸。此概念參閱圖6:

圖6: 視域體的高由垂直可視範圍的角度a定義

        垂直可視範圍的角度是a。這個角度被z軸一分為二,所以根據基本的三角函式,你可以寫下面的方程,關聯a和近平面n以及螢幕高度h:

        

        這個表示式可以取代投影矩陣中的高度。此外,使用橫縱比r代替寬度,r定義為顯示區域的寬比高的橫縱比。所以,得到:

        

        因此,有了用垂直可視範圍角度a和橫縱比r構成的透視投影矩陣:

        在Direct3D中,你可以使用D3DXMatrixPerspectiveFovLH()方法得到這種形式的矩陣。這種形式特別有用,因為你可以直接把r設定成渲染視窗的橫縱比,並且可視範圍角度為p / 4比較好。所以,你真正需要擔心的事情只是定義視域體沿著z軸的範圍。

        總結

        這就是所有的你需要的投影變換背後的數學概念。還有一些其他的不太常用的投影方法,並且如果你使用右手座標系或者一個不同的規範視域體就會和我們討論的有點不同,但是以本文的結論作為基礎你應該很容易能夠推匯出那些公式。如果你想知道更多的關於投影或者其他變換的資訊,看一看Tomas Moller和Eric Haines的Real-Time Rendering,或者James D. Foley, Andries van Dam, Steven K. Feiner和John F.Hughes的Computer Graphics: Principles and Practice;這兩本是優秀的關於計算機圖形的書。

        如果你對本文有任何問題,或者需要指出任何需要更正的地方,你可以通過CodeGuru論壇聯絡我,我的名字是Smasher/Devourer。

        Happy coding!