这几天研究了一下DX,目标是DX3D , DXDraw , DXInput。
- DX3D :
DX3D 首先通过函数Direct3DCreate8创建一个D3D对象,这个对象是一个DLL导出的函数,函数返回LPDIRECT3D8对象。这个对象是个COM对象。这下有趣的东西来了。C++编译器对COM对象编译的结果如下:
00401068 |. 50 push eax
00401069 |. 6A 00 push 0
0040106B |. 8B0D 58C04100 mov ecx, dword ptr [g_pD3D]
00401071 |. 8B11 mov edx, dword ptr [ecx]
00401073 |. A1 58C04100 mov eax, dword ptr [g_pD3D]
00401078 |. 50 push eax
00401079 |. FF52 20 call dword ptr [edx+20] ; d3d8.6DD3D510而C代码如: g_pD3D->GetAdapterDisplayMode( D3DADAPTER_DEFAULT, &d3ddm ) ..很明显,C++将COM指针放进了堆栈的最后一个参数。如同C++的类的this指针一样。同样有趣的是,如果大家回忆一下C++对虚函数的编译方式就会发现 call dword ptr [edx+20] 中的Edx = [[g_pD3D]]。这下可好了,我们要接管这个指针,我们可以先通过一个程序得到LPDIRECT3D8指针后,然后通过QueryInferace得到其他函数的指针。有了g_pD3D了,你就可以发挥自己的想象了。
然后就要开始画图了,看看C代码的骨架:
VOID Render()
{
if( NULL == g_pd3dDevice )
return;// Clear the backbuffer to a blue color
g_pd3dDevice->Clear( 0, NULL, D3DCLEAR_TARGET, D3DCOLOR_XRGB(0,0,255), 1.0f, 0 );
// Begin the scene
g_pd3dDevice->BeginScene();
// Rendering of scene objects can happen here
// End the scene
g_pd3dDevice->EndScene();
// Present the backbuffer contents to the display
g_pd3dDevice->Present( NULL, NULL, NULL, NULL );
}显然,画游戏界面的就是在 BeginScene EndScene之间填写的。。然后通过Present函数显示出来。关于这3个函数的说明google出来结果如下:
BeginScene通过锁定后台缓冲区使设备准备好,以进行后续的操作。EndScene方法告知设备已经完成绘制,并取消后台缓冲区的锁定。必须始终在调用BeginScene之后调用EndScene,否则后台缓冲区将保持锁定状态。BeginScene和EndScene方法与Present方法紧密协作,一起管理后台缓冲区;如果其中一个方法失败,其他两个方法也会失败。
又在查了下资料,其实Present只是把DX渲染好的图像显示出来。真正渲染的动作一般都放在BeginScene和EndScene之间。代码骨架如下:
SetTransform ; ---坐标变换,包括旋转平移等等。
for( DWORD i=0; i
{
if( m_bUseMaterials )
{
if( m_pMaterials[i].Diffuse.a < 1.0f )
continue;
pd3dDevice->SetMaterial( &m_pMaterials[i] );
pd3dDevice->SetTexture( 0, m_pTextures[i] );
}
m_pLocalMesh->DrawSubset( i );
}
m_pd3dDevice->SetStreamSource( 0, m_pEarthVB, sizeof(BUMPVERTEX) ); 这里把要渲染的顶点坐标送入T&L管线
m_pd3dDevice->SetVertexShader( D3DFVF_BUMPVERTEX );
m_pd3dDevice->DrawPrimitive( D3DPT_TRIANGLESTRIP, 0, m_dwNumSphereVertices-2 ); /渲染吧……
需要多说一嘴的是:
g_pD3D->CreateDevice( D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, hWnd,
D3DCREATE_SOFTWARE_VERTEXPROCESSING,
&d3dpp, &g_pd3dDevice ) 创建设备的时候,通过d3dpp参数可以指定一些重要参数。
这里指定了长,宽,多刷新缓冲的个数,是否窗口化这些参数。
- DX_Draw
dxDraw基本就是DX的那一套。通过DirectDrawCreateEx( NULL, (VOID**)&m_pDD,IID_IDirectDraw7, NULL ) ) )创建好dxdraw对象,记住是个COM指针。然后m_pDD->SetCooperativeLevel( hWnd, DDSCL_NORMAL );设置协作模式,这里可以窗口化或者全屏化。然后m_pDD->CreateSurface( &ddsd, &m_pddsFrontBuffer, NULL )创建画图的Surface,通过这个参数m_pddsFrontBuffer你可以指定详细创建的参数。
同样,在WM_PAINT消息中画游戏显示,其中很多函数和GDI函数名字相同。没又需要多说的。D3D替代DDraw是趋势。
- DX_Input
同样 DirectInput8Create( GetModuleHandle(NULL), DIRECTINPUT_VERSION, _IDirectInput8, (VOID**)&g_pDI, NULL ) ) )函数就可以创建一个IDirectInput8的COM指针。 DXIuput可以是键盘Keyboard或者鼠标Mouse. g_pDI->CreateDevice( GUID_SysKeyboard, &g_pKeyboard, NULL ) 查询到键盘的COM指针 , g_pDI->CreateDevice( GUID_SysMouse, &g_pMouse, NULL ) ) )查询到鼠标的COM指针 。然后通过GetDeviceState函数就可以得到键盘和鼠标的输入信息了。
比如下面是查询是哪个键盘被按下了的代码:
-
立即模式
BYTE diks[256];
ZeroMemory( &diks, sizeof(diks) );
hr = g_pKeyboard->GetDeviceState( sizeof(diks), &diks );
hr = g_pKeyboard->Acquire();
while( hr == DIERR_INPUTLOST )
hr = g_pKeyboard->Acquire();
for( i = 0; i < 256; i++ ) {
if( diks[i] & 0x80 )
{
wsprintf( strElement, TEXT("0x%02x "), i );
_tcscat( strNewText, strElement );
}
}
- 缓冲模式
hr = g_pKeyboard->Acquire();
while( hr == DIERR_INPUTLOST )
hr = g_pKeyboard->Acquire();
for( i = 0; i < dwElements; i++ )
{
// this will display then scan code of the key
// plus a 'D' - meaning the key was pressed
// or a 'U' - meaning the key was released
wsprintf( strLetter, TEXT("0x%02x%s "), didod[ i ].dwOfs,
(didod[ i ].dwData & 0x80) ? TEXT("D") : TEXT("U"));
_tcscat( strNewText, strLetter );
}
同样鼠标的如下:
- 立即模式
DIMOUSESTATE2 dims2;
hr = g_pMouse->GetDeviceState( sizeof(DIMOUSESTATE2), &dims2 );
hr = g_pMouse->Acquire();
while( hr == DIERR_INPUTLOST )
hr = g_pMouse->Acquire();
_stprintf( strNewText, TEXT("(X=% 3.3d, Y=% 3.3d, Z=% 3.3d) B0=%c B1=%c B2=%c B3=%c B4=%c B5=%c B6=%c B7=%c"),
dims2.lX, dims2.lY, dims2.lZ,
(dims2.rgbButtons[0] & 0x80) ? '1' : '0',
(dims2.rgbButtons[1] & 0x80) ? '1' : '0',
(dims2.rgbButtons[2] & 0x80) ? '1' : '0',
(dims2.rgbButtons[3] & 0x80) ? '1' : '0',
(dims2.rgbButtons[4] & 0x80) ? '1' : '0',
(dims2.rgbButtons[5] & 0x80) ? '1' : '0',
(dims2.rgbButtons[6] & 0x80) ? '1' : '0',
(dims2.rgbButtons[7] & 0x80) ? '1' : '0');
- 缓冲模式
DIDEVICEOBJECTDATA didod[ SAMPLE_BUFFER_SIZE ];
DWORD dwElements;
hr = g_pMouse->GetDeviceData( sizeof(DIDEVICEOBJECTDATA),
didod, &dwElements, 0 );
hr = g_pMouse->Acquire();
while( hr == DIERR_INPUTLOST )
hr = g_pMouse->Acquire();
for( i = 0; i < dwElements; i++ )
{
// this will display then scan code of the key
// plus a 'D' - meaning the key was pressed
// or a 'U' - meaning the key was released
switch( didod[ i ].dwOfs )
{
case DIMOFS_BUTTON0:
_tcscat( strNewText, TEXT("B0") );
break;
case DIMOFS_BUTTON1:
_tcscat( strNewText, TEXT("B1") );
break;
case DIMOFS_BUTTON2:
_tcscat( strNewText, TEXT("B2") );
break;
case DIMOFS_BUTTON3:
_tcscat( strNewText, TEXT("B3") );
break;
case DIMOFS_X:
_tcscat( strNewText, TEXT("X") );
break;
case DIMOFS_Y:
_tcscat( strNewText, TEXT("Y") );
break;
case DIMOFS_Z:
_tcscat( strNewText, TEXT("Z") );
break;
default:
_tcscat( strNewText, TEXT("") );
}
switch( didod[ i ].dwOfs )
{
case DIMOFS_BUTTON0:
case DIMOFS_BUTTON1:
case DIMOFS_BUTTON2:
case DIMOFS_BUTTON3:
if( didod[ i ].dwData & 0x80 )
_tcscat( strNewText, TEXT("U ") );
else
_tcscat( strNewText, TEXT("D ") );
break;
case DIMOFS_X:
case DIMOFS_Y:
case DIMOFS_Z:
{
TCHAR strCoordValue[20];
wsprintf( strCoordValue, TEXT("%d "), didod[ i ].dwData );
_tcscat( strNewText, strCoordValue );
break;
}
}
}
========================================================
呵呵,通过大体研究,基本清楚。如有错误,初学者难免。