Direct3D轮回:判断物体是否进入视野——外接体VS视截体

对于尺寸较大甚至于无限尺寸的游戏场景,我们不可能要求设备每时每刻绘制场景内的所有物体,那样做没有任何意义,只会让3D设备吃不消。

理想情况下,我们只要绘制场景中进入我们视野的物体就够了~

我们在3D场景中的视野范围由Direct3D流水线中的摄影变换和投影变换共同决定。它的形状就好象一个被截去了顶端部分的四棱锥,因此,我们称其为“视截体”或“视域体”。

就如下图所示:

Direct3D轮回:判断物体是否进入视野——外接体VS视截体

假如物体的某一部分进入这个视截体当中,我们就认为该物体已经进入了我们的视野,而后对其绘制即可。

但是如何来判断物体的某一部分是否与视截体相交呢?当然,遍历物体的所有顶点加以判断是最精确的做法,但不可行,因为性能上会受不了。

这里我们引入外接体的概念:外接体是一种具备标准形状且刚好可以容纳目标物体的最小体积单位。常见的有外接盒(长方体)、外接球、圆柱外接体、胶囊外接体等。其中最为常用的是外接盒和外接球。

它们的形状如下图所示:

Direct3D轮回:判断物体是否进入视野——外接体VS视截体 Direct3D轮回:判断物体是否进入视野——外接体VS视截体

通过判断外接体是否位于视野中从而断定物体是否可见。这种做法可以极大的改善性能,且在精确度上不会出现太大的偏差,是一种十分可行的方案。

下面,我们来看如何构造D3D中的外接体对象。

首先声明一个外接体基类CBoundingVolume:

/* -------------------------------------

代码清单:BoundingVolume.h
来自:
http://www.cnblogs.com/kenkao

-------------------------------------
*/

#include 
" D3DInit.h "

#pragma  once

//  外接体基类
class  CBoundingVolume
{
public :
    CBoundingVolume(
void );
    
virtual   ~ CBoundingVolume( void );
public :
    
virtual   bool  IsPointInside(D3DXVECTOR3 pos)  =   0 //  单点检测
};

 

/* -------------------------------------

代码清单:BoundingVolume.cpp
来自:
http://www.cnblogs.com/kenkao

-------------------------------------
*/

#include 
" StdAfx.h "
#include 
" BoundingVolume.h "

CBoundingVolume::CBoundingVolume(
void )
{
}

CBoundingVolume::
~ CBoundingVolume( void )
{
}

它只是一种标准,且提供了一些规则以供子类重写。之后我们就可以在其基础上派上具体形状的子类了。

首先来看外接盒:

/* -------------------------------------

代码清单:BoundingBox.h
来自:
http://www.cnblogs.com/kenkao

-------------------------------------
*/

#pragma  once
#include 
" boundingvolume.h "

class  CBoundingBox :
    
public  CBoundingVolume
{
public :
    CBoundingBox(
void );
    CBoundingBox(D3DXVECTOR3 min,    
//  外接盒最小坐标
                 D3DXVECTOR3 max);    //  外接盒最大坐标
     ~ CBoundingBox( void );
public :
    D3DXVECTOR3 GetMin(){
return  m_Min;}
    
void         SetMin(D3DXVECTOR3 min){m_Min  =  min;}
    D3DXVECTOR3 GetMax(){
return  m_Max;}
    
void         SetMax(D3DXVECTOR3 max){m_Max  =  max;}
public :
    
virtual   bool  IsPointInside(D3DXVECTOR3 pos);   //  单点检测
     static    bool  ComputeBoundingBox(CBoundingBox *  pOut, D3DXVECTOR3 *  pVertexBuffer, DWORD NumVertices, DWORD dwStride);  // 计算外接盒(静态、通用)
private :
    D3DXVECTOR3 m_Min;
    D3DXVECTOR3 m_Max;
};

 

/* -------------------------------------

代码清单:BoundingBox.cpp
来自:
http://www.cnblogs.com/kenkao

-------------------------------------
*/

#include 
" StdAfx.h "
#include 
" BoundingBox.h "

CBoundingBox::CBoundingBox(
void ) : m_Min(D3DXVECTOR3_ZERO), m_Max(D3DXVECTOR3_ZERO)
{
}

CBoundingBox::CBoundingBox(D3DXVECTOR3 min, D3DXVECTOR3 max) : m_Min(min), m_Max(max)
{
}

CBoundingBox::
~ CBoundingBox( void )
{
}

bool  CBoundingBox::ComputeBoundingBox(CBoundingBox *  pOut, D3DXVECTOR3 *  pVertexBuffer, DWORD NumVertices, DWORD dwStride)
{
    D3DXVECTOR3 min 
=  D3DXVECTOR3_ZERO;
    D3DXVECTOR3 max 
=  D3DXVECTOR3_ZERO;
    
if (FAILED(D3DXComputeBoundingBox(pVertexBuffer, NumVertices, dwStride,  & min,  & max)))
        
return   false ;
    pOut 
=   new  CBoundingBox(min,max);
    
return   true ;
}

bool  CBoundingBox::IsPointInside(D3DXVECTOR3 pos)
{
    
return  pos.x  >=  m_Min.x  &&  pos.y  >=  m_Min.y  &&  pos.z  >=  m_Min.z  &&
        pos.x 
<=  m_Max.x  &&  pos.y  <=  m_Max.y  &&  pos.z  <=  m_Max.z;
}

对于一个长方体,我们没有必要知道全部8个顶点的坐标,而只需要知道其中的两个就可以了——xyz最小和最大的最小坐标和最大坐标。至于其他点,高等数学中的组合即可求得~

CBoundingBox重写了基类的单点检测方法,同时对外提供两种构造外接盒的方法:第一种直接传入两个关键坐标即可获得;第二种通过调用D3DAPI来实现,只需令其获知某物体的所有顶点信息即可,无需受制于某种专有的数据格式,便捷、通用。具体可参看代码。

然后是外接球的构建:

/* -------------------------------------

代码清单:BoundingSphere.h
来自:
http://www.cnblogs.com/kenkao

-------------------------------------
*/

#pragma  once
#include 
" boundingvolume.h "

class  CBoundingSphere :
    
public  CBoundingVolume
{
public :
    CBoundingSphere(
void );
    CBoundingSphere(D3DXVECTOR3 center,   
//  外接球球心
                     float  radius);         //  外接球半径
     ~ CBoundingSphere( void );
public :
    D3DXVECTOR3 GetCenter(){
return  m_Center;}
    
void         SetCenter(D3DXVECTOR3 center){m_Center  =  center;}
    
float        GetRadius(){ return  m_Radius;}
    
void         SetRadius( float  radius){m_Radius  =  radius;}
public :
    
virtual   bool  IsPointInside(D3DXVECTOR3 pos);   //  单点检测
     static    bool  ComputeBoundingSphere(CBoundingSphere *  pOut, D3DXVECTOR3 *  pVertexBuffer, DWORD NumVertices, DWORD dwStride);  //  计算外接球(静态、通用)
private :
    D3DXVECTOR3 m_Center;
    
float        m_Radius;
};

 

/* -------------------------------------

代码清单:BoundingSphere.cpp
来自:
http://www.cnblogs.com/kenkao

-------------------------------------
*/

#include 
" StdAfx.h "
#include 
" BoundingSphere.h "

CBoundingSphere::CBoundingSphere(
void ) : m_Center(D3DXVECTOR3_ZERO), m_Radius( 0.0f )
{
}

CBoundingSphere::CBoundingSphere(D3DXVECTOR3 center, 
float  radius) : m_Center(center), m_Radius(radius)
{
}

CBoundingSphere::
~ CBoundingSphere( void )
{
}

bool  CBoundingSphere::ComputeBoundingSphere(CBoundingSphere *  pOut, D3DXVECTOR3  * pVertexBuffer, DWORD NumVertices, DWORD dwStride)
{
    D3DXVECTOR3 center 
=  D3DXVECTOR3_ZERO;
    
float  radius  =   0.0f ;
    
if (FAILED(D3DXComputeBoundingSphere(pVertexBuffer, NumVertices, dwStride,  & center,  & radius)))
        
return   false ;
    pOut 
=   new  CBoundingSphere(center,radius);
    
return   true ;
}

