摄像机类的实现,主要是因为D3DXMatrixLookAtLH()求取的是指定摄像机位置和观察坐标系向量的矩阵,但如果摄像机位置和观察坐标系向量是根据用户输入来确定的,那么D3DXMatrixLookAtLH显得力不从心。原因:
D3DXMATRIX* D3DXMatrixLookAtLH( _Inout_ D3DXMATRIX *pOut, _In_ const D3DXVECTOR3 *pEye, _In_ const D3DXVECTOR3 *pAt, _In_ const D3DXVECTOR3 *pUp );其中的pAt是观察的朝向,摄像机的z = (pAt - pEye).Normalize(), 摄像机的y = pUp, 摄像机的x= 摄像机z x 摄像机y再规范单位化。但是用户输入时候,很难从改变pAt位置来改变摄像机z。设置也很不直观,所以需要开发自己的摄像机类,特别是在飞行或者第一人称射击游戏中。
开发自己的摄像机类,主要思想是:定义摄像机的位置_pos,方向向量_look, _right, _up;主角可以在这些方向上进行移动摄像机的位置_pos(这些方向向量也是有自己的xyz的),也可以绕这些方向进行旋转(旋转时候只针对某个轴进行旋转,然后对其它轴乘以该旋转矩阵进行变换);从而可以简单的得到新的_pos,_look, _right, _up,将整个世界坐标系中的物体变换到观察坐标系中即可。因为模型坐标系转换到世界坐标系要进行RT,所以世界坐标系中旋转到视图坐标系(模型坐标系)中进行V = T-1R-1矩阵变换即可。这个变换也是
D3DXMatrixLookAtLH函数得到视图矩阵的方法, 这里只不过是使得输入移动和旋转更加直观。
如果不是第一人称类型的摄像机操作,而是RPG类型的飞行摄像机,不能输入位置和摄像机旋转,那么可以直接配置pos 位置。2D中通过D3DXMatrixOrthoLH函数的参数,放大传入的w、h可以实现模拟的远离缩小;拉动摄像机的y坐标就可以上升了。3D中设置固定的摄像机位置y值上下移动,z值可以设置近大远小效果,还是通过D3DXMatrixLookAtLH设置变换矩阵即可。
#ifndef __cameraH__ #define __cameraH__ #include <d3dx9.h> class Camera { public: enum CameraType { LANDOBJECT, AIRCRAFT }; Camera(); Camera(CameraType cameraType); ~Camera(); void strafe(float units); // left/right void fly(float units); // up/down void walk(float units); // forward/backward void pitch(float angle); // rotate on right vector void yaw(float angle); // rotate on up vector void roll(float angle); // rotate on look vector void getViewMatrix(D3DXMATRIX* V); void setCameraType(CameraType cameraType); void getPosition(D3DXVECTOR3* pos); void setPosition(D3DXVECTOR3* pos); void getRight(D3DXVECTOR3* right); void getUp(D3DXVECTOR3* up); void getLook(D3DXVECTOR3* look); private: CameraType _cameraType; D3DXVECTOR3 _right; D3DXVECTOR3 _up; D3DXVECTOR3 _look; D3DXVECTOR3 _pos; }; #endif
#include "camera.h" Camera::Camera() { _cameraType = AIRCRAFT; _pos = D3DXVECTOR3(0.0f, 0.0f, 0.0f); _right = D3DXVECTOR3(1.0f, 0.0f, 0.0f); _up = D3DXVECTOR3(0.0f, 1.0f, 0.0f); _look = D3DXVECTOR3(0.0f, 0.0f, 1.0f); } Camera::Camera(CameraType cameraType) { _cameraType = cameraType; _pos = D3DXVECTOR3(0.0f, 0.0f, 0.0f); _right = D3DXVECTOR3(1.0f, 0.0f, 0.0f); _up = D3DXVECTOR3(0.0f, 1.0f, 0.0f); _look = D3DXVECTOR3(0.0f, 0.0f, 1.0f); } Camera::~Camera() { } void Camera::getPosition(D3DXVECTOR3* pos) { *pos = _pos; } void Camera::setPosition(D3DXVECTOR3* pos) { _pos = *pos; } void Camera::getRight(D3DXVECTOR3* right) { *right = _right; } void Camera::getUp(D3DXVECTOR3* up) { *up = _up; } void Camera::getLook(D3DXVECTOR3* look) { *look = _look; } void Camera::walk(float units) { // move only on xz plane for land object // 观察摄像机,在方向_look只能沿着xz( 不一定是(0,0,1) )改变前行 if( _cameraType == LANDOBJECT ) _pos += D3DXVECTOR3(_look.x, 0.0f, _look.z) * units; if( _cameraType == AIRCRAFT ) _pos += _look * units; } void Camera::strafe(float units) { // move only on xz plane for land object // 观察摄像机,在方向_right只能沿着xz( 不一定是(1,0,0) )改变前行 if( _cameraType == LANDOBJECT ) _pos += D3DXVECTOR3(_right.x, 0.0f, _right.z) * units; if( _cameraType == AIRCRAFT ) _pos += _right * units; } void Camera::fly(float units) { // move only on y-axis for land object // 观察摄像机,在_up方向上只能向上_pos.y移动 if( _cameraType == LANDOBJECT ) _pos.y += units; if( _cameraType == AIRCRAFT ) _pos += _up * units; } void Camera::pitch(float angle) { // 绕_right轴旋转,地面摄像机和空中摄像机都可以 D3DXMATRIX T; D3DXMatrixRotationAxis(&T, &_right, angle); // rotate _up and _look around _right vector // 因为绕_right轴移动,所以_up需要进行T旋转 D3DXVec3TransformCoord(&_up,&_up, &T); // _look轴需要进行T旋转 D3DXVec3TransformCoord(&_look,&_look, &T); } void Camera::yaw(float angle) { D3DXMATRIX T; // rotate around world y (0, 1, 0) always for land object // 地面摄像机不能做_up旋转,因为_up不一定是世界坐标系的y,所以只能做基于世界坐标系Y轴旋转 if( _cameraType == LANDOBJECT ) D3DXMatrixRotationY(&T, angle); // rotate around own up vector for aircraft if( _cameraType == AIRCRAFT ) D3DXMatrixRotationAxis(&T, &_up, angle); // rotate _right and _look around _up or y-axis D3DXVec3TransformCoord(&_right,&_right, &T); D3DXVec3TransformCoord(&_look,&_look, &T); } void Camera::roll(float angle) { // only roll for aircraft type // 地面观察摄像机,不能进行绕_look轴旋转,因为太怪异了 if( _cameraType == AIRCRAFT ) { D3DXMATRIX T; D3DXMatrixRotationAxis(&T, &_look, angle); // rotate _up and _right around _look vector D3DXVec3TransformCoord(&_right,&_right, &T); D3DXVec3TransformCoord(&_up,&_up, &T); } } void Camera::getViewMatrix(D3DXMATRIX* V) { // Keep camera's axes orthogonal to eachother // 摄像机的视图变换,本来是物体坐标系到世界坐标系中需要进行S*R*T变换(缩放,旋转,平移) // 现在是世界坐标系变换到物体坐标系(观察坐标系中),也就是要进行T-1R-1S-1变换(逆向平移,逆向旋转,逆向缩放) // 因为这里没有缩放S所以进行T-1R-1变换就得到在视图坐标系描述世界坐标系中物体的位置。 // 因为摄像机_look,_right,_up向量由于外部的位置移动和旋转,发生了改变,所以需要先规格化为单位向量,方便旋转 D3DXVec3Normalize(&_look, &_look); // 由于_right,_look求得_up向量,认为 D3DXVec3Cross(&_up, &_look, &_right); D3DXVec3Normalize(&_up, &_up); // 如果_right存在精度问题,那么认为_up是正确的,求取_right向量 D3DXVec3Cross(&_right, &_up, &_look); D3DXVec3Normalize(&_right, &_right); // Build the view matrix: // V = T-1R-1平移部分,进行平移 float x = -D3DXVec3Dot(&_right, &_pos); float y = -D3DXVec3Dot(&_up, &_pos); float z = -D3DXVec3Dot(&_look, &_pos); // V = T-1R-1旋转部分,R-1刚好是R的转置 (*V)(0,0) = _right.x; (*V)(0, 1) = _up.x; (*V)(0, 2) = _look.x; (*V)(0, 3) = 0.0f; (*V)(1,0) = _right.y; (*V)(1, 1) = _up.y; (*V)(1, 2) = _look.y; (*V)(1, 3) = 0.0f; (*V)(2,0) = _right.z; (*V)(2, 1) = _up.z; (*V)(2, 2) = _look.z; (*V)(2, 3) = 0.0f; (*V)(3,0) = x; (*V)(3, 1) = y; (*V)(3, 2) = z; (*V)(3, 3) = 1.0f; // 得到V后,就可以通过Device->SetTransform(D3DTS_VIEW, &V);就可以实现视图变换了 } void Camera::setCameraType(CameraType cameraType) { _cameraType = cameraType; }调用:
#include "d3dUtility.h" #include "camera.h" // // Globals // IDirect3DDevice9* Device = 0; const int Width = 640; const int Height = 480; Camera TheCamera(Camera::AIRCRAFT/*LANDOBJECT*/); // // Framework functions // bool Setup() { // // Setup a basic scene. The scene will be created the // first time this function is called. // d3d::DrawBasicScene(Device, 0.0f); // // Set projection matrix. // D3DXMATRIX proj; D3DXMatrixPerspectiveFovLH( &proj, D3DX_PI * 0.25f, // 45 - degree (float)Width / (float)Height, 1.0f, 1000.0f); Device->SetTransform(D3DTS_PROJECTION, &proj); return true; } void Cleanup() { // pass 0 for the first parameter to instruct cleanup. d3d::DrawBasicScene(0, 0.0f); } bool Display(float timeDelta) { if( Device ) { // // Update: Update the camera. // if( ::GetAsyncKeyState('W') & 0x8000f ) TheCamera.walk(4.0f * timeDelta); if( ::GetAsyncKeyState('S') & 0x8000f ) TheCamera.walk(-4.0f * timeDelta); if( ::GetAsyncKeyState('A') & 0x8000f ) TheCamera.strafe(-4.0f * timeDelta); if( ::GetAsyncKeyState('D') & 0x8000f ) TheCamera.strafe(4.0f * timeDelta); if( ::GetAsyncKeyState('R') & 0x8000f ) TheCamera.fly(4.0f * timeDelta); if( ::GetAsyncKeyState('F') & 0x8000f ) TheCamera.fly(-4.0f * timeDelta); if( ::GetAsyncKeyState(VK_UP) & 0x8000f ) TheCamera.pitch(1.0f * timeDelta); if( ::GetAsyncKeyState(VK_DOWN) & 0x8000f ) TheCamera.pitch(-1.0f * timeDelta); if( ::GetAsyncKeyState(VK_LEFT) & 0x8000f ) TheCamera.yaw(-1.0f * timeDelta); if( ::GetAsyncKeyState(VK_RIGHT) & 0x8000f ) TheCamera.yaw(1.0f * timeDelta); if( ::GetAsyncKeyState('N') & 0x8000f ) TheCamera.roll(1.0f * timeDelta); if( ::GetAsyncKeyState('M') & 0x8000f ) TheCamera.roll(-1.0f * timeDelta); // Update the view matrix representing the cameras // new position/orientation. D3DXMATRIX V; TheCamera.getViewMatrix(&V); //D3DXMatrixLookAtLH() Device->SetTransform(D3DTS_VIEW, &V); // // Render // Device->Clear(0, 0, D3DCLEAR_TARGET | D3DCLEAR_ZBUFFER, 0x00000000, 1.0f, 0); Device->BeginScene(); d3d::DrawBasicScene(Device, 1.0f); Device->EndScene(); Device->Present(0, 0, 0, 0); } return true; } // // WndProc // LRESULT CALLBACK d3d::WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) { switch( msg ) { case WM_DESTROY: ::PostQuitMessage(0); break; case WM_KEYDOWN: if( wParam == VK_ESCAPE ) ::DestroyWindow(hwnd); break; } return ::DefWindowProc(hwnd, msg, wParam, lParam); } // // WinMain // int WINAPI WinMain(HINSTANCE hinstance, HINSTANCE prevInstance, PSTR cmdLine, int showCmd) { if(!d3d::InitD3D(hinstance, Width, Height, true, D3DDEVTYPE_HAL, &Device)) { ::MessageBox(0, "InitD3D() - FAILED", 0, 0); return 0; } if(!Setup()) { ::MessageBox(0, "Setup() - FAILED", 0, 0); return 0; } d3d::EnterMsgLoop( Display ); Cleanup(); Device->Release(); return 0; }