D3D Frustum裁剪原理

来源:互联网 发布:mindmanager mac破解版 编辑:程序博客网 时间:2024/05/17 21:55

D3D <wbr>Frustum裁剪原理

概念介绍
Frustum裁剪是CLOD中很重要的一个算法,很多文章都是一句话就过去,或者直接给出代码。但是数学推导很少给出,本文章的目的就是解释大家看这些代码中的疑问。
透视投影是将相机空间中的点从视锥体(view frustum)变换到规则观察体(Canonical ViewVolume,CCV)中,即是世界空间的视锥体(viewfrustum)中的任何一个点,如果经过投影矩阵变换后,它必定规则观察体(Canonical ViewVolume,CCV)中,也即是在(-1,-1,0) ~ (1,1,1)之间的值如图1所示
这里 我们定义
P0=(-1,-1,0)      
P1=(1,-1,0)
P2=(1,-1,1)
P3=(-1,-1,1)
P4=(-1,1,0)
P5=(1,1,0)
P6=(1,1,1)
P7=(-1,1,1)
 
我们通过这8个点构建6个面,通过3点共面,假设面是Pos0,pos1,pos2构成,u=Pos1-Pos0,v=Pos2-Pos0,那么法向量n=u×v。则d=-(n×Pos0)。得到每个平面的平面公式n,d,从而得到A,B,C,D,(n的xn,yn,zn,d,就是a,b,c,d)。这里要注意构造面试后P点的顺序,d3D是按顺时针来构造面。法线都是由frustum里到外。
Near:   (P0,P4,P5)   n=(0,0,-1),d=0        0x+0y-1z+0=0       
Far:    (P2,P6,P7)   n=(0,0,1),d=-1        0x+0y+1z-1=0      
Left:   (P0,P3,P7)   n=(-1,0,0),d=-1       -1x+0y+0z-1=0      
Right:   (P1,P5,P6)   n=(1,0,0),d=-1        1x+0y+0z-1=0       
Top:    (P4,P7,P6)   n=(0,1,0),d=-1        0x+1y+0z-1=0       
Bottom:  (P0,P1,P2)  n=(0,-1,0),d=-1       0x-1y+0z-1=0       
我们假设这六个平面中某个平面上存在一个点P’(x’,y’,c’,1),那它的平面方程为A’x’ + B’y’+ C’z’+D’= 0,在进行投影变换之前的坐标为。投影变换前P(x,y,z,1),在世界空间中的平面方程Ax + By + Cz + D =0
我们假设这六个平面中某个平面上存在一个点P’(x’,y’,c’,1),那它的平面方程为A’x’ + B’y’+ C’z’+D’= 0,在进行投影变换之前的坐标为P(x,y,z,1),在世界空间中的平面方程Ax + By + Cz + D =0,如果P‘点在CVV中,那点P(x,y,z,1)必定在view Frustum中。

1.推理
我们知道在P(x,y,z,1)经过Tviewproj矩阵变换后得到点P’(x’,y’,z’,w’),这个点在前面的推导过程中,保证是在frustum的某个平面上的.这个时候我们知道如果对P的每个分量都除以w’就可以把P’归一化到一个长方体的空间,即x’/w’,y’/w’在[-1,1]区间,z’/w’在[0,1]区间,所以如果有一个投影转换后的点P1’,它的x1’,y1’在[-w’,w’]区间,z1’在[0,w’]区间,这个点肯定就在视锥体内.
假设Tviewproj =  [v0,v1,v2,v3] =
|a11,a12,a13,a14|
|a21,a22,a23,a24|
|a31,a32,a33,a34|
|a41,a42,a43,a44|
其中V0,v1,v2,v3是四个列向量.

P ’= P×Tviewproj = (P*v0,  P*v1, P*v2,  P*v3 ) =(x’,y’,z’,w’) 

