40. D3D平面

平面是一个无限扩展的区域。读者可以将其形象化为沿着上下两个方向无限扩展的平面墙。如果很难理解平面这个概念,那么试着考虑一下宇宙。想象一样,宇宙非常平而且沿着所有其他边不断无限扩展的情况。

       虽然屏幕区域有限,但可以在屏幕上绘制平面,因为只要对有限数量的像素做阴影处理即可。然而,在Direct3D和OpenGL中并没有内置渲染平面的方法。所以,如果不能渲染平面,那就会出现问题,即平面到底对谁有好处?关于这个问题,答案有很多。最常见的一个答案就是几何图形选择和碰撞检测

有了几何图形选择,就可以创建一个平面或一系列平面,而这取决于几何图形位于平面的哪一侧,并可以快速确定是否需要绘制该对象。因为这可以很大程度地加速程序的渲染次数,所以这种方法很有用。使用选择法通过几个快速数学运算,就可以快速拒绝将要发送给渲染API的几何图形。如果场景中有数十、数百或是更多的浏览者不可见的对象,那么使用选择法就可以节省大量的时间和处理操作。另一方面,如果真正要渲染场景中的每个几何图形,而不考虑它是否可见,那就会注意到程序帧数显著下降。下面的公式是从数学上定义一个平面:
V*N + D = 0

       方程中的D代表平面到原点的距离。变量V代表位于平面某个位置上的点。该点可以在平面的任意位置上。变量N是平面法线。平面法线是与平面正交的矢量,它朝向平面前面的方向。利用该信息可以确定平面的哪一侧是前面,哪一侧是后面。这样就可以测试一个点或其他几何图形是在平面前面、后面或是在平面扩展处。如果某个视角周围有6个平面,分别代表左、右、上、下、远、近,那么就可以描述对象可见、不可见或部分可见。此时,可以拒绝或渲染测试后的几何图形。对大场景而言,这样做可以节省大量的时间。

 平面类中的成员变量并不多,所需的全部成员变量就是平面的法线(由x、y、z变成了a、b、c)和平面距离。这意味着屏幕结构主要由4个浮点变量a、b、c、d构成。

class CPlane
{
public:
CPlane();
CPlane(
float A, float B, float C, float D);

void CreatePlaneFromTri(Vector3D &v1, Vector3D &v2,
Vector3D
&v3);

bool Intersect(CPlane &p1, Vector3D *point);
bool Intersect(CAabb &aabb);

int ClassifyPolygon(CPolygon &pol);
int ClassifyPoint(Vector3D &v);

float GetDistance(Vector3D &v);

float a, b, c, d;
};

  

CPlane::CPlane()
{
a
= 0;
b
= 0;
c
= 0;
d
= 0;
}


CPlane::CPlane(
float A, float B, float C, float D)
{
a
= A;
b
= B;
c
= C;
d
= D;
}

  平面类中的下一个函数是CreatePlaneFromTri()。该函数利用三角形信息计算平面法线和距离。该函数以构成三角形的三个顶点为参数。CreatePlaneFromTri()函数内部计算三角形的法线,并将三角形的x、y、z成员变量设为平面的a、b、c成员变量。距离d通过计算平面法线和第一个顶点的点积,并对该点积求反得到。CreatePlaneFromTri()函数如程序

void CPlane::CreatePlaneFromTri(Vector3D &v1, Vector3D &v2, Vector3D &v3)
{
Vector3D normal, e1, e2;

e1
= v3 - v1;
e2
= v2 - v1;

e1.Normal();
e2.Normal();

normal.CrossProduct(e1, e2);
normal.Normal();

a
= normal.x;
b
= normal.y;
c
= normal.z;
d
= - (a * tril.x + b * tril.y + c * tril.z);
}

  

   平面类中的下一个函数是第一个交叉点函数。Intersect()函数用于测试两个平面之间是否相交。如果这两个平面相交,函数返回true(真)及相交的交叉点。该函数的参数包括第二个平面对象,以及如果要和平面相交将要存储交叉的矢量对象上的点。测试两个平面是否相交的Intersect()函数的完整代码如程序清单

bool CPlane::Intersect(CPlane &pl, Vector3D *intersectPoint)
{
Vector3D cross;
Vector3D normal(a, b, c);
Vector3D plNormal(pl.a, pl.b, pl.c);
float length = 0;

cross.CrossProduct(normal, plNormal);
length
= cross.DotProduct3(cross);

if(length < 1e-08f) return false;

if(intersectPoint)
{
float l0 = normal.DotProduct3(normal);
float l1 = normal.DotProduct3(plNormal);
float l2 = plNormal.DotProduct3(plNormal);
float det = l0 * l2 - l1 * l1;
float invDet = 0;

if(fabs(det) < 1e-08f) return false;

invDet
= 1 / det;
float d0 = (l2 * d - l1 * pl.d) * invDet;
float d1 = (l0 * pl.d - l1 * d) * invDet;

(
*intersectPoint) = normal * d0 + plNormal * d1;
}

return true;
}

  平面类中的另一个交叉函数是重载的Intersect()函数。该函数测试平面是否和轴对称的边界框相交。这将在本书稍后的BSP树演示程序中派上用场。

