对于尺寸较大甚至于无限尺寸的游戏场景,我们不可能要求设备每时每刻绘制场景内的所有物体,那样做没有任何意义,只会让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(
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;
}
最后是主体代码和效果图:
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);
}
以上,谢谢 ^ ^