之前用包围盒和重心坐标法做过光栅化实现,但是那个方法存在问题,这次要实现一个更高效的光栅化方法.
包围盒的问题
之前使用的包围盒方法确定光栅化范围,但是包围盒确定的范围是矩形,要光栅化的是三角形,那么每次都会有不需要的像素参加运算,一个面当然没啥问题,但是如果是100个面问题就大了.
改进方法
在 <3d游戏编程大师技巧> 中作者使用了三角形扫描线插值的方法,效率不错,实现方法类似下图:
通过计算三角形三条边的方程和每条扫描线的两个交点以确定光栅化x值的范围,然后就能够准确地进行光栅化计算,这样就不用考虑不可能到达的像素了.
实现方法
空间中直线有如下参数方程:
p=p0+(p1-p0)*t p是直线上的某一点,p0是直线向量起点,p1是直线向量的终点,如果t=0则点在p0上而t=1则点在p1上,所以如果要让目标点在线段p0p1上则t的取值范围应该是0到1.
扫描线是从三角形顶遍历到三角形底,从y0到y1,那么有类似如下方程
y=n n的取值范围从y0到y1.三条边的参数方程和扫描线方程联立方程组可以求出三个t,扫描线的左右边界应该处于线段之中,所以排除掉t在0到1范围之外的那个,留下其余两个作为左右边界点的t,代入直线方程求出x就可以确定扫描线的左右范围了.这是一般情况,还有平顶平底情况没有讨论,碰到这种情况不要求和扫描线平行的那个边的直线方程,求出另外两条边的直线方程,然后求出两个t和两个x就可以了,下面是具体实现:
if(scrAY==scrBY&&scrAY==scrCY) //屏幕坐标所有y都相等,直线不进行光栅化 return; int minY=max(0,min(scrAY,min(scrBY,scrCY))); //顶上的y0 int maxY=min(fb->height-1,max(scrAY,max(scrBY,scrCY))); //底部的y1 for(int scrY=minY;scrY<=maxY;scrY++) { float x1,x2; if(scrAY==scrBY) { //平顶平底情况 float paramAC=((float)scrY-scrAY)/(scrCY-scrAY); float paramBC=((float)scrY-scrBY)/(scrCY-scrBY); x1=(scrCX-scrAX)*paramAC+scrAX; x2=(scrCX-scrBX)*paramBC+scrBX; } else if(scrAY==scrCY) { //平顶平底情况 float paramAB=((float)scrY-scrAY)/(scrBY-scrAY); float paramBC=((float)scrY-scrBY)/(scrCY-scrBY); x1=(scrBX-scrAX)*paramAB+scrAX; x2=(scrCX-scrBX)*paramBC+scrBX; } else if(scrBY==scrCY) { //平顶平底情况 float paramAB=((float)scrY-scrAY)/(scrBY-scrAY); float paramAC=((float)scrY-scrAY)/(scrCY-scrAY); x1=(scrBX-scrAX)*paramAB+scrAX; x2=(scrCX-scrAX)*paramAC+scrAX; } else { //正常情况 float paramAB=((float)scrY-scrAY)/(scrBY-scrAY); float paramAC=((float)scrY-scrAY)/(scrCY-scrAY); float paramBC=((float)scrY-scrBY)/(scrCY-scrBY); bool ab=(paramAB<=1.0&¶mAB>=0.0)?true:false; bool ac=(paramAC<=1.0&¶mAC>=0.0)?true:false; bool bc=(paramBC<=1.0&¶mBC>=0.0)?true:false; float xAB=(scrBX-scrAX)*paramAB+scrAX; float xAC=(scrCX-scrAX)*paramAC+scrAX; float xBC=(scrCX-scrBX)*paramBC+scrBX; if(!ab) { x1=xAC; x2=xBC; } else if(!ac) { x1=xAB; x2=xBC; } else if(!bc) { x1=xAB; x2=xAC; } else { x1=xAC; x2=xBC; if(x1==x2) //排除两线交点的情况 x2=xAB; } } int minX=max(0,min(x1,x2)-0.5); //扫描线左边的x值 int maxX=min(fb->width-1,max(x1,x2)+0.5); //扫描线右边的x值
接下来的光栅化步骤和之前一样用的重心坐标法:
for(int scrX=minX;scrX<=maxX;scrX++) { 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; if(pa<0||pb<0||pc<0) continue; float sum=pa+pb+pc; pa/=sum; pb/=sum; pc/=sum; 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<frag.ndcZ) continue; writeDepth(db,scrX,scrY,frag.ndcZ); } 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.wx,face->clipB.wx,face->clipC.wx,frag.wx); interpolate3f(pa,pb,pc,face->clipA.wy,face->clipB.wy,face->clipC.wy,frag.wy); interpolate3f(pa,pb,pc,face->clipA.wz,face->clipB.wz,face->clipC.wz,frag.wz); interpolate3f(pa,pb,pc,face->clipA.ww,face->clipB.ww,face->clipC.ww,frag.ww); 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); }
使用这个方法可以避免进行不必要的光栅化运算,比之前的包围盒方法效率更高.
参考: <3d游戏编程大师技巧>
http://ocw.mit.edu/courses/electrical-engineering-and-computer-science/6-837-computer-graphics-fall-2012/lecture-notes/MIT6_837F12_Lec21.pdf
版权声明:本文为博主原创文章,未经博主允许不得转载。