DirectX 3D_实践之DirectX3D中的物体拾取技术
2013-06-20 21:02
399 查看
今天我们谈一下,个人的核心竞争力,每个从事IT的人,都必须有自己的核心竞争力,有了这个,你才能有不可替代性,才可以在就业中,游刃有余,人的精力是有限的,不可能让自己在各个方面都有很深的研究和了解,但必须,在某一个方面,进行深入的研究,之前,我有讲过,我们每个人都在挖井,其实地下水在地下的深度是一致的,很多觉得当前的技术不好,没有进行深入,就重新去挖了一口井,过一段时间,又挖了一口井,但是他一直没有挖出地下水。我们应该在挖出水的情况下,再去,挖另外的井,因为现在的企业对人的素质,掌握的知识,都要求很全面,喜欢复合性人才,所以,我们必须在保证自己的核心竞争力的情况下,去学些其他的知识。触类旁通。对于自己,还是应该在驱动上面面再次往深入一些,尽量的多做几个复合性的驱动,多对几种
框架结构有所了解。
今天来看一下,DirectX3D的拾取,拾取技术在许多游戏和3D程序中都得到了很广泛的应用。例如玩家需要通过单击鼠标与场景中各种各样的物体进行交互。玩家可以通过单击敌人而向其开火,也可以单击某些道具将其捡起。我们一般将拾取分解为以下4个步骤:
给定所单击的屏幕点S,求出它在投影窗口中的对应点p.
计算拾取射线。即自坐标原点发出且通过点p的那条射线。
将拾取射线和物体模型变换至同一个坐标系中。
进行物体/射线的相交判断。相交的物体即为用户所拾取的屏幕对象。
我们先来看DirectX3D.h的代码:
再来看DirectX3D.cpp的代码:
最后来看wmain.cpp的代码:
最后来看程序运行时的截图:
![](http://img.blog.csdn.net/20130620205558453?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvWjE4XzI4XzE5/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/Center)
每日总结:
拾取是一项根据用户在屏幕上单击的位置(3D物体在屏幕上的投影)来确定用户是否选中以及选中哪个3D物体的技术。
如果一条射线的起点与观察坐标系的原点重合,且经过用户所单击的屏幕点,则该射线即对应一条拾取射线。
要判断射线与某物体是否相交,可测试射线是否与构成物体的某一面片相交或射线是否与该物体的外接体相交。
框架结构有所了解。
今天来看一下,DirectX3D的拾取,拾取技术在许多游戏和3D程序中都得到了很广泛的应用。例如玩家需要通过单击鼠标与场景中各种各样的物体进行交互。玩家可以通过单击敌人而向其开火,也可以单击某些道具将其捡起。我们一般将拾取分解为以下4个步骤:
给定所单击的屏幕点S,求出它在投影窗口中的对应点p.
计算拾取射线。即自坐标原点发出且通过点p的那条射线。
将拾取射线和物体模型变换至同一个坐标系中。
进行物体/射线的相交判断。相交的物体即为用户所拾取的屏幕对象。
我们先来看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; }; struct Ray { D3DXVECTOR3 _origin; D3DXVECTOR3 _direction; }; // // 常量 // /* 常量INFINITY用来表示float类型所能存储的最大浮点数。由于我们不可能取得一个比FLT_MAX更大的浮点数,我们可将 该值概念化为无穷大,这样可使表达了无穷大概念的代码更具可读性。常量EPSLION是我们定义的一个很小的数,如果某个 数小于该值,我们就可认为该数为0.这样做是很有必要的,因为浮点数运算具有不精确性,一个本应为0的数在计算机中表示 可能出现微小的偏差。这样,就会得出该数与0不相等的结果。这样,我们就将判断某个数是否等于0转化为判断某个数是否小于 EPSILON. */ const float INFINITY = FLT_MAX; const float EPSILON = 0.001f; bool DrawBasicScene( IDirect3DDevice9* device,// Pass in 0 for cleanup. float scale); // uniform scale // // Vertex 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; float _nx, _ny, _nz; float _u, _v; static const DWORD FVF; }; } #endif
再来看DirectX3D.cpp的代码:
#include "DirectX3D.h" // vertex formats const DWORD d3d::Vertex::FVF = D3DFVF_XYZ | D3DFVF_NORMAL | D3DFVF_TEX1; 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; } bool d3d::DrawBasicScene(IDirect3DDevice9* device, float scale) { static IDirect3DVertexBuffer9* floor = 0; static IDirect3DTexture9* tex = 0; static ID3DXMesh* pillar = 0; HRESULT hr = 0; if( device == 0 ) { if( floor && tex && pillar ) { // they already exist, destroy them d3d::Release<IDirect3DVertexBuffer9*>(floor); d3d::Release<IDirect3DTexture9*>(tex); d3d::Release<ID3DXMesh*>(pillar); } } else if( !floor && !tex && !pillar ) { // they don't exist, create them device->CreateVertexBuffer( 6 * sizeof(d3d::Vertex), 0, d3d::Vertex::FVF, D3DPOOL_MANAGED, &floor, 0); Vertex* v = 0; floor->Lock(0, 0, (void**)&v, 0); v[0] = Vertex(-20.0f, -2.5f, -20.0f, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f); v[1] = Vertex(-20.0f, -2.5f, 20.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f); v[2] = Vertex( 20.0f, -2.5f, 20.0f, 0.0f, 1.0f, 0.0f, 1.0f, 0.0f); v[3] = Vertex(-20.0f, -2.5f, -20.0f, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f); v[4] = Vertex( 20.0f, -2.5f, 20.0f, 0.0f, 1.0f, 0.0f, 1.0f, 0.0f); v[5] = Vertex( 20.0f, -2.5f, -20.0f, 0.0f, 1.0f, 0.0f, 1.0f, 1.0f); floor->Unlock(); D3DXCreateCylinder(device, 0.5f, 0.5f, 5.0f, 20, 20, &pillar, 0); D3DXCreateTextureFromFile( device, "desert.bmp", &tex); } else { // // Pre-Render Setup // device->SetSamplerState(0, D3DSAMP_MAGFILTER, D3DTEXF_LINEAR); device->SetSamplerState(0, D3DSAMP_MINFILTER, D3DTEXF_LINEAR); device->SetSamplerState(0, D3DSAMP_MIPFILTER, D3DTEXF_POINT); D3DXVECTOR3 dir(0.707f, -0.707f, 0.707f); 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); // // Render // D3DXMATRIX T, R, P, S; D3DXMatrixScaling(&S, scale, scale, scale); // used to rotate cylinders to be parallel with world's y-axis D3DXMatrixRotationX(&R, -D3DX_PI * 0.5f); // draw floor D3DXMatrixIdentity(&T); T = T * S; device->SetTransform(D3DTS_WORLD, &T); device->SetMaterial(&d3d::WHITE_MTRL); device->SetTexture(0, tex); device->SetStreamSource(0, floor, 0, sizeof(Vertex)); device->SetFVF(Vertex::FVF); device->DrawPrimitive(D3DPT_TRIANGLELIST, 0, 2); // draw pillars device->SetMaterial(&d3d::BLUE_MTRL); device->SetTexture(0, 0); for(int i = 0; i < 5; i++) { D3DXMatrixTranslation(&T, -5.0f, 0.0f, -15.0f + (i * 7.5f)); P = R * T * S; device->SetTransform(D3DTS_WORLD, &P); pillar->DrawSubset(0); D3DXMatrixTranslation(&T, 5.0f, 0.0f, -15.0f + (i * 7.5f)); P = R * T * S; device->SetTransform(D3DTS_WORLD, &P); pillar->DrawSubset(0); } } return true; }
最后来看wmain.cpp的代码:
#include "DirectX3D.h" #include <fstream> #include <vector> // // Globals // IDirect3DDevice9* Device = 0; const int Width = 640; const int Height = 480; ID3DXMesh* Teapot = 0; ID3DXMesh* Sphere = 0; D3DXMATRIX World; d3d::BoundingSphere BSphere; // // Framework Functions // // // Functions // d3d::Ray CalcPickingRay(int x, int y) { float px = 0.0f; float py = 0.0f; //得到视口的指针 D3DVIEWPORT9 vp; Device->GetViewport(&vp); //进行投影变换 D3DXMATRIX proj; Device->GetTransform(D3DTS_PROJECTION, &proj); px = ((( 2.0f*x) / vp.Width) - 1.0f) / proj(0, 0); py = (((-2.0f*y) / vp.Height) + 1.0f) / proj(1, 1); d3d::Ray ray; ray._origin = D3DXVECTOR3(0.0f, 0.0f, 0.0f); ray._direction = D3DXVECTOR3(px, py, 1.0f); return ray; } void TransformRay(d3d::Ray* ray, D3DXMATRIX* T) { // transform the ray's origin, w = 1. /*函数D3DXVec3TransformCoord,和函数D3DXVec3TransformNormal均已3D向量作为其参数,但要注意,使用D3DXVec3TransformCoord 时,向量参数的第4个分量应理解为1,D3DXVec3TransformNormal时,第4个参数应理解为0,所以我们用D3DXVec3TransformCoord来实现 点的变换,D3DXVec3TransformNormal来实现向量的变换*/ D3DXVec3TransformCoord( &ray->_origin, &ray->_origin, T); // transform the ray's direction, w = 0. D3DXVec3TransformNormal( &ray->_direction, &ray->_direction, T); // normalize the direction D3DXVec3Normalize(&ray->_direction, &ray->_direction); } bool RaySphereIntTest(d3d::Ray* ray, d3d::BoundingSphere* sphere) { D3DXVECTOR3 v = ray->_origin - sphere->_center; float b = 2.0f * D3DXVec3Dot(&ray->_direction, &v); float c = D3DXVec3Dot(&v, &v) - (sphere->_radius * sphere->_radius); // find the discriminant float discriminant = (b * b) - (4.0f * c); // test for imaginary number if( discriminant < 0.0f ) return false; discriminant = sqrtf(discriminant); float s0 = (-b + discriminant) / 2.0f; float s1 = (-b - discriminant) / 2.0f; // if a solution is >= 0, then we intersected the sphere if( s0 >= 0.0f || s1 >= 0.0f ) return true; return false; } bool Setup() { // // 创建茶壶 // D3DXCreateTeapot(Device, &Teapot, 0); // //通过网格的顶点得到外接球的尺寸 // BYTE* v = 0; Teapot->LockVertexBuffer(0, (void**)&v); D3DXComputeBoundingSphere( (D3DXVECTOR3*)v, Teapot->GetNumVertices(), D3DXGetFVFVertexSize(Teapot->GetFVF()), &BSphere._center, &BSphere._radius); Teapot->UnlockVertexBuffer(); // // 创建外接球 // D3DXCreateSphere(Device, BSphere._radius, 20, 20, &Sphere, 0); // // 设置光照 // D3DXVECTOR3 dir(0.707f, -0.0f, 0.707f); 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, false); // // 设置观察矩阵,进行取景变换 // D3DXVECTOR3 pos(0.0f, 0.0f, -10.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.25f, // 45 - degree (float)Width / (float)Height, 1.0f, 1000.0f); Device->SetTransform(D3DTS_PROJECTION, &proj); return true; } //将之前分配的内存进行清理,也就是顶点缓存和索引缓存 void Cleanup() { d3d::Release<ID3DXMesh*>(Teapot); d3d::Release<ID3DXMesh*>(Sphere); } bool Display(float timeDelta) { if( Device ) { // // Update: Update Teapot. // static float r = 0.0f; static float v = 1.0f; static float angle = 0.0f; D3DXMatrixTranslation(&World, cosf(angle) * r, sinf(angle) * r, 10.0f); // 不断的修改,外接球的球心位置 // BSphere._center = D3DXVECTOR3(cosf(angle)*r, sinf(angle)*r, 10.0f); r += v * timeDelta; if( r >= 8.0f ) v = -v; // reverse direction if( r <= 0.0f ) v = -v; // reverse direction angle += 1.0f * D3DX_PI * timeDelta; if( angle >= D3DX_PI * 2.0f ) angle = 0.0f; // // Render // Device->Clear(0, 0, D3DCLEAR_TARGET | D3DCLEAR_ZBUFFER, 0xffffffff, 1.0f, 0); Device->BeginScene(); //设置茶壶的材质,并绘制茶壶 Device->SetTransform(D3DTS_WORLD, &World); Device->SetMaterial(&d3d::YELLOW_MTRL); Teapot->DrawSubset(0); // 将外接球和茶壶进行融合 Device->SetRenderState(D3DRS_ALPHABLENDENABLE, true); Device->SetRenderState(D3DRS_SRCBLEND, D3DBLEND_SRCALPHA); Device->SetRenderState(D3DRS_DESTBLEND, D3DBLEND_INVSRCALPHA); //设置融合后的物体的材质,并进行绘制 D3DMATERIAL9 blue = d3d::BLUE_MTRL; blue.Diffuse.a = 0.25f; // 25% opacity Device->SetMaterial(&blue); Sphere->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); case WM_LBUTTONDOWN: // 通过鼠标按下时,点的坐标,计算出射线,我们用世界坐标系中的原点为射线的起点 d3d::Ray ray = CalcPickingRay(LOWORD(lParam), HIWORD(lParam)); // transform the ray to world space D3DXMATRIX view; Device->GetTransform(D3DTS_VIEW, &view); D3DXMATRIX viewInverse; D3DXMatrixInverse(&viewInverse, 0, &view); TransformRay(&ray, &viewInverse); // test for a hit if( RaySphereIntTest(&ray, &BSphere) ) ::MessageBox(0, "Hit!", "HIT", 0); 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; }
最后来看程序运行时的截图:
每日总结:
拾取是一项根据用户在屏幕上单击的位置(3D物体在屏幕上的投影)来确定用户是否选中以及选中哪个3D物体的技术。
如果一条射线的起点与观察坐标系的原点重合,且经过用户所单击的屏幕点,则该射线即对应一条拾取射线。
要判断射线与某物体是否相交,可测试射线是否与构成物体的某一面片相交或射线是否与该物体的外接体相交。
相关文章推荐
- DirectX 3D_实践之DirectX3D中的融合技术
- DirectX 3D_实践之DirectX3D中模板缓存的使用
- DirectX 3D_实践之DirectX3D中颜色的表示
- DirectX 3D_实践之DirectX3D纹理映射
- DirectX 3D_实践之DirectX3D 网格的外接体的创建和使用
- DirectX 3D_实践之DirectX3D中网格的使用
- DirectX 3D_实践之在DirectX3D中实现摄像机方位的动态变换
- DirectX 3D_实践之DirectX3D的基本绘制流程
- DirectX 3D_实践之DirectX3D光照的实现
- DirectX 3D_实践之DirectX3D中的文本绘制
- DirectX 3D_基础之拾取 屏幕到投影窗口的变换 对射线进行变换 射线/物体相交判断
- DirectX 3D_实践之初始化DirectX3D
- DirectX 3D_实践之DirectX3D中高度图的使用
- DirectX 3D_实践之DirectX3D中渐进网格的使用
- DirectX 3D_实践之DirectX3D中通过XFile文件来创建网格
- DirectX 3D_实践之DirectX3D中粒子系统的使用
- DirectX 3D_基础之粒子系统 广告牌技术 粒子和点精灵 点精灵的结构 点精灵的绘制状态 粒子及其属性
- 3D物体拾取及XNA实现
- MIT 3D图像交互技术:实现触摸真实物体
- 3D拾取技术