天行健 君子当自强而不息

D3D中的拾取

新建网页 1

假设用户点击了屏幕上的点 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是投影矩阵,因为P00P11转换距阵缩放点的xy坐标,我们得到:


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

下面的方法用来计算选取射线(从屏幕空间点击的点所对应的视图空间的点xy坐标):

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);
    }

D3DXVec3TransformCoordD3DXVec3TransformNormal接受一个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是标准化的,我们解t0t1

15.4显示可能返回的t0t1,并显示了一些返回值的几何意义:

下列函数测试如果射线与边界球相交,返回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;
    }

下载源程序

posted on 2008-04-04 16:31 lovedday 阅读(5060) 评论(2)  编辑 收藏 引用 所属分类: ■ DirectX 9 Program

评论

# re: D3D中的拾取 2009-01-14 11:09 GhostPython

好像有点问题,在球体外面的也可以拾取成功
投影矩阵的计算忽略了非客户区的大小,但是使用GetClientRect计算,结果仍然不对  回复  更多评论   

# re: D3D中的拾取 2010-02-06 14:45 EL

3Q,文章简洁而深刻,让我明白了数学在3D中如何应用,非常感谢  回复  更多评论   


只有注册用户登录后才能发表评论。
【推荐】超50万行VC++源码: 大型组态工控、电力仿真CAD与GIS源码库
网站导航: 博客园   IT新闻   BlogJava   知识库   博问   管理


公告

导航

统计

常用链接

随笔分类(178)

3D游戏编程相关链接

搜索

最新评论