스키닝

스키닝은 말그대로 피부를 붙이는 것이다,
3차원 메시는 관절과 관절 사이가 사람과 달리 끊김 현상이 발생할 수 있는데, 이를 막는 기법을 스키닝이라구한다.

'단일메시 + 가중치사용'의 방법이 가장 이상적인 형태로, A의 애니메이션 행렬 Ma와 B의 애니메이션 행렬 Mb가 각각 가중치 Wa와 Wb값에 의해 결합되어 최종값을 만드는 것을 알 수 있다.

 

메시를 애니메이션할 때 접합부위를 자연스럽게 이어붙이기 위한 기술들을 총칭하여 스키닝이라고 하며, 이러한 스키닝에는 다양한 기법들이 있으나, 일반적으로는 뼈대의 애니메이션 행렬이 메시의 정점에 얼마만큼의 가중치로 결합될 것인가 하는 것이 핵심요소라고 할 수 있다.






매트릭스 팔레트

매트릭스 팔레트 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
Posted by 긍정왕오킹