bool  CBoundingSphere::IsPointInside(D3DXVECTOR3 pos)
{
    D3DXVECTOR3 distance 
=   this -> m_Center  -  pos;
    
return  sqrt(pow(distance.x, 2 +  pow(distance.y, 2 +  pow(distance.z, 2 ))  <=   this -> m_Radius;
}

同样的,我们只需知道球心和半径即可描述该外接球。

CBoundingSphere同样提供单点检测和两种构造外接球的方法,同CBoundingBox类似,原理非常简单。

有了具体的外接盒、外接球,接下来我们来看视截体对象的构建方法。视截体可以理解为外接体的一种特殊类型,因此我们令CBoundingFrustum继承自CBoundingVolume即可:

/* -------------------------------------

代码清单:BoundingFrustum.h
来自:
http://www.cnblogs.com/kenkao

-------------------------------------
*/

#pragma  once
#include 
" boundingvolume.h "
#include 
" BoundingBox.h "
#include 
" BoundingSphere.h "

class  CBoundingFrustum :
    
public  CBoundingVolume
{
public :
    CBoundingFrustum(
void );
    
~ CBoundingFrustum( void );
public :
    
void  Update(D3DXMATRIX Matrix);   //  更新视截体
     void  Release(){};                 //  释放视截体(可以不执行)
public :
    
bool  Contains(CBoundingBox *  pBoundingBox);         //  判断外接盒是否被视截体包含
     bool  Contains(CBoundingSphere *  pBoundingSphere);   //  判断外接球是否被视截体包含
     virtual   bool  IsPointInside(D3DXVECTOR3 pos);       //  判断某一点是否位于视截体内
public :
    D3DXPLANE  GetNear()  {
return  m_planes[ 0 ];}        //  获得近、远、左、右、上、下六个平面
    D3DXPLANE  GetFar()   { return  m_planes[ 1 ];}
    D3DXPLANE  GetLeft()  {
return  m_planes[ 2 ];}
    D3DXPLANE  GetRight() {
return  m_planes[ 3 ];}
    D3DXPLANE  GetTop()   {
return  m_planes[ 4 ];}
    D3DXPLANE  GetButtom(){
return  m_planes[ 5 ];}
    D3DXMATRIX GetMatrix(){
return  m_Matrix;}           //  获得视截体摄影·投影变换矩阵
private :
    D3DXPLANE   m_planes[
6 ];  //  视截体六个平面
    D3DXMATRIX  m_Matrix;     //  视截体摄影·投影变换矩阵
};

 

Direct3D轮回:判断物体是否进入视野——外接体VS视截体 BoundingFrustum.cpp
/* -------------------------------------

代码清单:BoundingFrustum.cpp
来自:
http://www.cnblogs.com/kenkao

-------------------------------------
*/

#include 
" StdAfx.h "
#include 
" BoundingFrustum.h "

CBoundingFrustum::CBoundingFrustum(
void )
{
}

CBoundingFrustum::
~ CBoundingFrustum( void )
{
}

bool  CBoundingFrustum::IsPointInside(D3DXVECTOR3 pos)
{
    
for ( int  i = 0 ;i < 6 ;i ++ )
    {
        
//  如果该点位于视截体六个面中任何一个面外,则判定为不被包含
         if ( D3DXPlaneDotCoord( & m_planes[i],  & pos)  <   0.0f  )
            
return  FALSE;
    }
    
return  TRUE;
}

bool  CBoundingFrustum::Contains(CBoundingBox *  pBoundingBox)
{
    D3DXVECTOR3 vertex[
8 ];
    D3DXVECTOR3 min 
=  pBoundingBox -> GetMin();
    D3DXVECTOR3 max 
=  pBoundingBox -> GetMax();

    vertex[
0 =  min;
    vertex[
1 =  D3DXVECTOR3(max.x,min.y,min.z);
    vertex[
2 =  D3DXVECTOR3(min.x,max.y,min.z);
    vertex[
3 =  D3DXVECTOR3(min.x,min.y,max.z);
    vertex[
4 =  D3DXVECTOR3(max.x,max.y,min.z);
    vertex[
5 =  D3DXVECTOR3(min.x,max.y,max.z);
    vertex[
6 =  D3DXVECTOR3(max.x,min.y,max.z);
    vertex[
7 =  max;

    
for  ( int  i = 0 ;i < 8 ;i ++ )
    {
        
//  如果外接盒的任何一个顶点位于视截体中,则判定为被包含
         if (IsPointInside(vertex[i]))
            
return   true ;
    }
    
return   false ;
}

bool  CBoundingFrustum::Contains(CBoundingSphere *  pBoundingSphere)
{
    
for ( int  i = 0 ;i < 6 ;i ++ )
    {
        
//  如果外接球球心位于视截体六个面中任何一个面外且其距离大于半径,则判定为不被包含
         if ( D3DXPlaneDotCoord( & m_planes[i], & pBoundingSphere -> GetCenter())  <   - pBoundingSphere -> GetRadius())
            
return  FALSE;
    }
    
return  TRUE;
}

void  CBoundingFrustum::Update(D3DXMATRIX Matrix)
{
    
//  更新视截体六个平面
    m_Matrix  =  Matrix;
    
//  近平面
    m_planes[ 0 ].a  =  Matrix._14  +  Matrix._13; 
    m_planes[
0 ].b  =  Matrix._24  +  Matrix._23;
    m_planes[
0 ].c  =  Matrix._34  +  Matrix._33;
    m_planes[
0 ].d  =  Matrix._44  +  Matrix._43;
    D3DXPlaneNormalize(
& m_planes[ 0 ],  & m_planes[ 0 ]);   // 所得平面要执行单位化,以利于后期计算
    
//  远平面
    m_planes[ 1 ].a  =  Matrix._14  -  Matrix._13; 
    m_planes[
1 ].b  =  Matrix._24  -  Matrix._23;
    m_planes[
1 ].c  =  Matrix._34  -  Matrix._33;
    m_planes[
1 ].d  =  Matrix._44  -  Matrix._43;
    D3DXPlaneNormalize(
& m_planes[ 1 ],  & m_planes[ 1 ]);
    
//  左平面
    m_planes[ 2 ].a  =  Matrix._14  -  Matrix._11;
    m_planes[
2 ].b  =  Matrix._24  -  Matrix._21;
    m_planes[
2 ].c  =  Matrix._34  -  Matrix._31;
    m_planes[
2 ].d  =  Matrix._44  -  Matrix._41;
    D3DXPlaneNormalize(
& m_planes[ 2 ],  & m_planes[ 2 ]);
    
//  右平面
    m_planes[ 3 ].a  =  Matrix._14  +  Matrix._11; 
    m_planes[
3 ].b  =  Matrix._24  +  Matrix._21;
    m_planes[
3 ].c  =  Matrix._34  +  Matrix._31;
    m_planes[
3 ].d  =  Matrix._44  +  Matrix._41;
    D3DXPlaneNormalize(
& m_planes[ 3 ],  & m_planes[ 3 ]);
    
//  上平面
    m_planes[ 4 ].a  =  Matrix._14  -  Matrix._12;
    m_planes[
4 ].b  =  Matrix._24  -  Matrix._22;
    m_planes[
4 ].c  =  Matrix._34  -  Matrix._32;
    m_planes[
4 ].d  =  Matrix._44  -  Matrix._42;
    D3DXPlaneNormalize(
& m_planes[ 4 ],  & m_planes[ 4 ]);
    
//  下平面
    m_planes[ 5 ].a  =  Matrix._14  +  Matrix._12; 
    m_planes[
5 ].b  =  Matrix._24  +  Matrix._22;
    m_planes[
5 ].c  =  Matrix._34  +  Matrix._32;
    m_planes[
5 ].d  =  Matrix._44  +  Matrix._42;
    D3DXPlaneNormalize(
& m_planes[ 5 ],  & m_planes[ 5 ]);
}

与外接球及外接盒不同,我们需要六个平面来描述一个视截体。

我们通过传入摄影*投影矩阵来Update视截体的六个平面,除单点检测之外,同时提供重载的Contains函数判断其与外接盒或者外接球的包容关系,你也可以自行重载该方法,从而提供针对于其他形状外接体的判别机制。

其中的原理细节,大家可参看网友 laizhishen 的原创文章:http://hi.baidu.com/laizhishen/blog/item/3d206d209cca9c54ac34de46.html

至此我们仅需获得某物体对应的外接体,就可利用视截体进一步判断该物体是否可见、是否有必要绘制。

为观察可视化效果,我们来丰富CSimpleXMesh类的功能,使其具备加载并渲染.X model外接体的能力:

/* -------------------------------------

代码清单:SimpleXMesh.h
来自:
http://www.cnblogs.com/kenkao

-------------------------------------
*/

#include 
" D3DInit.h "
#include 
" BoundingSphere.h "
#include 
" BoundingBox.h "

#pragma  once

class  CSimpleXMesh
{
public :
    CSimpleXMesh(
void );
    
~ CSimpleXMesh( void );
public :
    
bool  LoadXMesh(TCHAR *  szXFileName);         //  加载.X网格
     void  DrawXMesh();                           //  绘制.X网格
     void  DrawXMesh(D3DXVECTOR3 pos);
    
void  DrawXMesh(D3DXMATRIX trans);
    
void  DrawXMeshSubset( int  index);            //  绘制.X网格子集
     void  Release();                             //  释放.X网格
public :
    
bool  LoadXMeshWithBoundingSphere(TCHAR *  szXFileName);   //  加载.X网格(携带外接球)
     bool  LoadXMeshWithBoundingBox(TCHAR *  szXFileName);      //  加载.X网格(携带外接盒)
     void  DrawXMeshWithBoundingBox();                        //  绘制.X网格(携带外接盒)
     void  DrawXMeshWithBoundingSphere();                     //  绘制.X网格(携带外接球)
public :
    CBoundingBox
*     GetBoundingBox()   { return  m_pBoundingBox;}      //  获得外接盒
    CBoundingSphere *  GetBoundingSphere(){ return  m_pBoundingSphere;}   //  获得外接球
public :
    DWORD        GetMaterialNum()           {
return  m_dwMaterials;}                      //  获得网格材质数
    D3DMATERIAL9 GetMaterial( int  index)     { return  m_pD3DMaterialArray[index];}         //  获得网格材质
    IDirect3DTexture9 *  GetTexture( int  index){ return  m_ppDirect3DTextureArray[index];}    //  获得网格纹理
private :
    
bool  ComputeBoundingSphere();               //  计算外接球
     bool  ComputeBoundingBox();                  //  计算外接盒
private :
    ID3DXBuffer
*  m_pAdjacencyBuffer;                //  邻接三角形信息缓冲区
    ID3DXBuffer *  m_pMaterialBuffer;                 //  材质缓冲区
    D3DMATERIAL9  * m_pD3DMaterialArray;              //  材质数组
    IDirect3DTexture9  ** m_ppDirect3DTextureArray;   //  纹理数组
    DWORD m_dwMaterials;                            //  材质数
    ID3DXMesh *  m_pD3DXMesh;                         //  .X网格对象指针
private :
    CBoundingBox
*  m_pBoundingBox;                //  外接盒
    CBoundingSphere *  m_pBoundingSphere;          //  外接球
    ID3DXMesh *  m_pBoundingBoxMesh;               //  外接盒网格
    ID3DXMesh *  m_pBoundingSphereMesh;            //  外接球网格
};

 

Direct3D轮回:判断物体是否进入视野——外接体VS视截体 SimpleXMesh.cpp
/* -------------------------------------

代码清单:SimpleXMesh.cpp
来自:
http://www.cnblogs.com/kenkao

-------------------------------------
*/

#include 
" StdAfx.h "
#include 
" SimpleXMesh.h "
#include 
" D3DGame.h "

extern  IDirect3DDevice9  * g_pD3DDevice;

CSimpleXMesh::CSimpleXMesh(
void ):m_pAdjacencyBuffer(NULL),
                                 m_pMaterialBuffer(NULL),
                                 m_pD3DMaterialArray(NULL),
                                 m_ppDirect3DTextureArray(NULL),
                                 m_dwMaterials(
0 ),
                                 m_pD3DXMesh(NULL),
                                 m_pBoundingBox(NULL),
                                 m_pBoundingSphere(NULL),
                                 m_pBoundingBoxMesh(NULL),
                                 m_pBoundingSphereMesh(NULL)
{

}

CSimpleXMesh::
~ CSimpleXMesh( void )
{

}

bool  CSimpleXMesh::LoadXMesh(TCHAR *  szXFileName)
{
    
//  加载X网格
     if (FAILED(D3DXLoadMeshFromX(
        szXFileName,                            
// .X文件名
        D3DXMESH_MANAGED,                        // 内存托管模式
        g_pD3DDevice,                            // Direct3D设备
         & m_pAdjacencyBuffer,                     // 邻接三角形信息缓冲区指针
         & m_pMaterialBuffer,                      // 材质缓冲区指针
         0 ,                                       // 特效缓冲区指针,由于没有用到特效,我们在这里置0即可
         & m_dwMaterials,                          // 材质数
         & m_pD3DXMesh                             // 得到的X网格
        ))){
            
return   false ;
    }
    
//  错误判断
     if (m_pMaterialBuffer == NULL  ||  m_dwMaterials == 0 )  
        
return   false ;
    
//  获得材质缓冲区指针
    D3DXMATERIAL *  pD3DXMaterial = (D3DXMATERIAL * )m_pMaterialBuffer -> GetBufferPointer();
    
if (pD3DXMaterial != NULL){
        
//  初始化材质数组
        m_pD3DMaterialArray = new  D3DMATERIAL9[m_dwMaterials];
        
//  初始化纹理数组
        m_ppDirect3DTextureArray = new  IDirect3DTexture9 * [m_dwMaterials];
        
//  遍历材质缓冲区,填充材质及纹理数组
         for (DWORD i = 0 ;i < m_dwMaterials;i ++ ){
            m_pD3DMaterialArray[i]
= pD3DXMaterial[i].MatD3D;
            
if (pD3DXMaterial[i].pTextureFilename != NULL)
            {
                
if (FAILED(D3DXCreateTextureFromFile(g_pD3DDevice,pD3DXMaterial[i].pTextureFilename, & m_ppDirect3DTextureArray[i]))){
                    m_ppDirect3DTextureArray[i]
= NULL;
                }
            }
            
else
            {
                m_ppDirect3DTextureArray[i]
= NULL;
            }
        }
    }
    
//  网格数据优化
    m_pD3DXMesh -> OptimizeInplace(
        D3DXMESHOPT_COMPACT  
|
        D3DXMESHOPT_ATTRSORT 
|
        D3DXMESHOPT_VERTEXCACHE,                           
// 优化模式,具体参看SDK
        (DWORD * )m_pAdjacencyBuffer -> GetBufferPointer(),     // 邻接三角形信息缓冲区指针
        NULL, NULL, NULL);                                  // 参看SDK

    
//  有效数据已经填充到材质及纹理数组,释放材质缓冲区
    m_pMaterialBuffer -> Release();
    
//  网格数据优化完毕,释放邻接三角形信息缓冲区
    m_pAdjacencyBuffer -> Release();
    
//  当然,这两个缓冲区的释放放到最后的Release函数里也没有问题 ^ ^
     return   true ;
}

bool  CSimpleXMesh::LoadXMeshWithBoundingBox(TCHAR *  szXFileName)
{
    
return  LoadXMesh(szXFileName) && ComputeBoundingBox();
}

bool  CSimpleXMesh::LoadXMeshWithBoundingSphere(TCHAR  * szXFileName)
{
    
return  LoadXMesh(szXFileName) && ComputeBoundingSphere();
}

void  CSimpleXMesh::DrawXMesh()
{
    
//  绘制X网格
     for (DWORD i = 0 ;i < m_dwMaterials;i ++ )
    {
        g_pD3DDevice
-> SetMaterial( & m_pD3DMaterialArray[i]);
        g_pD3DDevice
-> SetTexture( 0 ,m_ppDirect3DTextureArray[i]);
        m_pD3DXMesh
-> DrawSubset(i);
    }
}

void  CSimpleXMesh::DrawXMesh(D3DXVECTOR3 pos)
{
    D3DXMATRIX matWorld;
    g_pD3DDevice
-> GetTransform(D3DTS_WORLD, & matWorld);
    D3DXMATRIX newMatWorld;
    D3DXMatrixTranslation(
& newMatWorld,pos.x,pos.y,pos.z);
    g_pD3DDevice
-> SetTransform(D3DTS_WORLD, & newMatWorld);
    DrawXMesh();
    g_pD3DDevice
-> SetTransform(D3DTS_WORLD, & matWorld);
}

void  CSimpleXMesh::DrawXMesh(D3DXMATRIX trans)
{
    D3DXMATRIX matWorld;
    g_pD3DDevice
-> GetTransform(D3DTS_WORLD, & matWorld);
    g_pD3DDevice
-> SetTransform(D3DTS_WORLD, & trans);
    DrawXMesh();
    g_pD3DDevice
-> SetTransform(D3DTS_WORLD, & matWorld);
}

void  CSimpleXMesh::DrawXMeshSubset( int  index)
{
    m_pD3DXMesh
-> DrawSubset(index);
}

void  CSimpleXMesh::DrawXMeshWithBoundingBox()
{
    DrawXMesh();
    
//  生成半透明材质
    D3DMATERIAL9 blue;
    D3DCOLORVALUE blueColor;
    blueColor.a 
=   1.0f ;
    blueColor.r 
=   0.0f ;
    blueColor.g 
=   0.0f ;
    blueColor.b 
=   1.0f ;
    blue.Ambient  
=  blueColor;
    blue.Diffuse  
=  blueColor;
    blue.Specular 
=  blueColor;
    blue.Power    
=   20 ;
    blue.Diffuse.a 
=   0.10f ;
    g_pD3DDevice
-> SetMaterial( & blue);
    g_pD3DDevice
-> SetTexture( 0 0 );
    g_pD3DDevice
-> SetRenderState(D3DRS_ALPHABLENDENABLE,  true );
    g_pD3DDevice
-> SetRenderState(D3DRS_SRCBLEND, D3DBLEND_SRCALPHA);
    g_pD3DDevice
-> SetRenderState(D3DRS_DESTBLEND, D3DBLEND_INVSRCALPHA);
    
//  绘制外接盒网格
     if (m_pBoundingBoxMesh)
        m_pBoundingBoxMesh
-> DrawSubset( 0 );
    g_pD3DDevice
-> SetRenderState(D3DRS_ALPHABLENDENABLE,  false );
}

void  CSimpleXMesh::DrawXMeshWithBoundingSphere()
{
    DrawXMesh();
    
//  生成半透明材质
    D3DMATERIAL9 green;
    D3DCOLORVALUE greenColor;
    greenColor.a 
=   1.0f ;
    greenColor.r 
=   0.0f ;
    greenColor.g 
=   1.0f ;
    greenColor.b 
=   0.0f ;
    green.Ambient  
=  greenColor;
    green.Diffuse  
=  greenColor;
    green.Specular 
=  greenColor;
    green.Power    
=   20 ;
    green.Diffuse.a 
=   0.10f ;
    g_pD3DDevice
-> SetMaterial( & green);
    g_pD3DDevice
-> SetTexture( 0 0 );
    g_pD3DDevice
-> SetRenderState(D3DRS_ALPHABLENDENABLE,  true );
    g_pD3DDevice
-> SetRenderState(D3DRS_SRCBLEND, D3DBLEND_SRCALPHA);
    g_pD3DDevice
-> SetRenderState(D3DRS_DESTBLEND, D3DBLEND_INVSRCALPHA);
    
//  绘制外接球网格
     if (m_pBoundingSphereMesh)
        m_pBoundingSphereMesh
-> DrawSubset( 0 );
    g_pD3DDevice
-> SetRenderState(D3DRS_ALPHABLENDENABLE,  false );
}

bool  CSimpleXMesh::ComputeBoundingSphere()
{
    HRESULT hr 
=   0 ;
    BYTE
*  v  =   0 ;
    D3DXVECTOR3 center; 
float  radius;
    
//  计算得到外接球球心及半径
    m_pD3DXMesh -> LockVertexBuffer( 0 , ( void ** ) & v);
    hr 
=  D3DXComputeBoundingSphere(
        (D3DXVECTOR3
* )v,
        m_pD3DXMesh
-> GetNumVertices(),
        D3DXGetFVFVertexSize(m_pD3DXMesh
-> GetFVF()),
        
& center,
        
& radius);
    m_pD3DXMesh
-> UnlockVertexBuffer();
    
if ( FAILED(hr) )
        
return   false ;
    
//  生成外接球
    m_pBoundingSphere  =   new  CBoundingSphere(center,radius);
    
//  生成外接球网格
    hr  =  D3DXCreateSphere(g_pD3DDevice,
        m_pBoundingSphere
-> GetRadius(),
        
20 , 20 , & m_pBoundingSphereMesh, 0 );
    
if ( FAILED(hr) )
        
return   false ;
    
return   true ;
}

bool  CSimpleXMesh::ComputeBoundingBox()
{
    HRESULT hr 
=   0 ;
    BYTE
*  v  =   0 ;
    D3DXVECTOR3 min; D3DXVECTOR3 max;
    
//  计算得到外接盒最大坐标与最小坐标
    m_pD3DXMesh -> LockVertexBuffer( 0 , ( void ** ) & v);
    hr 
=  D3DXComputeBoundingBox(
        (D3DXVECTOR3
* )v,
        m_pD3DXMesh
-> GetNumVertices(),
        D3DXGetFVFVertexSize(m_pD3DXMesh
-> GetFVF()),
        
& min,
        
& max);
    m_pD3DXMesh
-> UnlockVertexBuffer();
    
if ( FAILED(hr) )
        
return   false ;
    
//  生成外接盒
    m_pBoundingBox  =   new  CBoundingBox(min,max);
    
//  生成外接盒网格
    hr  =  D3DXCreateBox(g_pD3DDevice,
        m_pBoundingBox
-> GetMax().x  -  m_pBoundingBox -> GetMin().x,
        m_pBoundingBox
-> GetMax().y  -  m_pBoundingBox -> GetMin().y,
        m_pBoundingBox
-> GetMax().z  -  m_pBoundingBox -> GetMin().z,
        
& m_pBoundingBoxMesh,  0 );
    
if ( FAILED(hr) )
        
return   false ;
    
return   true ;
}

void  CSimpleXMesh::Release(){
    
//  释放纹理数组
     for (DWORD i = 0 ;i < m_dwMaterials;i ++ ){
        ReleaseCOM(m_ppDirect3DTextureArray[i]);
    }
    delete[] m_ppDirect3DTextureArray;
    
//  释放材质数组
    delete[] m_pD3DMaterialArray;
    
//  释放网格对象
    ReleaseCOM(m_pD3DXMesh);
    
if (m_pBoundingBox  !=  NULL)
        delete m_pBoundingBox;
    
if (m_pBoundingSphere  !=  NULL)
        delete m_pBoundingSphere;
}

最后是主体代码和效果图:

Direct3D轮回:判断物体是否进入视野——外接体VS视截体 D3DGame.cpp
/* -------------------------------------

代码清单:D3DGame.cpp
来自:
http://www.cnblogs.com/kenkao

-------------------------------------
*/

#include 
" StdAfx.h "
#include 
" D3DGame.h "
#include 
" D3DSprite.h "
#include 
" SpriteBatch.h "
#include 
" D3DFont.h "
#include 
" D3DCamera.h "
#include 
" BaseTerrain.h "
#include 
" SimpleXMesh.h "
#include 
" BoundingFrustum.h "
#include 
" BoundingSphere.h "
#include 
< stdio.h >
#include 
< time.h >

// ---通用全局变量

HINSTANCE  g_hInst;
HWND       g_hWnd;
D3DXMATRIX g_matWorld;
D3DXMATRIX g_matProjection;
D3DPRESENT_PARAMETERS g_D3DPP;

// ---D3D全局变量

IDirect3D9       
* g_pD3D            =  NULL;
IDirect3DDevice9 
* g_pD3DDevice      =  NULL;
CMouseInput      
* g_pMouseInput     =  NULL;
CKeyboardInput   
* g_pKeyboardInput  =  NULL;
CD3DSprite       
* g_pSprite         =  NULL;
CD3DCamera       
* g_pD3DCamera      =  NULL;
CBaseTerrain     
* g_pBaseTerrain    =  NULL;
CSpriteBatch     
* g_pSpriteBatch    =  NULL;
CD3DFont         
* g_pFont           =  NULL;
CD3DFont         
* g_pFont2          =  NULL;
CSimpleXMesh     
* g_pMesh           =  NULL;
CBoundingFrustum 
* g_pBoundingFrustum  =  NULL;

// ---全局函数
D3DLIGHT9 InitDirectionalLight(D3DXVECTOR3 *  direction, D3DXCOLOR *  color);   //  初始化光源
void       BeginEffect();   //  开启光照特效(固定功能流水线)
void       EndEffect();     //  关闭光照特效(固定功能流水线)

void  Initialize(HINSTANCE hInst, HWND hWnd)
{
    g_hInst 
=  hInst;
    g_hWnd  
=  hWnd;
    InitD3D(
& g_pD3D,  & g_pD3DDevice, g_D3DPP, g_matProjection, hWnd);
    g_pMouseInput 
=   new  CMouseInput;
    g_pMouseInput
-> Initialize(hInst,hWnd);
    g_pKeyboardInput 
=   new  CKeyboardInput;
    g_pKeyboardInput
-> Initialize(hInst,hWnd);
    srand(time(
0 ));
}

void  LoadContent()
{
    g_pD3DCamera 
=   new  CD3DCamera;
    g_pSprite 
=   new  CD3DSprite(g_pD3DDevice);
    
//  声明并加载两种不同的字体
    g_pFont  =   new  CD3DFont(g_pD3DDevice, & g_D3DPP);
    g_pFont
-> LoadFont( " 宋体 " , 8 );
    g_pFont2 
=   new  CD3DFont(g_pD3DDevice, & g_D3DPP);
    g_pFont2
-> LoadFont( " 隶书 " , 16 );
    
//  声明并加载模型极其外接球
    g_pMesh  =   new  CSimpleXMesh;
    g_pMesh
-> LoadXMeshWithBoundingSphere( " bigship1.x " );
    
//  声明视截体
    g_pBoundingFrustum  =   new  CBoundingFrustum;
}

void  Update(CGameTime *  gameTime)
{
    
//  统计FPS
    gameTime -> CalcFPS();
    g_pMouseInput
-> GetState();
    g_pKeyboardInput
-> GetState();
    g_pD3DCamera
-> Update();
    
//  更新视截体
    g_pBoundingFrustum -> Update(g_pD3DCamera -> GetViewMatrix()  *  g_matProjection);
}

void  Draw(CGameTime *  gameTime)
{
    
bool  isInView;
    g_pD3DDevice
-> SetTransform(D3DTS_VIEW, & g_pD3DCamera -> GetViewMatrix());
    g_pD3DDevice
-> Clear( 0 , NULL, D3DCLEAR_TARGET  |  D3DCLEAR_ZBUFFER, D3DCOLOR_RGBA( 100 , 149 , 237 , 255 ),  1.0f 0 );
    
if (SUCCEEDED(g_pD3DDevice -> BeginScene())) 
    {
        
//  判断Model是否位于视野中
        isInView  =  g_pBoundingFrustum -> Contains(g_pMesh -> GetBoundingSphere());
        
//  如果Model位于视野中则绘制,否则不予绘制
         if (isInView)
        {
            BeginEffect();
            g_pMesh
-> DrawXMeshWithBoundingSphere();
            EndEffect();
        }
        
//  开启Sprite绘制
        g_pSprite -> Begin(D3DXSPRITE_ALPHABLEND);
        
//  显示FPS
        g_pSprite -> DrawText(g_pFont, gameTime -> ShowFPS(), D3DXVECTOR2( 100 , 100 ), D3DXCOLOR_WHITE);
        
if (isInView)
            g_pSprite
-> DrawText(g_pFont2,  " 飞船可见! " , D3DXVECTOR2( 100 , 150 ), D3DXCOLOR_BLUE);
        
else
            g_pSprite
-> DrawText(g_pFont2,  " 飞船不可见! " , D3DXVECTOR2( 100 , 150 ), D3DXCOLOR_RED);
        
//  结束Sprite绘制
        g_pSprite -> End();
        g_pD3DDevice
-> EndScene();
    }
    g_pD3DDevice
-> Present(NULL, NULL, NULL, NULL);
}

void  UnloadContent()
{
    ReleaseCOM(g_pBoundingFrustum);
    ReleaseCOM(g_pMesh);
    ReleaseCOM(g_pFont2);
    ReleaseCOM(g_pFont);
    ReleaseCOM(g_pSprite);
}

void  Dispose()
{
    ReleaseCOM(g_pKeyboardInput);
    ReleaseCOM(g_pMouseInput);
    ReleaseCOM(g_pD3DDevice);
    ReleaseCOM(g_pD3D);
}

D3DLIGHT9 InitDirectionalLight(D3DXVECTOR3
*  direction, D3DXCOLOR *  color)
{
    D3DLIGHT9 light;
    ::ZeroMemory(
& light,  sizeof (light));
    light.Type      
=  D3DLIGHT_DIRECTIONAL;
    light.Ambient   
=   * color  *   0.4f ;
    light.Diffuse   
=   * color;
    light.Specular  
=   * color  *   0.6f ;
    light.Direction 
=   * direction;
    
return  light;
}

void  BeginEffect()
{
    D3DXVECTOR3 dir(
- 1.0f - 1.0f 1.0f );
    D3DXCOLOR col(
1.0f 1.0f 1.0f 1.0f );
    D3DLIGHT9 light 
=  InitDirectionalLight( & dir,  & col);
    g_pD3DDevice
-> SetLight( 0 & light);
    g_pD3DDevice
-> SetRenderState(D3DRS_LIGHTING,         TRUE);
    g_pD3DDevice
-> LightEnable( 0 true );
    g_pD3DDevice
-> SetRenderState(D3DRS_NORMALIZENORMALS, TRUE);
    g_pD3DDevice
-> SetRenderState(D3DRS_SPECULARENABLE,   TRUE);
}

void  EndEffect()
{
    g_pD3DDevice
-> SetRenderState(D3DRS_LIGHTING,         FALSE);
    g_pD3DDevice
-> SetRenderState(D3DRS_NORMALIZENORMALS, FALSE);
    g_pD3DDevice
-> SetRenderState(D3DRS_SPECULARENABLE,   FALSE);
}

 

Direct3D轮回:判断物体是否进入视野——外接体VS视截体

 

Direct3D轮回:判断物体是否进入视野——外接体VS视截体

 

以上,谢谢 ^ ^

 

你可能感兴趣的:(DI)