首页 > 代码库 > 视景体方程与剔除

视景体方程与剔除

这次来讨论一下视景体剔除的一些技巧,视景体在3d程序中是个很重要的概念,我们可以把视景体看作是一台摄像机也就是肉眼能够看到的空间.视景体以外的东西是看不到的,可以利用这点来剔除多余的模型,于是不必要的顶点将不会进入管线,这能够提高不少fps.

首先来看一下视景体是个啥东西




就是这么个六面棱锥,这是经过视图变换的样子,那么接下来应该进行投影变换,那么视景体就会变成这样




是一个立方体,范围是-1到1.(NDC坐标系)

假设空间中一点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就是空间中的点坐标

那么对应的代码就是:

    mFrustum[1][0] = clip[ 3] + clip[ 0];
    mFrustum[1][1] = clip[ 7] + clip[ 4];
    mFrustum[1][2] = clip[11] + clip[ 8];
    mFrustum[1][3] = clip[15] + clip[12];

其中的clip就是变换矩阵ModelViewProjectMatrix

clips=projs*modls;

然后对平面方程进行发现归一化,这对于求点到面的距离有好处:

	t = GLfloat(sqrt( mFrustum[0][0] * mFrustum[0][0] + mFrustum[0][1] * mFrustum[0][1] + mFrustum[0][2] * mFrustum[0][2] ));
	mFrustum[0][0] /= t;
	mFrustum[0][1] /= t;
	mFrustum[0][2] /= t;
	mFrustum[0][3] /= t;

其它5个面也这样推导,最终的代码是这样的:

GLfloat mFrustum[6][4];

void updateFrustum() {
	MATRIX4X4 clips;
	MATRIX4X4 projs;
	MATRIX4X4 modls;
	GLfloat   t;

	glGetFloatv( GL_PROJECTION_MATRIX, projs );
	glGetFloatv( GL_MODELVIEW_MATRIX, modls );

	clips=projs*modls;

	GLfloat   clip[16];
	for(int i=0;i<16;i++)
		clip[i]=clips.entries[i];

	/* Extract the numbers for the RIGHT plane */
	mFrustum[0][0] = clip[ 3] - clip[ 0];
	mFrustum[0][1] = clip[ 7] - clip[ 4];
	mFrustum[0][2] = clip[11] - clip[ 8];
	mFrustum[0][3] = clip[15] - clip[12];

	/* Normalize the result */
	t = GLfloat(sqrt( mFrustum[0][0] * mFrustum[0][0] + mFrustum[0][1] * mFrustum[0][1] + mFrustum[0][2] * mFrustum[0][2] ));
	mFrustum[0][0] /= t;
	mFrustum[0][1] /= t;
	mFrustum[0][2] /= t;
	mFrustum[0][3] /= t;

    /* Extract the numbers for the LEFT plane */
    mFrustum[1][0] = clip[ 3] + clip[ 0];
    mFrustum[1][1] = clip[ 7] + clip[ 4];
    mFrustum[1][2] = clip[11] + clip[ 8];
    mFrustum[1][3] = clip[15] + clip[12];

    /* Normalize the result */
    t = GLfloat(sqrt( mFrustum[1][0] * mFrustum[1][0] + mFrustum[1][1] * mFrustum[1][1] + mFrustum[1][2] * mFrustum[1][2] ));
    mFrustum[1][0] /= t;
    mFrustum[1][1] /= t;
    mFrustum[1][2] /= t;
    mFrustum[1][3] /= t;

	/* Extract the BOTTOM plane */
    mFrustum[2][0] = clip[ 3] + clip[ 1];
    mFrustum[2][1] = clip[ 7] + clip[ 5];
    mFrustum[2][2] = clip[11] + clip[ 9];
    mFrustum[2][3] = clip[15] + clip[13];

    /* Normalize the result */
    t = GLfloat(sqrt( mFrustum[2][0] * mFrustum[2][0] + mFrustum[2][1] * mFrustum[2][1] + mFrustum[2][2] * mFrustum[2][2] ));
    mFrustum[2][0] /= t;
    mFrustum[2][1] /= t;
    mFrustum[2][2] /= t;
    mFrustum[2][3] /= t;

    /* Extract the TOP plane */
    mFrustum[3][0] = clip[ 3] - clip[ 1];
    mFrustum[3][1] = clip[ 7] - clip[ 5];
    mFrustum[3][2] = clip[11] - clip[ 9];
    mFrustum[3][3] = clip[15] - clip[13];

    /* Normalize the result */
    t = GLfloat(sqrt( mFrustum[3][0] * mFrustum[3][0] + mFrustum[3][1] * mFrustum[3][1] + mFrustum[3][2] * mFrustum[3][2] ));
    mFrustum[3][0] /= t;
    mFrustum[3][1] /= t;
    mFrustum[3][2] /= t;
    mFrustum[3][3] /= t;

    /* Extract the FAR plane */
    mFrustum[4][0] = clip[ 3] - clip[ 2];
    mFrustum[4][1] = clip[ 7] - clip[ 6];
    mFrustum[4][2] = clip[11] - clip[10];
    mFrustum[4][3] = clip[15] - clip[14];

    /* Normalize the result */
    t = GLfloat(sqrt( mFrustum[4][0] * mFrustum[4][0] + mFrustum[4][1] * mFrustum[4][1] + mFrustum[4][2] * mFrustum[4][2] ));
    mFrustum[4][0] /= t;
    mFrustum[4][1] /= t;
    mFrustum[4][2] /= t;
    mFrustum[4][3] /= t;

    /* Extract the NEAR plane */
    mFrustum[5][0] = clip[ 3] + clip[ 2];
    mFrustum[5][1] = clip[ 7] + clip[ 6];
    mFrustum[5][2] = clip[11] + clip[10];
    mFrustum[5][3] = clip[15] + clip[14];

    /* Normalize the result */
    t = GLfloat(sqrt( mFrustum[5][0] * mFrustum[5][0] + mFrustum[5][1] * mFrustum[5][1] + mFrustum[5][2] * mFrustum[5][2] ));
    mFrustum[5][0] /= t;
    mFrustum[5][1] /= t;
    mFrustum[5][2] /= t;
    mFrustum[5][3] /= t;
}

然后判断球体在视景体内部还是外部,根据点到平面的距离公式计算

D=a*x+b*y+c*z-(a*xp+b*yp+c*zp) (xp,yp,zp)是面上的点,且平面方程是经过法线规范化处理的.

a*xp+b*yp+c*zp=-d

那么公式变为

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

D是有正负的,正距离是与法向量同向,负距离与法向量反向.

那么判断是否在视景体内部的代码如下:

bool sphereInFrustum(float x,float y,float z,float radius) {
	for(int i = 0; i < 6; i++) {
		if(mFrustum[i][0] * x + mFrustum[i][1] * y + mFrustum[i][2] * z + mFrustum[i][3] <= -radius)
			return false;
	}

	return true;
}


在每一帧都调用updateFrustum()就能够更新视景体平面,然后把模型的中心,大致半径作为变量调用sphereInFrustum即可判断模型是否能够看得见.

赶紧构造视景体,尽情地剔除看不见的东西吧!


参考:

http://www.opengl-tutorial.org/beginners-tutorials/tutorial-3-matrices/

http://www.cnblogs.com/summericeyl/archive/2011/09/30/2196284.html

http://songho.ca/math/plane/plane.html

视景体方程与剔除