posts - 0, comments - 1, trackbacks - 0, articles - 25
  C++博客 :: 首页 :: 新随笔 :: 联系 :: 聚合  :: 管理

애니메이션, 스키닝

Posted on 2009-10-08 14:27 cngamedev 阅读(899) 评论(0)  编辑 收藏 引用 所属分类: DirectX


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

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

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

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

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

      (1)                                              (2)

      ◆       ◇       ◇       ◇       ◇       ◆  

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

 

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

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

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

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

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

 

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

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

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

 

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

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

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

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

 

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

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

  

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

 

 - 사원수 관련 함수

 D3DXQuaternionSlerp()                     /// 회전값의 구면선형보간
 D3DXMatrixRotationQuaternion()        /// 사원수를 회전행렬값으로 변환

 

 소스를 봐보면..

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도)       // 마지막 프레임(회전값)
}

 

float Linear( float v0, float v1, float t )  /// 선형보간(Linear Interpolation) 함수
{
 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줄로 바꿀수 있다. //( 지난달에 했던 Lerp 함수.)
// 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;

 D3DXMatrixRotationZ( &g_matRChild, GetTickCount()/500.0f );  /// 자식메시의 Z축 회전행렬
 D3DXMatrixTranslation( &g_matTMChild, 3, 3, 3 );  

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

렌더함수 전체부분..

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

 

* 스키닝(skinning)

  피부를 붙인다. 3차원 메시는 관절과 관절 사이가 끊김 현상이 발생하기 때문에

  이를 보완하는 기법이다. 마치 사람의 뼈 위에 피부가 있어서 관절부위가 보이지 않는 것처럼..

 

  스키닝에는 다양한 기법들이 있다고하나.. 핵심은.. 뼈대(bone)의 애니메이션 행렬이

  메시의 정점에 얼마만큼의 가중치(weight)로 결합될 것인가 하는것이다.

  암튼. 버텍스가 본에 영향받는다.

 일반적인 스키닝 버텍스 구조체..

struct SkinedVertex

{

    D3DXVECTOR3   pos;                   // 위치.

    float                  weight[3];           // 본(bone)에 의한 가중치.

    unsigned char    BI[4];                 // 본(bone) 인덱스

    D3DXVECTOR3   Normal;               // 노말 좌표.

    D3DXVECTOR2   Tex;                    // 텍스쳐 좌표.

}

 

- 매트릭스 팔레트(Matrix Palette)

   기본적인 개념은 정점이 최대 4개까지 가중치를 가질 수 있는데, 이들의 가중치에 따라서

   다른 행렬을 곱해준다는 것이다. 이때 곱해주는 행렬을 최대 256개까지 매트릭스 팔레트라는

   영역에 셋팅해 놓으면 나머지는 D3D가 알아서 DrawPrimitive()함수 호출시에 처리해준다.  

   최대 256개까지 지원하는 매트릭스 팔레트에 뼈대의 애니메이션 키를 등록해 놓고 각각에

   뼈대가 영향을 미치는 정점에 가중치와 인덱스만 적절하게 셋팅되어있으면 모든것이

   DrawPrimitive() 호출 한번으로 끝나는것이다.

 

ex) 버텍스 구조.

ex) Vl[100] =  |  x  |   y  |  z  |  // 위치.

                    | 0.6 | 0.2 | 0.1 |  // 4번째 가중치는 1.0 - ( 0.6 + 0.2 + 0.1 )

                    | 5 | 6 | 10 | 11 |  //5번 본(bone)은 0.6의 가중치, 6번은0.2, 10번은0.1, 11번은0.1

                    | Nx  | Ny  | Nz |  // 노말

                    | Tex x  | Tex y |  // 텍스쳐

 

ex) 50개의 본이 매트릭스 팔레트에 셋팅되었다고 하고...

ex)  Vw[100] = Vl[100] * MP[5] * 0.6 

                       + Vl[100] * MP[6] * 0.2

                       + Vl[100] * MP[10] * 0.1

                       + Vl[100] * MP[11] * 0.1

 

  ※  ( Vl = 로컬  , Vw = 월드  )

 

* 메트릭스 팔레트의 지원.

  ATI 는 메트릭스 팔레트를 최대 37개까지 지원한다.

  nVidia 의 GeForce는 전혀 지원하지 않는다.

  그렇기 때문에 매트릭스 팔레트를 비롯한 모든 스키닝 기법은 GPU 프로그래밍을 통해

  직접 구현한다.

 

* 버텍스 쉐이더는 CPU에서도 가능하지만 GPU에서 하면 더 나은 속도향상을 가져온다.

  장치 생성시 MIXED 설정으로 한다.

 

* 픽셀 쉐이더는 GPU에서만 지원된다.

 

* 책의 예제소스를 보면..

- Init 부분의 코드조각..

/// 컬링기능을 끈다.
    g_pd3dDevice->SetRenderState( D3DRS_CULLMODE, D3DCULL_NONE );

    /// Z버퍼기능을 켠다.
    g_pd3dDevice->SetRenderState( D3DRS_ZENABLE, TRUE );

    /// 정점에 색깔값이 있으므로, 광원기능을 끈다.
    g_pd3dDevice->SetRenderState( D3DRS_LIGHTING, FALSE );

 /// matrix palette 사용
    g_pd3dDevice->SetRenderState( D3DRS_INDEXEDVERTEXBLENDENABLE, TRUE );

 /// blend weight는 4개(오타 아님! 4개!)
    g_pd3dDevice->SetRenderState( D3DRS_VERTEXBLEND, D3DVBF_3WEIGHTS );

 

- 버텍스 초기화 부분...

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.0f;
        pVertices[2*i+1].index  = 0x0001;   /// 0번 가중치는 1번 행렬의 영향을 0.5만큼 받음
        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;
}

 

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

 

<< 파일 설명 >>

08.KeyFrameAni.zip        :     키프레임 애니메이션 예제소스 (책)

09.Skinning.zip              :     스키닝 예제소스(책)


只有注册用户登录后才能发表评论。
网站导航: 博客园   IT新闻   BlogJava   博问   Chat2DB   管理