根据上面的x的范围我们有: 
-w’ <= x’  就是 -P*v3<= P*v0 就是 P*v0 + P*v3 <= 0 得到 P*(v0+v3) <=0 

把列向量换成矩阵的元素有 
(x,y,z,1).(m_11 + m_14, m12 + m24, m13 + m34, m14 + m44 )<= 0 
就是 
(m_11+m_14)*x + (m_12 + m_24)*y + (m_13 + m_34)*z + (m_14 +m_44)*1 <=0 

简单地看这是一个 A*x + B*y + C*z + D*1 <= 0描述了一个半空间,就是平面A*x + B*y + C*z + D*1 =0右边的空间 
所以我们知道是锥体的左裁减面为
(m_11+m_14)*x + (m_12 + m_24)*y + (m_13 + m_34)*z + (m_14 +m_44)*1= 0. 
相应地可以计算出 6个裁减面 
Left= (v0 + v3).
Right =(v3 - v0) 
Bottom =(v3 + v1) 
Top = (v3 - v1 ) 
Near = (v2) 
Far = (v3 - v2) 
裁减的时候把点带入公式Ax+By+Cz+Dw看大与0还是小与0就可以知道在平面的里面还是外面,在实际计算中需要对A,B,C,D进行归一化。因为我们在处理坐标的时候,点几乎都是经过归一化的。
2.代码
void CFrustum::InitFrustum(constD3DXMATRIX& aoViewMatrix,constD3DXMATRIX& aoProjMatrix)
{   
D3DXMATRIX loComboMatrix;
D3DXMatrixMultiply(&loComboMatrix,&aoViewMatrix,&aoProjMatrix);
// calculate the planes
// Near
D3DXPLANE* lpPlane =&moFrustumPlane[FRUSTUM_PLANE_NEAR];
lpPlane->a = loComboMatrix._14 +loComboMatrix._13; 
lpPlane->b = loComboMatrix._24 +loComboMatrix._23;
lpPlane->c = loComboMatrix._34 +loComboMatrix._33;
lpPlane->d = loComboMatrix._44 +loComboMatrix._43;
D3DXPlaneNormalize(lpPlane,lpPlane);
// Far
lpPlane =&moFrustumPlane[FRUSTUM_PLANE_FAR];
lpPlane->a = loComboMatrix._14 -loComboMatrix._13; 
lpPlane->b = loComboMatrix._24 -loComboMatrix._23;
lpPlane->c = loComboMatrix._34 -loComboMatrix._33;
lpPlane->d = loComboMatrix._44 -loComboMatrix._43;
D3DXPlaneNormalize(lpPlane,lpPlane);
//Left
lpPlane =&moFrustumPlane[FRUSTUM_PLANE_LEFT];
lpPlane->a = loComboMatrix._14 +loComboMatrix._11; // Left
lpPlane->b = loComboMatrix._24 +loComboMatrix._21;
lpPlane->c = loComboMatrix._34 +loComboMatrix._31;
lpPlane->d = loComboMatrix._44 +loComboMatrix._41;
D3DXPlaneNormalize(lpPlane,lpPlane);
// Right
lpPlane =&moFrustumPlane[FRUSTUM_PLANE_RIGHT];
lpPlane->a = loComboMatrix._14 -loComboMatrix._11; 
lpPlane->b = loComboMatrix._24 -loComboMatrix._21;
lpPlane->c = loComboMatrix._34 -loComboMatrix._31;
lpPlane->d = loComboMatrix._44 -loComboMatrix._41;
D3DXPlaneNormalize(lpPlane,lpPlane);
// Top
lpPlane =&moFrustumPlane[FRUSTUM_PLANE_TOP];
lpPlane->a = loComboMatrix._14 -loComboMatrix._12; 
lpPlane->b = loComboMatrix._24 -loComboMatrix._22;
lpPlane->c = loComboMatrix._34 -loComboMatrix._32;
lpPlane->d = loComboMatrix._44 -loComboMatrix._42;
D3DXPlaneNormalize(lpPlane,lpPlane);
// Bottom
lpPlane =&moFrustumPlane[FRUSTUM_PLANE_BOTTOM];
lpPlane->a = loComboMatrix._14 +loComboMatrix._12; // Bottom
lpPlane->b = loComboMatrix._24 +loComboMatrix._22;
lpPlane->c = loComboMatrix._34 +loComboMatrix._32;
lpPlane->d = loComboMatrix._44 +loComboMatrix._42;
D3DXPlaneNormalize(lpPlane,lpPlane);
}
第2种方法在没有看见之间,先看见的是代码http://www.racer.nl/reference/vfc_markmorley.htm,自己想了好久都没考虑清楚。最后看见解释,鄙视自己的数学逻辑推理。3d看来困难的就在于思考方式,同样的代码处理,第二种方式明显快很多。
判断裁剪
我们已经有了裁剪体的方程,当我们需要裁剪一个顶点的时候,这六个方程已经足够了。但是我们要判断一个区域的可见性时,我们进行一些额外的计算。如图2所示,一个物体和投影体的关系大致可以分为:包围、被包围、相交和相离四种情况。图中最大的浅蓝色的矩形包围了整个投影体。深绿色的小矩形则完全被投影体包围。浅绿色的矩形和投影体相交。这三种情况下物体都是可以被看到的。剩下红色的矩形则和投影体相离、只有它完全不可见
 
