|
-Twinsen編寫 透視投影是3D固定流水線的重要組成部分,是將相機(jī)空間中的點從視錐體(frustum)變換到規(guī)則觀察體(Canonical View Volume)中,待裁剪完畢后進(jìn)行透視除法的行為。在算法中它是通過透視矩陣乘法和透視除法兩步完成的。
透視投影變換是令很多剛剛進(jìn)入3D圖形領(lǐng)域的開發(fā)人員感到迷惑乃至神秘的一個圖形技術(shù)。其中的理解困難在于步驟繁瑣,對一些基礎(chǔ)知識過分依賴,一旦對它們中的任何地方感到陌生,立刻導(dǎo)致理解停止不前。
沒錯,主流的3D APIs如OpenGL、D3D的確把具體的透視投影細(xì)節(jié)封裝起來,比如 gluPerspective(…)就可以根據(jù)輸入生成一個透視投影矩陣。而且在大多數(shù)情況下不需要了解具體的內(nèi)幕算法也可以完成任務(wù)。但是你不覺得,如果想要成為一個職業(yè)的圖形程序員或游戲開發(fā)者,就應(yīng)該真正降伏透視投影這個家伙么?我們先從必需的基礎(chǔ)知識著手,一步一步深入下去(這些知識在很多地方可以單獨找到,但我從來沒有在同一個地方全部找到,但是你現(xiàn)在找到了J)。
我們首先介紹兩個必須掌握的知識。有了它們,我們才不至于在理解透視投影變換的過程中迷失方向(這里會使用到向量幾何、矩陣的部分知識,如果你對此不是很熟悉,可以參考《向量幾何在游戲編程中的使用》系列文章)。
齊次坐標(biāo)表示
透視投影變換是在齊次坐標(biāo)下進(jìn)行的,而齊次坐標(biāo)本身就是一個令人迷惑的概念,這里我們先把它理解清楚。
根據(jù)《向量幾何在游戲編程中的使用6》中關(guān)于基的概念。對于一個向量v以及基oabc,
可以找到一組坐標(biāo)(v1,v2,v3),使得
v = v1 a + v2 b + v3 c (1)
而對于一個點p,則可以找到一組坐標(biāo)(p1,p2,p3),使得
p – o = p1 a + p2 b + p3 c (2)
從上面對向量和點的表達(dá),我們可以看出為了在坐標(biāo)系中表示一個點(如p),我們把點的位置看作是對這個基的原點o所進(jìn)行的一個位移,即一個向量——p – o(有的書中把這樣的向量叫做位置向量——起始于坐標(biāo)原點的特殊向量),我們在表達(dá)這個向量的同時用等價的方式表達(dá)出了點p:
p = o + p1 a + p2 b + p3 c (3)
(1)(3)是坐標(biāo)系下表達(dá)一個向量和點的不同表達(dá)方式。這里可以看出,雖然都是用代數(shù)分量的形式表達(dá)向量和點,但表達(dá)一個點比一個向量需要額外的信息。如果我寫出一個代數(shù)分量表達(dá)(1, 4, 7),誰知道它是個向量還是個點!
我們現(xiàn)在把(1)(3)寫成矩陣的形式:
“齊次坐標(biāo)表示是計算機(jī)圖形學(xué)的重要手段之一,它既能夠用來明確區(qū)分向量和點,同時也更易用于進(jìn)行仿射(線性)幾何變換。”—— F.S. Hill, JR
這樣,上面的(1, 4, 7)如果寫成(1,4,7,0),它就是個向量;如果是(1,4,7,1),它就是個點。
從普通坐標(biāo)轉(zhuǎn)換成齊次坐標(biāo)時, 如果(x,y,z)是個點,則變?yōu)?x,y,z,1); 如果(x,y,z)是個向量,則變?yōu)?x,y,z,0)
從齊次坐標(biāo)轉(zhuǎn)換成普通坐標(biāo)時, 如果是(x,y,z,1),則知道它是個點,變成(x,y,z); 如果是(x,y,z,0),則知道它是個向量,仍然變成(x,y,z)
以上是通過齊次坐標(biāo)來區(qū)分向量和點的方式。從中可以思考得知,對于平移T、旋轉(zhuǎn)R、縮放S這3個最常見的仿射變換,平移變換只對于點才有意義,因為普通向量沒有位置概念,只有大小和方向,這可以通過下面的式子清楚地看出:
而旋轉(zhuǎn)和縮放對于向量和點都有意義,你可以用類似上面齊次表示來檢測。從中可以看出,齊次坐標(biāo)用于仿射變換非常方便。
此外,對于一個普通坐標(biāo)的點P=(Px, Py, Pz),有對應(yīng)的一族齊次坐標(biāo)(wPx, wPy, wPz, w),其中w不等于零。比如,P(1, 4, 7)的齊次坐標(biāo)有(1, 4, 7, 1)、(2, 8, 14, 2)、(-0.1, -0.4, -0.7, -0.1)等等。因此,如果把一個點從普通坐標(biāo)變成齊次坐標(biāo),給x,y,z乘上同一個非零數(shù)w,然后增加第4個分量w;如果把一個齊次坐標(biāo)轉(zhuǎn)換成普通坐標(biāo),把前三個坐標(biāo)同時除以第4個坐標(biāo),然后去掉第4個分量。
由于齊次坐標(biāo)使用了4個分量來表達(dá)3D概念,使得平移變換可以使用矩陣進(jìn)行,從而如F.S. Hill, JR所說,仿射(線性)變換的進(jìn)行更加方便。由于圖形硬件已經(jīng)普遍地支持齊次坐標(biāo)與矩陣乘法,因此更加促進(jìn)了齊次坐標(biāo)使用,使得它似乎成為圖形學(xué)中的一個標(biāo)準(zhǔn)。
簡單的線性插值
這是在圖形學(xué)中普遍使用的基本技巧,我們在很多地方都會用到,比如2D位圖的放大、縮小,Tweening變換,以及我們即將看到的透視投影變換等等?;舅枷胧牵航o一個x屬于[a, b],找到y(tǒng)屬于[c, d],使得x與a的距離比上ab長度所得到的比例,等于y與c的距離比上cd長度所得到的比例,用數(shù)學(xué)表達(dá)式描述很容易理解:
這樣,從a到b的每一個點都與c到d上的唯一一個點對應(yīng)。有一個x,就可以求得一個y。 此外,如果x不在[a, b]內(nèi),比如x < a或者x > b,則得到的y也是符合y < c或者y > d,比例仍然不變,插值同樣適用。
透視投影變換
好,有了上面兩個理論知識,我們開始分析這次的主角——透視投影變換。這里我們選擇OpenGL的透視投影變換進(jìn)行分析,其他的APIs會存在一些差異,但主體思想是相似的,可以類似地推導(dǎo)。經(jīng)過相機(jī)矩陣的變換,頂點被變換到了相機(jī)空間。這個時候的多邊形也許會被視錐體裁剪,但在這個不規(guī)則的體中進(jìn)行裁剪并非那么容易的事情,所以經(jīng)過圖形學(xué)前輩們的精心分析,裁剪被安排到規(guī)則觀察體(Canonical View Volume, CVV)中進(jìn)行,CVV是一個正方體,x, y, z的范圍都是[-1,1],多邊形裁剪就是用這個規(guī)則體完成的。所以,事實上是透視投影變換由兩步組成:
1) 用透視變換矩陣把頂點從視錐體中變換到裁剪空間的CVV中。 2) CVV裁剪完成后進(jìn)行透視除法(一會進(jìn)行解釋)。
我們一步一步來,我們先從一個方向考察投影關(guān)系。
同理,有
這樣,我們便得到了P投影后的點P’
從上面可以看出,投影的結(jié)果z’始終等于-N,在投影面上。實際上,z’對于投影后的P’已經(jīng)沒有意義了,這個信息點已經(jīng)沒用了。但對于3D圖形管線來說,為了便于進(jìn)行后面的片元操作,例如z緩沖消隱算法,有必要把投影之前的z保存下來,方便后面使用。因此,我們利用這個沒用的信息點存儲z,處理成:
這個形式最大化地使用了3個信息點,達(dá)到了最原始的投影變換的目的,但是它太直白了,有一點蠻干的意味,我感覺我們最終的結(jié)果不應(yīng)該是它,你說呢?我們開始結(jié)合CVV進(jìn)行思考,把它寫得在數(shù)學(xué)上更優(yōu)雅一致,更易于程序處理。假入能夠把上面寫成這個形式:
那么我們就可以非常方便的用矩陣以及齊次坐標(biāo)理論來表達(dá)投影變換:
其中
哈,看到了齊次坐標(biāo)的使用,這對于你來說已經(jīng)不陌生了吧?這個新的形式不僅達(dá)到了上面原始投影變換的目的,而且使用了齊次坐標(biāo)理論,使得處理更加規(guī)范化。注意在把
矩陣
就是我們投影矩陣的第一個版本。你一定會問為什么要把z寫成
有兩個原因:
1) P’的3個代數(shù)分量統(tǒng)一地除以分母-z,易于使用齊次坐標(biāo)變?yōu)槠胀ㄗ鴺?biāo)來完成,使得處理更加一致、高效。 2) 后面的CVV是一個x,y,z的范圍都為[-1,1]的規(guī)則體,便于進(jìn)行多邊形裁剪。而我們可以適當(dāng)?shù)倪x擇系數(shù)a和b,使得
接下來我們就求出a和b:
這樣我們就得到了透視投影矩陣的第一個版本:
為了能在x和y方向把頂點從Frustum情形變成CVV情形,我們開始對x和y進(jìn)行處理。先來觀察我們目前得到的最終變換結(jié)果:
下面要做的就是從這個新形式出發(fā)反推出下一個版本的透視投影矩陣。注意到
便利的投影矩陣生成函數(shù)
3D APIs都提供了諸如gluPerspective(fov, aspect, near, far)或者D3DXMatrixPerspectiveFovLH(pOut, fovY, Aspect, zn, zf)這樣的函數(shù)為用戶提供快捷的透視矩陣生成方法。我們還是用OpenGL的相應(yīng)方法來分析它是如何運(yùn)作的。
gluPerspective(fov, aspect, near, far)
fov即視野,是視錐體在xz平面或者yz平面的開角角度,具體哪個平面都可以。OpenGL和D3D都使用yz平面。
aspect即投影平面的寬高比。
near是近裁剪平面的距離
far是遠(yuǎn)裁剪平面的距離。
上圖中左邊是在xz平面計算視錐體,右邊是在yz平面計算視錐體。可以看到左邊的第3步top = right / aspect使用了除法(圖形程序員討厭的東西),而右邊第3步right = top x aspect使用了乘法,這也許就是為什么圖形APIs采用yz平面的原因吧!
到目前為止已經(jīng)完成了對透視投影變換的闡述,我想如果你一直跟著我的思路下來,應(yīng)該能夠?qū)ν敢曂队白儞Q有一個細(xì)節(jié)層次上的認(rèn)識。當(dāng)然,很有可能你已經(jīng)是一個透視投影變換專家,如果是這樣的話,一定給我寫信,指出我認(rèn)識上的不足,我會非常感激J。Bye!
本文來自CSDN博客,轉(zhuǎn)載請標(biāo)明出處:http://blog.csdn.net/popy007/archive/2007/09/23/1797121.aspx 在上一篇文章中我們討論了透視投影變換的原理,分析了OpenGL所使用的透視投影矩陣的生成方法。正如我們所說,不同的圖形API因為左右手坐標(biāo)系、行向量列向量矩陣以及變換范圍等等的不同導(dǎo)致了矩陣的差異,可以有幾十個不同的透視投影矩陣,但它們的原理大同小異。這次我們準(zhǔn)備討論一下Direct3D(以下簡稱D3D)以及J2ME平臺上的JSR184(M3G)(以下簡稱M3G)的透視投影矩陣,主要出于以下幾個目的:
(1) 我們在寫圖形引擎的時候需要采用不同的圖形API實現(xiàn),當(dāng)前主要是OpenGL和D3D。雖然二者的推導(dǎo)極為相似,但D3D的自身特點導(dǎo)致了一些地方仍然需要澄清。 (2) DirectX SDK的手冊中有關(guān)于透視投影矩陣的一些說明,但并不詳細(xì),甚至有一些錯誤,從而使初學(xué)者理解起來變得困難,而這正是本文寫作的目的。 (3) M3G是J2ME平臺上的3D開發(fā)包,采用了OpenGL作為底層標(biāo)準(zhǔn)進(jìn)行封裝。它的透視投影矩陣使用OpenGL的環(huán)境但又進(jìn)行了簡化,值得一提。
本文努力讓讀者清楚地了解D3D與M3G透視投影矩陣的原理,從而能夠知道它與OpenGL的一些差別,為構(gòu)建跨API的圖形引擎打好基礎(chǔ)。需要指出的一點是為了完全理解本文的內(nèi)容,請讀者先理解上一篇文章《深入探索透視投影變換》的內(nèi)容,因為OpenGL和它們的透視投影矩陣的原理非常相似,因此這里不會像上一篇文章從基礎(chǔ)知識講起,而是對比它們的差異來推導(dǎo)變換矩陣。我們開始!
OpenGL與D3D的基本差異
(1) OpenGL默認(rèn)使用右手坐標(biāo)系,而D3D 默認(rèn)使用左手坐標(biāo)系。
(2) OpenGL使用列向量矩陣乘法而D3D使用行向量矩陣乘法。
(3) OpenGL的CVV的Z范圍是[-1, 1],D3D的CVV的Z范圍是[0, 1]。
以上這些差異導(dǎo)致了最終OpenGL和D3D的透視投影矩陣的不同。
D3D的透視投影矩陣推導(dǎo)
這里我們考察的是xz平面上的關(guān)系,yz平面上的關(guān)系同理。這里o是相機(jī)位置。np是近裁剪平面,也是投影平面,N是它到相機(jī)的距離。fp是遠(yuǎn)裁剪平面,F(xiàn)是它到相機(jī)的位置。p是需要投影的點,p’是投影之后的點。根據(jù)相似三角形定理,我們有
則有
注意到OpenGL使用右手坐標(biāo)系,因此應(yīng)該使用-N(請參考上一篇文章的這一步),而D3D使用左手坐標(biāo)系,因此使用N,這是二者的不同點之一。這樣,我們得到投影之后的點
第三個信息點是變換之后的z在投影平面上的位置,也就是N,它已經(jīng)沒用了,我們把p’寫成
從而用第三個沒用信息點它來存儲z(如果讀者對這一點不太了解,請參考上一篇文章)。接下來我們求出a和b,從而在z方向上構(gòu)建CVV。請注意這里是OpenGL和D3D的另一個不同點,OpenGL的CVV的z范圍是[-1, 1],而D3D的CVV的z范圍是[0, 1]。也就是說,D3D 中在近裁剪平面上的點投影之后的點會處于CVV的z=0平面上,而在遠(yuǎn)裁剪平面上的點投影之后的點會在CVV的z=1平面上。這樣我們的計算方程就是
從而我們得到了透視投影矩陣的第一個版本
即
這個時候第三個分量變換到CVV情形了,CVV的z范圍是[0,1]。接下來根據(jù)上一篇文章所講到的,我們要把前兩個分量變成CVV情形,CVV的x和y范圍是[-1, 1],如下圖所示:
使用線性插值,我們有:
這里left和right是投影平面的左右范圍,top和bottom是投影平面的上下范圍。xcvv和ycvv是我們需要算出的在CVV情形中的x和y,也就是我們要計算出的結(jié)果。但在算出它們之前,我們先把上面的式子寫成:
這里有一個需要注意的地方,如果投影平面在x方向上居中,則
那么第一個式子就可以銷掉等號兩邊的1/2,寫成
同理,如果投影平面在y方向上居中,則第二個式子可以寫成
則我們現(xiàn)在分兩種情況討論: (1) 投影平面的中心和x-y平面的中心重合(在x和y方向上都居中) (2) 一般情況 我們分別討論:
(1)特殊情況方程
這組是特殊情況,方程比較簡單,但也是使用頻率最高的方式(這是D3DXMatrixPerspectiveLH、D3DXMatrixPerspectiveRH、D3DXMatrixPerspectiveFovLH、D3DXMatrixPerspectiveFovRH四個方法所使用的情況)。我們導(dǎo)出它:
則我們反推出透視投影矩陣:
其中
而r-l和t-b可以分別看作是投影平面的寬w和高h(yuǎn)。最后那個矩陣就是D3D的透視投影矩陣之一。另外呢,如果我們不知道right、left、top以及bottom這幾個參量,也可以根據(jù)視野(FOV – Field Of View)參量來求得。下面是兩個平面的視野關(guān)系圖:
其中,兩個fov分別是在x-z以及y-z平面上的視野。如果只給了一個視野,也可以通過投影平面的寬高比計算出來:
用一個視野算出w或者h(yuǎn),然后用寬高比算出h或者w。
(2)一般情況的方程
這組方程比較繁瑣,但更具一般性(和OpenGL一般矩陣的推導(dǎo)一致,這也是D3DXMatrixPerspectiveOffCenterLH和D3DXMatrixPerspectiveOffCenterRH兩個方法所使用的情況)。我們導(dǎo)出它:
我們繼續(xù)反推出透視投影矩陣:
其中
最后那個矩陣就是D3D的一般透視投影矩陣。
好了,目前為止,我們已經(jīng)導(dǎo)出了D3D的兩個透視投影矩陣。下面我把上一篇導(dǎo)出的OpenGL的透視投影矩陣寫出來,大家可以拿它和剛剛導(dǎo)出的D3D的一般性透視投影矩陣做一個對比。
如果仔細(xì)觀察,可以發(fā)現(xiàn)二者在元素的布局上是一個轉(zhuǎn)置的關(guān)系,這個就是由它們使用的左右手坐標(biāo)系以及使用的行列矩陣的差異造成的。而另外在一些元素的細(xì)節(jié)上也存在著差異,這是由于D3D的CVV的z范圍不同造成的。可見在原理相同的情況下,細(xì)微的環(huán)境差異可以造成非常大的變化,而這就是透視投影矩陣存在諸多不同版本的原因。一般情況的透視投影矩陣也可以使用視野方式來定義,方法和特殊情況相同。 M3G的透視投影矩陣
上面是OpenGL透視投影矩陣的最終版本,也是一般性版本,我們要把它變成特殊性,版本,非常簡單,和上面D3D的特殊情況一樣,我們從對x和y進(jìn)行插值的那一步來看:
和D3D的第一種情況一樣,銷掉兩邊的1/2,得到:
則我們反推出透視投影矩陣:
最右邊那個矩陣就是M3G的透視投影矩陣。仍然可以通過視野參數(shù)來設(shè)置透視投影矩陣,這里請讀者自行推導(dǎo),方法與上面D3D的完全相同。
結(jié)束語
本文來自CSDN博客,轉(zhuǎn)載請標(biāo)明出處:http://blog.csdn.net/popy007/archive/2009/04/19/4091967.aspx |
|
|