您的位置:首页 > 其它

近面裁剪

2015-07-11 16:48 477 查看
最近在写软渲染的时候碰到了一个问题. 如果视图空间里顶点在视点之后直接乘以投影矩阵会发生错误. 如图中绿色的点直接乘以投影矩阵会被投影到紫黑色的点上明显就不对了:



所以需要重新想个办法修补这个bug,由于近裁剪面后面的点都是看不到的,可以将这些点裁剪掉,并且生成新的顶点,就如下图这样:



还有一种两个点在近裁剪面后面的情况:



紫色的两个点就是需要重新生成的新顶点,然后将红色的顶点和紫色的顶点组成新面参与之后的光栅化步骤. 新顶点在视图坐标系的位置可以求解两条直线和z=-clipNear平面的交点得到,顶点的属性值可以通过和直线的头尾顶点进行插值得到. 一个面中的一个顶点在近裁剪面后将生成两个新面,两个顶点在近裁剪面后将生成一个新面,抛弃老的面,把新面提交给管线.
由于已知最多会生成两个面,预先分配两个面的空间,这样就不必频繁的操作内存了:

void initFixFace() {
nFace1=new Face();
nFace2=new Face();
}
void releaseFixFace() {
delete nFace1;
delete nFace2;
}


接下来检测顶点在近裁剪面之后的情况:

int checkFace(Face* face) {
bool failA=false,failB=false,failC=false;
int nFail=0;
if(face->clipA.vz/face->clipA.vw>-clipNear) {
failA=true;
nFail++;
}
if(face->clipB.vz/face->clipB.vw>-clipNear) {
failB=true;
nFail++;
}
if(face->clipC.vz/face->clipC.vw>-clipNear) {
failC=true;
nFail++;
}

if(nFail==3)
return 000;
else if(nFail==0)
return 111;
else if(nFail==2) {
if(failA&&failB)
return 001;
else if(failA&&failC)
return 010;
else if(failB&&failC)
return 100;
} else if(nFail==1) {
if(failA)
return 011;
else if(failB)
return 101;
else if(failC)
return 110;
}
return 000;
}


这边顶点都是视图空间坐标,而且是三维坐标,所以应该除以齐次坐标w分量转到三维坐标.

由于有两种情况,这边可以定义两个修补函数:

void fix1FailFace(VertexOut fail,VertexOut succ1,VertexOut succ2);
void fix2FailFace(VertexOut fail1,VertexOut fail2,VertexOut succ);


fail开头指在近裁剪面后的点,succ开头指在近裁剪面前的点.

然后根据之前的检测情况分别调用不同的修补函数:

void fixFaces(Face* face,int fixFlag) {
switch(fixFlag) {
case 011:
fix1FailFace(face->clipA,face->clipB,face->clipC);
break;
case 101:
fix1FailFace(face->clipB,face->clipA,face->clipC);
break;
case 110:
fix1FailFace(face->clipC,face->clipA,face->clipB);
break;
case 001:
fix2FailFace(face->clipA,face->clipB,face->clipC);
break;
case 010:
fix2FailFace(face->clipA,face->clipC,face->clipB);
break;
case 100:
fix2FailFace(face->clipB,face->clipC,face->clipA);
break;
}
}


现在看一下修补函数的具体实现.
首先利用直线的参数方程和平面方程求出交点:

float z=-clipNear;
VECTOR3D pFail(fail.vx/fail.vw,fail.vy/fail.vw,fail.vz/fail.vw);
VECTOR3D pSucc1(succ1.vx/succ1.vw,succ1.vy/succ1.vw,succ1.vz/succ1.vw);
VECTOR3D pSucc2(succ2.vx/succ2.vw,succ2.vy/succ2.vw,succ2.vz/succ2.vw);
float param1=calcZPara(pFail.z,pSucc1.z,z);
VECTOR3D interPoint1=calcParaEqu(pFail,pSucc1,param1);


求参数和解方程的函数这么实现:

float calcZPara(float v1z,float v2z,float z) {
return (z-v2z)/(v1z-v2z);
}

VECTOR3D calcParaEqu(VECTOR3D vect1,VECTOR3D vect2,float param) {
VECTOR3D result;
result=param*(vect1-vect2)+vect2;
return result;
}


然后分别求出交点到两顶点之间的距离,然后分别缩放至两者和为1的情况:

float sp=(pFail-interPoint1).GetLength();
float fp=(pSucc1-interPoint1).GetLength();
float sum=sp+fp;
sp/=sum; fp/=sum;


然后插值算出新顶点的属性:

VertexOut inter1;
interpolate2v(sp,fp,succ1,fail,inter1);


插值算法这么实现:

void interpolate2f(float pa,float pb,
float a,float b,
float& result) {
result=pa*a+pb*b;
}

