matlab z变换离散化_用C++编写一个简单的光栅化渲染器:3D篇
3D光柵化與2D光柵化在圖元繪制方面差別并不大,3D光柵化主要是多了很多坐標(biāo)系(Local,world,View...),除此外遮擋算法和裁剪算法也會(huì)稍微復(fù)雜一些。
本篇文章的重點(diǎn)就主要集中在各種坐標(biāo)系變換上。
1.基本3D變換
本文所采用的向量(vector)表示為行主序(Row Major),向量與矩陣(matrix)相乘方式為左乘(left or pre-multiplication),向量與矩陣相乘表示如下:
a.縮放變換(Scale)
縮放變換即對(duì)一個(gè)三維向量的x,y,z分量分別進(jìn)行縮放,三維向量a在x,y,z方向上進(jìn)行縮放操作可以表示為:
考慮到使用向量和矩陣相乘實(shí)現(xiàn)縮放變換,可知:
可以得出
, , ,則縮放變換的矩陣表示形式為:縮放變換的逆變換的矩陣表示形式為:
b.旋轉(zhuǎn)變換(Rotation)
向量v以單位向量n為軸旋轉(zhuǎn)如上圖,向量
以單位向量 為軸旋轉(zhuǎn)角度 ,得到旋轉(zhuǎn)后的向量 , 與 之間的夾角為 , 為 在 方向上的投影向量,則旋轉(zhuǎn)后向量 可以表示為:令
, ,則:將上式中的參數(shù)帶入到矩陣中即可到旋轉(zhuǎn)變換的矩陣表示:
由于
為正交矩陣(各行,各列為單位向量,且兩兩正交),因此 ,旋轉(zhuǎn)變換的逆變換為:特別地,當(dāng)
為x,y,z軸時(shí)(即: , , ),旋轉(zhuǎn)矩陣分別為:c.平移(Translation)
3D空間中的點(diǎn)和向量都可以用三維向量表示,前面所介紹的縮放和旋轉(zhuǎn)變換對(duì)向量都適用,但平移變換對(duì)向量并無意義(平移后的向量與原向量完全相同),然而3D空間中的點(diǎn)卻可適用平移變換。為了區(qū)分點(diǎn)和向量,同時(shí)一致地對(duì)它們進(jìn)行表示,我們可以采用齊次坐標(biāo)(homogeneous coordinates),即:
- 當(dāng)表示向量時(shí)其坐標(biāo)為: ,
- 當(dāng)表示點(diǎn)時(shí)其坐標(biāo)為:
使用齊次做表時(shí)對(duì)應(yīng)的縮放和旋轉(zhuǎn)矩陣為(齊次坐標(biāo)為1x4向量,應(yīng)與4x4矩陣相乘,矩陣第四列為[0,0,0,1],保證相乘后點(diǎn)和向量的w分量保持不變):
對(duì)空間中一點(diǎn)
施加平移變換 可表示為:由矩陣和向量相乘運(yùn)算規(guī)則可以知:
, , , , , 。因此平移變換的矩陣表示為:平移變換的逆變換矩陣表示形式為:
d.基本變換組合
可以將三種基本變換組合起來表示更復(fù)雜的變換,如:
表示對(duì)點(diǎn) 先后進(jìn)行縮放,旋轉(zhuǎn)和平移變換,但不同組合順序的基本變換會(huì)得到完全不同的復(fù)雜變換。已知點(diǎn)
,縮放變換 (x,y,z分量分別縮放7,5,3),旋轉(zhuǎn)變換 (繞y軸旋轉(zhuǎn)45度)和 (沿x,y,z方向分別平移6,2,4),則對(duì)點(diǎn) 施加 變換后得到點(diǎn) ,對(duì)點(diǎn) 施加 則得到的點(diǎn) 。因此在組合基本變換時(shí)需要注意運(yùn)算順序,本文采用的組合順序?yàn)? (先縮放,然后旋轉(zhuǎn),最后平移)。2.3D坐標(biāo)系變換
本文采用的坐標(biāo)系規(guī)范與DirectX相同(左手坐標(biāo)系),如下圖所示:
已知坐標(biāo)系A(chǔ)和坐標(biāo)系B,坐標(biāo)系B的x,y,z軸在坐標(biāo)系A(chǔ)下可表示為
, , ,坐標(biāo)系B的原點(diǎn)在坐標(biāo)系A(chǔ)下表示為 。坐標(biāo)系A(chǔ)與B則將坐標(biāo)系B中一點(diǎn)
從坐標(biāo)系B變換到坐標(biāo)系A(chǔ)的變換矩陣為:變換過程中點(diǎn)
在空間中的位置并沒有發(fā)生改變,只是參考坐標(biāo)系發(fā)生了改變,從B坐標(biāo)系變到A坐標(biāo)系。(縮放,旋轉(zhuǎn),平移變換只有在同一坐標(biāo)系下才有意義。)a. 本地空間(Local Space,Local Coordinate System)
3D渲染中用到的每個(gè)模型都有自己的本地坐標(biāo)系,因?yàn)槊總€(gè)模型都在獨(dú)立的坐標(biāo)系中進(jìn)行建模,所以本地坐標(biāo)系也被稱為模型坐標(biāo)系(model space)。使用本地坐標(biāo)系有如下好處:
- 建模更加方便,每個(gè)模型在自己的本地空間的中央進(jìn)行建模,不同模型之間互不干擾。
- 建好的模型可以被應(yīng)用到多個(gè)場(chǎng)景(多個(gè)不同坐標(biāo)系中),而不用對(duì)模型做任何改動(dòng)。
- 有利于大規(guī)模的重復(fù)的實(shí)例繪制(instance)。
b. 世界空間(World Space,World Coordinate System)
在本地坐標(biāo)系中建好的模型都會(huì)經(jīng)過縮放,旋轉(zhuǎn),平移等操作后變換到世界坐標(biāo)系中的不同位置構(gòu)成渲染場(chǎng)景。
模型一開始放置在世界坐標(biāo)系中時(shí),其本地坐標(biāo)系與世界坐標(biāo)系重合,經(jīng)過一系列縮放,旋轉(zhuǎn),平移變換后被放置在世界坐標(biāo)系中的適當(dāng)位置構(gòu)成渲染場(chǎng)景,經(jīng)過變換后的Local Space的x,y,z軸及原點(diǎn)在World Space下表示為
, , 及 。則從本地坐標(biāo)系到世界坐標(biāo)系的世界變換 可以表示為:可以通過世界變換將本地坐標(biāo)系中的點(diǎn)轉(zhuǎn)換到世界坐標(biāo)系中,即
。c. 觀察空間(View Space,View Coordinate System)
view space與view volume有了場(chǎng)景之后,還需要在場(chǎng)景中放置一個(gè)虛擬的攝像機(jī)才能在場(chǎng)景中實(shí)現(xiàn)漫游,以攝像機(jī)的角度來觀察游戲場(chǎng)景。此時(shí)可以為攝像機(jī)附加一個(gè)坐標(biāo)系,相機(jī)所看的方向?yàn)樽鴺?biāo)系z(mì)軸,x軸指向相機(jī)的右側(cè),y軸指向相機(jī)的上方,這個(gè)附加在相機(jī)上的坐標(biāo)系即為觀察坐標(biāo)系(相機(jī)坐標(biāo)系)。
相機(jī)能夠?qū)⒖梢暦秶鷥?nèi)的3D場(chǎng)景轉(zhuǎn)化為2D圖像(不在view volume內(nèi)的物體可以直接剔除)。
若觀察坐標(biāo)系的x,y,z軸以及原點(diǎn)在世界坐標(biāo)系下可以表示為:
, , 及 ,則可以得到從觀察坐標(biāo)系到世界坐標(biāo)系的變換 :但我們需要的是觀察坐標(biāo)系到世界坐標(biāo)系的逆變換,即世界坐標(biāo)系到觀察坐標(biāo)系的變換
。而從世界坐標(biāo)系到觀察坐標(biāo)系的變換只涉及到旋轉(zhuǎn)和平移變換,則 又可以表示為 。因此從世界坐標(biāo)系到觀察坐標(biāo)系的變換 可以表示為:d. 齊次裁剪空間與歸一化設(shè)備坐標(biāo)系(Homogeneous Clip Space and Normalized Device Coordinates)
現(xiàn)在需要將相機(jī)可視范圍內(nèi)的物體投影到2D平面上,相機(jī)的可視范圍可以用一個(gè)處于近平面(near plane)與遠(yuǎn)平面(far plane)之間的平截棱錐(view frustum)表示:
View frustum這里采用view frustum的near plane作為投影平面,由于從3D場(chǎng)景投影轉(zhuǎn)換為2D圖像后會(huì)損失一個(gè)維度,因此在投影過程中還需要保留物體在view space中的z值來判斷物體之間的遮擋關(guān)系。如下圖:
需要深度值z(mì)來判斷空間中點(diǎn)的遮擋關(guān)系空間中的兩點(diǎn)p0,p1經(jīng)過投影后都位于near plane上的q點(diǎn),需要根據(jù)p0,p1在view space中的深度值(z值)來判斷應(yīng)該繪制p0還是p1點(diǎn)。因?yàn)樾枰4嫔疃戎档骄彺嬷?#xff0c;并根據(jù)深度值來判斷空間物體遮擋關(guān)系,這種遮擋算法就被稱為z-buffer。
PS:雖然原理上是使用View Space的z值進(jìn)行遮擋判斷,但其實(shí)DirectX的z-buffer里存儲(chǔ)的是NDC Space的z值。z-buffer是單通道的圖片,可以用一個(gè)Image<float>對(duì)象表示。
View volume變換到CVV投影的同時(shí)還需要對(duì)穿過view frustum的圖元進(jìn)行裁剪,為了方便裁剪,可以將view frustum變換成為一個(gè)長方體,這樣就可以更快捷的判斷圖元與view frustum的關(guān)系。經(jīng)過變換后的view frustum被稱為canonical view volume(CVV)。處在CVV內(nèi)的點(diǎn)
滿足一下關(guān)系:經(jīng)過變換后CVV所處的坐標(biāo)系就被稱為歸一化坐標(biāo)系(Normalized Device Coordinates,NDC)(裁剪,投影都是在這一變換過程中進(jìn)行的)。
將View space內(nèi)的點(diǎn)p投影到near plane上已知View space中的一點(diǎn)
,要將其投影到near plane( )上,投影到2D平面后的點(diǎn)為 ,且near plane的寬度和高度分別為 和 ,由幾何關(guān)系可知:投影后的點(diǎn)
位于near plane內(nèi),因此滿足:經(jīng)過以下變換后可滿足CVV內(nèi)點(diǎn)坐標(biāo)的要求:
由于以上變換為非線性變換的,因此無法用矩陣表示,可以將上訴變換拆分為兩個(gè)部分:線性部分和非線性部分,非線性部分表示為除以
(透視除法)。這時(shí)將點(diǎn) 變換到CVV內(nèi)點(diǎn) 的變換 可以表示為:CVV內(nèi)點(diǎn)
可以表示為 :現(xiàn)在還需要將點(diǎn)
的 坐標(biāo)變換到CVV坐標(biāo)范圍內(nèi),位于view volume點(diǎn) 的 坐標(biāo)滿足 :經(jīng)過變換
變換到NDC空間后的點(diǎn) 的坐標(biāo)滿足:- 時(shí)
- 時(shí)
由矩陣與向量運(yùn)算規(guī)則可知:
則:
可以解出
,因此變換矩陣
可以表示為:PS:投影矩陣還有其他推導(dǎo)和表示形式,使用參數(shù)不同但效果相同。
整個(gè)投影變換包含兩個(gè)部分:
- (透視除法)
若投影變換前的點(diǎn)
的深度值(z)為0,則進(jìn)行透視除法時(shí)會(huì)出現(xiàn)除0的情況,為了避免除0,必須在透視除法之前對(duì)穿過w=0平面的圖元進(jìn)行裁剪,而在透視除法之前的空間就被稱為齊次裁剪空間。在齊次裁剪空間中位于view frustum內(nèi)的點(diǎn)
滿足:由于每個(gè)頂點(diǎn)的z值不同,所以圖元中每個(gè)頂點(diǎn)在齊次裁剪空間中的clip volume大小也完全不同。
e. 屏幕空間(Screen Space)
最后位于view frustum內(nèi)的圖元經(jīng)過了前面一系列變換后,將會(huì)被變換到屏幕空間(2D坐標(biāo)系)中進(jìn)行光柵化。此坐標(biāo)系與上一篇2D光柵化所使用的屏幕空間坐標(biāo)系相同:
此2D坐標(biāo)系以Viewport左上角為原點(diǎn),處于Viewport中的點(diǎn)
的坐標(biāo)范圍為:可以通過如下變換將NDC空間內(nèi)的點(diǎn)
變換到屏幕空間中(由于screen space的y軸與NDC space 的y軸相反,所以需要反轉(zhuǎn)y軸):f. 坐標(biāo)系變換總覽
3. 3D光柵化
3D光柵化發(fā)生在圖元被變換到Screen space之后,因?yàn)檫@里的Screen space與2D的Screen Space完全一致,所以2D的光柵化算法在這里也依然適用。
然而由于圖元經(jīng)過了投影變換,且投影變換為非線性變換,所以不能用簡單的線性插值來獲取fragment的屬性。
投影變換不會(huì)保持相對(duì)距離不變性如上圖所示,view space中的線段v0v1上兩點(diǎn)
, 在near plane上的投影為點(diǎn) , 。 , 中間一點(diǎn) 在near plane上的投影為點(diǎn) 。從圖中可以看出點(diǎn)v到p0,p1的距離比值與點(diǎn)q到s0,s1的距離比值完全不同,投影變換不保持距離不變。為了執(zhí)行z-buffer算法,需要通過點(diǎn)
獲取到點(diǎn) 的深度值(z)。點(diǎn)
的深度值可以通過如下方法插值得到:證明如下:
由于點(diǎn)
為點(diǎn) 在near plane上的投影,因此點(diǎn) 與點(diǎn) 的關(guān)系為:且
位于 之間,則:- 式(1)
由點(diǎn)
在 , 之間, 點(diǎn) 在 , 之間則有:帶入式(1)可得:
式(2)又s0和s1分別為p0和p1在near plane上的投影,則:
帶入式(2)可得:
化簡得:
則:
帶入式(1)可得:
則若View space中三角形
,變換到Screen Space后為三角形 , 內(nèi)一點(diǎn) 在Screen Space的投影為 內(nèi)的點(diǎn) ,對(duì)三角形 內(nèi)的點(diǎn)(fragment) ,可以通過如下方法取得fragment 在View Space中對(duì)應(yīng)的深度值: 為點(diǎn) 在三角形 內(nèi)的重心坐標(biāo)。執(zhí)行z-buffer算法時(shí),若當(dāng)前光柵化的點(diǎn)
(fragment or pixel)的深度值小于z-buffe中點(diǎn) 處對(duì)應(yīng)位置的深度值,則當(dāng)前光柵化的點(diǎn)未被遮擋,可以將點(diǎn) 的深度值寫入到z-buffer,并對(duì) 進(jìn)行著色 (shading)操作。在對(duì)點(diǎn)進(jìn)行shading時(shí)還需要點(diǎn)的其他屬性值(紋理坐標(biāo),點(diǎn)的顏色,法線等...)
如上圖已知view space中,三角形兩邊上兩點(diǎn)
和 對(duì)應(yīng)深度值和屬性值分別為, 和 , ,則線段 內(nèi)一點(diǎn) 的深度值和屬性值為 , ,由線性插值可知深度值與屬性值存在的關(guān)系為: 式(3)且點(diǎn)
的深度值與 , 的深度值存在的關(guān)系為:帶入式(3)可得:
則 :
可得對(duì)Screen space三角形
內(nèi)一點(diǎn) 的任意屬性插值的公式為: 為點(diǎn) 的重心坐標(biāo), 分別為 在view space中對(duì)應(yīng)點(diǎn)的深度值。PS:可以用這個(gè)方法插值得到
在NDC Space內(nèi)對(duì)應(yīng)點(diǎn)的深度值。整個(gè)3D光柵化算法可以由如下偽代碼表示:
// Local space Triangle tri;// *W ,變換到World Space TransformToWorldSpace(tri, W);// *V ,變換到View Space TransformToViewSpace(tri, V);// *P ,變換到Homogeneous Clip Space TransformToHomogeneousClipSpace(tri, P);// 在Homogeneous Clip Space進(jìn)行裁剪和剔除 Clip(tri);// 除以w分量 ,經(jīng)過透視除法后變換到NDC Space PerspectiveDivide(tri);// 獲取三角形頂點(diǎn)在Screen Space中的坐標(biāo) screen_space_triangle = GetScreenSpacePositon(tri);// 進(jìn)入Screen Space后就可以同2D一樣可以采用Half-Space光柵化算法 {//獲取Screen Space三角形的包圍盒GetBoundingBox(screen_space_triangle, box_min, box_max);//遍歷包圍盒中的fragmentfor (fragment(or pixel) in BoundingBox){//用PrepDotP判斷fragment是否在三角形中,if (fragment in screen_space_triangle){//若fragment在三角形中,則計(jì)算fragment的重心坐標(biāo)用于插值GetBarycentricCoordinates(λ0, λ1, λ2);//插值獲得fragment在View Space中的深度值view_z = GetViewSpaceZ();//再次插值獲得fragment在NDC Space中的深度值ndc_z = GetNDCSpaceZ(view_z);//若當(dāng)前光柵化的fragment的深度值小于Z-Buffer中對(duì)應(yīng)位置的fragment的深度值,則當(dāng)前fragment未被遮擋if (ndc_z < ZBuffer(fragment.pos)){//插值當(dāng)前fragment的屬性(normal,uv...)InterpolatedAttributes(fragment);//著色Shading(fragment);}}} }光柵化部分的代碼可以參考2D光柵化篇。
4. 3D裁剪
3D裁剪發(fā)生在齊次裁剪空間。齊次裁剪空間中CVV內(nèi)的點(diǎn)需要滿足一下要求:
可以在裁剪之前對(duì)不在CVV內(nèi)的三角形直接剔除(三個(gè)頂點(diǎn)均不在CVV內(nèi)),只需要對(duì)穿過CVV的三角形進(jìn)行裁剪(裁剪方法與2D裁剪相似)。
CVV由6個(gè)面組成(left,right,top,bottom,near,far),每個(gè)面將空間分為兩個(gè)區(qū)域,因此可以用6bit二進(jìn)制編碼對(duì)這些區(qū)域進(jìn)行編碼。對(duì)齊次裁剪空間內(nèi)一點(diǎn)
,若:- ,則點(diǎn)在left裁剪平面外側(cè),第一位編碼為1;
- ,則點(diǎn)在right裁剪平面外側(cè),第二位編碼為1;
- ,則點(diǎn)在bottom裁剪平面外側(cè),第三位編碼為1;
- ,則點(diǎn)在top裁剪平面外側(cè),第四位編碼為1;
- ,則點(diǎn)在near裁剪平面外側(cè),第五位編碼為1;
- ,則點(diǎn)在far裁剪平面外側(cè),第六位編碼為1;
對(duì)三角形進(jìn)行裁剪時(shí),先根據(jù)三角形三個(gè)頂點(diǎn)的Clip Code判斷三角形與裁剪平面的關(guān)系,然后再采用Sutherland–Hodgman算法對(duì)三角形進(jìn)行裁剪。
若采用Half-Space光柵化算法則只需要對(duì)near裁剪平面進(jìn)行裁剪即可(防止透視除法時(shí)除0,在near plane上的點(diǎn)w=camera space z=near。由于Half-Space算法只處理三角形包圍盒與Viewport交集內(nèi)的fragment,所以left,right,top,bottom裁剪平面可以不做處理)。
對(duì)齊次裁剪空間內(nèi)的線段
若其穿過near裁剪平面,則其與near裁剪平面的交點(diǎn) 滿足:即:
由于
位于near裁剪平面上,有 ,可以求出 ,進(jìn)而求出交點(diǎn) 的坐標(biāo)。下面給出使用Sutherland–Hodgman算法對(duì)穿過near plane的三角形進(jìn)行裁剪的代碼(Sutherland–Hodgman的介紹可以參考2D篇):
void其他裁剪平面的處理與near plane相似(更完整的裁剪代碼可以參考我的光柵化項(xiàng)目)
到這里為止整個(gè)3D光柵化的流程也就結(jié)束了,有了fragment插值后的屬性即可進(jìn)行光照著色,渲染相關(guān)的內(nèi)容這里就不再提及了。
Tips1:背面剔除(Backface Culling)
根據(jù)使用的光柵化算法的不同可以選擇在不用的坐標(biāo)系中進(jìn)行背面剔除。
如果使用Scan-Line算法,則可以在齊次裁剪空間或者NDC Space進(jìn)行背面剔除,在這兩個(gè)空間中是以相機(jī)觀察方向?yàn)閦軸??梢杂貌娉饲蟪鋈切蔚拿娣ň€,然后用面法線和z軸做點(diǎn)乘,判斷三角形面是否為背面。
若使用Half-Space算法,則可以在轉(zhuǎn)換到Screen Space之后,光柵化之前做背面剔除??梢允褂肞repDotP(Edge Function)求三角形的面積,當(dāng)頂點(diǎn)為順時(shí)針時(shí)則PrepDotP大于
0,逆時(shí)針則小于0。可以借此判斷三角形是否為背面。
三角形頂點(diǎn)順時(shí)針和逆時(shí)針排列時(shí)PrepDotP的結(jié)果不同。Tips2:Top-Left rule
對(duì)于有共享邊的相鄰三角形,會(huì)對(duì)共享邊進(jìn)行重復(fù)光柵化。
共享邊會(huì)重復(fù)光柵化為了解決共享邊的重復(fù)繪制,可以采用Top-Left rule,對(duì)三角形的top邊和Left邊不進(jìn)行光柵化。
Screen Space y軸向下對(duì)三角形的任意一條邊V[i]V[(i+1)%3],若:
- V[(i+1)%3].x-V[i].x >0 且 V[(i+1)%3].y-V[i].y = 0則為top edge(如第二個(gè)三角形的v2v0邊)。
- V[(i+1)%3].y-V[i].y < 0則為left edge。
Top-Left rule可以和PrepDotP結(jié)合起來判斷是否應(yīng)該對(duì)當(dāng)前的fragment進(jìn)行光柵化(fragment 在三角形內(nèi),且不為top or left edge)。
總結(jié)
以上是生活随笔為你收集整理的matlab z变换离散化_用C++编写一个简单的光栅化渲染器:3D篇的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: java---sychronized的深
- 下一篇: PreparedStatement 防止