图2
当处理节点的可见性的时候,由于节点的不规则性。我们还需要引入包围体的概念。所谓的包围体,就是用一个比较简单的几何体去度量另外一个比较复杂的几何体,让它刚好能包围另外一个几何体。比较合适的包围体外形有矩形、正方形和球体。其中球体处理最为简单,但是近似度也最差。我们为每一个节点都建立一个包围体,只要测试这个包围体,我们就可以决定一个节点的可见性,由于包围体肯定大于这个节点,因此我们可以保证不会有任何可见的节点被裁剪在投影体之外
BOOL CFrustum::PointInFrustum(float afX, float afY, float afZ )
{
// A*x+B*y+C*z+D = 0  is inplane,
for(int i = 0; i < FRUSTUM_PLANE_COUNT; i++)
{
D3DXPLANE &loPlane = moFrustumPlane[i];
//减少函数调用
//if(D3DXPlaneDotCoord(&moFrustumPlane[i],&D3DXVECTOR3(afX, afY, afZ)) <0.0f)
if(loPlane.a * afX + loPlane.b * afY + loPlane.c * afZ +loPlane.d < 0.0f)
return FALSE;

}
return TRUE;
}

BOOL CFrustum::SphereInFrustum(float afX, float afY, float afZ, float AfRadius )
{
// A*x+B*y+C*z+D = -radius  is inplane,
for(int i = 0; i < FRUSTUM_PLANE_COUNT; i++)
{
D3DXPLANE &loPlane = moFrustumPlane[i];
//减少函数调用,直接用公式运算
//if(D3DXPlaneDotCoord(&moFrustumPlane[i],&D3DXVECTOR3(afX, afY, afZ)) <-AfRadius)
if( loPlane.a * afX + loPlane.b * afY + loPlane.c * afZ +loPlane.d <= -AfRadius )
{
return false;
}
}
return TRUE;
}
BOOL CFrustum::CubeInFrustum(float afX, float afY, float afZ, float aiSize,BOOL& abIsCompletelyContained )
{
float lfAlfaX  = afX + aiSize;
float lfDeltaX = afX - aiSize;
float lfAlfaY  = afY + aiSize;
float lfDeltaY = afY - aiSize;
float lfAlfaZ  = afZ + aiSize;
float lfDeltaZ = afZ - aiSize;
DWORD ldwNumPointInFrustum = 0;
for(int i = 0; i < FRUSTUM_PLANE_COUNT; i++)
{
int j = 8;
BOOL lbIsInAllPlanes = TRUE;
D3DXPLANE &loPlane = moFrustumPlane[i];

//if(D3DXPlaneDotCoord(&loPlane[i],&D3DXVECTOR3(lfDeltaX,lfDeltaY, lfDeltaZ))< 0.0f) 
if(loPlane.a * lfDeltaX + loPlane.b * lfDeltaY + loPlane.c *lfDeltaZ + loPlane.d < 0.0f)
{
lbIsInAllPlanes = FALSE;
j--;
}

//if(D3DXPlaneDotCoord(&loPlane[i],&D3DXVECTOR3(lfAlfaX,lfDeltaY, lfDeltaZ))< 0.0f) 
if(loPlane.a * lfAlfaX + loPlane.b * lfDeltaY + loPlane.c *lfDeltaZ + loPlane.d < 0.0f)
{
lbIsInAllPlanes = FALSE;
j--;
}

//if(D3DXPlaneDotCoord(&loPlane[i],&D3DXVECTOR3(lfDeltaX,lfAlfaY, lfDeltaZ))< 0.0f) 
if(loPlane.a * lfDeltaX + loPlane.b * lfAlfaY + loPlane.c *lfDeltaZ + loPlane.d < 0.0f)
{
lbIsInAllPlanes = FALSE;
j--;
}

//if(D3DXPlaneDotCoord(&loPlane[i],&D3DXVECTOR3(lfAlfaX,lfAlfaY, lfAlfaZ))< 0.0f) 
if(loPlane.a * lfAlfaX + loPlane.b * lfAlfaY + loPlane.c *lfDeltaZ + loPlane.d < 0.0f)
{
lbIsInAllPlanes = FALSE;
j--;
}
//if(D3DXPlaneDotCoord(&loPlane[i],&D3DXVECTOR3(lfDeltaX,lfDeltaY, lfAlfaZ))< 0.0f) 
if(loPlane.a * lfDeltaX + loPlane.b * lfDeltaY + loPlane.c *lfAlfaZ + loPlane.d < 0.0f)
{
lbIsInAllPlanes = FALSE;
j--;
}
//if(D3DXPlaneDotCoord(&loPlane[i],&D3DXVECTOR3(lfAlfaX,lfDeltaY, lfAlfaZ))< 0.0f) 
if(loPlane.a * lfAlfaX + loPlane.b * lfDeltaY + loPlane.c *lfAlfaZ + loPlane.d < 0.0f)
{
lbIsInAllPlanes = FALSE;
j--;
}
//if(D3DXPlaneDotCoord(&loPlane[i],&D3DXVECTOR3(lfDeltaX,lfDeltaY, lfAlfaZ))< 0.0f) 
if(loPlane.a * lfDeltaX + loPlane.b * lfDeltaY + loPlane.c *lfAlfaZ + loPlane.d < 0.0f)
{
lbIsInAllPlanes = FALSE;
j--;
}

//if(D3DXPlaneDotCoord(&loPlane[i],&D3DXVECTOR3(lfAlfaX,lfAlfaY, lfAlfaZ))< 0.0f) 
if(loPlane.a * lfAlfaX + loPlane.b * lfAlfaY + loPlane.c *lfAlfaZ + loPlane.d < 0.0f)
{
lbIsInAllPlanes = FALSE;
j--;
}

// if none contained, return FALSE.
if(0 == j)
return FALSE;

// update counter if they were all in front of plane.
if(lbIsInAllPlanes)
++ldwNumPointInFrustum;
}

abIsCompletelyContained = (BOOL)(ldwNumPointInFrustum ==FRUSTUM_PLANE_COUNT);

return TRUE;
}

上面这些原理都来至于网络,一些细节是我在理解过程中自己整理,没有什么特别之处,主要是归类而已。在学习3D中一定要将原理吃透。这样当积累到一定的时候,你自己就会发现3D也不过如此,但关键自己一定要勤于动手和自己推理一遍


0 0
原创粉丝点击