代码改变世界

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

2011-09-16 16:48  独孤残云  阅读(2594)  评论(1编辑  收藏  举报

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

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

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

就如下图所示:

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

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

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

它们的形状如下图所示:

 

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

下面,我们来看如何构造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;    // 视截体摄影·投影变换矩阵
};

 

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;           // 外接球网格
};

 

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(00);
    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(00);
    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;
}

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

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.0f0);
    
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.0f1.0f);
    D3DXCOLOR col(
1.0f1.0f1.0f1.0f);
    D3DLIGHT9 light 
= InitDirectionalLight(&dir, &col);
    g_pD3DDevice
->SetLight(0&light);
    g_pD3DDevice
->SetRenderState(D3DRS_LIGHTING,         TRUE);
    g_pD3DDevice
->LightEnable(0true);
    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);
}

 

 

 

以上,谢谢 ^ ^