[DX]基于像素的屏幕拾取场景物体的方法
屏幕拾取场景物体的方法有很多,使用一定的数据结构保存(树,链表)场景物体,再计算得到拾取射线对物体进行射线碰撞测试就可以得到对应的结果。在很多情况下,检测都是对物体的包围体进行的,因为这样可以加快检测速度,但是也同时需要对物体深度顺序排序,而且也会使拾取的结果容易出现误差。比如两个靠得很近的物体选择容易出错,还有物体的轮廓双比较复杂的情况下很容易出现误差。(虽然我没有做过太多的尝试,呵呵)
这个基于像素的屏幕拾取场景物体的方法利用了GPU渲染过程。一般的机器的字长是32位的也与DWORD长度相同,而颜色值D3DCOLOR同样也是32位长度的DWORD。也就是说这里完全可以将地址数据转换成颜色值渲染到一张RenderTarget(格式是32位长度D3DFMT_A8R8G8B8或D3DFMT_X8R8G8B8等)中,而渲染得到的RenderTarget就是物体内存地址的Surface了。因此,在渲染每一个物体时将物体的内存地址作为材质,设置不可以对渲染过程进行采样或者过滤,当然也不进行任何的效果处理。再根据鼠标点击的位置取出颜色(地址)值。就可以得到精确到像素拾取到的物体了。
代码
class PixelPick
{
protected:
struct PICKOBJ
{
PICKOBJ( D3DXMATRIX& RefMatrix , LPD3DXMESH& RefMesh , DWORD Data ) : RefenceMeshMatrix( RefMatrix ) ,
RefenceMesh( RefMesh ) ,
RefenceData( Data ){}
D3DXMATRIX& RefenceMeshMatrix;
LPD3DXMESH& RefenceMesh;
DWORD RefenceData;
};
public:
PixelPick();
~PixelPick();
void Init( LPDIRECT3DDEVICE9 pIDD , UINT Width , UINT Height , DWORD ClearData );
void AddPickObj( D3DXMATRIX & RefenceMeshMatrix , LPD3DXMESH & Mesh , DWORD Data );
DWORD Pick( POINT ptClient , HWND hWnd , D3DXMATRIX * pmViewProj );
void Clear();
void Release();
protected:
D3DXCOLOR ConvertToColor( DWORD Data );
protected:
DWORD m_ClearData;
LPDIRECT3DDEVICE9 m_pIDD;
D3DVIEWPORT9 m_ViewPort;
DX_auto_ptr< LPD3DXEFFECT > m_pEffect;
DX_auto_ptr< LPD3DXRENDERTOSURFACE > m_pRenderEnv;
DX_auto_ptr< LPDIRECT3DSURFACE9 > m_pRetrieveSurface;
DX_auto_ptr< LPDIRECT3DTEXTURE9 > m_pRTTexture;
std::list< PICKOBJ > m_PickObjs;
};
PixelPick::PixelPick()
{
}
PixelPick::~PixelPick()
{
}
void PixelPick::AddPickObj( D3DXMATRIX & RefenceMeshMatrix , LPD3DXMESH& Mesh , DWORD Data )
{
m_PickObjs.push_back( PICKOBJ( RefenceMeshMatrix , Mesh , Data ) );
}
void PixelPick::Init( LPDIRECT3DDEVICE9 pIDD , UINT Width , UINT Height , DWORD ClearData )
{
m_pIDD = pIDD;
m_ClearData = ClearData;
D3DFORMAT SurfaceFormat = D3DFMT_X8R8G8B8;
char PreCode[] = "\
matrix g_mWorldViewProj;\
float4 PickData;\
float4 PixelPickVS( float3 Position : POSITION ) : POSITION\
{\
return mul( float4( Position , 1.0f ) , g_mWorldViewProj );\
}\
float4 PixelPickPS() : COLOR\
{\
return PickData;\
}\
technique PixelPick\
{\
pass p0\
{\
VertexShader = compile vs_2_0 PixelPickVS();\
PixelShader = compile ps_2_0 PixelPickPS();\
}\
}";
D3DXCreateEffect( m_pIDD , PreCode , strlen( PreCode ) ,
NULL , NULL , 0 , NULL , &m_pEffect , NULL );
m_pIDD->CreateOffscreenPlainSurface( Width , Height , SurfaceFormat ,
D3DPOOL_SYSTEMMEM , &m_pRetrieveSurface , 0 );
D3DXCreateRenderToSurface( m_pIDD , Width , Height ,
SurfaceFormat , true , D3DFMT_D16 , &m_pRenderEnv );
m_pIDD->CreateTexture( Width , Height , 1 , D3DUSAGE_RENDERTARGET ,
SurfaceFormat , D3DPOOL_DEFAULT , &m_pRTTexture , 0 );
m_ViewPort.Height = Height;
m_ViewPort.Width = Width;
m_ViewPort.X = 0;
m_ViewPort.Y = 0;
m_ViewPort.MinZ = 0.0f;
m_ViewPort.MaxZ = 1.0f;
}
void PixelPick::Clear()
{
m_PickObjs.clear();
}
D3DXCOLOR PixelPick::ConvertToColor( DWORD Data )
{
return D3DXCOLOR( Data );
}
DWORD PixelPick::Pick( POINT ptClient , HWND hWnd , D3DXMATRIX * pmViewProj )
{
RECT re;
GetClientRect( hWnd , &re );
float ClientToBufferX = m_ViewPort.Width / (float) (re.right - re.left);
float ClientTobufferY = m_ViewPort.Height / (float) (re.bottom - re.top);
DX_auto_ptr< LPDIRECT3DSURFACE9 > pSurface;
m_pRTTexture->GetSurfaceLevel( 0 , &pSurface );
m_pRenderEnv->BeginScene( pSurface , &m_ViewPort );
m_pIDD->Clear( 0 , 0 , D3DCLEAR_TARGET | D3DCLEAR_ZBUFFER , m_ClearData , 1.0f , 0 );
m_pEffect->SetTechnique( "PixelPick" );
m_pEffect->Begin( NULL , NULL );
m_pEffect->BeginPass( 0 );
std::list< PICKOBJ >::const_iterator Iter = m_PickObjs.begin();
for( ; Iter != m_PickObjs.end() ; ++Iter )
{
const PICKOBJ & pRenderObject = *Iter;
D3DXCOLOR DataColor = ConvertToColor( pRenderObject.RefenceData );
m_pEffect->SetVector( "PickData" , (D3DXVECTOR4 *)&DataColor );
m_pEffect->SetMatrix( "g_mWorldViewProj" , D3DXMatrixMultiply( &D3DXMATRIX() , &pRenderObject.RefenceMeshMatrix , pmViewProj ) );
m_pEffect->CommitChanges();
pRenderObject.RefenceMesh->DrawSubset( 0 );
}
m_pEffect->EndPass();
m_pEffect->End();
m_pRenderEnv->EndScene( D3DX_FILTER_NONE );
m_pIDD->GetRenderTargetData( pSurface , m_pRetrieveSurface );
D3DLOCKED_RECT lr;
m_pRetrieveSurface->LockRect( &lr , 0 , 0 );
DWORD* pCurRow = (DWORD*)(lr.pBits);
DWORD RetrieveData = pCurRow[(int)( ptClient.y * ClientTobufferY ) * m_ViewPort.Width +
(int)( ptClient.x * ClientToBufferX )];
m_pRetrieveSurface->UnlockRect();
return RetrieveData;
}
void PixelPick::Release()
{
m_pEffect.Release();
m_pRenderEnv.Release();
m_pRetrieveSurface.Release();
m_pRTTexture.Release();
}
class PixelPick
{
protected:
struct PICKOBJ
{
PICKOBJ( D3DXMATRIX& RefMatrix , LPD3DXMESH& RefMesh , DWORD Data ) : RefenceMeshMatrix( RefMatrix ) ,
RefenceMesh( RefMesh ) ,
RefenceData( Data ){}
D3DXMATRIX& RefenceMeshMatrix;
LPD3DXMESH& RefenceMesh;
DWORD RefenceData;
};
public:
PixelPick();
~PixelPick();
void Init( LPDIRECT3DDEVICE9 pIDD , UINT Width , UINT Height , DWORD ClearData );
void AddPickObj( D3DXMATRIX & RefenceMeshMatrix , LPD3DXMESH & Mesh , DWORD Data );
DWORD Pick( POINT ptClient , HWND hWnd , D3DXMATRIX * pmViewProj );
void Clear();
void Release();
protected:
D3DXCOLOR ConvertToColor( DWORD Data );
protected:
DWORD m_ClearData;
LPDIRECT3DDEVICE9 m_pIDD;
D3DVIEWPORT9 m_ViewPort;
DX_auto_ptr< LPD3DXEFFECT > m_pEffect;
DX_auto_ptr< LPD3DXRENDERTOSURFACE > m_pRenderEnv;
DX_auto_ptr< LPDIRECT3DSURFACE9 > m_pRetrieveSurface;
DX_auto_ptr< LPDIRECT3DTEXTURE9 > m_pRTTexture;
std::list< PICKOBJ > m_PickObjs;
};
PixelPick::PixelPick()
{
}
PixelPick::~PixelPick()
{
}
void PixelPick::AddPickObj( D3DXMATRIX & RefenceMeshMatrix , LPD3DXMESH& Mesh , DWORD Data )
{
m_PickObjs.push_back( PICKOBJ( RefenceMeshMatrix , Mesh , Data ) );
}
void PixelPick::Init( LPDIRECT3DDEVICE9 pIDD , UINT Width , UINT Height , DWORD ClearData )
{
m_pIDD = pIDD;
m_ClearData = ClearData;
D3DFORMAT SurfaceFormat = D3DFMT_X8R8G8B8;
char PreCode[] = "\
matrix g_mWorldViewProj;\
float4 PickData;\
float4 PixelPickVS( float3 Position : POSITION ) : POSITION\
{\
return mul( float4( Position , 1.0f ) , g_mWorldViewProj );\
}\
float4 PixelPickPS() : COLOR\
{\
return PickData;\
}\
technique PixelPick\
{\
pass p0\
{\
VertexShader = compile vs_2_0 PixelPickVS();\
PixelShader = compile ps_2_0 PixelPickPS();\
}\
}";
D3DXCreateEffect( m_pIDD , PreCode , strlen( PreCode ) ,
NULL , NULL , 0 , NULL , &m_pEffect , NULL );
m_pIDD->CreateOffscreenPlainSurface( Width , Height , SurfaceFormat ,
D3DPOOL_SYSTEMMEM , &m_pRetrieveSurface , 0 );
D3DXCreateRenderToSurface( m_pIDD , Width , Height ,
SurfaceFormat , true , D3DFMT_D16 , &m_pRenderEnv );
m_pIDD->CreateTexture( Width , Height , 1 , D3DUSAGE_RENDERTARGET ,
SurfaceFormat , D3DPOOL_DEFAULT , &m_pRTTexture , 0 );
m_ViewPort.Height = Height;
m_ViewPort.Width = Width;
m_ViewPort.X = 0;
m_ViewPort.Y = 0;
m_ViewPort.MinZ = 0.0f;
m_ViewPort.MaxZ = 1.0f;
}
void PixelPick::Clear()
{
m_PickObjs.clear();
}
D3DXCOLOR PixelPick::ConvertToColor( DWORD Data )
{
return D3DXCOLOR( Data );
}
DWORD PixelPick::Pick( POINT ptClient , HWND hWnd , D3DXMATRIX * pmViewProj )
{
RECT re;
GetClientRect( hWnd , &re );
float ClientToBufferX = m_ViewPort.Width / (float) (re.right - re.left);
float ClientTobufferY = m_ViewPort.Height / (float) (re.bottom - re.top);
DX_auto_ptr< LPDIRECT3DSURFACE9 > pSurface;
m_pRTTexture->GetSurfaceLevel( 0 , &pSurface );
m_pRenderEnv->BeginScene( pSurface , &m_ViewPort );
m_pIDD->Clear( 0 , 0 , D3DCLEAR_TARGET | D3DCLEAR_ZBUFFER , m_ClearData , 1.0f , 0 );
m_pEffect->SetTechnique( "PixelPick" );
m_pEffect->Begin( NULL , NULL );
m_pEffect->BeginPass( 0 );
std::list< PICKOBJ >::const_iterator Iter = m_PickObjs.begin();
for( ; Iter != m_PickObjs.end() ; ++Iter )
{
const PICKOBJ & pRenderObject = *Iter;
D3DXCOLOR DataColor = ConvertToColor( pRenderObject.RefenceData );
m_pEffect->SetVector( "PickData" , (D3DXVECTOR4 *)&DataColor );
m_pEffect->SetMatrix( "g_mWorldViewProj" , D3DXMatrixMultiply( &D3DXMATRIX() , &pRenderObject.RefenceMeshMatrix , pmViewProj ) );
m_pEffect->CommitChanges();
pRenderObject.RefenceMesh->DrawSubset( 0 );
}
m_pEffect->EndPass();
m_pEffect->End();
m_pRenderEnv->EndScene( D3DX_FILTER_NONE );
m_pIDD->GetRenderTargetData( pSurface , m_pRetrieveSurface );
D3DLOCKED_RECT lr;
m_pRetrieveSurface->LockRect( &lr , 0 , 0 );
DWORD* pCurRow = (DWORD*)(lr.pBits);
DWORD RetrieveData = pCurRow[(int)( ptClient.y * ClientTobufferY ) * m_ViewPort.Width +
(int)( ptClient.x * ClientToBufferX )];
m_pRetrieveSurface->UnlockRect();
return RetrieveData;
}
void PixelPick::Release()
{
m_pEffect.Release();
m_pRenderEnv.Release();
m_pRetrieveSurface.Release();
m_pRTTexture.Release();
}
虽然没有进行过时间的统计,使是这个方法需要从显存中取出Surface的数据,在速度上应该不是很快,,当然可以利用视锥体剔除不必要的物体或者使用更低细节度的网格代替再进行渲染。又或者将渲染的目标设置为0.5*Height,0.5*Width的RT。但是可以实现几乎是像素级别的拾取,有着一定的精确度。