키프레임방식 애니메이션



* 키 프레임 애니메이션(key frame animation)

  :  전체 애니메이션중에서 중요한 몇개의 프레임에 애니메이션 키 값을 등록하고,

     나머지 들은 자동으로 생성하는 방법이다.

     프레임은 애니메이션에서 출력될 한 장면 한 장면을 말한다.

     자. 이제 밑에 사각형이다.


     (1)                                             (2)

      ◆       ◇       ◇       ◇       ◇       ◆  

 (-10,0,0)                                       (10,0,0) 

 

   전체 6프레임짜리 애니메이션이라고 치자

   꺼먼색 마름모가 처음1번에서부터 2번까지 이동하는 애니메이션이다.

   여기서 키값이라 하면 그 지점에서의 좌표라고 생각하면된다.

   긍까 프레임은 (1)번 프레임 (2)번 프레임 두개만 만들어놓고

   나머지 4개의 중간프레임( ◇ ) 들은 보간(interpolate)하여 자동으로 생성한다.

 

  책의 예제를 보면 위치가 이동하면서 회전까지 한다.

  크기와 이동은 선형보간으로 해결이 가능하지만..

  회전은.. 사원수를 이용한다.

 

* 사원수( 쿼터니언(Quaternion) )    :   4개의 값으로 이루어진 복소수체계.. ㅡㅡ;;

   3차원 그래픽에서 회전을 표현할때, 행렬 대신 사용하는 수학적 개념이다.

   행렬에 비해 연산속도도 빠르고 차지하는 메모리의 양도 적다.

   행렬에서는 짐발락(Gimbal Lock) 등 오류가 발생하지만 사원수는 그렇지 않다. 

 

   암튼 쿼터니언은 회전메트릭스간의 보간을 위해 쓴다.( 중간값을 뽑기위해)

   중간값을 뽑아서 다시 회전 매트릭스로 만들어야 한다. 그래야 쓸 수 있다.

  

  이넘의 사원수의 수학적 개념은 월욜날 한다고 하였고.. 후...



/***************************************************************** * 키 프레임 방식의 애니메이션 * 파일 : Animate.cpp * * 설명 : 애니메이션의 가장 대표적인 기법은 키 프레임 애니메이션이다. * 우리는 위치와 회전의 키값을 만들고, 이들 키를 보간(interpoate)하는 * 애니메이션을 제작해 볼 것이다. *****************************************************************/ #include <d3d9.h> #include <d3dx9.h> /***************************************************************** * 전역 변수 *****************************************************************/ LPDIRECT3D9 g_pD3D = NULL; //D3D 디바이스를 생성할 D3D 객체 변수 LPDIRECT3DDEVICE9 g_pd3dDevice = NULL; //렌더링에 사용될 D3D 디바이스 LPDIRECT3DVERTEXBUFFER9 g_pVB = NULL; //정점을 보관할 정점 버퍼 LPDIRECT3DINDEXBUFFER9 g_pIB = NULL; //인덱스를 보관할 인덱스 버퍼 D3DXMATRIXA16 g_matTMParent; //부모의 TM D3DXMATRIXA16 g_matRParent; //부모의 회전 행렬 D3DXMATRIXA16 g_matTMChild; //자식의 TM D3DXMATRIXA16 g_matRChild; //자식의 회전 행렬 float g_fRot = 0.0f; //사용자 정점을 정의할 구조체 struct CUSTOMVERTEX { FLOAT x, y, z; //정점의 변환된 좌표 DWORD color; //정점의 색깔 }; //사용자 정점 구조체에 관한 정보를 나타내는 FVF값 #define D3DFVF_CUSTOMVERTEX (D3DFVF_XYZ | D3DFVF_DIFFUSE) struct MYINDEX { WORD _0, _1, _2; //일반적으로 인덱스는 16비트의 크기를 갖는다. }; //애니메이션 키값을 보간하기 위한 배열 D3DXVECTOR3 g_aniPos[2]; //위치(position)키 값 D3DXQUATERNION g_aniRot[2]; //회전(quaternion)키 값 /***************************************************************** * Direct3D 초기화 *****************************************************************/ HRESULT InitD3D(HWND hWnd) { //디바이스를 생성하기 위한 D3D 객체 생성 if(NULL == (g_pD3D = Direct3DCreate9(D3D_SDK_VERSION))) { return E_FAIL; } //디바이스를 생성할 구조체 //복잡한 오브젝트를 그릴 것이므로 Z 버퍼가 필요 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_pD3D->CreateDevice(D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, hWnd, D3DCREATE_SOFTWARE_VERTEXPROCESSING, &d3dpp, &g_pd3dDevice))) { return E_FAIL; } //컬링 기능을 끈다. g_pd3dDevice->SetRenderState(D3DRS_CULLMODE, D3DCULL_CCW); //Z 버퍼 기능을 켠다. g_pd3dDevice->SetRenderState(D3DRS_ZENABLE, TRUE); //정점에 색깔값이 있으므로 광원 기능을 끈다. g_pd3dDevice->SetRenderState(D3DRS_LIGHTING, FALSE); return S_OK; } /***************************************************************** * 정점 버퍼를 생성하고 정점값을 채워넣는다. *****************************************************************/ HRESULT InitVB() { //상자(cube)를 렌더링하기 위해 8개의 정점 선언 CUSTOMVERTEX vertices[] = { { -1, 1, 1, 0xffff0000 }, //v0 { 1, 1, 1, 0xff00ff00 }, //v1 { 1, 1, -1, 0xff0000ff }, //v2 { -1, 1, -1, 0xffffff00 }, //v3 { -1, -1, 1, 0xff00ffff }, //v4 { 1, -1, 1, 0xffff00ff }, //v5 { 1, -1, -1, 0xff000000 }, //v6 { -1, -1, -1, 0xffffffff } //v7 }; //정점 버퍼 생성 //8개의 사용자 정점을 보관할 메모리를 할당한다. //FVF를 지정하여 보관할 데이터의 형식을 지정한다. if(FAILED(g_pd3dDevice->CreateVertexBuffer(8*sizeof(CUSTOMVERTEX), 0, D3DFVF_CUSTOMVERTEX, D3DPOOL_DEFAULT, &g_pVB, NULL))) { return E_FAIL; } //정점 버퍼를 값으로 채운다. //정점 버퍼의 Lock()함수를 호출하여 포인터를 얻어온다. VOID* pVertices; if(FAILED(g_pVB->Lock(0, sizeof(vertices), (void**)&pVertices, 0))) { return E_FAIL; } memcpy(pVertices, vertices, sizeof(vertices)); g_pVB->Unlock(); return S_OK; } /***************************************************************** * 인덱스 버퍼를 생성하고 인덱스값을 채워넣는다. *****************************************************************/ HRESULT InitIB() { //상자(cube)를 렌더링하기 위해 12개의 면 선언 MYINDEX indices[] = { { 0, 1, 2 }, { 0, 2, 3 }, //윗면 { 4, 6, 5 }, { 4, 7, 6 }, //아랫면 { 0, 3, 7 }, { 0, 7, 4 }, //왼쪽면 { 1, 5, 6 }, { 1, 6, 2 }, //오른쪼면 { 3, 2, 6 }, { 3, 6, 7 }, //앞면 { 0, 4, 5 }, { 0, 5, 1 } //뒷면 }; //인덱스 버퍼 생성 //D3DFMT_INDEX16은 인덱스의 단위가 16비트라는 것이다. //우리는 MYINDEX구조체에서 WORD형으로 선언했으므로 D3DFMT_INDEX16을 사용 if(FAILED(g_pd3dDevice->CreateIndexBuffer(12*sizeof(MYINDEX), 0, D3DFMT_INDEX16, D3DPOOL_DEFAULT, &g_pIB, NULL))) { return E_FAIL; } //인덱스 버퍼를 값으로 채운다. //인덱스 버퍼의 Lock() 함수를 호출하여 포인터를 얻어온다. VOID* pIndices; if(FAILED(g_pIB->Lock(0, sizeof(indices), (void**)&pIndices, 0))) { return E_FAIL; } memcpy(pIndices, indices, sizeof(indices)); g_pIB->Unlock(); return S_OK; } void InitAnimation() { g_aniPos[0] = D3DXVECTOR3(0, 0, 0); //위치키 (0, 0, 0) g_aniPos[1] = D3DXVECTOR3(5, 5, 5); //위치키 (5, 5, 5) FLOAT Yaw = D3DX_PI * 90.0f / 180.0f; //Y축 90도 회전 FLOAT Pitch = 0; FLOAT Roll = 0; D3DXQuaternionRotationYawPitchRoll(&g_aniRot[0], Yaw, Pitch, Roll); //사원수 키(Y축 90도) Yaw = 0; Pitch = D3DX_PI * 90.0f / 180.0f; //X축 90도 회전 Roll = 0; D3DXQuaternionRotationYawPitchRoll(&g_aniRot[1], Yaw, Pitch, Roll); //사원수 키(X축 90도) } /***************************************************************** * 기하 정보 초기화 *****************************************************************/ HRESULT InitGeometry() { if(FAILED(InitVB())) { return E_FAIL; } if(FAILED(InitIB())) { return E_FAIL; } InitAnimation(); return S_OK; } /***************************************************************** * 카메라 행렬 설정 *****************************************************************/ void SetupCamera() { //월드 행렬 설정 D3DXMATRIXA16 matWorld; D3DXMatrixIdentity(&matWorld); g_pd3dDevice->SetTransform(D3DTS_WORLD, &matWorld); //뷰 행렬 설정 D3DXVECTOR3 vEyePt(0.0f, 10.0f, -20.0f); D3DXVECTOR3 vLookatPt(0.0f, 0.0f, 0.0f); D3DXVECTOR3 vUpVec(0.0f, 1.0f, 0.0f); D3DXMATRIXA16 matView; D3DXMatrixLookAtLH(&matView, &vEyePt, &vLookatPt, &vUpVec); g_pd3dDevice->SetTransform(D3DTS_VIEW, &matView); //프로젝션 행렬 설정 D3DXMATRIXA16 matProj; D3DXMatrixPerspectiveFovLH(&matProj, D3DX_PI/4, 1.0f, 1.0f, 100.0f); g_pd3dDevice->SetTransform(D3DTS_PROJECTION, &matProj); } //선형보간(linear interpolation) 함수 float Linear(float v0, float v1, float t) { return v0 * (1.0f - t) + v1 * t; //다음 줄로 바꿔도 된다. //return v0 + t * (v1 - v0); } /***************************************************************** * 애니메이션 행렬 생성 *****************************************************************/ VOID Animate() { static float t = 0; float x, y, z; D3DXQUATERNION quat; if(t > 1.0f) { t = 0.0f; } //위치값의 선형 보간 x = Linear(g_aniPos[0].x, g_aniPos[1].x, t); y = Linear(g_aniPos[0].y, g_aniPos[1].y, t); z = Linear(g_aniPos[0].z, g_aniPos[1].z, t); D3DXMatrixTranslation(&g_matTMParent, x, y, z); //이동 행렬을 구한다. //위의 4줄은 다음의 3줄로 바꿀 수 있다. //D3DXVECTOR3 v; //D3DXVec3Lerp(&v, &g_aniPos[0], &g_aniPos[1], t); //D3DXMatrixTranslation(&g_matTMParent, v.x, v.y, v.z); //회전값의 구면 선형 보간 D3DXQuaternionSlerp(&quat, &g_aniRot[0], &g_aniRot[1], t); D3DXMatrixRotationQuaternion(&g_matRParent, &quat); //사원수를 회전 행렬값으로 변환 t += 0.005f; //자식 메시의 Z축 회전 행렬 D3DXMatrixRotationZ(&g_matRChild, GetTickCount()/500.0f); //자식 메시는 원점으로부터 (3, 3, 3)거리에 있음 D3DXMatrixTranslation(&g_matTMChild, 3, 3, 3); } /***************************************************************** * 초기화 객체들 소거 *****************************************************************/ VOID Cleanup() { if(g_pIB != NULL) { g_pIB->Release(); } if(g_pVB != NULL) { g_pVB->Release(); } if(g_pd3dDevice != NULL) { g_pd3dDevice->Release(); } if(g_pD3D != NULL) { g_pD3D->Release(); } } /******************************************************************** * 메시 그리기 ********************************************************************/ void DrawMesh(D3DXMATRIXA16* pMat) { g_pd3dDevice->SetTransform(D3DTS_WORLD, pMat); g_pd3dDevice->SetStreamSource(0, g_pVB, 0, sizeof(CUSTOMVERTEX)); g_pd3dDevice->SetFVF(D3DFVF_CUSTOMVERTEX); g_pd3dDevice->SetIndices(g_pIB); g_pd3dDevice->DrawIndexedPrimitive(D3DPT_TRIANGLELIST, 0, 0, 8, 0, 12); } /******************************************************************** * 화면 그리기 ********************************************************************/ VOID Render() { D3DXMATRIXA16 matWorld; //후면 버퍼와 Z버퍼 초기화 g_pd3dDevice->Clear(0, NULL, D3DCLEAR_TARGET | D3DCLEAR_ZBUFFER, D3DCOLOR_XRGB(0, 0, 255), 1.0f, 0); //애니메이션 행렬 설정 Animate(); //렌더링 시작 if(SUCCEEDED(g_pd3dDevice->BeginScene())) { matWorld = g_matRParent * g_matTMParent; DrawMesh(&matWorld); //부모 상자 그리기 matWorld = g_matRChild * g_matTMChild * matWorld; //matWorld = g_matRChild * g_matTMChild * g_matRParent * g_matTMParent; //위 행렬과 같은 결과 DrawMesh(&matWorld); //자식 상자 그리기 //렌더링 종료 g_pd3dDevice->EndScene(); } //후면 버퍼를 보이는 화면으로 g_pd3dDevice->Present(NULL, NULL, NULL, NULL); } /******************************************************************** * 윈도우 프로시저 ********************************************************************/ #define ROT_DELTA 0.1f LRESULT WINAPI MsgProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam) { switch(msg) { case WM_DESTROY: Cleanup(); PostQuitMessage(0); return 0; case WM_KEYDOWN: if(wParam == VK_LEFT) { g_fRot -= ROT_DELTA; } if(wParam == VK_RIGHT) { g_fRot += ROT_DELTA; } break; } return DefWindowProc(hWnd, msg, wParam, lParam); } /******************************************************************** * 프로그램 시작점 ********************************************************************/ INT WINAPI WinMain(HINSTANCE hInst, HINSTANCE, LPSTR, INT) { //윈도우 클래스 등록 WNDCLASSEX wc = { sizeof(WNDCLASSEX), CS_CLASSDC, MsgProc, 0L, 0L, GetModuleHandle(NULL), NULL, NULL, NULL, NULL, "BasicFrame", NULL }; RegisterClassEx(&wc); //윈도우 생성 HWND hWnd = CreateWindow("BasicFrame", "Keyframe Animation", WS_OVERLAPPEDWINDOW, 100, 100, 500, 500, GetDesktopWindow(), NULL, wc.hInstance, NULL); //Direct3D 초기화 if(SUCCEEDED(InitD3D(hWnd))) { if(SUCCEEDED(InitGeometry())) { //카메라 행렬 설정 SetupCamera(); //윈도우 출력 ShowWindow(hWnd, SW_SHOWDEFAULT); UpdateWindow(hWnd); //메시지 루프 MSG msg; ZeroMemory(&msg, sizeof(msg)); while(msg.message != WM_QUIT) { //메시지 큐에 메시지가 있으면 메시지 처리 if(PeekMessage(&msg, NULL, 0U, 0U, PM_REMOVE)) { TranslateMessage(&msg); DispatchMessage(&msg); } else { //처리할 메시지가 없으면 Render() 함수 호출 Render(); } } } } //등록된 클래스 소거 UnregisterClass("D3D Tutorial", wc.hInstance); return 0; }


결과
사용자 삽입 이미지


출저:연화의 설정은 열정!! :: 12. 애니메이션

이 글을 공유하기

댓글

Designed by JB FACTORY