天天看點

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

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

        http://www.codeguru.com/cpp/misc/misc/math/article.php/c10123__1/Deriving-Projection-Matrices.htm,由于本人能力有限,有譯的不明白的地方大家可以參考原文,謝謝^-^!

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

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

        概述: 什麼是投影?

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

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

圖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:

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

圖2: 正交投影

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

        就是這樣!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。如果你在正交投影矩陣中應用上面說的,那麼你将得到這個相當簡化的版本:

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

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

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

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

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

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

        透視投影(Perspective Projection)

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

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

圖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:

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

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

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

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

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

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

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

         是以,x坐标是x * n/z,y坐标是y * n/z。第一步做完了。

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

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

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

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

        現在,通過乘以z:

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

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

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

        但很明顯,現在還做不到,是以現在看起來進入了僵局。應該做什麼呢?如果你能找到個辦法獲得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,你可以解得:

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

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

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

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

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

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

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

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

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

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

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

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

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

        當你把這個矩陣用于點(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改寫矩陣中的各項:

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

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

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

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

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

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

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

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

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

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

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

        在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!

        譯者: 流星上的潴

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

繼續閱讀