近面裁剪

Posted yfceshi

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了近面裁剪相关的知识,希望对你有一定的参考价值。

近期在写软渲染的时候碰到了一个问题. 假设视图空间里顶点在视点之后直接乘以投影矩阵会错误发生. 如图中绿色的点直接乘以投影矩阵会被投影到紫黑色的点上明显就不正确了:

技术分享


所以须要又一次想个办法修补这个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);
	}
}

加大近裁剪面的值试试看效果怎样:

技术分享


正确裁剪,看来是成功了.





















以上是关于近面裁剪的主要内容,如果未能解决你的问题,请参考以下文章

在片段中裁剪图像

视频二次裁剪时间计算出原片的时间片段算法

视频二次裁剪时间计算出原片的时间片段算法

Android平台上裁剪m4a

怎么裁剪视频时长?建议收藏这些方法

Android:裁剪图像并在 ImageView 上显示