계층구조

중요하다

일반적으로 3D프로그램을 제작하다보면, 인간이나 동물, 기계등의 애니메이션을 할 때가 대부분이다. 또한, 이러한 물체들의 공통점은 대부분이 관절체로 이루어져 있다는 것이다.
이러한 관절체의 가장 큰 특징은 부모-자식 관계를 갖는 계층구조로 이루어져 있다는 것이다.
이런 계층 구조를 어떻게 구현하는지 원리를 알아보도록허자




트리
일반적으로 계층구조는 자료구조중에서 트리로 구현 가능하다.
트리는 3D그래픽스에서 가장 애용되는 자료구조로 건물내부의 지형처리에 주로 애용되는 BSP트리(Binary Surface Partition tree, 2진트리;; 에이 이진트리아니냐), 거대 지형 처리에 사용되는 쿼드트리(4진트리), 거대 공간 처리에 사용되는 옥트리(8진트리)등이 있다.


계층구조 표현방법
계층구조는 간단하게 생각하면 부모-자식 관계를 구현하는거야,
부모-자식관계를 3D로 구현하는건 결국 3D적인 표현수단을 사용하는 것이고, 우리가 알고 있는 3D적인 표현수단에는 행렬이 있다!

먼저 렌더링 파이프라인을 기억해보면, 정점의 변환은 다음과 같다는 것을 상기할 수 있을 것이다.

Vworld = Vlocal * Mtransform
여기서, Mtransform을 간략히 TM(Transform Matrix)이라고 표기하면,,

Vworld = Vlocal * TM
여기서, 정점이 계층구조로 이루어져 있다면 부모의 행렬을 한번 더 곱해주면 된다.

Vworld = Vlocal * TMchild * TMparent
부모에 해당하는 행렬이 여러개라면 해당하는 개수만큼 곱해주면 된다.
만약 부모에 해당하는 행렬이 n개라면 다음과 같이 처리한다.

Vworld = Vlocal * TMchild * TMparent(n) * TMparent(n-1) .... * TMparent(1)
수식을 잘 살펴보면 부모 중에서 최상위에 속하는 행렬이 가장 나중에 곱해지며, 자식의 직계 부모가 가장 먼저 곱해진다는 사실을 알 수 있다.


계층구조 구현 예


예를들면 이런거지

이런 구조일때, 오른쪽발 메시가 그려지기 위해서는 다음과 같은 과정을 거쳐야만 한다.

Vw = V오른발 * TM오른발 * TM오른다리 * TM오른허벅지 * TM엉덩이 * TM허리


이걸 프로그램으로 구현한다면 아래와 같은 의사코드(슈도코드^^)가 될 것이다.


D3DXMATRIX TM

TM = TM오른발 * TM오른다리 * TM오른허벅지 * TM엉덩이 * TM허리

m_pd3dDevice->SetTransform(D3DTS_WORLD, &TM);

m_od3dDevice->DrawPrimitive(m_pVB[X3DS_FOOT_R]);



계층구조 실제구현

복잡한 관절체 애니메이션의 첫걸음은 의외로 간단한 상자돌리기에서 시작한다.

일반적으로는 지구와 달과의 관계나 태양계 전체를 시뮬레이션 하는 것으로 계층구조 구현을 공부하는 경우가 많지만, 일단 2개의 상자가지고 해보자.


1. 부모메시는 Y축 회전을 한다.

2. 자식메시는 Z축 회전을 한다.

3. 자식메시는 부모로부터 일정거리(3,3,3) 떨어져있다.


개념도를보자

부모메시 : 기본변환 - TMparent

                Y축 회전 - Rparent

자식메시 : 기본변환 - TMchild

                Z축 회전 - Rchild

               부모행렬 - Mparent


부모 최종행렬 : Mparent = Rparent * TMparent

자식 최종행렬 : Mchild = Rchild * TMchild * Mparent

                                 = Rchild * TMchild * (Rparent * TMparent)




#include <d3d9.h>

#include <d3dx9.h>


LPDIRECT3D9 g_pD3D = NULL;

LPDIRECT3DDEVICE9 g_pd3dDevice = NULL;

LPDIRECT3DVERTEXBUFFER9 g_pVB = NULL;

LPDIRECT3DINDEXBUFFER9 g_pIB = NULL;


D3DXMATRIXA16 g_matTMParent; //부모의 TM

D3DXMATRIXA16 g_matRParent; //부모의 회전행렬


D3DXMATRIXA16 g_matTMChild; //자식의 TM

D3DXMATRIXA16 g_matRChild; //자식의 회전행렬


struct CUSTOMVERTEX

{

FLOAT x, y, z;

DWORD color;

};


#define D3DFVF_CUSTOMVERTEX (D3DFVF_XYZ|D3DFVF_DIFFUSE)


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_CCW);

/// Z버퍼를 켠다

g_pd3dDevice->SetRenderState(D3DRS_ZENABLE, TRUE);


/// 정점에 색깔값이 없으므로 광원기능을 끈다

g_pd3dDevice->SetRenderState(D3DRS_LIGHTING, FALSE);


    return S_OK;

}


HRESULT InitVB()

{

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

};


if(FAILED(g_pd3dDevice->CreateVertexBuffer(8*sizeof(CUSTOMVERTEX), 0, D3DFVF_CUSTOMVERTEX,

D3DPOOL_DEFAULT, &g_pVB, NULL)))

{

return E_FAIL;

}


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()

{

// 상자를 렌더링하기위해 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 SetupCamera()

{

D3DXMATRIXA16 matWorld;

D3DXMatrixIdentity(&matWorld);

g_pd3dDevice->SetTransform(D3DTS_WORLD, &matWorld);


D3DXVECTOR3 vEyePt(0.0f, 10.0f, -10.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()

{

// 부모메시는 원점에 있으므로, TM은 단위행렬

D3DXMatrixIdentity(&g_matTMParent);

// 부모메시의 Y축 회전행렬

D3DXMatrixRotationY(&g_matRParent, GetTickCount()/500.0f);


// 자식 메시의 Z축 회전행렬

D3DXMatrixRotationZ(&g_matRChild, GetTickCount()/500.0f);

// 자식 메시는 원점으로부터 (3,3,3)거리에 있음

D3DXMatrixTranslation(&g_matTMChild, 3,3,3);

}


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(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);

}


HRESULT InitGeometry()

{

if(FAILED(InitVB())) return E_FAIL;

if(FAILED(InitIB())) return E_FAIL;


return S_OK;

}


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 * g_matRParent * g_matTMParent;

//matWorld = g_matRChild * g_matTMChild * matWorld; // 위와 같은 결과


DrawMesh(&matWorld); // 자식상자 그리기

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 - 스키닝  (0) 2014.07.30
Direct X - 애니메이션  (0) 2014.07.29
Direct X - 법선맵핑  (0) 2014.07.25
Direct 3D - 빌보드  (0) 2014.07.24
Direct 3D - 라이트 맵핑  (0) 2014.07.23
Posted by 긍정왕오킹