bool CPlane::Intersect(CAabb &aabb)
{
Vector3D min, max;
Vector3D normal(a, b, c);

if (normal.x >= 0.0f)
{
min.x
= aabb.m_min.x;
max.x
= aabb.m_max.x;
}
else
{
min.x
= aabb.m_max.x;
max.x
= aabb.m_min.x;
}

if (normal.y >= 0.0f)
{
min.y
= aabb.m_min.y;
max.y
= aabb.m_max.y;
}
else
{
min.y
= aabb.m_max.y;
max.y
= aabb.m_min.y;
}

if (normal.z >= 0.0f)
{
min.z
= aabb.m_min.z;
max.z
= aabb.m_max.z;
}
else
{
min.z
= aabb.m_max.z;
max.z
= aabb.m_min.z;
}

if ((normal.DotProduct3(min)+d) > 0.0f) return false;
if ((normal.DotProduct3(max)+d) >= 0.0f) return true;

return false;
}

     对平面而言,要知道点位于平面哪一侧。点可以在平面上,也可以在平面前面,还可以在平面后面。通常在不同的选择算法中会使用它,而且还可用它确定是否要绘制某些内容。同样可以在碰撞时使用它。如果旧位置位于平面前面,而新位置位于平面后面,那么因为位置穿过平面,所以可知发生了碰撞。确定点位于平面哪一侧的方法称为“分类点”。在平面类中有一个名为ClassifyPoint()的函数。ClassifyPoint()函数如程序清单8.25所示,它以3D位置为参数。该函数通过计算平面和位置的点积,并加上平面距离而返回点和平面的相对位置,点要么在平面前面、要么在平面后面,要么在平面上。如果距离小于0,表示点在平面后面;大于0,表示在平面前面;等于0,则在平面上。

int CPlane::ClassifyPoint(Vector3D &v)
{
float distance = a * v.x + b * v.y + c * v.z + d;

if(distance > 0.001) return UGP_FRONT;
if(distance < -0.001) return UGP_BACK;

return UGP_ON_PLANE;
}

  了解了点分类的方法就可以确认三角形或多边形位于平面哪一侧。这通过将图元中的所有点分类加以确认。如果所有的点都在多边形的一侧,那么整个图元就在该多边形的那一侧。如果某些点在多边形一侧,某些点在另一侧,那么该图元穿过平面。程序清单8.26中的ClassifyPolygon()函数完成测试。它以一个多边形对象(稍后将会是更多的多边形对象)为参数,并返回多边形是在平面的前面、后面还是穿过该平面的结果。

int CPlane::ClassifyPolygon(CPolygon &pol)
{
int frontPolys = 0;
int backPolys = 0;
int planePolys = 0;
int type = 0;

for(int i = 0; i < 3; i++)
{
type
= ClassifyPoint(pol.m_vertexList[i]);

switch(type)
{
case UGP_FRONT:
frontPolys
++;
break;

case UGP_BACK:
backPolys
++;
break;

default:
frontPolys
++;
backPolys
++;
planePolys
++;
break;
}
}

if(planePolys == 3) return UGP_ON_PLANE;
else if(frontPolys == 3) return UGP_FRONT;
else if(backPolys == 3) return UGP_BACK;

return UGP_CLIPPED;
}

   平面类的最后一个函数计算平面到一个3D矢量的距离。计算平面和3D矢量之间的点积可以得到该距离,返回结果为一浮点值。程序清单8.27给出了平面GetDistance()成员函数的实现代码。

float CPlane::GetDistance(Vector3D &v)
{
return a*v.x + b*v.y + c*v.z + d;
}

  Direct3D平面

       如同所有其他数学结构一样,Direct3D还包含了一套平面对象。Direct3D平面对象D3DXPLANE包含了计算平面的4D矢量点积的D3DXPlaneDot()函数、计算平面和3D矢量点积的D3DXPlaneDotCoord()函数、使用假定w为0计算平面和3D矢量点积的D3DXPlaneDotCoord()函数、由三角形计算平面的D3DXPlaneFromPoints()函数及由点和法线计算平面的D3DXPlaneFromPointNormal()函数。如果准备使用Direct3D平面,那么可以查看DirectX SDK文档获取完整的信息。D3DXPLANE结构如程序清单8.28所示。

typedef struct D3DXPLANE {
FLOAT a;
FLOAT b;
FLOAT c;
FLOAT d;
} D3DXPLANE;

  

原文地址:https://www.cnblogs.com/kex1n/p/2173585.html