光柵化是在計(jì)算機(jī)上生成圖像的重要步驟,然而無論是opengl還是directx還是其他的圖形接口都封裝了光柵化方法。我自己做了個(gè)光柵器,接下來就說一下怎樣實(shí)現(xiàn)光柵化的。
為什么要光柵化?
圖形管線的輸入是圖元頂點(diǎn),輸出的則是像素(pixel),這個(gè)步驟其中還有個(gè)中間產(chǎn)物叫做片段(fragment),一個(gè)片段相應(yīng)一個(gè)像素,但片段比像素多了用于計(jì)算的屬性,比如:深度值和法向量。通過片段能夠計(jì)算出終于將要生成像素的顏色值,我們把輸入頂點(diǎn)計(jì)算片段的過程叫作光柵化。為什么要光柵化?由于要生成用以計(jì)算終于顏色的片段。
光柵化的輸入和輸出各自是啥?
和普通函數(shù)一樣,光柵化函數(shù)也須要輸入和輸出,從之前的定義來看函數(shù)的輸入就是組成圖元的頂點(diǎn)結(jié)構(gòu),輸出的就是片段結(jié)構(gòu),為什么說是結(jié)構(gòu)?由于這些能夠用c語(yǔ)言中的struct描寫敘述。
光柵化發(fā)生在哪一步?
通常在圖形接口中會(huì)暴露頂點(diǎn)處理程序和片段處理程序(感覺著色器聽起來也是云里霧里就換成處理程序),可是這其中gpu會(huì)進(jìn)行光柵化插值計(jì)算,這也就是為什么片段處理程序的input是頂點(diǎn)處理程序的output經(jīng)過了插值以后得到的值。既然光柵化是在頂點(diǎn)處理程序以后發(fā)生的步驟,那么輸入的頂點(diǎn)結(jié)構(gòu)是經(jīng)過頂點(diǎn)處理以后的,也就是進(jìn)行過mvp變換,乘以透視矩陣之后的頂點(diǎn),注意:這步還沒有做透視除法,光柵化插值發(fā)生在裁剪空間,絕不是標(biāo)準(zhǔn)化空間,所以頂點(diǎn)位置是四維齊次坐標(biāo)不是三維坐標(biāo)!
怎么實(shí)現(xiàn)光柵化方法?
首先我們能夠確定的是光柵化的輸入和輸出各自是啥。而且應(yīng)該知道手上能夠是用的數(shù)據(jù)都是啥。
先對(duì)輸入的頂點(diǎn)進(jìn)行處理變換到屏幕坐標(biāo),對(duì)把裁剪空間的頂點(diǎn)坐標(biāo)轉(zhuǎn)換成標(biāo)準(zhǔn)化空間,就像這樣:
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;
接著對(duì)頂點(diǎn)的標(biāo)準(zhǔn)坐標(biāo)進(jìn)行視口變換:”
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);
然后得到三個(gè)二維坐標(biāo)代表三個(gè)頂點(diǎn)終于在屏幕上的位置,它們能夠組成一個(gè)二維三角形,求取三角形的包圍盒:”
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)));
要注意不要超過屏幕范圍,屏幕范圍以外的點(diǎn)都裁剪掉。
遍歷這個(gè)包圍盒,取得潛在可能片段的屏幕位置:
for(int scrX=minX;scrX《=maxX;scrX++) {
for(int scrY=minY;scrY《=maxY;scrY++) {
。。.。
}
}
分別求取片段相應(yīng)的標(biāo)準(zhǔn)化空間坐標(biāo):
invViewPortTransform(scrX,scrY,fb-》width,fb-》height,ndcX,ndcY);
這里用了逆視口變換,視口變換和逆視口變換非常方便,僅僅要對(duì)坐標(biāo)進(jìn)行縮放和平移即可了。
那么我們得到了可能片段的標(biāo)準(zhǔn)化空間的x和y坐標(biāo),為什么是可能片段呢?由于如今還沒法確定這些片段在將要被光柵化三角形的外部還是內(nèi)部,我們僅僅計(jì)算三角形內(nèi)部的片段。
然而知道了這些有什么用呢?
這邊有一個(gè)公式能夠算出三個(gè)頂點(diǎn)對(duì)片段產(chǎn)生影響的比例,也叫權(quán)值:
這個(gè)公式的a b c分別代表三角形的三個(gè)頂點(diǎn), ax ay aw 各自是頂點(diǎn)a在裁剪空間的齊次坐標(biāo)(是四維的)的x y w值,這邊沒用到z值,由于z也要通過這個(gè)權(quán)值進(jìn)行計(jì)算。
這個(gè)怎么推導(dǎo)這個(gè)公式?
已知待光柵化三角形abc的三個(gè)頂點(diǎn)在裁剪空間的齊次坐標(biāo),把權(quán)值alpha beta gamma設(shè)為pa pb pc,可得每一個(gè)片段的裁剪空間齊次坐標(biāo)為:
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
然后計(jì)算片段在標(biāo)準(zhǔn)化坐標(biāo)系的坐標(biāo)值為:
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
轉(zhuǎn)換為3x3矩陣就是
ax bx cx pa w*nx ay by cy * pb = w*ny aw bw cw pc w
當(dāng)中nx和ny就是之前取得的片段在標(biāo)準(zhǔn)化坐標(biāo)系的x y值;而且因?yàn)閜a pb pc是比值,所以w能夠去除;這樣僅僅要求取3x3矩陣的逆就能夠取得pa pb pc的值。
可是要注意pa+pb+pc=1,所以計(jì)算出值以后要進(jìn)行例如以下處理:
float sum=pa+pb+pc;
pa/=sum; pb/=sum; pc/=sum;
然后把有比值小于0的片段拋棄:
if(pa《0||pb《0||pc《0)
continue;
接下來就能夠用這三個(gè)權(quán)值對(duì)頂點(diǎn)屬性進(jìn)行插值運(yùn)算了。
詳細(xì)的光柵化函數(shù)是這樣:
void rasterize(FrameBuffer* fb,DepthBuffer* db,F(xiàn)ragmentShader fs,F(xiàn)ace* 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);
}
}
}
光柵化完畢了,這下就能自己實(shí)現(xiàn)opengl和directx了!
評(píng)論
查看更多