在三维图形程序中的一个模型对应空间中的一个物体,在现实世界中要完全定位一个物体需要6个参数,物体位置坐标的3个分量(x,
y, z)和3个欧拉角(偏航角yaw,俯仰角pitch,侧倾角roll)。
3个欧拉角的定义为:
(1)偏航角:物体绕自身y轴(即上向量up)旋转的角度。
(2)俯仰角:物体绕自身x轴(即右向量right)旋转的角度。
(3)侧倾角:物体绕自身z轴(即前向量look)旋转的角度。
通过矩阵实现模型旋转
在三维图形程序中模型在世界空间中的位置和姿态都是通过通过其世界矩阵来表示的,所以要在程序中确定一个模型的位置和姿态,就是将控制其状态的6个参数应用到其世界矩阵中。
通过物体的位置和物体的3个自身坐标轴朝向(3个向量)同样也可以完全定位模型,实际上在Direct3D程序中,模型的世界矩阵本身包含了模型的位置向量和3个方向向量,这些向量在世界矩阵中存储的顺序是:第一行是right向量,第二行是up向量,第三行是look向量,第四行是位置向量pos。通过函数D3DXMatrixIdentity()将矩阵设置为单位矩阵,同时也将4个向量都设置为默认值,因此right向量为(1.0f,
0.0f, 0.0f),up向量为(0.0, 1.0f,
0.0f),look向量为(0.0f, 0.0f,
1.0f),pos向量为(0.0f, 0.0f,
0.0f)。这时模型位于世界坐标系原点,并且朝向和世界坐标系的3个坐标轴方向相同。
要改变模型的状态,就是移动物体到指定位置,旋转物体改变其朝向。旋转一个物体实质上就是将look、up、right向量中的两个绕另一个作旋转。比如要横滚物体,就需要将up和right向量绕look向量旋转;要使物体产生俯仰,必须将up和look向量绕right向量旋转;要使物体产生偏航,必须将look和right向量绕up向量旋转。
向量的旋转需要分别借助偏航、俯仰和横滚矩阵来完成,这些矩阵可借助于D3DXMatrixRotationAxis()函数产生,该函数的声明如下:
Builds a matrix that rotates around an arbitrary axis.
D3DXMATRIX * D3DXMatrixRotationAxis(
D3DXMATRIX * pOut,
CONST D3DXVECTOR3 * pV,
FLOAT Angle
);
Parameters
- pOut
- [in, out] Pointer to the D3DXMATRIX structure that
is the result of the operation.
- pV
- [in] Pointer to the arbitrary axis. See
D3DXVECTOR3.
- Angle
- [in] Angle of rotation in radians. Angles are
measured clockwise when looking along the rotation axis toward the origin.
Return Values
Pointer to a D3DXMATRIX structure rotated around
the specified axis.
Remarks
The return value for this function is the same value
returned in the pOut parameter. In this way, the D3DXMatrixRotationAxis
function can be used as a parameter for another function.
有了偏航、俯仰和横滚矩阵,就可以使用函数D3DXVec3TransformCoord()完成这种向量旋转的计算,该函数的声明如下:
Transforms a 3D vector by a given matrix, projecting
the result back into w = 1.
D3DXVECTOR3 * D3DXVec3TransformCoord(
D3DXVECTOR3 * pOut,
CONST D3DXVECTOR3 * pV,
CONST D3DXMATRIX * pM
);
Parameters
- pOut
- [in, out] Pointer to the D3DXVECTOR3 structure
that is the result of the operation.
- pV
- [in] Pointer to the source D3DXVECTOR3
structure.
- pM
- [in] Pointer to the source D3DXMATRIX structure.
Return Values
Pointer to a D3DXVECTOR3 structure that is the
transformed vector.
Remarks
This function transforms the vector, pV (x, y, z, 1),
by the matrix, pM, projecting the result back into w=1.
The return value for this function is the same value
returned in the pOut parameter. In this way, the D3DXVec3TransformCoord
function can be used as a parameter for another function.
以下代码具体说明了实现这种旋转的核心内容:
static D3DXMATRIX mat_around_right, mat_around_up, mat_around_look;
D3DXMatrixRotationAxis(&mat_around_up, &up, angle_around_up);
D3DXVec3TransformCoord(&look, &look, &mat_around_up);
D3DXVec3TransformCoord(&right, &right, &mat_around_up);
D3DXMatrixRotationAxis(&mat_around_look, &look, angle_around_look);
D3DXVec3TransformCoord(&right, &right, &mat_around_look);
D3DXVec3TransformCoord(&up, &up, &mat_around_look);
D3DXMatrixRotationAxis(&mat_around_right, &right, angle_around_right);
D3DXVec3TransformCoord(&look, &look, &mat_around_right);
D3DXVec3TransformCoord(&up, &up, &mat_around_right);
由于计算机对浮点数的处理存在精度问题,所以在向量旋转计算过程中会带来稍许的累加误差。在经过几次旋转之后,这些舍入误差将使3个向量不再相互垂直。以下代码用于归一化所有向量并使其互相垂直。
D3DXVec3Normalize(&look, &look);
D3DXVec3Cross(&right, &up, &look);
D3DXVec3Normalize(&right, &right);
D3DXVec3Cross(&up, &look, &right);
D3DXVec3Normalize(&up, &up);
示例程序演示了使用矩阵旋转一个飞机模型,程序运行时按下"D"和"A"键,可使飞机模型绕look向量旋转;按下"S"和"W"键,可使飞机模型绕right向量旋转;按下"Q"和"E"键,可使飞机模型绕up向量旋转;按下"F"和"V"键,可使飞机模型向前和向后运动。
源程序:
#include <d3dx9.h>
#pragma warning(disable : 4127)
#define CLASS_NAME "GameApp"
#define release_com(p) do { if(p) { (p)->Release(); (p) = NULL; } } while(0)
IDirect3D9* g_d3d;
IDirect3DDevice9* g_device;
ID3DXMesh* g_mesh;
D3DMATERIAL9* g_mesh_materials;
IDirect3DTexture9** g_mesh_textures;
DWORD g_num_materials;
BYTE g_keys[256];
D3DXMATRIX g_mat_world;
void setup_world_matrix()
{
static long previous_time = 0;
static float elapsed_time = 0.0f;
elapsed_time = (timeGetTime() - previous_time) / 1000.0f;
previous_time = timeGetTime();
float angle_around_right = 0.0f, angle_around_up = 0.0f, angle_around_look = 0.0f;
if(g_keys['D']) angle_around_look -= 3 * elapsed_time;
if(g_keys['A']) angle_around_look += 3 * elapsed_time;
if(g_keys['S']) angle_around_right -= 3 * elapsed_time;
if(g_keys['W']) angle_around_right += 3 * elapsed_time;
if(g_keys['Q']) angle_around_up -= 3 * elapsed_time;
if(g_keys['E']) angle_around_up += 3 * elapsed_time;
static D3DXVECTOR3 right, up, look, pos;
// save old model pos
right.x = g_mat_world._11;
right.y = g_mat_world._12;
right.z = g_mat_world._13;
up.x = g_mat_world._21;
up.y = g_mat_world._22;
up.z = g_mat_world._23;
look.x = g_mat_world._31;
look.y = g_mat_world._32;
look.z = g_mat_world._33;
pos.x = g_mat_world._41;
pos.y = g_mat_world._42;
pos.z = g_mat_world._43;
// now, calculate ratation matrix.
static D3DXMATRIX mat_around_right, mat_around_up, mat_around_look;
D3DXMatrixRotationAxis(&mat_around_up, &up, angle_around_up);
D3DXVec3TransformCoord(&look, &look, &mat_around_up);
D3DXVec3TransformCoord(&right, &right, &mat_around_up);
D3DXMatrixRotationAxis(&mat_around_look, &look, angle_around_look);
D3DXVec3TransformCoord(&right, &right, &mat_around_look);
D3DXVec3TransformCoord(&up, &up, &mat_around_look);
D3DXMatrixRotationAxis(&mat_around_right, &right, angle_around_right);
D3DXVec3TransformCoord(&look, &look, &mat_around_right);
D3DXVec3TransformCoord(&up, &up, &mat_around_right);
// normalize look, right, up to avoid float calculation error
D3DXVec3Normalize(&look, &look);
D3DXVec3Cross(&right, &up, &look);
D3DXVec3Normalize(&right, &right);
D3DXVec3Cross(&up, &look, &right);
D3DXVec3Normalize(&up, &up);
// update model pos
g_mat_world._11 = right.x;
g_mat_world._12 = right.y;
g_mat_world._13 = right.z;
g_mat_world._21 = up.x;
g_mat_world._22 = up.y;
g_mat_world._23 = up.z;
g_mat_world._31 = look.x;
g_mat_world._32 = look.y;
g_mat_world._33 = look.z;
// move model forward or backward
if(g_keys['F'])
{
g_mat_world._41 += 30 * elapsed_time * look.x;
g_mat_world._42 += 30 * elapsed_time * look.y;
g_mat_world._43 += 30 * elapsed_time * look.z;
}
if(g_keys['V'])
{
g_mat_world._41 -= 30 * elapsed_time * look.x;
g_mat_world._42 -= 30 * elapsed_time * look.y;
g_mat_world._43 -= 30 * elapsed_time * look.z;
}
g_device->SetTransform(D3DTS_WORLD, &g_mat_world);
}
void setup_view_proj_matrix()
{
// setup view matrix
D3DXVECTOR3 eye(0.0f, 10.0f, -20.0f);
D3DXVECTOR3 at(0.0f, 0.0f, 0.0f);
D3DXVECTOR3 up(0.0f, 1.0f, 0.0f);
D3DXMATRIX mat_view;
D3DXMatrixLookAtLH(&mat_view, &eye, &at, &up);
g_device->SetTransform(D3DTS_VIEW, &mat_view);
// setup projection matrix
D3DXMATRIX mat_proj;
D3DXMatrixPerspectiveFovLH(&mat_proj, D3DX_PI/4, 1.0f, 1.0f, 500.0f);
g_device->SetTransform(D3DTS_PROJECTION, &mat_proj);
}
bool init_geometry()
{
ID3DXBuffer* material_buffer;
/*
D3DXLoadMeshFromXA(
LPCSTR pFilename,
DWORD Options,
LPDIRECT3DDEVICE9 pD3DDevice,
LPD3DXBUFFER *ppAdjacency,
LPD3DXBUFFER *ppMaterials,
LPD3DXBUFFER *ppEffectInstances,
DWORD *pNumMaterials,
LPD3DXMESH *ppMesh);
*/
if(FAILED(D3DXLoadMeshFromX("airplane.x", D3DXMESH_MANAGED, g_device, NULL, &material_buffer, NULL,
&g_num_materials, &g_mesh)))
{
MessageBox(NULL, "Could not find airplane.x", "ERROR", MB_OK);
return false;
}
D3DXMATERIAL* xmaterials = (D3DXMATERIAL*) material_buffer->GetBufferPointer();
g_mesh_materials = new D3DMATERIAL9[g_num_materials];
g_mesh_textures = new IDirect3DTexture9*[g_num_materials];
for(DWORD i = 0; i < g_num_materials; i++)
{
g_mesh_materials[i] = xmaterials[i].MatD3D;
// set ambient reflected coefficient, because .x file do not set it.
g_mesh_materials[i].Ambient = g_mesh_materials[i].Diffuse;
g_mesh_textures[i] = NULL;
if(xmaterials[i].pTextureFilename != NULL && strlen(xmaterials[i].pTextureFilename) > 0)
D3DXCreateTextureFromFile(g_device, xmaterials[i].pTextureFilename, &g_mesh_textures[i]);
}
material_buffer->Release();
return true;
}
bool init_d3d(HWND hwnd)
{
g_d3d = Direct3DCreate9(D3D_SDK_VERSION);
if(g_d3d == NULL)
return false;
D3DPRESENT_PARAMETERS d3dpp;
ZeroMemory(&d3dpp, sizeof(d3dpp));
d3dpp.Windowed = TRUE;
d3dpp.SwapEffect = D3DSWAPEFFECT_DISCARD;
d3dpp.BackBufferFormat = D3DFMT_UNKNOWN;
d3dpp.EnableAutoDepthStencil = TRUE;
d3dpp.AutoDepthStencilFormat = D3DFMT_D16;
if(FAILED(g_d3d->CreateDevice(D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, hwnd, D3DCREATE_SOFTWARE_VERTEXPROCESSING,
&d3dpp, &g_device)))
{
return false;
}
if(! init_geometry())
return false;
D3DXMatrixIdentity(&g_mat_world);
setup_view_proj_matrix();
g_device->SetTextureStageState(0, D3DTSS_COLORARG1, D3DTA_TEXTURE);
g_device->SetTextureStageState(0, D3DTSS_COLOROP, D3DTOP_SELECTARG1);
return true;
}
void cleanup()
{
delete[] g_mesh_materials;
if(g_mesh_textures)
{
for(DWORD i = 0; i < g_num_materials; i++)
release_com(g_mesh_textures[i]);
delete[] g_mesh_textures;
}
release_com(g_mesh);
release_com(g_device);
release_com(g_d3d);
}
void render()
{
g_device->Clear(0, NULL, D3DCLEAR_TARGET | D3DCLEAR_ZBUFFER, D3DCOLOR_XRGB(5, 5, 5), 1.0f, 0);
g_device->BeginScene();
setup_world_matrix();
for(DWORD i = 0; i < g_num_materials; i++)
{
g_device->SetMaterial(&g_mesh_materials[i]);
g_device->SetTexture(0, g_mesh_textures[i]);
g_mesh->DrawSubset(i);
}
g_device->EndScene();
g_device->Present(NULL, NULL, NULL, NULL);
}
LRESULT WINAPI WinProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
switch(msg)
{
case WM_KEYDOWN:
g_keys[wParam] = 1;
if(wParam == VK_ESCAPE)
DestroyWindow(hwnd);
break;
case WM_KEYUP:
g_keys[wParam] = 0;
break;
case WM_DESTROY:
PostQuitMessage(0);
return 0;
}
return DefWindowProc(hwnd, msg, wParam, lParam);
}
int WINAPI WinMain(HINSTANCE inst, HINSTANCE, LPSTR, INT)
{
WNDCLASSEX wc;
wc.cbSize = sizeof(WNDCLASSEX);
wc.style = CS_CLASSDC;
wc.lpfnWndProc = WinProc;
wc.cbClsExtra = 0;
wc.cbWndExtra = 0;
wc.hInstance = inst;
wc.hIcon = NULL;
wc.hCursor = NULL;
wc.hbrBackground = NULL;
wc.lpszMenuName = NULL;
wc.lpszClassName = CLASS_NAME;
wc.hIconSm = NULL;
if(! RegisterClassEx(&wc))
return -1;
HWND hwnd = CreateWindow(CLASS_NAME, "Direct3D App", WS_OVERLAPPEDWINDOW, 200, 100, 640, 480,
NULL, NULL, wc.hInstance, NULL);
if(hwnd == NULL)
return -1;
if(init_d3d(hwnd))
{
ShowWindow(hwnd, SW_SHOWDEFAULT);
UpdateWindow(hwnd);
MSG msg;
ZeroMemory(&msg, sizeof(msg));
while(msg.message != WM_QUIT)
{
if(PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
render();
}
}
cleanup();
UnregisterClass(CLASS_NAME, wc.hInstance);
return 0;
}
下载示例工程