XML Exporter: 소프트웨어 스킨 애니메이션

스킨 애니메이션을 적용하는 방법은 소프트웨어적인 구현, 하드웨어 팔레트를 이용한 구현, 셰이더를 이용한 구현 하는 3가지 방법이 있다.

캐릭터의 애니메이션에 PHYSIQUE와 SKIN을 소프트웨어적으로 구현하는 방법을 먼저 알아본다.
소프트웨어 적인 방법은 가중치에 의해 변화된 정점 뿐만 아니라 원래의 정점정보도 들고 있어야 한다.
아래의 실행 결과 데이터는 김용준님의 3D 게임 프로그래밍에 있는 데이터이다. 샘플에는 포함 되어 있지 않다.

< Xml정보를 메시, 인덱스, 본 참조 인덱스, 가중치 데이터로 변환 >

XmlMesh에서 Xml로 로딩후 정점 정보, 정점 인덱스 정보, 노드 참조 인덱스, 가중치를 원하는 데이터로 변환한다.

void XmlMesh::ConvertMeshInfo()

{

    SetUseDataChild( false );

 

    //정점 정보 읽기

    XmlNode* pVertexXmlNode = FindFirstChild( "Vertex" );

    if( pVertexXmlNode == NULL )

        return;

    XmlMesh* pXmlMesh = (XmlMesh*)pVertexXmlNode;

    pXmlMesh->FetchVertex( m_vertexArr );

 

    //정점의 인덱스 리스트 읽기

    XmlNode* pFaceXmlNode = FindFirstChild( "Face" );

    if( pFaceXmlNode  == NULL )

        return;

    pXmlMesh = (XmlMesh*)pFaceXmlNode;

    pXmlMesh->FetchFace( m_indexArr );

 

    //본의 리깅 인덱스 읽기

    XmlNode* pBoneIndexXmlNode = FindFirstChild( "boneIndex" );

    if( pBoneIndexXmlNode  == NULL )

        return;

    pXmlMesh = (XmlMesh*)pBoneIndexXmlNode;

    pXmlMesh->FetchBoneIndex( m_vertexArr );

 

    //본의 리깅 Weight 읽기

    XmlNode* pWeightXmlNode = FindFirstChild( "weight" );

    if( pWeightXmlNode  == NULL )

        return;

    pXmlMesh = (XmlMesh*)pWeightXmlNode;

    pXmlMesh->FetchBoneWeight( m_vertexArr );

}

< 메시 정보들을 ESoftwareBoneMesh 정보로 변환 >

ENode* XmlMesh::OnXmlToENode( ENode* pParent )

{

    ENode* pNode = NULL;

 

    if( m_vertexArr.size() > 0 )

    {

        //EStaticMesh* pMesh = new EStaticMesh( g_d3d_Device );

        ESoftwareBoneMesh* pMesh = new ESoftwareBoneMesh( g_d3d_Device );

        pMesh->CreateVertexBuffer( this );

        pNode = pMesh;

    }

    else

        pNode = new ENode( g_d3d_Device );

 

    if( pParent == NULL )

        pNode->CreateAnimationHeader( this );

 

    pNode->SetClassType( this->GetName()->c_str() );

    pNode->SetName( this->GetAttribute("name")->c_str() );

    pNode->SetMatrix( m_matLocal );

    pNode->SetID( m_id );

    ENode::m_indexMap.insert( std::make_pair( m_id, pNode ) );

 

    if( m_aniArray )

        pNode->CreateAnimation( this );

    return pNode;

}

< SoftwareBoneVertex OnFrameMove, OnRender에서 애니메이션 행렬을 월드 행렬로>

ENode, ESoftwareBoneMesh 클래스의 OnFrameMove에서 월드 행렬에 애니메이션 행렬을 대입한다.

void ESoftwareBoneMesh::OnFrameMove()

{

    //소프트웨어적으로 스키닝 애니메이션 구현

    if( this->m_nAniSize > 0 )

    {

        int nFrame = GameTime::GetFrame();   

        m_matWorld = m_aniArray[nFrame];   

    }

    else

        D3DXMatrixIdentity(&m_matWorld);

}

< 스킨 데이터를 소프트웨어적으로 렌더링 >

ApplyVertexWeight()에서 정점 정보들을 가중치에 의해서 변환해준다. 뒤에서 설명하겟다.

DIRECT3DDEVICE9::DrawIndexedPrimitiveUP()로 렌더링한다.

int ESoftwareBoneMesh::OnRender()

{

#if  1   

    //모델 축소

    D3DXMATRIXA16 scaleMat;

    D3DXMatrixIdentity( &scaleMat );

    D3DXMatrixScaling( &scaleMat, 0.4f, 0.4f, 0.4f );

 

    ApplyVertexWeight();

    m_pDevice->SetTransform( D3DTS_WORLD, &(m_matWorld * scaleMat) );

#else

    ApplyVertexWeight();

    m_pDevice->SetTransform( D3DTS_WORLD, &m_matWorld );

#endif

    m_pDevice->SetRenderState( D3DRS_FILLMODE, D3DFILL_WIREFRAME);

    m_pDevice->SetFVF( SoftwareBoneVertex::FVF );

    m_pDevice->DrawIndexedPrimitiveUP( D3DPT_TRIANGLELIST,

     0, m_nVertex, m_nTriangle, m_pIndex, D3DFMT_INDEX16, m_pVerts, sizeof(SoftwareBoneVertex) );

    return 1;

}

< 가중치에 의한 정점 구하기 >

참조하는 노드의 월드 행렬과 가중치를 본 갯수 만큼 구한다.
 mat += ( worldMat * fw )
정점 좌표와 mat를 D3DXVec3TransformCoord()에 넣어서 최종 월드 좌표를 구한다.

void ESoftwareBoneMesh::ApplyVertexWeight()

{

    if( (int)m_vertexArr.size()  < 3 )

        return;

 

    int nCount = (int)m_vertexArr.size();

    for( int i = 0; i < nCount; ++i )

    {

        VertexInfo& vertexInfo = m_vertexArr[i];

        int boneCount = (int)vertexInfo.boneIndex.size();

 

        D3DXMATRIXA16    mat;

        memset(&mat, 0, sizeof(D3DXMATRIXA16));

 

        for( int j = 0; j < boneCount; ++j )

        {

            float fw = vertexInfo.boneWeight[j];

            int   id = vertexInfo.boneIndex[j];

            IndexENodeMap::iterator iter = m_indexMap.find( id );

            if( iter == m_indexMap.end() )

            {

                assert( 0 && "인덱스가 없으면 에러이다" );

            }

            ENode* eNode = iter->second;

            const D3DXMATRIXA16& worldMat = eNode->GetWorldMatrix();

 

            mat += ( worldMat * fw );

        }

        D3DXVECTOR3 v;

        v.x = vertexInfo.p.x;

        v.y = vertexInfo.p.y;

        v.z = vertexInfo.p.z;

 

        D3DXVec3TransformCoord(&m_pVerts[i].p, &v, &mat);

    }

}

프로젝트 : viewer_softwareSkin.zip