如何寫一個軟渲染(4)-Clipping
提要
首先說一下Clipping和Culling的區別。
Culling翻譯成中文是剔除,指的是將不要的東西完全乾掉。
Clipping指的是裁剪,將要的部分留下,不要的部分去掉。
今天主題就是三角形的視錐裁剪,相關的演算法網上的大部分的軟渲染都沒有去實現,因為他們只把要渲染的東西放到螢幕中間,這樣就不用裁剪了。要麼就是實現的有問題,希望今天能徹底搞明白。
Clipping in Homogenious coordinate
回顧一下流水線,裁剪是發生在頂點座標乘以MVP矩陣之後,得到的裁剪座標,除以到NDC座標。

回顧一下齊次座標的意義
1.便於4*4的矩陣進行座標變換計算
2.可以通過矩陣乘法用齊次座標表示透視關係。
在NDC座標系下,x象限下的可見範圍為 -1<=x/w<=1,那麼在Clip座標系下(即未進行透視除法之前), 即是 –w<x<w,其他象限依此類推。
三維平面的定義
平面可以看成距離原點一定距離的點的集合,所以定義一個平面只需要兩個變數,法線和距離
Vector3 normal; float d;
空間中某個點到平面的距離可以通過向量點乘計算出來
float Plane::operator *( const Vector3 &i_vVal ) const { return Vector3::Dot( normal, i_vVal ) + d; }
三維空間線段裁剪
在說三角形的裁剪之前,我們先看線段的裁剪

對於線段AB,以及裁剪面x=-w,法線為(1,0,0),距離為w。現在要做兩件事,1
算出A到平面的距離為d1,B到平面的距離為d2。
d1和d2異號的時候,線段是穿過平面的,都為負數,剔除,都為正數,不用裁剪。
如上圖所示的情況我們需要留下的是BC線段,所以問題就是求出C點的資訊,因為是三維空間還沒做透視除法,直接插值就可以。
C = Lerp(A, B, d2/(d2-d1))
再說Cohen-Sutherland algorithm
之前繪製線段的時候就有用到這個演算法,忘記的可以回顧一下
ofollow,noindex" target="_blank">拳四郎:如何寫一個軟渲染(2)-Primitive現在就擴充套件到三維空間,裁剪的物件是三角形。
裁剪的邊界變成了6的面
Plane mClippingPlanes[6]; mClippingPlanes[cp_left] = Plane(1, 0, 0, 1); mClippingPlanes[cp_right] = Plane(-1, 0, 0, 1); mClippingPlanes[cp_top] = Plane(0, -1, 0, 1); mClippingPlanes[cp_bottom] = Plane(0, 1, 0, 1); mClippingPlanes[cp_near] = Plane(0, 0, 1, 0); mClippingPlanes[cp_far] = Plane(0, 0, -1, 1);
三角形和麵的裁剪關係只可能有兩種

左邊的這種情況會生成兩個新的三角形(如果是多個面裁剪就會生成更多三角形),右邊的這種情況生成一個新的三角形,新的頂點以及頂點資料都可以通過直接插值獲得。腦補充一下下面這個三角形被上下左右四個面裁剪的過程?

最後的結果是這樣

不同顏色代表不同邊界裁剪的過程。
關鍵程式碼
int RenderSystem::ClipToPlane(uint32 i_iNumVertices, uint32 i_iSrcIndex, const Plane &i_plane) { VSOutput **ppSrcVertices = mpClipVertices[i_iSrcIndex]; VSOutput **ppDestVertices = mpClipVertices[(i_iSrcIndex + 1) & 1]; uint32 iNumClippedVertices = 0; for (uint32 i = 0, j = 1; i < i_iNumVertices; ++i, ++j) { if (j == i_iNumVertices) // wrap over j = 0; float di, dj; // Distances of current and next vertex to clipping plane. di = i_plane * ppSrcVertices[i]->position; dj = i_plane * ppSrcVertices[j]->position; if (di >= 0.0f) { ppDestVertices[iNumClippedVertices++] = ppSrcVertices[i]; if (dj < 0.0f) { InterpolateVertex(&mClipVertices[mNextFreeClipVertex], ppSrcVertices[i], ppSrcVertices[j], di / (di - dj)); ppDestVertices[iNumClippedVertices++] = &mClipVertices[mNextFreeClipVertex]; mNextFreeClipVertex = (mNextFreeClipVertex + 1) % 20; // TODO: 20 enough? } } else { if (dj >= 0.0f) { InterpolateVertex(&mClipVertices[mNextFreeClipVertex], ppSrcVertices[j], ppSrcVertices[i], dj / (dj - di)); ppDestVertices[iNumClippedVertices++] = &mClipVertices[mNextFreeClipVertex]; mNextFreeClipVertex = (mNextFreeClipVertex + 1) % 20; // TODO: 20 enough? } } } return iNumClippedVertices; }
一些裁剪結果

