計算機圖形學三角形基元填充算法即三角形光柵化重心雙線性插值算法
我們學過數學知識的人,都知道直線的隱函數:
f01(x,y)=(y0-y1)*x+(x1-x0)*y+x0*y1-x1*y0
這個函數的好處在於計算機計算時無需進行除法操作指令,我們學過彙編指令的都知道計算機在除法指令運行很多個週期才能計算出一個高度精確的結果。所以,在這裏巧妙地迴避了除法運算。
那麼,三角形的光刪化步驟如下所示:
實現三角形光柵化,尋找三頂點的包圍矩形,只對該矩形內進行候選像素執行循環,尋找三角形的重心,利用重心,進行三角形光柵化。
通過以下式子確定直線
f01(x,y)=(y0-y1)*x+(x1-x0)*y+x0*y1-x1*y0
f12(x,y)=(y1-y2)*x+(x2-x1)*y+x1*y2-x2*y1
f20(x,y)=(y2-y0)*x+(x0-x2)*y+x2*y0-x0*y2
因此重心座標(a ,b,c),即a+b+c=1
根據的三角形的3個頂點,分別XMIN,XMAX,YMIN,YMAX,得到了一個包含此三角形的最小的矩形區域,然後對此矩形區域的點進行逐一掃描。
此時令a=f12(x,y)/fa
b=f20(x,y)/fb
c=f01(x,y)/fc
同時判斷條件 如果a、b、c同時都大於0,那麼就把這個點繪製出來,顏色爲 V=a*R+b*G+c*B,否則就不繪製,直到遍歷完整個矩形區域,三角形的光柵化也就完成了。
至於光刪化的代碼就相當簡單了,讀者可以根據以上介紹的原理自己用win32編程實現,我在這裏寫出部分代碼以作參考使用
////////////////繪製填充三角形(利用三角形重心光柵化算法實現) 2016年1月19記
// 三角形的三個頂點v1,v2,v3
void FillTriangle(const M3DVector3f v1, const M3DVector3f v2, const M3DVector3f v3, const FLOATCOLORRGBA clr1,
const FLOATCOLORRGBA clr2, const FLOATCOLORRGBA clr3, CColorBuffer* pCB, CZBuffer* pZB)
{
// 三邊的斜率k[3]數組
M3DVector3f k;
// 計算直線斜率
k[0]=( (v1[1]-v2[1]) * v3[0] + (v2[0] - v1[0]) * v3[1] + v1[0] * v2[1] - v2[0] * v1[1] );
k[1]=( (v2[1]-v3[1]) * v1[0] + (v3[0] - v2[0]) * v1[1] + v2[0] * v3[1] - v3[0] * v2[1] );
k[2]=( (v3[1]-v1[1]) * v2[0] + (v1[0] - v3[0]) * v2[1] + v3[0] * v1[1] - v1[0] * v3[1] );
// 不繪製計算結果k爲0的三角形 (處理不符合條件的三角形)
if ( ABS(k[0]) < FLOAT_ERROR_RANGE )return;
if ( ABS(k[1]) < FLOAT_ERROR_RANGE )return;
if ( ABS(k[2]) < FLOAT_ERROR_RANGE )return;
// 計算直線斜率的倒數
k[0] = 1 / k[0];
k[1] = 1 / k[1];
k[2] = 1 / k[2];
// 第一步找出XMAX YMAX XMIN YMIN找出最小矩形
int xMIN,xMAX,yMIN,yMAX;
xMIN = ROUND( (v1[0] < v2[0] ? v1[0] : v2[0]) < v3[0] ? (v1[0] < v2[0] ? v1[0] : v2[0]) : v3[0] );
xMAX = ROUND( (v1[0] > v2[0] ? v1[0] : v2[0]) > v3[0] ? (v1[0] > v2[0] ? v1[0] : v2[0]) : v3[0] );
yMIN = ROUND( (v1[1] < v2[1] ? v1[1] : v2[1]) < v3[1] ? (v1[1] < v2[1] ? v1[1] : v2[1]) : v3[1] );
yMAX = ROUND( (v1[1] > v2[1] ? v1[1] : v2[1]) > v3[1] ? (v1[1] > v2[1] ? v1[1] : v2[1]) : v3[1] );
// 頂點顏色臨時存儲變量
FLOATCOLORRGBA clrTemp;
// 存儲頂點位置以及深度信息臨時變量
M3DVector3f pTemp;
// zInBuffer緩衝區中的深度 zNowBuffer當前計算深度值
float zInBuffer, zNowBuffer;
// 三頂點對顏色的貢獻值,a,b,c
// 在這裏初始化對第一個頂點顏色的貢獻值默認爲三者的平均值0.33
float a = 0.333f, b = 0.333f,c = 0.333f;
// 循環變量
int i,j;
for( i = xMIN; i <= xMAX; ++i ){
for(j = yMIN; j <= yMAX; ++j ){
pTemp[0] = (float)(i);
pTemp[1] = (float)(j);
zInBuffer = pZB->GetDepth(pTemp);
zNowBuffer = a * v1[2] + b * v2[2] + c * v3[2];
if ( zNowBuffer < zInBuffer){
if(CalcWeight(i, j, v1, v2, v3, k, a, b, c)){
// 三頂點對顏色的貢獻值進行融合(調色板)
clrTemp.red = a * clr1.red + b * clr2.red + c * clr3.red;
clrTemp.green = a * clr1.green + b * clr2.green + c * clr3.green;
clrTemp.blue = a * clr1.blue + b * clr2.blue + c * clr3.blue;
clrTemp.alpha = a * clr1.alpha + b * clr2.alpha + c * clr3.alpha;
// 深度值重置
pTemp[2] = zNowBuffer;
// 點亮該點
pCB->SetPixel(pTemp, clrTemp.red, clrTemp.green, clrTemp.blue, clrTemp.alpha);
// 重置該點的ZBuffer值
pZB->SetDepth(pTemp);
}
}// if ( zNowBuffer < zInBuffer)end
}// for end
}// for end
}// FillTriangle end
// 計算權重 CalculateWeight
BOOL CalcWeight(int i, int j, const M3DVector3f v1, const M3DVector3f v2, const M3DVector3f v3,
const M3DVector3f k, float &a, float &b, float &c)
{
// 計算權重
a = ((v2[1] - v3[1]) * i + (v3[0] - v2[0]) * j + v2[0] * v3[1] - v3[0] * v2[1]) * k[1];
b = ((v3[1] - v1[1]) * i + (v1[0] - v3[0]) * j + v3[0] * v1[1] -v1[0] * v3[1]) * k[2];
// 由於重心a+b+c=1 所以在這裏計算c=1-a-b來優化算法
// c=((v1[1]-v2[1])*i+(v2[0]-v1[0])*j+v1[0]*v2[1]-v2[0]*v1[1])*k[0];
c= 1 - a - b;
// 判斷是否在三角形內 連同邊上的點進行處理
if ( a >= 0 && b >= 0 && c >= 0 ){
return TRUE;
}else{
return FALSE;
}
}
效果截圖: