스키닝
메시를 애니메이션할 때 접합부위를 자연스럽게 이어붙이기 위한 기술들을 총칭하여 스키닝이라고 하며, 이러한 스키닝에는 다양한 기법들이 있으나, 일반적으로는 뼈대의 애니메이션 행렬이 메시의 정점에 얼마만큼의 가중치로 결합될 것인가 하는 것이 핵심요소라고 할 수 있다.
매트릭스 팔레트
매트릭스 팔레트 or 인덱스 매트릭스라고 하는 방식은 Direct 3D 8.0에서 새롭게 도입된 방식으로 7.0에 있던 WORLD(0) ~ WORLD(3)에 오브젝트마다 매번 행렬을 대입하면서 사용했던 불편한 정점 블렌딩을 발전시킨 것이다.
기본적인 개념은 각각의 정점이 최대 4개까지 blend weight라는 가중치를 가질 수 있는데, 이들의 가중치에 따라서 다른 행렬을 곱해준다는 것이다. 이 때, 곱해주는 행렬을 최대 256개까지 매트릭스 팔레트라는 영역에 세팅해놓으면 나머지는 D3D가 알아서 DrawPrimitive()함수 호출 시에 처리해주는 것이다.
매트릭스 팔레트는 캐릭터를 위해서 탄생했다고 봐도 과언이 아니다.
다른용도로도 사용될 수 있으나, 최대 256개까지 지원하는 매트릭스 팔레트에 뼈대의 애니메이션 키를 등록해 놓고 각각의 뼈대가 영향을 미치는 정점에 가중치와 인덱스만 적절하게 세팅되어 있으면 모든것이 DrawPrimitive() 호출 한번으로 끝나는 것이다
오
연산수식은 다음과 같다
Vworld = Vlocal * M[index0] * W0 + Vlocal * M[index1] * W1 +
Vlocal * M[index2] * W2 + Vlocal * M[index3] * W3
(단, W3 = 1.0 - (W0 + W1 + W2)이며, Vlocal은 정점의 로컬좌표, M[index(n)]은 매트릭스 팔레트에 등록되어 있는 n번째 행렬이다)
정리하면, 하나의 정점이 여러개의 행렬로부터 영향을 받아 월드 좌표계로 변형된다는 것이다.
오
사용자 정의 정점이 다음과 같이 선언되어있다고 하자.
struct MYVERTEX
{
float x,y,z;
float w1, w2, w3;
DWORD index;
DWORD diffuse;
};
#define D3DFVF_MYVERTEX(D3DFVF_XYZB4|D3DFVF_LASTBETA_UBYTE4|D3DFVF_DIFFUSE)
W0는 DWORD index의 가장 마지막 바이트 값이 매트릭스 팔레트의 인덱스 번호가 된다.
즉, DWORD 타입인 index는 4개의 바이트로 구성되어 있는데, 이를 최상위 바이트부터 순서대로 배열하면 다음과 같은 모양이 될 것이다.
b3, b2, b1, b0
자
구현해보자
구현
이렇게 만들어진 매트릭스 팔레트는 D3D의 API를 통해 구현될 수 있다.
문제는 API를 통해 매트릭스 팔레트를 구현할 경우, 하드웨어 가속 지원이 불확실하다는 것이다. ATI의 Radeon계열에서는 매트릭스 팔레트를 37개 지원하는데 지포스는 지원하지 않아 하지만 요즘에는 지원하지 않을까? (아직 확인 안되었습니다.)
#include <d3d9.h>
#include <d3dx9.h>
LPDIRECT3D9 g_pD3D = NULL;
LPDIRECT3DDEVICE9 g_pd3dDevice = NULL;
LPDIRECT3DVERTEXBUFFER9 g_pVB = NULL;
LPDIRECT3DINDEXBUFFER9 g_pIB = NULL;
LPDIRECT3DTEXTURE9 g_pTexture = NULL;
D3DXMATRIXA16 g_mat0; // 0번행렬
D3DXMATRIXA16 g_mat1; // 1번행렬
struct CUSTOMVERTEX
{
D3DXVECTOR3 position; // 정점의 변환된 좌표
FLOAT b[3]; // 가중치
DWORD index; // 가중치 인덱스
DWORD color; // 정점의 색깔
FLOAT tu, tv; // 텍스쳐 좌표
};
// D3DFVF_XYZB4 : 4개의 블렌드값
// D3DFVF_LASTBETA_UBYTE4 : 마지막 DWORD index값은 unsigned byte형 4개를 나타낸다
#define D3DFVF_CUSTOMVERTEX (D3DFVF_XYZB4|D3DFVF_LASTBETA_UBYTE4|D3DFVF_DIFFUSE|D3DFVF_TEX1)
struct MYINDEX
{
WORD _0, _1, _2; // 일반적으로 인덱스는 16비트의 크기를 갖는다
};
HRESULT InitD3D( HWND hWnd )
{
/// 디바이스를 생성하기위한 D3D객체 생성
if( NULL == ( g_pD3D = Direct3DCreate9( D3D_SDK_VERSION ) ) )
return E_FAIL;
D3DPRESENT_PARAMETERS d3dpp; /// 디바이스 생성을 위한 구조체
ZeroMemory( &d3dpp, sizeof(d3dpp) ); /// 반드시 ZeroMemory()함수로 미리 구조체를 깨끗이 지워야 한다.
d3dpp.Windowed = TRUE; /// 창모드로 생성
d3dpp.SwapEffect = D3DSWAPEFFECT_DISCARD; /// 가장 효율적인 SWAP효과
d3dpp.BackBufferFormat = D3DFMT_UNKNOWN; /// 현재 바탕화면 모드에 맞춰서 후면버퍼를 생성
d3dpp.AutoDepthStencilFormat = D3DFMT_D16; /// 복잡한 오브젝트를 그릴것이라 Z버퍼가 필요하다
d3dpp.EnableAutoDepthStencil = TRUE;
/// 디바이스를 다음과 같은 설정으로 생성한다.
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_NONE);
/// Z버퍼를 켠다
g_pd3dDevice->SetRenderState(D3DRS_ZENABLE, TRUE);
/// 정점에 색깔값이 있으므로 광원기능을 끈다
g_pd3dDevice->SetRenderState(D3DRS_LIGHTING, FALSE);
/// 매트릭스 팔레트 사용
g_pd3dDevice->SetRenderState(D3DRS_INDEXEDVERTEXBLENDENABLE, TRUE);
/// 가중치는 4개 (가중치 3이 곧 4개야 오타아니야)
g_pd3dDevice->SetRenderState(D3DRS_VERTEXBLEND, D3DVBF_3WEIGHTS);
return S_OK;
}
HRESULT InitVB()
{
/// 정점 버퍼 생성
if(FAILED(g_pd3dDevice->CreateVertexBuffer(50*2*sizeof(CUSTOMVERTEX), 0, D3DFVF_CUSTOMVERTEX,
D3DPOOL_DEFAULT, &g_pVB, NULL)))
{
return E_FAIL;
}
CUSTOMVERTEX* pVertices;
if(FAILED(g_pVB->Lock(0, 0, (void**)&pVertices, 0)))
return E_FAIL;
for(DWORD i = 0; i < 50; i++){
FLOAT theta = (2*D3DX_PI*i)/(50-1);
pVertices[2*i+0].position = D3DXVECTOR3(sinf(theta), -1.0f, cosf(theta));
pVertices[2*i+0].b[0] = 1.0f;
pVertices[2*i+0].b[1] = 0.0f;
pVertices[2*i+0].b[2] = 0.0f;
pVertices[2*i+0].index = 0x0000; // 0번 가중치는 0번 행렬의 형향을 1.0만큼 받음
pVertices[2*i+0].color = 0xffffffff;
pVertices[2*i+0].tu = ((FLOAT)i)/(50-1);
pVertices[2*i+0].tv = 1.0f;
pVertices[2*i+1].position = D3DXVECTOR3(sinf(theta), 1.0f, cosf(theta));
pVertices[2*i+1].b[0] = 0.5f;
pVertices[2*i+1].b[1] = 0.5f;
pVertices[2*i+1].b[2] = 0.5f;
pVertices[2*i+1].index = 0x0001; // 0번 가중치는 0번 행렬의 형향을 1.0만큼 받음
pVertices[2*i+1].color = 0xff808080;
pVertices[2*i+1].tu = ((FLOAT)i)/(50-1);
pVertices[2*i+1].tv = 0.0f;
}
g_pVB->Unlock();
return S_OK;
}
HRESULT InitIB()
{
return S_OK;
}
void SetupCamera()
{
/// 월드행렬 설정
D3DXMATRIXA16 matWorld;
D3DXMatrixIdentity( &matWorld );
g_pd3dDevice->SetTransform( D3DTS_WORLD, &matWorld );
/// 뷰행렬을 설정
D3DXVECTOR3 vEyePt( 0.0f, 2.0f, -3.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 );
}
VOID Animate()
{
// 0번 행렬은 단위행렬
D3DXMatrixIdentity(&g_mat0);
// 0~2PI까지 (0~360도) 값을 변화시킴 Fixed Point 기법 사용
DWORD d = GetTickCount() % ((int)((D3DX_PI*2) * 1000));
// Y축 회전 행렬
D3DXMatrixRotationY(&g_mat1, d/1000.0f);
}
VOID CleanUp()
{
if(g_pVB != NULL)
g_pVB->Release();
if(g_pIB != NULL)
g_pIB->Release();
if(g_pd3dDevice != NULL)
g_pd3dDevice->Release();
if(g_pD3D != NULL)
g_pD3D->Release();
}
VOID DrawMesh(void)
{
g_pd3dDevice->SetStreamSource(0, g_pVB, 0, sizeof(CUSTOMVERTEX));
g_pd3dDevice->SetFVF(D3DFVF_CUSTOMVERTEX);
g_pd3dDevice->DrawPrimitive(D3DPT_TRIANGLESTRIP, 0, 2*50-2);
}
HRESULT InitGeometry()
{
if(FAILED(InitVB())) return E_FAIL;
if(FAILED(InitIB())) return E_FAIL;
if(FAILED(D3DXCreateTextureFromFile(g_pd3dDevice, "lake.bmp", &g_pTexture)))
{
return E_FAIL;
}
return S_OK;
}
VOID Render()
{
D3DXMATRIXA16 matWorld;
/// 후면버퍼와 Z버퍼를 지운다
g_pd3dDevice->Clear( 0, NULL, D3DCLEAR_TARGET|D3DCLEAR_ZBUFFER, D3DCOLOR_XRGB(255,255,255), 1.0f, 0 );
Animate();
/// 렌더링 시작
if( SUCCEEDED( g_pd3dDevice->BeginScene() ) )
{
// 0번 매트릭스 팔레트에 단위 행렬
g_pd3dDevice->SetTransform(D3DTS_WORLDMATRIX(0), &g_mat0);
// 1번 매트릭스 팔레트에 회전 행렬
g_pd3dDevice->SetTransform(D3DTS_WORLDMATRIX(1), &g_mat1);
g_pd3dDevice->SetTexture(0, g_pTexture);
g_pd3dDevice->SetTextureStageState(0, D3DTSS_COLOROP, D3DTOP_MODULATE);
g_pd3dDevice->SetTextureStageState(0, D3DTSS_COLORARG1, D3DTA_TEXTURE);
g_pd3dDevice->SetTextureStageState(0, D3DTSS_COLORARG2, D3DTA_DIFFUSE);
g_pd3dDevice->SetTextureStageState(0, D3DTSS_ALPHAOP, D3DTOP_DISABLE);
DrawMesh();
g_pd3dDevice->EndScene();
}
/// 후면버퍼를 보이는 화면으로!
g_pd3dDevice->Present( NULL, NULL, NULL, NULL );
}
LRESULT WINAPI MsgProc( HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam )
{
switch( msg )
{
case WM_DESTROY:
CleanUp();
PostQuitMessage( 0 );
return 0;
}
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", "Hierarchy",
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();
}
}
}
/// 등록된 클래스 소거
UnregisterClass( "D3D Tutorial", wc.hInstance );
return 0;
}
이 원통이 회전하면서 메시의 꺾어지는부분을 보간해준다
'Graphics > DirectX' 카테고리의 다른 글
Direct X - 카메라 (2) | 2014.08.04 |
---|---|
Direct X - 지형 처리 기법 (0) | 2014.07.31 |
Direct X - 애니메이션 (0) | 2014.07.29 |
Direct X - 계층구조 (3) | 2014.07.28 |
Direct X - 법선맵핑 (0) | 2014.07.25 |