今天来聊一下程序的孤独感,孤独是每一个走向成功的程序员必将经历的过程,也是磨练的必修课。这是跟程序员这个职业,紧密相关的,这个职业注定了,我们大多时候都是和极强打交道多于人。我们善于思考,大部分的时候,就是在看资料,规格书,想问题,所以跟人交流就少了很多。我觉得,大部分的优秀程序员都是孤独的,我们应该享受这份特有的孤独。我想这也就是大多程序员喜欢开夜车的原因。因为寂静和孤独,多了一份宁静,可以,让你少去许多杂念。可以在代码的海洋的畅游。可以在知识的海洋里奔跑。代码不像人可以善变,对就对,错就错,这也许,就是我们喜欢它的真正原因。
今天我们来看以下,外接体和外接球,我们知道外接体和外接球常用于可见性检测和碰撞性检测。假定场景中有一个发射物,我们想要确定该发射无视否会击中场景中的某一个物体。由于物体是三角形面片组成的,所以我们需要遍历每个物体的每个面片,并检测发射物体(其数学模型为射线)是否会击中某一面片。该方法需要进行大量的射线/三角形相交测试。外接球和外接体可替代物体的面片,从而减少大量的测试。
我们先看一下,DirectX3D.h的代码:
#ifndef __DirectX3DH__
#define __DirectX3DH__
#include <d3dx9.h>
#include <string>
namespace d3d
{
bool InitD3D(
HINSTANCE hInstance, // [in] Application instance.
int width, int height, // [in] Backbuffer dimensions.
bool windowed, // [in] Windowed (true)or full screen (false).
D3DDEVTYPE deviceType, // [in] HAL or REF
IDirect3DDevice9** device);// [out]The created device.
int EnterMsgLoop(
bool (*ptr_display)(float timeDelta));
LRESULT CALLBACK WndProc(
HWND hwnd,
UINT msg,
WPARAM wParam,
LPARAM lParam);
template<class T> void Release(T t)
{
if( t )
{
t->Release();
t = 0;
}
}
template<class T> void Delete(T t)
{
if( t )
{
delete t;
t = 0;
}
}
const D3DXCOLOR WHITE( D3DCOLOR_XRGB(255, 255, 255) );
const D3DXCOLOR BLACK( D3DCOLOR_XRGB( 0, 0, 0) );
const D3DXCOLOR RED( D3DCOLOR_XRGB(255, 0, 0) );
const D3DXCOLOR GREEN( D3DCOLOR_XRGB( 0, 255, 0) );
const D3DXCOLOR BLUE( D3DCOLOR_XRGB( 0, 0, 255) );
const D3DXCOLOR YELLOW( D3DCOLOR_XRGB(255, 255, 0) );
const D3DXCOLOR CYAN( D3DCOLOR_XRGB( 0, 255, 255) );
const D3DXCOLOR MAGENTA( D3DCOLOR_XRGB(255, 0, 255) );
//
// Lights
//
D3DLIGHT9 InitDirectionalLight(D3DXVECTOR3* direction, D3DXCOLOR* color);
D3DLIGHT9 InitPointLight(D3DXVECTOR3* position, D3DXCOLOR* color);
D3DLIGHT9 InitSpotLight(D3DXVECTOR3* position, D3DXVECTOR3* direction, D3DXCOLOR* color);
//
// Materials
//
D3DMATERIAL9 InitMtrl(D3DXCOLOR a, D3DXCOLOR d, D3DXCOLOR s, D3DXCOLOR e, float p);
const D3DMATERIAL9 WHITE_MTRL = InitMtrl(WHITE, WHITE, WHITE, BLACK, 2.0f);
const D3DMATERIAL9 RED_MTRL = InitMtrl(RED, RED, RED, BLACK, 2.0f);
const D3DMATERIAL9 GREEN_MTRL = InitMtrl(GREEN, GREEN, GREEN, BLACK, 2.0f);
const D3DMATERIAL9 BLUE_MTRL = InitMtrl(BLUE, BLUE, BLUE, BLACK, 2.0f);
const D3DMATERIAL9 YELLOW_MTRL = InitMtrl(YELLOW, YELLOW, YELLOW, BLACK, 2.0f);
//
// 外接体对象
//
struct BoundingBox
{
BoundingBox();
bool isPointInside(D3DXVECTOR3& p);
D3DXVECTOR3 _min;
D3DXVECTOR3 _max;
};
struct BoundingSphere
{
BoundingSphere();
D3DXVECTOR3 _center;
float _radius;
};
//
// 常量
//
/*
常量INFINITY用来表示float类型所能存储的最大浮点数。由于我们不可能取得一个比FLT_MAX更大的浮点数,我们可将
该值概念化为无穷大,这样可使表达了无穷大概念的代码更具可读性。常量EPSLION是我们定义的一个很小的数,如果某个
数小于该值,我们就可认为该数为0.这样做是很有必要的,因为浮点数运算具有不精确性,一个本应为0的数在计算机中表示
可能出现微小的偏差。这样,就会得出该数与0不相等的结果。这样,我们就将判断某个数是否等于0转化为判断某个数是否小于
EPSILON.
*/
const float INFINITY = FLT_MAX;
const float EPSILON = 0.001f;
}
#endif
再来看一下,DirectX3D.cpp中的代码:
#include "DirectX3D.h"
bool d3d::InitD3D(
HINSTANCE hInstance,
int width, int height,
bool windowed,
D3DDEVTYPE deviceType,
IDirect3DDevice9** device)
{
//
// Create the main application window.
//
WNDCLASS wc;
wc.style = CS_HREDRAW | CS_VREDRAW;
wc.lpfnWndProc = (WNDPROC)d3d::WndProc;
wc.cbClsExtra = 0;
wc.cbWndExtra = 0;
wc.hInstance = hInstance;
wc.hIcon = LoadIcon(0, IDI_APPLICATION);
wc.hCursor = LoadCursor(0, IDC_ARROW);
wc.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH);
wc.lpszMenuName = 0;
wc.lpszClassName = "Direct3D9App";
if( !RegisterClass(&wc) )
{
::MessageBox(0, "RegisterClass() - FAILED", 0, 0);
return false;
}
HWND hwnd = 0;
hwnd = ::CreateWindow("Direct3D9App", "Direct3D9App",
WS_EX_TOPMOST,
0, 0, width, height,
0 /*parent hwnd*/, 0 /* menu */, hInstance, 0 /*extra*/);
if( !hwnd )
{
::MessageBox(0, "CreateWindow() - FAILED", 0, 0);
return false;
}
::ShowWindow(hwnd, SW_SHOW);
::UpdateWindow(hwnd);
//
// Init D3D:
//
//第一步
//要初始化IDirect3D 首先必须获取IDirect3D9的指针,使用一个专门的Direct3D函数就可以很容易做到
IDirect3D9 * _d3d9;
//这个对象的主要有两个用途:设备枚举以及创建IDirect3DDevice9类型的对象。设备枚举是指获取系统中可用的的每块图形卡的
//性能,显示模型,格式以及其他信息。这个函数调用失败会返回一个NULL指针。
if(NULL == (_d3d9 = Direct3DCreate9(D3D_SDK_VERSION))){
return FALSE;
}
//第二步
//创建一个代表主显卡的IDirect3DDevice9类型对象时,必须指定使用该对象进行顶点运算的类型。如果可以,我们希望使用硬件顶点运算
//但是由于并非所有的显卡都支持硬件顶点运算,我们必须首先检查图形卡是否支持该类型的运算。
//要进行检查,必须先根据主显卡的性能参数初始化一个IDirect3DDevice9类型的对象。我们使用如下方法来完成初始化:
/*
HRESULT IDirect3D9:GetDeviceCaps(
UINT Adapter,
D3DDEVTYPE DeviceType,
D3DCAPS9 * pCaps;
)
Adapter : 指定物理显卡的序号。
DeviceType:指定设备类(例如硬件设备(D3DDEVTYPE_HAL)或软件设备(D3DDEVTYPE_REF));
pCaps 返回已初始化的设备性能结构实例。
*/
D3DCAPS9 caps;
int vp = 0; //代表顶点如何操作
_d3d9->GetDeviceCaps(D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, &caps);
if(caps.DevCaps & D3DDEVCAPS_HWTRANSFORMANDLIGHT){
vp = D3DCREATE_HARDWARE_VERTEXPROCESSING;
}
else
{
vp = D3DCREATE_SOFTWARE_VERTEXPROCESSING;
}
//第三步是填充D3DPRESENT_PARAMETER结构
//该结构用于指定所要创建的IDirect3DDevice9类型对象的一些特性,该结构定义如下:
/*
typedef struct _D3DPRESENT_PARAMETERS_{
UINT BackBufferWidth;
UINT BackBufferHeight;
UINT BackBufferFormat;
UINT BackBufferCount;
D3DMULTISAMPLE_TYPE MultiSampleType;
DWORD MultiSampleQuality;
D3DSWAPEFFECT SwapEffect;
HWND hDeviceWindow;
BOOL Windowed;
BOOL EnableAutoDepthStencil;
D3DFORMAT AutoDepthStencilFormat;
DWORD Flags;
UINT FullScreen_RefreshRateInHz;
UINT PresentationInterval;
};
*/
/*
BackBufferWidth: 后台缓存中表面的宽度,单位为像素。
BackBufferHeight:后台缓存中表面的高度,单位为像素。
BackBufferFormat:后台缓存的像素格式(如32位像素格式:D3DFMT_A8R8G8B8);
BackBufferCount: 所需使用的后台缓存的个数,通常指定为1,表明我们仅需要一个后台缓存。
MultiSampleType: 后台缓存所使用的多重采样类型。
MultiSampleQuality:多重采样的质量水平。
SwapEffect:D3DSWAPEFFECT 枚举类型的一个成员。该枚举类型指定了交换链中的缓存的页面置换方式。指定D3DSWAPEFFECT_DISCARD时效率最高。
hDeviceWindow:与设备相关的窗口句柄。指定了所要进行绘制的应用程序窗口。
Windowed:为true时表示窗口模式,false时为全屏模式
EnableAutoDepthStencil:设为true,则Direct3D自动创建并维护深度缓存或模板缓存。
AutoDepthStencilFormat:深度缓存或模板缓存的像素格式(例如,用24位表示深度并将8位保留供模板缓存使用,D3DFMT_D24S8).
Flags:一些附加的特性。可以指定为0,表示无标记,或D3DPRESENTFLAG集合中的一个成员,其中两个成员较常用。
D3DPRESENTFLAG_LOCKABLE_DEPTHBUFFER 指定为可锁定的后台缓存。注意,使用一个可锁定的后台缓存会降低性能。
D3DPRESENTFLAG_DISCARD_DEPTHBUFFER 指定当下一个后台缓存提交时,哪个深度或模块缓存将被丢弃。丢弃的意思是深度或模板缓存存储区
中的内容别丢弃或无效。这样可以提升性能。
FullScreen_RefreshRateInHz: 刷新频率,如果想使用默认的刷新频率,则可将该参数指定为D3DPRESENT_RATE_DEFAULT;
PresentationInterval:D3DPRESENT集合的一个成员,其中有两个比较常用。
D3DPRESENT_INTERVAL_IMMEDIATE 立即提交。
D3DPRESENT_INTERVAL_DEFAULT 由Direct3D来选择提交频率,通常该值等于刷新频率。
*/
D3DPRESENT_PARAMETERS d3dpp;
ZeroMemory(&d3dpp, sizeof(d3dpp));
d3dpp.BackBufferWidth = 800;
d3dpp.BackBufferHeight = 600;
d3dpp.BackBufferFormat = D3DFMT_A8R8G8B8;
d3dpp.BackBufferCount = 1;
d3dpp.MultiSampleType = D3DMULTISAMPLE_NONE;
d3dpp.MultiSampleQuality = 0;
d3dpp.SwapEffect = D3DSWAPEFFECT_DISCARD;
d3dpp.hDeviceWindow = hwnd;
d3dpp.Windowed = true;
d3dpp.EnableAutoDepthStencil = true;
d3dpp.AutoDepthStencilFormat = D3DFMT_D24S8;
d3dpp.Flags = 0;
d3dpp.FullScreen_RefreshRateInHz = 0;
d3dpp.PresentationInterval = D3DPRESENT_INTERVAL_IMMEDIATE;
//第四步 创建IDirectDevice9类型的对象
/*
HRESULT IDirect3D9::CreateDevice(
UINT Adapter,
D3DDEVTYPE DeviceType,
HWND hFocusWindow,
DWORD BehaviorFlags,
D3DPRESENT_PARAMETERS *pPresentationParameters,
IDirect3DDevice9 ** ppReturnedDeviceInterface
);
Adapter:指定我们希望用已创建的IDirect3DDevice9对象代表哪块物理显卡。
DeviceType:指定需要使用的设备类型()如,硬件设备用D3DDEVTYPE_HAL,或D3DDEVTYPE_REF代表软件设备。
hFocusWindow:与设备相关的窗口句柄。通常情况下是指设备所要进行绘制的目标窗口。
为了达到预期的目的,该句柄与D3DPRESENT_PARAMETER结构的数据成员hDeviceWindow应为同一个句柄。
BehaviorFlags:该参数可为D3DCREATE_HARDWARE_VERTEXPROCESSING或D3DCREATE_SOFTWARE_VERTEXPROCESSING.
pPresentationParameters:一个已经完成初始化的D3DPRESENT_PARAMETERS类型的实例,该实例定义了设备的一些特性。
ppReturnedDeviceInterface:返回所创建的设备。
*/
if(FAILED(_d3d9->CreateDevice(D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL,
hwnd, vp, &d3dpp, device)))
return FALSE;
_d3d9->Release();
return TRUE;
}
int d3d::EnterMsgLoop( bool (*ptr_display)(float timeDelta) )
{
MSG msg;
::ZeroMemory(&msg, sizeof(MSG));
static float lastTime = (float)timeGetTime();
while(msg.message != WM_QUIT)
{
if(::PeekMessage(&msg, 0, 0, 0, PM_REMOVE))
{
::TranslateMessage(&msg);
::DispatchMessage(&msg);
}
else
{
float currTime = (float)timeGetTime();
float timeDelta = (currTime - lastTime)*0.001f;
ptr_display(timeDelta);
lastTime = currTime;
}
}
return msg.wParam;
}
D3DLIGHT9 d3d::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;
}
D3DLIGHT9 d3d::InitPointLight(D3DXVECTOR3* position, D3DXCOLOR* color)
{
D3DLIGHT9 light;
::ZeroMemory(&light, sizeof(light));
light.Type = D3DLIGHT_POINT;
light.Ambient = *color * 0.4f;
light.Diffuse = *color;
light.Specular = *color * 0.6f;
light.Position = *position;
light.Range = 1000.0f;
light.Falloff = 1.0f;
light.Attenuation0 = 1.0f;
light.Attenuation1 = 0.0f;
light.Attenuation2 = 0.0f;
return light;
}
D3DLIGHT9 d3d::InitSpotLight(D3DXVECTOR3* position, D3DXVECTOR3* direction, D3DXCOLOR* color)
{
D3DLIGHT9 light;
::ZeroMemory(&light, sizeof(light));
light.Type = D3DLIGHT_SPOT;
light.Ambient = *color * 0.4f;
light.Diffuse = *color;
light.Specular = *color * 0.6f;
light.Position = *position;
light.Direction = *direction;
light.Range = 1000.0f;
light.Falloff = 1.0f;
light.Attenuation0 = 1.0f;
light.Attenuation1 = 0.0f;
light.Attenuation2 = 0.0f;
light.Theta = 0.5f;
light.Phi = 0.7f;
return light;
}
D3DMATERIAL9 d3d::InitMtrl(D3DXCOLOR a, D3DXCOLOR d, D3DXCOLOR s, D3DXCOLOR e, float p)
{
D3DMATERIAL9 mtrl;
mtrl.Ambient = a;
mtrl.Diffuse = d;
mtrl.Specular = s;
mtrl.Emissive = e;
mtrl.Power = p;
return mtrl;
}
d3d::BoundingBox::BoundingBox()
{
// infinite small
_min.x = d3d::INFINITY;
_min.y = d3d::INFINITY;
_min.z = d3d::INFINITY;
_max.x = -d3d::INFINITY;
_max.y = -d3d::INFINITY;
_max.z = -d3d::INFINITY;
}
bool d3d::BoundingBox::isPointInside(D3DXVECTOR3& p)
{
if( p.x >= _min.x && p.y >= _min.y && p.z >= _min.z &&
p.x <= _max.x && p.y <= _max.y && p.z <= _max.z )
{
return true;
}
else
{
return false;
}
}
d3d::BoundingSphere::BoundingSphere()
{
_radius = 0.0f;
}
最后看一下,wmain.cpp的代码:
#include "DirectX3D.h"
#include <fstream>
#include <vector>
//
// Globals
//
IDirect3DDevice9* Device = 0;
const int Width = 640;
const int Height = 480;
ID3DXMesh* Mesh = 0; //
ID3DXMesh* SphereMesh = 0;
ID3DXMesh* BoxMesh = 0;
std::vector<D3DMATERIAL9> Mtrls(0);// 创建一个容器对象,里面的元素是材质
std::vector<IDirect3DTexture9*> Textures(0);//创建一个容器对象,里面的元素是纹理。
bool RenderBoundingSphere = true;
//
// Classes and Structures
//
struct Vertex
{
Vertex(){}
Vertex(float x, float y, float z,
float nx, float ny, float nz, float u, float v)
{
_x = x; _y = y; _z = z;
_nx = nx; _ny = ny; _nz = nz;
_u = u; _v = v;
}
float _x, _y, _z, _nx, _ny, _nz, _u, _v;
static const DWORD FVF;
};
const DWORD Vertex::FVF = D3DFVF_XYZ | D3DFVF_NORMAL | D3DFVF_TEX1;
//
// Framework Functions
//
bool ComputeBoundingSphere(ID3DXMesh* mesh, d3d::BoundingSphere* sphere);
bool ComputeBoundingBox(ID3DXMesh* mesh, d3d::BoundingBox* box);
bool Setup()
{
//我们用一个ID3DMesh对象存储XFile文件中的加载的网格数据。
/*
现在的建模工具,比如3DS Max和Maya,都可以将网格数据(几何信息,材质,动画以及其他可能的有用数据)导出到文件中。
我们可以编写一个文件读取程序来提取网格数据,并在我们的3D应用程序中使用。另外还有一种更简单的方法,有一种
特殊的网格文件格式称为XFile格式(扩展名为.x),许多3D建模工具都可以将模型数据导成这个格式,而且也有许多转换程序可以将
其他较流行的网格文件格式转换为.x格式,XFile之所以使用方便,主要的原因是他是DirectX定义的格式,提供相应的函数对其直接操作
*/
//另外我们用两个向量分别存储该网格的材质和纹理数据。
HRESULT hr = 0;
ID3DXBuffer * adjBuffer = 0;
ID3DXBuffer * mtrlBuffer = 0;
DWORD numMtrls = 0;
/*
看一下这个函数的参数说明:
HRESULT D3DXLoadMeshFromX(
__in LPCTSTR pFilename,
__in DWORD Options,
__in LPDIRECT3DDEVICE9 pD3DDevice,
__out LPD3DXBUFFER *ppAdjacency,
__out LPD3DXBUFFER *ppMaterials,
__out LPD3DXBUFFER *ppEffectInstances,
__out DWORD *pNumMaterials,
__out LPD3DXMESH *ppMesh
);
pFilename: 所要加载的XFile文件名。
Options:创建网格时所使用的创建标记。标记选项的完整列表可参阅SDK文档中与枚举累型D3DXMESH相关的部分。常用标记如下。
D3DXMESH_32BIT 网格将使用32位索引。
D3DXMESH_MANAGED 网格数据将被存储于托管内存池中。
D3DXMESH_WRITEONLY 指定网格数据为只读。
D3DXMESH_DYNAMIC 网格缓存将使用动态内存。
pD3DDevice:与该网格对象相关的设备指针。
ppAdjacency:返回一个ID3DXBuffer对象,该对象包含了一个描述了该网格对象的邻接信息的DWORD类型的数组。
ppMaterials:返回一个ID3DXBuffer对象,该对象包含了一个描述了该网格对象的材质数据D3DXMATERIAL类型的结构数组
ppEffectInstances:返回一个ID3DXBuffer对象,该对象包含了一个D3DXEFFECTINSTANCE结构。我们可以通过将该参数设置0将器忽略。
pNumMaterials:返回网格中的材质数目。
ppMesh:返回所创建的并一填充了XFile几何数据的ID3DXMesh对象。
XFile材质:
D3DXLoadMeshFromX函数中的第7个参数返回了该网格对象所含的材质数目,第5个参数返回了一个存储了材质数据的D3DXMATRIAL类型的结构数组。
D3DXMATERIAL结构的定义如下:
typedef struct D3DXMATERIAL{
D3DMATERIAL9 MatD3D;
LPSTR pTextureFilename;
}D3DXMATERIAL;
该结构较为简单,包含了一个D3DMATERIAL9结构和一个指向以NULL结尾的字符串指针,该字符串指定了与网格相关的纹理文件名。
XFile中并为存储纹理数据,是包含了纹理图像文件名,该文件名是对包含了实际纹理数据的纹理对象的引用。这样,当用
D3DXLoadMeshFromX函数加载一个XFile文件后,必须根据指定的纹理文件名加载纹理数据。
*/
hr = D3DXLoadMeshFromX(
"bigship1.x",
D3DXMESH_MANAGED,
Device,
&adjBuffer,
&mtrlBuffer,
0,
&numMtrls,
&Mesh);
if(FAILED(hr))
{
::MessageBox(0, "D3DXLoadMeshFromX() - FAILED", 0, 0);
return false;
}
//得到材质和纹理。
if( mtrlBuffer != 0 && numMtrls != 0 ) //如果第5个参数,材质空间不为NUL,而且材质的数目不为0
{
D3DXMATERIAL* mtrls = (D3DXMATERIAL*)mtrlBuffer->GetBufferPointer(); //得到材质指针。
for(int i = 0; i < numMtrls; i++)
{
// 将环境光和镜面光设置为一样
mtrls[i].MatD3D.Ambient = mtrls[i].MatD3D.Diffuse;
// 保存材质数据到容器中
Mtrls.push_back( mtrls[i].MatD3D );
// 检查相应的纹理文件名
if( mtrls[i].pTextureFilename != 0 )
{
// 如果文件名不为NULL,用这个文件名,创建相应的纹理。
IDirect3DTexture9* tex = 0;
D3DXCreateTextureFromFile(
Device,
mtrls[i].pTextureFilename,
&tex);
// 保存纹理数据都容器中
Textures.push_back( tex );
}
else
{
// 如果纹理文件名为NULL,不保存
Textures.push_back( 0 );
}
}
}
//释放材质的指针
d3d::Release<ID3DXBuffer*>(mtrlBuffer);
//对网格进行优化,这里我们使用到了网格的邻接信息
hr = Mesh->OptimizeInplace(
D3DXMESHOPT_ATTRSORT |
D3DXMESHOPT_COMPACT |
D3DXMESHOPT_VERTEXCACHE,
(DWORD*)adjBuffer->GetBufferPointer(),
0,0,0);
d3d::Release<ID3DXBuffer*>(adjBuffer);
if(FAILED(hr))
{
::MessageBox(0, "OptimizeInplace() - FAILED", 0, 0);
d3d::Release<ID3DXBuffer*>(adjBuffer); // free
return false;
}
//创建网格的外接球和外接体
d3d::BoundingSphere boundingSphere;
d3d::BoundingBox boundingBox;
ComputeBoundingSphere(Mesh, &boundingSphere); //通过网格得到外接球的球心和半径
ComputeBoundingBox(Mesh, &boundingBox);//通过网格得到外接体的顶点的最大值和最小值。
//使用外接球的半径和球心创建外接球的网格
/*
HRESULT D3DXCreateSphere(
__in LPDIRECT3DDEVICE9 pDevice,
__in FLOAT Radius,
__in UINT Slices,
__in UINT Stacks,
__out LPD3DXMESH *ppMesh,
__out LPD3DXBUFFER *ppAdjacency
);
*/
D3DXCreateSphere(
Device,
boundingSphere._radius,
20,//纵向的切面个数
20,//横向的切面个数,这两个值越大,表示球体更细腻。
&SphereMesh,
0);
//使用外接体的顶点的最大值和最小值创建外接体的网格
/*
HRESULT D3DXCreateBox(
__in LPDIRECT3DDEVICE9 pDevice,
__in FLOAT Width,
__in FLOAT Height,
__in FLOAT Depth,
__out LPD3DXMESH *ppMesh,
__out LPD3DXBUFFER *ppAdjacency
);
*/
D3DXCreateBox(
Device,
boundingBox._max.x - boundingBox._min.x,//宽
boundingBox._max.y - boundingBox._min.y,//高
boundingBox._max.z - boundingBox._min.z,//深
&BoxMesh,
0);
//设置纹理过滤方式,和多级渐进纹理的处理方式。
Device->SetSamplerState(0, D3DSAMP_MAGFILTER, D3DTEXF_LINEAR);
Device->SetSamplerState(0, D3DSAMP_MINFILTER, D3DTEXF_LINEAR);
Device->SetSamplerState(0, D3DSAMP_MIPFILTER, D3DTEXF_POINT);
//设置光照
D3DXVECTOR3 dir(1.0f, -1.0f, 1.0f);
D3DXCOLOR col(1.0f, 1.0f, 1.0f, 1.0f);
D3DLIGHT9 light = d3d::InitDirectionalLight(&dir, &col);
Device->SetLight(0, &light);
Device->LightEnable(0, true);
Device->SetRenderState(D3DRS_NORMALIZENORMALS, true);
Device->SetRenderState(D3DRS_SPECULARENABLE, true);
//移动摄像机的位置,进行取景变换。
D3DXVECTOR3 pos(4.0f, 12.f, -20.0f);
D3DXVECTOR3 target(0.0f, 0.0f, 0.0f);
D3DXVECTOR3 up(0.0f, 1.0f, 0.0f);
D3DXMATRIX V;
D3DXMatrixLookAtLH(
&V,
&pos,
&target,
&up);
Device->SetTransform(D3DTS_VIEW, &V);
//进行投影变化:
D3DXMATRIX proj;
D3DXMatrixPerspectiveFovLH(
&proj,
D3DX_PI * 0.5f, // 90 - degree
(float)Width / (float)Height,
1.0f,
1000.0f);
Device->SetTransform(D3DTS_PROJECTION, &proj);
return true;
}
//将之前分配的内存进行清理,也就是顶点缓存和索引缓存
void Cleanup()
{
d3d::Release<ID3DXMesh*>(Mesh);
for(int i = 0; i < Textures.size(); i++)
d3d::Release<IDirect3DTexture9*>( Textures[i] );
d3d::Release<ID3DXMesh*>(SphereMesh);
d3d::Release<ID3DXMesh*>(BoxMesh);
}
bool Display(float timeDelta)
{
if( Device )
{
static float y = 0.0f;
D3DXMATRIX yRot;
D3DXMatrixRotationY(&yRot, y);
y += timeDelta;
if( y >= 6.28f )
y = 0.0f;
D3DXMATRIX World = yRot;
Device->SetTransform(D3DTS_WORLD, &World);
//
// 清屏
//
Device->Clear(0, 0, D3DCLEAR_TARGET | D3DCLEAR_ZBUFFER, 0xffffffff, 1.0f, 0);
Device->BeginScene();
/*
这里的绘制跟之前的网格基本一样,我们另外,增加了将网格以黄色材质绘制在物体的线框模型上,从而勾画出网格中的三角形面片。
这样做的好处是可以更清楚看到当我们调整LOD时,每个三角形面片是如何被加入和移除的。
*/
for(int i = 0; i < Mtrls.size(); i++)//从容器中得到保存的材质的数目
{ //纹理设置,并进行子集的绘制。
Device->SetMaterial( &Mtrls[i] );
Device->SetTexture(0, Textures[i]);
Mesh->DrawSubset(i);
/*
值得一提的是,D3DXLoadMeshFromX函数载入XFile数据后,返回的D3DXMATERIAL结构数组中的第i项就与第i个子集相对应。所以我们将
各子集按照0,1,2...n-1的顺序进行标记,其中n是子集和材质的总数。这样就可用一个简单的循环对全部子集进行遍历和绘制,从而完成
整个网格的绘制。*/
}
//
// 设置外接球的蓝色镜面光的偷光率为10%
D3DMATERIAL9 blue = d3d::BLUE_MTRL;
blue.Diffuse.a = 0.10f; // 10% opacity
Device->SetMaterial(&blue);
Device->SetTexture(0, 0); // 关闭纹理
//我们回忆一下,下面的绘制状态,D3DRS_ALPHABLENDENABLE设置为true,表示启用融合运算,默认状态下,融合运算是关闭的。
Device->SetRenderState(D3DRS_ALPHABLENDENABLE, true);
//将源融合因子设置为D3DBLEND_SRCALPHA
Device->SetRenderState(D3DRS_SRCBLEND, D3DBLEND_SRCALPHA);
//将目标融合因子设置为D3DBLEND_INVSRCALPHA
Device->SetRenderState(D3DRS_DESTBLEND, D3DBLEND_INVSRCALPHA);
if( RenderBoundingSphere )
SphereMesh->DrawSubset(0);
else
BoxMesh->DrawSubset(0);
Device->SetRenderState(D3DRS_ALPHABLENDENABLE, false);
Device->EndScene();
Device->Present(0, 0, 0, 0);
}
return true;
}
//
// WndProc
//
LRESULT CALLBACK d3d::WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
switch( msg )
{
case WM_DESTROY:
::PostQuitMessage(0);
break;
case WM_KEYDOWN:
if( wParam == VK_ESCAPE )
::DestroyWindow(hwnd);
if( wParam == VK_SPACE )
RenderBoundingSphere = !RenderBoundingSphere;
break;
}
return ::DefWindowProc(hwnd, msg, wParam, lParam);
}
//
// WinMain
//
int WINAPI WinMain(HINSTANCE hinstance,
HINSTANCE prevInstance,
PSTR cmdLine,
int showCmd)
{
if(!d3d::InitD3D(hinstance,
640, 480, true, D3DDEVTYPE_HAL, &Device))
{
::MessageBox(0, "InitD3D() - FAILED", 0, 0);
return 0;
}
if(!Setup())
{
::MessageBox(0, "Setup() - FAILED", 0, 0);
return 0;
}
d3d::EnterMsgLoop( Display );
Cleanup();
Device->Release();
return 0;
}
bool ComputeBoundingSphere(ID3DXMesh* mesh, d3d::BoundingSphere* sphere)
{
HRESULT hr = 0;
BYTE* v = 0;
mesh->LockVertexBuffer(0, (void**)&v);//得到网格的顶点缓存
/*
我们来看一下D3DX库提供的用来计算一个网格的外接球的函数
HRESULT D3DXComputeBoundingSphere(
__in const D3DXVECTOR3 *pFirstPosition,
__in DWORD NumVertices,
__in DWORD dwStride,
__in D3DXVECTOR3 *pCenter,
__in FLOAT *pRadius
);
pFirstPosition:指向顶点数组(该数组的每个元素都描述了对应顶点)中第一个顶点的位置向量的指针。我们可以通过网格对象得到顶点缓存的指针,最后
可转化为该值。
NumVertices:该网格中顶点数组中顶点的个数。可通过网格mesh->GetNumVertices()得到
dsStride:每个顶点的大小,单位为字节。该值很重要,因为一种顶点结构可能包含了许多该函数所不需要的附加信息,如法向量和纹理坐标等。这样该函数
就需要知道应跳过多少字节才能找到下一个顶点的位置。mesh->GetFVF()可以返回一个描述了顶点格式的DWORD类型值。D3DXGetFVFVertexSize这个
函数可以得到该顶点占用多少个字节。
pCenter:返回的外接球的球心位置。
pRadius: 返回外接球的半径。
*/
hr = D3DXComputeBoundingSphere(
(D3DXVECTOR3*)v,
mesh->GetNumVertices(),
D3DXGetFVFVertexSize(mesh->GetFVF()),
&sphere->_center,
&sphere->_radius);
mesh->UnlockVertexBuffer();
if( FAILED(hr) )
return false;
return true;
}
bool ComputeBoundingBox(ID3DXMesh* mesh, d3d::BoundingBox* box)
{
HRESULT hr = 0;
BYTE* v = 0;
mesh->LockVertexBuffer(0, (void**)&v);
//这里是D3DX中计算一个网格外接体的函数,跟外接球的函数很类似,只是最后两个参数,返回外接体的最大点和最小点。
hr = D3DXComputeBoundingBox(
(D3DXVECTOR3*)v,
mesh->GetNumVertices(),
D3DXGetFVFVertexSize(mesh->GetFVF()),
&box->_min,
&box->_max);
mesh->UnlockVertexBuffer();
if( FAILED(hr) )
return false;
return true;
}
最后来看一下程序运行截图:
每日总结:
我们可用函数D3DXComputeBoundingSphere和D3DXComputeBoundingBox分别计算出一个物体的外接球和外接体。外接体十分有用,因为它们对网格的体积进行了逼近,所以可用来加速那些与网格空间体积相关的运算。