430 likes | 702 Views
제 12 장. 유연한 카메라 클래스 만들기. 카메라 디자인. 사용자 입력에 반응하여 카메라를 이동 우향 벡터 , 상향 벡터 , 전방 벡터 , 위치 벡터의 네 가지 카메라 벡터를 이용 월드 좌표 시스템에서의 카메라 위치와 방향을 정의 우향 , 상향 , 전방의 세 벡터는 월드 내에 카메라의 방향을 정의 방위 벡터 (orientation vector) 라고 함. 방위벡터는 반드시 정직교여야 함 정직교가 되기 위해서는 각각의 벡터가 서로 수직이며 단위길이여야 함. U. P = 위치 벡터
E N D
제12장 유연한 카메라 클래스 만들기 컴퓨터게임(DirectX)
카메라 디자인 • 사용자 입력에 반응하여 카메라를 이동 • 우향 벡터, 상향 벡터, 전방 벡터, 위치 벡터의 네 가지 카메라 벡터를 이용 • 월드 좌표 시스템에서의 카메라 위치와 방향을 정의 • 우향, 상향, 전방의 세 벡터는 월드 내에 카메라의 방향을 정의 • 방위 벡터(orientation vector)라고 함 컴퓨터게임(DirectX)
방위벡터는 반드시 정직교여야 함 • 정직교가 되기 위해서는 각각의 벡터가 서로 수직이며 단위길이여야 함 U P = 위치 벡터 R = 우향 벡터 U = 상향 벡터 L = 전방 벡터 L R P 컴퓨터게임(DirectX)
카메라의 동작(6가지) • 우향 벡터를 기준으로 회전(pitch) • 상향 벡터를 기준으로 회전(yaw) • 전방 벡터를 기준으로 회전(roll) • 우향 벡터 방향으로 이동 • 상향 벡터 방향으로 날기 • 전방 벡터 방향으로 이동 • 이 여섯가지 동작을 통해 3 개의 축을 기준으로 하는 회전과 세 개의 축을 향한 이동이 가능하게 됨 => 모두 6 가지 종류의 자유 허용 컴퓨터게임(DirectX)
Camera 클래스 • 다음의 카메라 클래스 선언은 원하는 데이터와 메서드가 선언되는 방법을 보여준다. 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 컴퓨터게임(DirectX)
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); 컴퓨터게임(DirectX)
private: CameraType _cameraType; D3DXVECTOR3 _right; D3DXVECTOR3 _up; D3DXVECTOR3 _look; D3DXVECTOR3 _pos; }; 컴퓨터게임(DirectX)
CameraType : 카메라 타입을 지정하기 위한 것 • AIRCRAFT : • 공간을 자유롭게 이동할 수 있도록 해주며 6가지의 자유를 허용 • 1인칭 슈팅 게임과 같은 일부 게임에서는 주인공이 하늘을 날 수 없으므로 특정한 축으로의 이동을 제한 => LANDOBJECT 카메라 모델이 적합함. 컴퓨터게임(DirectX)
구현 방법 • 뷰 행렬 계산하기 • p = (px, py, pz), r = (rx, ry, rz), u = (ux, uy, uz), d = (dx, dy, dz); • 위 벡터를 위치, 우향, 상향, 전방 벡터라고 하자. • 2장에서 뷰 스페이스 변환은 월드 내의 기하정보를 변환하여 카메라를 원점에 위치, 좌표축에 따라 정렬시키는 과정임을 설명하였다. 컴퓨터게임(DirectX)
C C C A A A B B B D D D 월드 스페이스에서 뷰 스페이스로의 변환 월드 스페이스 내의 물체와 카메라 관찰점을 원점으로 변환, 물체도 함께 회전 Z-축에 맞도록 관찰점을 회전, 물체도 함께 회전 컴퓨터게임(DirectX)
변환 행렬 V로 다음과 같은 작업이 수행되어야 함 • pV = (0, 0, 0) : 행렬 V는 카메라를 원점으로 이동 • rV = (1, 0, 0) : 행렬 V는 우향 벡터를 월드 X축과 정렬 • uV = (0, 1, 0) : 행렬 V는 상향 벡터를 월드 Y축과 정렬 • dV = (0, 0, 1) : 행렬 V는 전방 벡터를 월드 Z축과 정렬 • 이 벡터를 찾는 과정은 카메라의 위치를 원점으로 이동하는 이동파트와 카메라와 벡터를 월드의 축과 정렬하는 회전 파트의 두 개의 파트로 나눔 컴퓨터게임(DirectX)
파트 1 : 이동 • P를 원점으로 이동하는 이동은 P – P = 0 이므로 • -P 로 쉽게 얻을 수 있다. • 즉 뷰 변환의 이동 파트는 간단하게 가능 • T = • 0 0 0 • 0 1 0 0 • 0 0 0 1 • -Px –Py –Pz 1 컴퓨터게임(DirectX)
파트 2 : 회 전 • 우향, 상향, 전방 벡터를 각각의 월드 x,y,z축과 정렬하는 3 * 3 행렬 A 가 필요 • 다음 방정식을 만족시켜야 함 rA = [rx, ry, rz] = [1, 0, 0] uA = [ux, uy, uz] = [0, 1, 0] dA = [dx, dy, dz] = [0, 0, 1] A00 A01 A02 A10 A11 A12 A20 A21 A22 A00 A01 A02 A10 A11 A12 A20 A21 A22 A00 A01 A02 A10 A11 A12 A20 A21 A22 컴퓨터게임(DirectX)
세 개의 시스템이 모두 동일한 계수 행렬A를 가지므로 한번에 해결 가능 • BA = = • BA = BB-1 = I 이므로 A는 B의 역행렬 • B는 직교 행렬이므로 역 행렬이 또한 전치 행렬 • 방위벡터를 월드의 축과 정렬하는 변환은 다음과 같다: • B-1 = BT = A = rx ry rz ux uy uz dx dy dz A00 A01 A02 A10 A11 A12 A20 A21 A22 • 0 0 • 0 1 0 • 0 0 1 rx ux dx ry uy dy rz uz dz 컴퓨터게임(DirectX)
1 0 0 0 0 1 0 0 0 0 0 1 -px –py –pz 1 rx ux dx 0 ry uy dy 0 rz uz dz 0 0 0 0 1 rx ux dx 0 ry uy dy 0 rz uz dz 0 -p•r -p•u -p•d 1 두 개의 파트를 조합하기 • 마지막으로 A를 4 * 4 행렬로 변환 • 이동 파트와 회전파트를 결합 • 뷰변환 행렬 V를 얻음. TA = = = V • Camera::getViewMatrix 메서드로 이 행렬을 구성 컴퓨터게임(DirectX)
void Camera::getViewMatrix(D3DXMATRIX* V) { D3DXVec3Normalize(&_look, &_look); D3DXVec3Cross(&_up, &_look, &_right); D3DXVec3Normalize(&_up, &_up); D3DXVec3Cross(&_right, &_up, &_look); D3DXVec3Normalize(&_right, &_right); float x = -D3DXVec3Dot(&_right, &_pos); float y = -D3DXVec3Dot(&_up, &_pos); float z = -D3DXVec3Dot(&_look, &_pos); (*V)(0,0) = _right.x; (*V)(0, 1) = _up.x; (*V)(0, 2) = _look.x; (*V)(0, 3) = 0.0f; 부동소수점 오류로 인해 카메라축이 서로 직각이 아닐 수 있어서 전방 벡터를 기준으로 상향 벡터와 우향 벡터를 재계산하여 항상 벡터가 서로 직각이 되도록 함 컴퓨터게임(DirectX)
(*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; } 컴퓨터게임(DirectX)
임의의 축으로 회전하기 • 카메라 회전 메서드를 구현하기 위해서는 임의의 축을 기준으로 회전할 수 있어야 함. D3DXMATRIX*& D3DMatrixRotationAxis (D3DXMATRIX *pOut, CONST D3DXVECTOR3 *pv, FLOAT Angle); • pOut : 회전 행렬을 리턴 • pV : 회전의 기준 축 • Angle : 라디안 단위의 회전할 각도 컴퓨터게임(DirectX)
Y • 벡터(0.707, 0.707, 0)로 정의된 축을 기준으로 • ∏ /2 라디안 만큼 회전시키고자 한다면 • D3DXMATRIX R; • D3DXVECTOR3 axis(0.707f, 0.707f, 0.0f); • D3DXMatrixRotationAxis(&R, &axis, D3DX_PI/2.0f); Z A X 컴퓨터게임(DirectX)
Pitch, Yaw, Roll • 방위 벡터는 월드 좌표 시스템에 상대적인 카메라의 방위를 나타냄 • 세 개의 축을 기준으로 한 회전을 통해 방위 벡터를 갱신하는 방법이 필요 • 다음의 그림은 pitch, yaw, roll 로 불리는 세 가지의 카메라 회전 방법을 보여줌 컴퓨터게임(DirectX)
카메라 회전 u u 카메라의 우향 벡터를 기준으로 회전 d r r d u u r 카메라의 상향 벡터를 기준으로 회전 d r d u u 카메라의 전방 벡터를 기준으로 회전 d d r r 컴퓨터게임(DirectX)
우향 벡터를 기준으로 회전할 때 (pitch) • 우향 벡터를 기준으로 지정된 각도만큼 상향 벡터와 전방 벡터를 회전시킴 • 상향 벡터를 기준으로 회전할 때 (yaw) • 상향 벡터를 기준으로 지정된 각도만큼 전방 벡터와 우향 벡터를 회전시킴 • 전방 벡터를 기준으로 회전할 때 (roll) • 전방 벡터를 기준으로 지정된 각도만큼 상향 벡터와 우향 벡터를 회전시킴 컴퓨터게임(DirectX)
D3DMatrixRotationAxis 함수가 필요한 것 • 우리가 회전의 기준으로 이용하는 이 세개의 벡터가 월드 내에서 임의의 방위를 가질 수 있기 때문이다. • LANDOBJECT의 경우 • yaw 메서드에서 카메라의 전방 벡터가 아닌 월드의 y축을 기준으로 회전할 것이며 • roll 메서드는 완전히 이용할 수 없도록 함 • 각자의 활용 분야에 따라 Camera를 수정해야 함 컴퓨터게임(DirectX)
Pitch, yaw, roll 메서드 구현 void Camera::pitch(float angle) { D3DXMATRIX T; D3DXMatrixRotationAxis(&T, &_right, angle); // _right를 기준으로 _up 과 _look 을 회전한다. D3DXVec3TransformCoord(&_up,&_up, &T); D3DXVec3TransformCoord(&_look,&_look, &T); } 컴퓨터게임(DirectX)
void Camera::yaw(float angle) { D3DXMATRIX T; // 지상 물체에 대해서는 월드 y (0, 1, 0) 을 기준으로 회전 if( _cameraType == LANDOBJECT ) D3DXMatrixRotationY(&T, angle); // 비행 물체에 대해서는 원래의 상향 벡터로 회전 if( _cameraType == AIRCRAFT ) D3DXMatrixRotationAxis(&T, &_up, angle); // rotate _right and _look around _up or y-axis D3DXVec3TransformCoord(&_right,&_right, &T); D3DXVec3TransformCoord(&_look,&_look, &T); } 컴퓨터게임(DirectX)
void Camera::roll(float angle) { // 비행 타입일 경우만 회전한다. if( _cameraType == AIRCRAFT ) { D3DXMATRIX T; D3DXMatrixRotationAxis(&T, &_look, angle); // look 벡터 기준으로 위와 오른쪽으로 회전한다. D3DXVec3TransformCoord(&_right,&_right, &T); D3DXVec3TransformCoord(&_up,&_up, &T); } } 컴퓨터게임(DirectX)
걷기, 옆걸음질, 날기 • ‘걷는다’ 라는 말은 현재 바라보는 방향으로 이동한다는 것을 의미 • 전방 벡터 방향으로 이동 • 옆걸음질은 바라보는 방향에서 양옆으로 이동 • 우향 벡터로 이동 • 날기는 상향 벡터로 이동하는 것이다. • 축들 중 한 방향으로 이동하기 위해서는 • 간단히 위치 벡터에 우리가 이동하고자 하는 방향을 가리키는 벡터를 더하면 된다. 컴퓨터게임(DirectX)
(그림12.7) 카메라 방위 벡터로의 이동 P’ = P + sL P’ = P + rU sL rU p tR P’ = P + tR 컴퓨터게임(DirectX)
지상의 물체를 이동할 때는 회전과 마찬가지로 약간의 제약이 필요 • LANDOBJECT 물체는 위를 보는 상태에서 전진하거나 기울어진 상태에서 옆걸음으로 공중에 뜰 수 없어야 함 • 즉 xz평면으로 움직임을 제한해야 함. • 계단이나 언덕을 오르는 등의 방법으로 고도를 변경할 수는 있으므로 • Camera::setPosition 메서드로 카메라의 높이나 위치를 지정 컴퓨터게임(DirectX)
walk., strafe, fly 메서드의 코드 구현 void Camera::walk(float units) { if (units == 0) return; // 지상의 물체는 xz 평면으로 움직임을 제한한다. if( _cameraType == LANDOBJECT ) _pos += D3DXVECTOR3(_look.x, 0.0f,_look.z) * units; if( _cameraType == AIRCRAFT ) _pos += _look * units; } 컴퓨터게임(DirectX)
void Camera::strafe(float units) { // 지상의 물체는 xz 평면으로 움직임을 제한한다. if( _cameraType == LANDOBJECT ) _pos += D3DXVECTOR3(_right.x, 0.0f, _right.z) *units; if( _cameraType == AIRCRAFT ) _pos += _right * units; } void Camera::fly(float units){ if( _cameraType == LANDOBJECT ) _pos.y += units; if( _cameraType == AIRCRAFT ) _pos += _up * units; } 컴퓨터게임(DirectX)
예제 애플리케이션 : 카메라 컴퓨터게임(DirectX)
키보드 입력을 통해 자유롭게 화면 내부 이동 • W/S : 전진/후진 • A/D: 왼쪽/오른쪽 옆걸음질 • R/F: 위/아래 날기 • 위/아래 화살표 키: Pitch • 왼쪽/오른쪽 화살표 키: Yaw • N/M: Roll 컴퓨터게임(DirectX)
실제 구현은 앞서 설명한 대로 Camera 클래스 내부에서 모두 해결됨 • 사용자의 키 입력은 Display 함수에서 처리 • 카메라 객체인 TheCamera를 전역으로 선언 컴퓨터게임(DirectX)
DrawBasicScene() 함수 • DrawBasicScene라는 새로운 함수를 d3d 네임스페이스에 추가하였다. • 위의 그림에서와 같이 장면을 그려낸다. • d3dUtility.h에 선언 • Bool DrawBasicScene( IDirect3DDevice9* device, float scale); • 올바른 장치 포인터를 이용해 함수를 호출하면 내부적으로 기하정보를 구성 • Setup 함수에서 처음 호출하는 것이 바람직 • 내부 기하정보를 정리하기 위해서는 장치 포인터에 null을 지정 • Cleanup 루틴 내에서 이 함수를 호출하면 된다. • 이 함수가 desert.bmp 파일을 읽어드린다. 컴퓨터게임(DirectX)
CameraApp.cpp IDirect3DDevice9* Device = 0; const int Width = 640; const int Height = 480; Camera TheCamera(Camera::LANDOBJECT); bool Setup() { d3d::DrawBasicScene(Device, 0.0f); D3DXMATRIX proj; D3DXMatrixPerspectiveFovLH( &proj, D3DX_PI * 0.25f, // 45 - degree (float)Width / (float)Height, 1.0f, 1000.0f); Device->SetTransform(D3DTS_PROJECTION, &proj); 컴퓨터게임(DirectX)
return true; } void Cleanup(){ d3d::DrawBasicScene(0, 0.0f); } bool Display(float timeDelta) { if( Device ) { if( ::GetAsyncKeyState('W') & 0x8000f ) TheCamera.walk(4.0f * timeDelta); if( ::GetAsyncKeyState('S') & 0x8000f ) TheCamera.walk(-4.0f * timeDelta); 컴퓨터게임(DirectX)
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); 컴퓨터게임(DirectX)
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); D3DXMATRIX V; TheCamera.getViewMatrix(&V); Device->SetTransform(D3DTS_VIEW, &V); 컴퓨터게임(DirectX)
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; } LRESULT CALLBACK d3d::WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAMlParam) 컴퓨터게임(DirectX)
switch( msg ) { case WM_DESTROY: ::PostQuitMessage(0); break; case WM_KEYDOWN: if( wParam == VK_ESCAPE ) ::DestroyWindow(hwnd); break; } return ::DefWindowProc(hwnd, msg, wParam, lParam); } 컴퓨터게임(DirectX)
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; } 컴퓨터게임(DirectX)
if(!Setup()) { ::MessageBox(0, "Setup() - FAILED", 0, 0); return 0; } d3d::EnterMsgLoop( Display ); Cleanup(); Device->Release(); return 0; } 컴퓨터게임(DirectX)