这次来讨论一下视景体剔除的一些技巧,视景体在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