首页 > 代码库 > 拾取技术

拾取技术

拾取技术在游戏开发中的到广泛的应用,主要用于游戏里的道具捡起、单机敌人而向其开火。

拾取理论可以分解为4个步骤:

1.给定所单击的屏幕点s,求出它在投影窗口中的对应点p。

2.计算拾取射线。从坐标原点发出且通过p的直线。

3.把观察坐标系的射线转换到世界坐标系中。

4.进行相交判断。相交的物体即为用户所拾取的屏幕对象。

 

一 求取投影窗口中的点p

  根据视口变换的矩阵反向求出显示窗口中s在投影窗口的p,编写拾取射线代码:

Ray CalcPickingRay(int x,int y)
{
float px=0;
float py=0;

D3DVIEWPORT9 vp; 
g_D3DDevice->GetViewport(&vp); //获取视口变换的矩阵

D3DXMATRIX proj;
g_D3DDevice->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) ;

Ray ray;
ray._origin = D3DXVECTOR3(0.0f,0.0f,0.0f);
ray._direction = D3DXVECTOR3(px,py,1.0f);

return ray;
}

二 编写射线变换到世界坐标的函数

void TransformRay(Ray* ray, D3DXMATRIX* T)
{
// transform the ray‘s origin, w = 1.
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(Ray* ray, 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;
}

然后对消息响应的回调函数做处理

case WM_LBUTTONDOWN:

// compute the ray in view space given the clicked screen point
Ray ray = CalcPickingRay(LOWORD(lParam), HIWORD(lParam));

// transform the ray to world space
D3DXMATRIX view;
g_D3DDevice->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;

运行结果如下: