OpenGL视椎体 裁剪和剔除

毕设做的是《基于四叉树的LOD地形渲染》,其中主要参考了潘李亮的《基于LOD的大规模真实感室外场景试试渲染技术的初步研究》一文,如有有兴趣做四叉树LOD的话,建议读一读这篇文章。但是在这篇文章的视椎体裁剪部分,说的不是很清除,而且在求解视椎体六个面的方程和判断AABB包围盒的与视椎体是否相交的方法上,不知道是我的理解问题还是他的实现有问题,裁剪出的结果总是有问题,查阅资料后,给出我的详细实现。

视椎体六个面方程的求解

在进行视椎体体剔除时,要求出视椎体六个面在局部坐标系中的方程才能进行判断。求解视椎体六个面的方程这篇文章给出了非常好的说明。此处就直接以来上文中的方法来说明吧。在进行了投影变换后,视椎体从平截头体变成了一个正方体(NDC规范化坐标),正方体的xyzzuo坐标都是(-1,1)之间。

假设空间中一点P(x,y,z,1)经过这一系列变换变成P1(x1/w1,y1/w1,z1/w1,1) 并且假设ProjectMatrix左乘ModelViewMatrix得到的第一行是(a,b,c,d),那么有

a*x+b*y+c*z+d=x1

假设变换矩阵的最后一行(e,f,g,h),那么有

e*x+f*y+g*z+h=w1

因为P1在NDC坐标系,范围是-1到1,那么在视景体左面上的点都符合x1/w1=-1,式子可以变为x1+w1=0

代入之前的等式:

a*x+b*y+c*z+d+e*x+f*y+g*z+h=0

于是得到: 

(a+e)*x+(b+f)*y+(c+g)*z+(d+h)=0

一般的平面方程是:

A*x+B*y+C*z+D=0 (A,B,C)是平面的法向量

于是左侧平面的方程就是

(a+e)*x+(b+f)*y+(c+g)*z+(d+h)=0 其中的x,y,z就是空间中的点坐标。

以上是理论基础,具体各个面的方程计算方法如下(假设矩阵clip=project*view*model。下面的clip[n]代表clip矩阵的第几列,nomalize代表将向量进行归一化,即将向量的模长变为1):

上平面:nomalize(clip[3]-clip[1])

下平面:nomalize(clip[3]+clip[1])

左平面:nomalize(clip[3]+clip[0])

右平面:nomalize(clip[3]-clip[0])

近平面:nomalize(clip[3]+clip[2])

远平面:nomalize(clip[3]-clip[2])

注意,以上计算的得到的结果是一个四维向量,向量中的xyzw值分别代表一个平面方程Ax+By+Cz+D=0中的A、B、C、D的值。另外需要注意的是,平面方程Ax+By+Cz+D=0中的(A,B,C)代表的是该平面的法向量,而且通过上式计算出的法向量(A,B,C)都是指向视椎体内部的。(指向外部也可以只需要Ax+By+Cz+D=0变成-Ax-By-Cz-D=0即可,此时法向量就是(-A,-B,-C))。

视椎体裁剪

视椎体裁剪就是将不在视椎体中的物体剔除掉不绘制,而在视椎体内部或与视椎体相交的物体绘制的方法,此举对性能的提升有着巨大 的帮助。通常,物体是不规则的,为了方便,我们通常制作一个包围体,判断包围体与视椎体的关系。包围体一般有球体和长方体两种。球包围体的判断非常简单,只需要判断球心到视椎体每个面的距离与半径的关系,此处不予讨论。主要说明长方体的判断,这也正是我认为潘李亮大神论文中有错误的地方。

首先说明,如何判断一个点在平面的某一侧的方法:已知平面方程Ax+By+Cz+D=0,将一个点P(x,1y1,z1)的坐标带入平面方程得r=Ax1+By1+Cz1+D,如果计算结果r>0,,则表示P在法向量(A,B,C)指向的一侧,同理,r<0,表示在法向量的另一侧。潘李亮大神的判断正是基于此,将长方体包围盒的顶点带入视椎体六个面的方程,如果计算得到的六个值都大于0,则表示该点在视椎体内。通过这种方法,只要长方体包围盒的8个顶点有一个顶点在视椎体内,就说明实际物体模型能看得见,就需要绘制。但是这个方法有一个致命的缺陷,就是有些长方体的8个顶点全在视椎体外,但长形体仍然与视椎体有交集,不能被剔除,如下图所示,黄色长方体不应该剔除,但是用上述方法就会被错误剔除。另一种情况是,长方体包围盒很大,视椎体在长方体内部,也会被错误剔除,我用这种方法就是有一些地形块被错误剔除渲染不出来。


下面说查阅到的正确方法:判断时,只有当长方体所有的顶点位于同一平面的外侧时,才认为这个长方体包围盒在视椎体外面。这样会将一些在视椎体外面的包围盒错误的判断成在与视椎体有交集,但是数量很少,可以接受。下面给出程序实现:

bool IsVisable()
{
	glm::vec3 coner[8];//通过xyz最大最小值构造AABB包围盒
	coner[0].x = minx; coner[0].y = miny; coner[0].z = minz;
	coner[1].x = maxx; coner[1].y = miny; coner[1].z = minz;
	coner[2].x = maxx; coner[2].y = miny; coner[2].z = maxz;
	coner[3].x = minx; coner[3].y = miny; coner[3].z = maxz;
	coner[4].x = minx; coner[4].y = maxy; coner[4].z = minz;
	coner[5].x = maxx; coner[5].y = maxy; coner[5].z = minz;
	coner[6].x = maxx; coner[6].y = maxy; coner[6].z = maxz;  
	coner[7].x = minx; coner[7].y = maxy; coner[7].z = maxz;
	int in,out;
	
	for (int i = 0; i < 6;i++)
	{
		in = out = 0;
		for (int k = 0; k < 8 && (in == 0 || out == 0);k++)
		{
			if ((m_frustum[i].x*coner[k].x + m_frustum[i].y* coner[k].y + m_frustum[i].z*coner[k].z + 

m_frustum[i].w)>=0)
				in++;
			else
				out++;
		}
		if (!in)
			return false;
		else
			return true;
	}
}

这样,视椎体裁剪就完成了,如有错误,欢迎批评指正。

毕设快做完了,其中遇到了挺多问题,等答辩结束了将整个四叉树LOD的构造过程写一个系列博客吧。


猜你喜欢

转载自blog.csdn.net/qq_31709249/article/details/80175119