原文地址http://www.cnblogs.com/graphics/archive/2012/07/25/2582119.html
概述
投影變換完成的是如何將三維模型顯示到二維視口上,這是一個三維到二維的過程。你可以將投影變換看作是調整照相機的焦距,它模擬了為照相機選擇鏡頭的過程。投影變換是所有變換中最復雜的一個。
視錐體
視錐體是一個三維體,他的位置和攝像機相關,視錐體的形狀決定了模型如何從camera space投影到屏幕上。最常見的投影類型-透視投影,使得離攝像機近的物體投影后較大,而離攝像機較遠的物體投影后較小。透視投影使用棱錐作為視錐體,攝像機位於棱錐的椎頂。該棱錐被前后兩個平面截斷,形成一個棱台,叫做View Frustum,只有位於Frustum內部的模型才是可見的。
透視投影的目的
透視投影的目的就是將上面的棱台轉換為一個立方體(cuboid),轉換后,棱台的前剪裁平面的右上角點變為立方體的前平面的中心(下圖中弧線所示)。由圖可知,這個變換的過程是將棱台較小的部分放大,較大的部分縮小,以形成最終的立方體。這就是投影變換會產生近大遠小的效果的原因。變換后的x坐標范圍是[-1, 1],y坐標范圍是[-1, 1],z坐標范圍是[0, 1](OpenGL略有不同,z值范圍是[-1, 1])。
透視投影矩陣推導
下面來推導一下透視投影矩陣,這樣我們就可以自己設置投影矩陣了,就可以模擬神奇的D3DXMatrixPerspectiveLH函數的功能了。那么透視投影到底做了什么工作呢?這一部分算是個難點,無論是DX SDK的幫助文檔,還是大多數圖形學書籍,對此都是一帶而過,很少有詳細討論的,早期的DX SDK文檔還討論的稍微多一些,而新近的文檔則完全取消了投影矩陣的推導過程。
我們可以將整個投影過程分為兩個部分,第一部分是從Frustum內一點投影到近剪裁平面的過程,第二部分是由近剪裁平面縮放的過程。假設Frustum內一點P(x,y,z)在近剪裁平面上的投影是P'(x',y',z'),而P'經過縮放后的最終坐標設為P''(x",y",z")。假設所求的投影矩陣為M,那么根據矩陣乘法可知,如下等式成立。
PM=P'',即
先看第一部分,為了簡化問題,我們考慮YOZ平面上的投影情況,見下圖。設P(x, y, z)是Frustum內一點,它在近剪裁平面上的投影是P'(x', y', z')。(注意:D3D以近剪裁平面作為投影平面),設視錐體在Y方向的夾角為Θ。
由上圖可知,三角形OP'Q'與三角形OPQ相似,於是有如下等式成立。
在看第二部分,將P'縮放的過程,假設投影平面的高度為H,由於轉換后cuboid的高度為2。所以有
又因為投影平面的縱橫比為Aspect,所以
最后看z'',當Frustum內的點投影到近剪裁平面的時候,實際上這個z'值已經沒有意義了,因為所有位於近剪裁平面上的點,其z'值都是n,看起來我們甚至可以拋棄這個z'值,可以么?當然不行!別忘了后面還有深度測試呢。由第一幅圖可知,所有位於線段p'p上的點,最終都會投影到p'點,那么如果這條線段上真的有多個點,如何確定最終保留哪一個呢?當然是離觀察這最近的這個了,也就是深度值(z值)最小的。所以z'坐標可以直接保存p點的z值。因為在光柵化之前,我們需要對z坐標的倒數進行插值(原因請參見Mathematics for 3D Game Programming and Computer Grahpics 3rd section 5.4),所以可以將z''寫成z的一次表達式形式,如下
在映射前,z的范圍是[n,f],這里n和f分別是近遠兩個剪裁平面到原點的距離,在映射后,z''的范圍是[0,1],將數據代入上面的一次式,可得下面的方程組
解這個方程組得到
所以
整理一下得
將X'',y'',z''代入最開始的矩陣乘法等式中得
由上式可見,x'',y'',z''都除以了Pz,於是我們將他們再乘以Pz(這並不該變齊次坐標的大小),得到如下等式。
注意這里,x即Px,y即Py,z即Pz,解矩陣的每一列得到
於是所求矩陣為
代碼
一般來說,在程序中我們通常給定四個參數來求透視投影矩陣,分別是y方向的視角,縱橫比,近剪裁平面到原點的距離及遠剪裁平面到原點的距離,通過這四個參數即可求出上面的矩陣,代碼如下。
D3DXMATRIX BuildProjectionMatrix(float fov, float aspect, float zn, float zf) { D3DXMATRIX proj; ZeroMemory(&proj, sizeof(proj)); proj.m[0][0] = 1 / (tan(fov * 0.5f) *aspect) ; proj.m[1][1] = 1 / tan(fov * 0.5f) ; proj.m[2][2] = zf / (zf - zn) ; proj.m[2][3] = 1.0f; proj.m[3][2] = (zn * zf) / (zn - zf); return proj ; }
矩陣求解完畢,現在可以用如下代碼試試效果,這和使用D3D函數D3DXMatrixPerspectiveFovLH所得效果是一致的。
D3DXMATRIX proj = BuildProjectionMatrix(D3DX_PI / 4, 1.0f, 1.0f, 1000); g_pd3dDevice->SetTransform(D3DTS_PROJECTION, &proj) ;
Happy Coding!!!