光柵化是在計算機上生成圖像的重要步驟,然而無論是opengl還是directx還是其他的圖形接口都封裝了光柵化方法。我自己做了個光柵器,接下來就說一下怎樣實現光柵化的。
為什么要光柵化?
圖形管線的輸入是圖元頂點,輸出的則是像素(pixel),這個步驟其中還有個中間產物叫做片段(fragment),一個片段相應一個像素,但片段比像素多了用于計算的屬性,比如:深度值和法向量。通過片段能夠計算出終于將要生成像素的顏色值,我們把輸入頂點計算片段的過程叫作光柵化。為什么要光柵化?由于要生成用以計算終于顏色的片段。
光柵化的輸入和輸出各自是啥?
和普通函數一樣,光柵化函數也須要輸入和輸出,從之前的定義來看函數的輸入就是組成圖元的頂點結構,輸出的就是片段結構,為什么說是結構?由于這些能夠用c語言中的struct描寫敘述。
光柵化發生在哪一步?
通常在圖形接口中會暴露頂點處理程序和片段處理程序(感覺著色器聽起來也是云里霧里就換成處理程序),可是這其中gpu會進行光柵化插值計算,這也就是為什么片段處理程序的input是頂點處理程序的output經過了插值以后得到的值。既然光柵化是在頂點處理程序以后發生的步驟,那么輸入的頂點結構是經過頂點處理以后的,也就是進行過mvp變換,乘以透視矩陣之后的頂點,注意:這步還沒有做透視除法,光柵化插值發生在裁剪空間,絕不是標準化空間,所以頂點位置是四維齊次坐標不是三維坐標!
怎么實現光柵化方法?
首先我們能夠確定的是光柵化的輸入和輸出各自是啥。而且應該知道手上能夠是用的數據都是啥。
先對輸入的頂點進行處理變換到屏幕坐標,對把裁剪空間的頂點坐標轉換成標準化空間,就像這樣:
ndcA.x=clipA.x/clipA.w;
ndcA.y=clipA.y/clipA.w;
ndcB.x=clipB.x/clipB.w;
ndcB.y=clipB.y/clipB.w;
ndcC.x=clipC.x/clipC.w;
ndcC.y=clipC.y/clipC.w;
接著對頂點的標準坐標進行視口變換:”
viewPortTransform(face-》ndcA.x,face-》ndcA.y,fb-》width,fb-》height,scrAX,scrAY);
viewPortTransform(face-》ndcB.x,face-》ndcB.y,fb-》width,fb-》height,scrBX,scrBY);
viewPortTransform(face-》ndcC.x,face-》ndcC.y,fb-》width,fb-》height,scrCX,scrCY);
然后得到三個二維坐標代表三個頂點終于在屏幕上的位置,它們能夠組成一個二維三角形,求取三角形的包圍盒:”
int minX=max(0,min(scrAX,min(scrBX,scrCX)));
int maxX=min(fb-》width-1,max(scrAX,max(scrBX,scrCX)));
int minY=max(0,min(scrAY,min(scrBY,scrCY)));
int maxY=min(fb-》height-1,max(scrAY,max(scrBY,scrCY)));
要注意不要超過屏幕范圍,屏幕范圍以外的點都裁剪掉。
遍歷這個包圍盒,取得潛在可能片段的屏幕位置:
for(int scrX=minX;scrX《=maxX;scrX++) {
for(int scrY=minY;scrY《=maxY;scrY++) {
。。.。
}
}
分別求取片段相應的標準化空間坐標:
invViewPortTransform(scrX,scrY,fb-》width,fb-》height,ndcX,ndcY);
這里用了逆視口變換,視口變換和逆視口變換非常方便,僅僅要對坐標進行縮放和平移即可了。
那么我們得到了可能片段的標準化空間的x和y坐標,為什么是可能片段呢?由于如今還沒法確定這些片段在將要被光柵化三角形的外部還是內部,我們僅僅計算三角形內部的片段。
然而知道了這些有什么用呢?
這邊有一個公式能夠算出三個頂點對片段產生影響的比例,也叫權值:
這個公式的a b c分別代表三角形的三個頂點, ax ay aw 各自是頂點a在裁剪空間的齊次坐標(是四維的)的x y w值,這邊沒用到z值,由于z也要通過這個權值進行計算。
這個怎么推導這個公式?
已知待光柵化三角形abc的三個頂點在裁剪空間的齊次坐標,把權值alpha beta gamma設為pa pb pc,可得每一個片段的裁剪空間齊次坐標為:
x=pa*ax+pb*bx+pc*cx
y=pa*ay+pb*by+pc*cy
z=pa*az+pb*bz+pc*cz
w=pa*aw+pb*bw+pc*cw
然后計算片段在標準化坐標系的坐標值為:
nx=x/w
ny=y/w
nz=z/w
nw=1
能夠推得:
x=w*nx
y=w*ny
w=w
由于:
x=pa*ax+pb*bx+pc*cx
y=pa*ay+pb*by+pc*cy
w=pa*aw+pb*bw+pc*cw
轉換為3x3矩陣就是
ax bx cx pa w*nx ay by cy * pb = w*ny aw bw cw pc w
當中nx和ny就是之前取得的片段在標準化坐標系的x y值;而且因為pa pb pc是比值,所以w能夠去除;這樣僅僅要求取3x3矩陣的逆就能夠取得pa pb pc的值。
可是要注意pa+pb+pc=1,所以計算出值以后要進行例如以下處理:
float sum=pa+pb+pc;
pa/=sum; pb/=sum; pc/=sum;
然后把有比值小于0的片段拋棄:
if(pa《0||pb《0||pc《0)
continue;
接下來就能夠用這三個權值對頂點屬性進行插值運算了。
詳細的光柵化函數是這樣:
void rasterize(FrameBuffer* fb,DepthBuffer* db,FragmentShader fs,Face* face) {
float ndcX=0,ndcY=0,clipW=0;
int scrAX,scrAY,scrBX,scrBY,scrCX,scrCY;
viewPortTransform(face-》ndcA.x,face-》ndcA.y,fb-》width,fb-》height,scrAX,scrAY);
viewPortTransform(face-》ndcB.x,face-》ndcB.y,fb-》width,fb-》height,scrBX,scrBY);
viewPortTransform(face-》ndcC.x,face-》ndcC.y,fb-》width,fb-》height,scrCX,scrCY);
int minX=max(0,min(scrAX,min(scrBX,scrCX)));
int maxX=min(fb-》width-1,max(scrAX,max(scrBX,scrCX)));
int minY=max(0,min(scrAY,min(scrBY,scrCY)));
int maxY=min(fb-》height-1,max(scrAY,max(scrBY,scrCY)));
for(int scrX=minX;scrX《=maxX;scrX++) {
for(int scrY=minY;scrY《=maxY;scrY++) {
invViewPortTransform(scrX,scrY,fb-》width,fb-》height,ndcX,ndcY);
VECTOR4D ndcPixel(ndcX,ndcY,1,0);
VECTOR4D proportion4D=face-》clipMatrixInv*ndcPixel;
VECTOR3D proportionFragment(proportion4D.x,proportion4D.y,proportion4D.z);
float pa=proportionFragment.x;
float pb=proportionFragment.y;
float pc=proportionFragment.z;
float sum=pa+pb+pc;
pa/=sum; pb/=sum; pc/=sum;
if(pa《0||pb《0||pc《0)
continue;
Fragment frag;
interpolate3f(pa,pb,pc,face-》clipA.w,face-》clipB.w,face-》clipC.w,clipW);
interpolate3f(pa,pb,pc,face-》clipA.z,face-》clipB.z,face-》clipC.z,frag.ndcZ);
frag.ndcZ/=clipW;
if(frag.ndcZ《-1||frag.ndcZ》1)
continue;
if(db!=NULL) {
float storeZ=readDepth(db,scrX,scrY);
if(storeZ
interpolate3f(pa,pb,pc,face-》clipA.x,face-》clipB.x,face-》clipC.x,frag.ndcX);
frag.ndcX/=clipW;
interpolate3f(pa,pb,pc,face-》clipA.y,face-》clipB.y,face-》clipC.y,frag.ndcY);
frag.ndcY/=clipW;
interpolate3f(pa,pb,pc,face-》clipA.nx,face-》clipB.nx,face-》clipC.nx,frag.nx);
interpolate3f(pa,pb,pc,face-》clipA.ny,face-》clipB.ny,face-》clipC.ny,frag.ny);
interpolate3f(pa,pb,pc,face-》clipA.nz,face-》clipB.nz,face-》clipC.nz,frag.nz);
interpolate3f(pa,pb,pc,face-》clipA.s,face-》clipB.s,face-》clipC.s,frag.s);
interpolate3f(pa,pb,pc,face-》clipA.t,face-》clipB.t,face-》clipC.t,frag.t);
FragmentOut outFrag;
fs(frag,outFrag);
drawPixel(fb,scrX,scrY,outFrag.r,outFrag.g,outFrag.b);
}
}
}
光柵化完畢了,這下就能自己實現opengl和directx了!
評論
查看更多