void interpolate2v(float pa,float pb,
VertexOut a,VertexOut b,
VertexOut& result) {
interpolate2f(pa,pb,a.x,b.x,result.x);
interpolate2f(pa,pb,a.y,b.y,result.y);
interpolate2f(pa,pb,a.z,b.z,result.z);
interpolate2f(pa,pb,a.w,b.w,result.w);
interpolate2f(pa,pb,a.wx,b.wx,result.wx);
interpolate2f(pa,pb,a.wy,b.wy,result.wy);
interpolate2f(pa,pb,a.wz,b.wz,result.wz);
interpolate2f(pa,pb,a.ww,b.ww,result.ww);
interpolate2f(pa,pb,a.vx,b.vx,result.vx);
interpolate2f(pa,pb,a.vy,b.vy,result.vy);
interpolate2f(pa,pb,a.vz,b.vz,result.vz);
interpolate2f(pa,pb,a.vw,b.vw,result.vw);
interpolate2f(pa,pb,a.nx,b.nx,result.nx);
interpolate2f(pa,pb,a.ny,b.ny,result.ny);
interpolate2f(pa,pb,a.nz,b.nz,result.nz);
interpolate2f(pa,pb,a.s,b.s,result.s);
interpolate2f(pa,pb,a.t,b.t,result.t);
}


接着生成另外一个顶点,方法类似.

完整修补方法实现如下:

void fix1FailFace(VertexOut fail,VertexOut succ1,VertexOut succ2) {
float z=-clipNear;
VECTOR3D pFail(fail.vx/fail.vw,fail.vy/fail.vw,fail.vz/fail.vw);
VECTOR3D pSucc1(succ1.vx/succ1.vw,succ1.vy/succ1.vw,succ1.vz/succ1.vw);
VECTOR3D pSucc2(succ2.vx/succ2.vw,succ2.vy/succ2.vw,succ2.vz/succ2.vw);
float param1=calcZPara(pFail.z,pSucc1.z,z);
VECTOR3D interPoint1=calcParaEqu(pFail,pSucc1,param1);
float sp=(pFail-interPoint1).GetLength();
float fp=(pSucc1-interPoint1).GetLength();
float sum=sp+fp;
sp/=sum; fp/=sum;
VertexOut inter1;
interpolate2v(sp,fp,succ1,fail,inter1);

float param2=calcZPara(pFail.z,pSucc2.z,z);
VECTOR3D interPoint2=calcParaEqu(pFail,pSucc2,param2);
sp=(pFail-interPoint2).GetLength();
fp=(pSucc2-interPoint2).GetLength();
sum=sp+fp;
sp/=sum; fp/=sum;
VertexOut inter2;
interpolate2v(sp,fp,succ2,fail,inter2);

nFace1->copy2FaceOut(succ1,inter1,inter2);
nFace2->copy2FaceOut(succ2,succ1,inter2);
}
void fix2FailFace(VertexOut fail1,VertexOut fail2,VertexOut succ) {
float z=-clipNear;
VECTOR3D pFail1(fail1.vx/fail1.vw,fail1.vy/fail1.vw,fail1.vz/fail1.vw);
VECTOR3D pFail2(fail2.vx/fail2.vw,fail2.vy/fail2.vw,fail2.vz/fail2.vw);
VECTOR3D pSucc(succ.vx/succ.vw,succ.vy/succ.vw,succ.vz/succ.vw);
float param1=calcZPara(pFail1.z,pSucc.z,z);
VECTOR3D interPoint1=calcParaEqu(pFail1,pSucc,param1);
float sp=(pFail1-interPoint1).GetLength();
float fp=(pSucc-interPoint1).GetLength();
float sum=sp+fp;
sp/=sum; fp/=sum;
VertexOut inter1;
interpolate2v(sp,fp,succ,fail1,inter1);

float param2=calcZPara(pFail2.z,pSucc.z,z);
VECTOR3D interPoint2=calcParaEqu(pFail2,pSucc,param2);
sp=(pFail2-interPoint2).GetLength();
fp=(pSucc-interPoint2).GetLength();
sum=sp+fp;
sp/=sum; fp/=sum;
VertexOut inter2;
interpolate2v(sp,fp,succ,fail2,inter2);

nFace1->copy2FaceOut(succ,inter1,inter2);
}


由于生成的顶点可以是已经经过顶点处理器计算的顶点,所以不需要在走一遍顶点处理器. 于是渲染的时候就像这么调用:

void drawFace(FrameBuffer* fb,DepthBuffer* db,VertexShader vs,FragmentShader fs,int cullFlag,Face* face) {
vs(face->modelA,face->clipA);
vs(face->modelB,face->clipB);
vs(face->modelC,face->clipC);
if(cullFace(face,cullFlag))
return;
int clipFlag=checkFace(face);
if(clipFlag!=111) {
if(clipFlag==000)
return;
fixFaces(face,clipFlag);
if(!cullFace(nFace1,cullFlag)) {
nFace1->calculateClipMatrixInv();
nFace1->calculateNDCVertex();
rasterize(fb,db,fs,nFace1);
}
if(clipFlag==011||clipFlag==101||clipFlag==110) {
if(!cullFace(nFace2,cullFlag)) {
nFace2->calculateClipMatrixInv();
nFace2->calculateNDCVertex();
rasterize(fb,db,fs,nFace2);
}
}
} else if(clipFlag==111) {
face->calculateClipMatrixInv();
face->calculateNDCVertex();
rasterize(fb,db,fs,face);
}
}


加大近裁剪面的值试试看效果如何:



正确裁剪,看来是成功了.
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: