今天来看一下,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物体的技术。
如果一条射线的起点与观察坐标系的原点重合,且经过用户所单击的屏幕点,则该射线即对应一条拾取射线。
要判断射线与某物体是否相交,可测试射线是否与构成物体的某一面片相交或射线是否与该物体的外接体相交。
二、下面来自:http://www.cppblog.com/lovedday/archive/2008/04/04/46264.html
假设用户点击了屏幕上的点 s (x, y)。 从图15.1我们能看到用户选取了茶壶。无论如何,应用程序无法根据给定的s点就立即确定茶壶是被选取。

我们知道一些知识:关于茶壶和它的关联点s,茶壶投影在围绕s点的区域,更准确的说是:它投影到投影窗口上围绕p点的区域,与它对应的屏幕点是s。因为这个问题依赖于3D物体与它的投影之间的关系,我们看图15.2就可以了解。

图15.2我们看到如果我们发射一条选取射线,从原点发出,经过点p,会与围绕p点投影的对象相交,即茶壶。所以一旦我们计算选取射线,我们可以遍例场景中的每个对象并测试,看射线是否与它相交。与射线相交的对象即是用户选择的对象,在这个例子中用户选取的对象是茶壶。
上面的例子讲解了点s与茶壶的关系。通常我们任意点击屏幕上的点,我们遍例场景中的每个对象,如果对象与射线相交,那么这个对象就是用户选取的对象。例如,图15.1中,如果用户没有点击5个对象中的一个,而是点击了白色的背景区域,射线将不能相交任何对象。因此,结论是:如果射线没有与场景中的任何对象相交,则用户没有点击任何一个对象,其它的我们不关心。
“选取”适用于所有种类的游戏和3D程序。例如,玩家通过用鼠标点击来影响3D世界中的不同对象,玩家可能点击向敌人射击,或点击拾取物品。好的程序会适当做出反应,程序需要知道哪个对象被选取(是敌人还是物品),和在3D空间中的位置(开枪会击中哪?或玩家将要移动到哪去拾取物品?)。选取回答了我们这些问题。
我们将选取分解成四步:
1) 给一个屏幕点s,找出它在投影窗口上相交的点,即p。
2) 计算射线,它是从原点出发并经过点p。
3) 转换射线与模型到同一空间。
4) 测试与射线相交的对象,相交的对象即是屏幕上点击的对象。
15.1屏幕到投影窗口的转换
首先,转换屏幕点到投影窗口,视口变换矩阵是:

因为前面的定义,投影窗口就是z=1的平面,所以pz = 1。
投影矩阵缩放投影窗口上的点,来模拟不同的视角。为了返回缩放前的点值,我们必须用与缩放相反的操作来转换点。P是投影矩阵,因为P00和 P11转换距阵缩放点的x和y坐标,我们得到:

15.2计算射线
回忆一下,射线能够描述参数方程:p(t) = p0 + tu。其中p0是射线的起点,用来描述它的位置,u是向量,用来描述它的方向。
如图15.2,我们知道射线的起点总是视图空间的原点,所以p0 = (0, 0, 0),如果p是射线穿过投影窗口上的点,方向向量u给出:u = p - p0 = (px, py, 1) - (0, 0, 0) = p。
下面的方法用来计算选取射线(从屏幕空间点击的点所对应的视图空间的点x、y坐标):
struct sRay
{
D3DXVECTOR3 origin;
D3DXVECTOR3 direction;
};
sRay calculate_picking_ray(int x, int y)
{
D3DVIEWPORT9 viewport;
g_device->GetViewport(&viewport);
D3DXMATRIX proj_matrix;
g_device->GetTransform(D3DTS_PROJECTION, &proj_matrix);
float px = ((( 2.0f * x) / viewport.Width) - 1.0f) / proj_matrix(0, 0);
float py = (((-2.0f * y) / viewport.Height) + 1.0f) / proj_matrix(1, 1);
sRay ray;
ray.origin = D3DXVECTOR3(0.0f, 0.0f, 0.0f);
ray.direction = D3DXVECTOR3(px, py, 1.0f);
return ray;
}
15.3变换射线
选取射线的计算被描述在视图空间,为了完成射线的相交的测试,射线和对象必须在同一个坐标系统。通常转换射线到世界空间(甚至对象在本地空间)要好于将所有对象转换到视图空间。
我们能够将一个变换矩阵转换为一条原点为p0,方向为u的射线r(t) = p0 + tu,注意:原点转换为一个点,方向转换为一个向量,下列函数转换一条射线:
void
transform_ray(sRay* ray, D3DXMATRIX* trans_matrix)
{
// transform the ray's origin, w = 1.
D3DXVec3TransformCoord(&ray->origin, &ray->origin, trans_matrix);
// transform the ray's direction, w = 0.
D3DXVec3TransformNormal(&ray->direction, &ray->direction, trans_matrix);
// normalize the direction
D3DXVec3Normalize(&ray->direction, &ray->direction);
}
D3DXVec3TransformCoord和D3DXVec3TransformNormal接受一个Ray类型参数(包含二个3D向量成员)。 D3DXVec3TransformCoord函数中,射线的原点(origin)向量的第四部分w = 1。相反,函数D3DXVec3TransformNormal中,射线的方向(direction)向量的第四部分w = 0。
这样,当我们向世界空间转换时,能够用D3DXVec3TransformCoord转换一个点,用D3DXVec3TransformNormal转换一个向量。
15.4射线-对象 交点
我们将射线和对象转换到同一坐标系统后,准备测试哪个对象与射线相交。因为我们将对象描述为三角形组成的网络,下面详细说明这种方法。遍例场景中每个对象的三角形列表并测试,如果射线相交于一个三角形,它就与三角形所在的对象相交。然而,通过遍例场景中的每个三角形来实现射线相交在计算上会增加时间,一种比较快的方法,虽然准确性会差一点。它将每个对象围成一个近似的球形(边界球),这样我们就能通过遍例每个边界球来测试射线相交。用边界球来描述相交的对象。
注意:射线可能相交多个对象,然而离照相机近的对象会被选取。因为近距离对象遮挡了后面的对象。
给出一个边界球的圆心c和半径r,使用下列恒等式能够测试点p是否在边界球上:
||p-c||-r = 0
如果恒等式满足,则点p在边界球上。如图15.3

假定射线p(t) = p0 + tu相交于边界球,我们将射线代入球的恒等式中,使参数t满足了球的恒等式。
将射线p(t) = p0 + tu代入球的恒等式:
||p(t) - c|| - r = 0 à ||p0 + tu - c|| - r = 0
通过以上推导,我们得到二次方程:
At2 + Bt + C = 0
其中A = u · u, B = 2(u · (p0 - c)),而C = (p0 - c) . (p0 - c) – r 2。
如果u是标准化的,那么A = 1。
因为u是标准化的,我们解t0和 t1:

图15.4显示可能返回的t0和 t1,并显示了一些返回值的几何意义:

下列函数测试如果射线与边界球相交,返回true;射线错过边界球,返回false。
bool
ray_sphere_intersect(sRay* ray, cBoundingSphere* sphere)
{
D3DXVECTOR3 v = ray->origin - sphere->m_center;
float
b = 2.0f * D3DXVec3Dot(&ray->direction, &v);
float
c = D3DXVec3Dot(&v, &v) - (sphere->m_radius * sphere->m_radius);
float
discriminant = (b * b) - (4.0f * c);
if
(discriminant < 0.0f)
return
false
;
discriminant = sqrt(discriminant);
float
s0 = (-b + discriminant) / 2.0f;
float
s1 = (-b - discriminant) / 2.0f;
// if one solution is >= 0, then we intersected the sphere.
return
(s0 >= 0.0f || s1 >= 0.0f);
}
15.5例子程序:选取
下图显示了该示例的屏幕截图,茶壶绕着屏幕移动,你可以用鼠标试着点击它。如果你点击到茶壶的边界球上,一个消息框将弹出,表示你点中了。我们通过测试WM_LBUTTONDOWN消息来处理鼠标点击事件。

执行程序:
#include "d3dUtility.h"
#pragma warning(disable : 4100)
const
int
WIDTH = 640;
const
int
HEIGHT = 480;
IDirect3DDevice9* g_device;
ID3DXMesh* g_teapot;
ID3DXMesh* g_sphere;
D3DXMATRIX g_world_matrix;
cBoundingSphere g_bounding_sphere;
///////////////////////////////////////////////////////////////////////////////////////////////////
/
sRay calculate_picking_ray(
int
x,
int
y)
{
D3DVIEWPORT9 viewport;
g_device->GetViewport(&viewport);
D3DXMATRIX proj_matrix;
g_device->GetTransform(D3DTS_PROJECTION, &proj_matrix);
float
px = ((( 2.0f * x) / viewport.Width) - 1.0f) / proj_matrix(0, 0);
float
py = (((-2.0f * y) / viewport.Height) + 1.0f) / proj_matrix(1, 1);
sRay ray;
ray.origin = D3DXVECTOR3(0.0f, 0.0f, 0.0f);
ray.direction = D3DXVECTOR3(px, py, 1.0f);
return
ray;
}
void
transform_ray(sRay* ray, D3DXMATRIX* trans_matrix)
{
// transform the ray's origin, w = 1.
D3DXVec3TransformCoord(&ray->origin, &ray->origin, trans_matrix);
// transform the ray's direction, w = 0.
D3DXVec3TransformNormal(&ray->direction, &ray->direction, trans_matrix);
// normalize the direction
D3DXVec3Normalize(&ray->direction, &ray->direction);
}
bool
ray_sphere_intersect(sRay* ray, cBoundingSphere* sphere)
{
D3DXVECTOR3 v = ray->origin - sphere->m_center;
float
b = 2.0f * D3DXVec3Dot(&ray->direction, &v);
float
c = D3DXVec3Dot(&v, &v) - (sphere->m_radius * sphere->m_radius);
float
discriminant = (b * b) - (4.0f * c);
if
(discriminant < 0.0f)
return
false
;
discriminant = sqrt(discriminant);
float
s0 = (-b + discriminant) / 2.0f;
float
s1 = (-b - discriminant) / 2.0f;
// if one solution is >= 0, then we intersected the sphere.
return
(s0 >= 0.0f || s1 >= 0.0f);
}
bool
setup()
{
D3DXCreateTeapot(g_device, &g_teapot, NULL);
// compute the bounding sphere
BYTE* v;
g_teapot->LockVertexBuffer(0, (
void
**)&v);
D3DXComputeBoundingSphere(
(D3DXVECTOR3*)v,
g_teapot->GetNumVertices(),
D3DXGetFVFVertexSize(g_teapot->GetFVF()),
&g_bounding_sphere.m_center,
&g_bounding_sphere.m_radius);
g_teapot->UnlockVertexBuffer();
// build a sphere mesh that describes the teapot's bounding sphere
D3DXCreateSphere(g_device, g_bounding_sphere.m_radius, 20, 20, &g_sphere, NULL);
// set light
D3DXVECTOR3 dir(0.707f, -0.0f, 0.707f);
D3DXCOLOR color(1.0f, 1.0f, 1.0f, 1.0f);
D3DLIGHT9 light = init_directional_light(&dir, &color);
g_device->SetLight(0, &light);
g_device->LightEnable(0, TRUE);
g_device->SetRenderState(D3DRS_NORMALIZENORMALS, TRUE);
g_device->SetRenderState(D3DRS_SPECULARENABLE, FALSE);
// Set view matrix
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 view_matrix;
D3DXMatrixLookAtLH(&view_matrix, &pos, &target, &up);
g_device->SetTransform(D3DTS_VIEW, &view_matrix);
// set the projection matrix
D3DXMATRIX proj;
D3DXMatrixPerspectiveFovLH(&proj, D3DX_PI/4.0f, (
float
)WIDTH/HEIGHT, 1.0f, 1000.0f);
g_device->SetTransform(D3DTS_PROJECTION, &proj);
return
true
;
}
//////////////////////////////////////////////////////////////////////////////////////////////////////
/
void
cleanup()
{
safe_release<ID3DXMesh*>(g_teapot);
safe_release<ID3DXMesh*>(g_sphere);
}
//////////////////////////////////////////////////////////////////////////////////////////////////////
/
bool
display(
float
time_delta)
{
// update teapot
static
float
radius = 0.0f;
static
float
angle = 0.0f;
D3DXMatrixTranslation(&g_world_matrix, cos(angle) * radius, sin(angle) * radius, 10.0f);
// transform the bounding sphere to match the teapot's position in the world
g_bounding_sphere.m_center = D3DXVECTOR3(cos(angle) * radius, sin(angle) * radius, 10.0f);
static
float
velocity = 1.0f;
radius += velocity * time_delta;
if
(radius >= 8.0f || radius <= 0.0f)
velocity = -velocity;
// reverse direction
angle += D3DX_PI * time_delta;
if
(angle >= D3DX_PI * 2.0f)
angle = 0.0f;
// render now
g_device->Clear(0, NULL, D3DCLEAR_TARGET | D3DCLEAR_ZBUFFER, 0x00000000, 1.0f, 0);
g_device->BeginScene();
g_device->SetTransform(D3DTS_WORLD, &g_world_matrix);
g_device->SetMaterial(&RED_MATERIAL);
g_teapot->DrawSubset(0);
// render the bounding sphere with alpha blending so we can see through it
g_device->SetRenderState(D3DRS_ALPHABLENDENABLE, TRUE);
g_device->SetRenderState(D3DRS_SRCBLEND, D3DBLEND_SRCALPHA);
g_device->SetRenderState(D3DRS_DESTBLEND, D3DBLEND_INVSRCALPHA);
D3DMATERIAL9 yellow_material = YELLOW_MATERIAL;
yellow_material.Diffuse.a = 0.25f;
// 25% opacity
g_device->SetMaterial(&yellow_material);
g_sphere->DrawSubset(0);
g_device->SetRenderState(D3DRS_ALPHABLENDENABLE, FALSE);
g_device->EndScene();
g_device->Present(NULL, NULL, NULL, NULL);
return
true
;
}
//////////////////////////////////////////////////////////////////////////////////////////////////////
/
LRESULT CALLBACK wnd_proc(HWND hwnd, UINT msg, WPARAM word_param, LPARAM long_param)
{
switch
(msg)
{
case
WM_DESTROY:
PostQuitMessage(0);
break
;
case
WM_KEYDOWN:
if
(word_param == VK_ESCAPE)
DestroyWindow(hwnd);
break
;
case
WM_LBUTTONDOWN:
// compute the ray in view space given by the clicked screen point
sRay ray = calculate_picking_ray(LOWORD(long_param), HIWORD(long_param));
// transform the ray to world space
D3DXMATRIX view_matrix, view_inverse_matrix;
g_device->GetTransform(D3DTS_VIEW, &view_matrix);
D3DXMatrixInverse(&view_inverse_matrix, NULL, &view_matrix);
transform_ray(&ray, &view_inverse_matrix);
if
(ray_sphere_intersect(&ray, &g_bounding_sphere))
MessageBox(NULL, "Hit teapot's bounding sphere!", "HIT", MB_OK);
break
;
}
return
DefWindowProc(hwnd, msg, word_param, long_param);
}
//////////////////////////////////////////////////////////////////////////////////////////////////////
/
int
WINAPI WinMain(HINSTANCE inst, HINSTANCE, PSTR cmd_line,
int
cmd_show)
{
if
(! init_d3d(inst, WIDTH, HEIGHT,
true
, D3DDEVTYPE_HAL, &g_device))
{
MessageBox(NULL, "init_d3d() - failed.", 0, MB_OK);
return
0;
}
if
(! setup())
{
MessageBox(NULL, "Steup() - failed.", 0, MB_OK);
return
0;
}
enter_msg_loop(display);
cleanup();
g_device->Release();
return
0;
}
三、下面来自:http://www.cnblogs.com/lancidie/archive/2010/10/08/1845772.html
简单总结一下D3D中的拾取问题,所谓拾取就是3D程序中当用户使用鼠标同3D世界内的物体进行交互的时候,如何能正确的实现从用户的鼠标到3D世界中的变换。
呵呵,如果要是推及到原理的话比较复杂,需要好好总结,先从简单的入手,D3D中提供了很多易用的API,使用这些API的话就可以绕过复杂的数学原理,所以呢,我们先来看实际应用中是怎样实现它的。
//首先获取世界、视角、投影矩阵
D3DXMATRIX matWorld,matView,matProj;
pd3dDevice->GetTransform(D3DTS_WORLD,&matWorld);
pd3dDevice->GetTransform(D3DTS_VIEW,&matView);
pd3dDevice->GetTransform(D3DTS_PROJECTION,&matProj);
//获取平面坐标
POINT ptCursor;
GetCursorPos(&ptCursor);
ScreenToClient(DXUTGetHWND(),&ptCursor);
D3DXVECTOR3 vScreen((float)ptCursor.x,(float)ptCursor.y,0.0f),vOut;
//创建视窗接口
D3DVIEWPORT9 viewPort;
pd3dDevice->GetViewport( &viewPort );
//从屏幕空间投影到3D空间
D3DXVec3Unproject(&vOut,&vScreen,&viewPort,&matProj,&matView,&matWorld);
D3DXVECTOR3 vMousePt;
D3DXPLANE plane;
D3DXVECTOR3 v1(1.0f,1.0f,0.0f);
D3DXVECTOR3 v2(1.0f,-1.0f,0.0f);
D3DXVECTOR3 v3(-1.0f,1.0f,0.0f);
D3DXPlaneFromPoints( &plane,&v1,&v2,&v3);
D3DXPlaneIntersectLine(&vMousePt,&plane,pCamera->GetEyePt(),&vOut);
//vMousePt.x,vMousePt.y和vMousePt.z就是鼠标根据视角所投射线同所指定平面的交点
很简单的一段代码,为了确保它的正确性,测试倒是费了半天劲-。-,全部都是用API来实现的,看程序不难理解,这段程序重点是D3DXVec3Unproject()函数,这个函数的作用便是进行屏幕到空间的投影(还有一个D3DXVec3project()函数是用来进行空间到屏幕的投影的^_^),这个函数需要的是设备的Viewport、决定3D空间的三个矩阵和屏幕的坐标,函数可以算出结果向量,不过测试的时候我发现这个向量好像既不是近裁剪面的投影点也不是远裁剪面的,这个地方确实还是存在疑惑的,不过不影响使用,呵呵。
其实有了这个向量后面的怎么用就看大家了,上面是通过D3DXPlaneIntersectLine()函数(计算直线与平面交点用函数)来实现的,还可以用D3DXIntersect()来做与三角面片的相交。