본문 바로가기

프로그래밍/DirectX

[DirectX] Direct3D SkinedMesh 소스분석 정리


D3DXLoadMeshHierarchyFromX

• 원래 상속받아서 쓰는 인터페이스임

• 즉, 원형만 만들어 놓은 추상 클래스임

• 프로그래머가 상속받아서 이용

• 스키닝 방법이나 기타 모든 것에 자유를 주면서, 일정한 형식을 갖추도록 한 것임

• 그 목적은 ID3DXAnimationController라는 인터페이스와 데이터가 묶이도록 하기 위해서임

• 즉, CAllocateHierarchy 클래스를 제대로 만들면 애니메이션을 다 구현했다는 것을 의미

• *.X 파일을 읽어오는 D3DXLoadMeshHierarchyFromX()이 클래스로 만든 객체의 포인터를 받아서 실제로 프레임 정보와 메시 컨테이너를 만들때 CAllocateHierarchy에 있는 함수를 사용하기 때문에 사용

• 지울 때도 CAllocateHierarchy에 있는 DestroyFrame() 함수와 DestroyMeshContainer()함수를 사용함

 

AllocateName()

 class CAllocateHierarchy: public ID3DXAllocateHierarchy
//----------------------------------------------------------------------

// Name: AllocateName() - Global
// Desc: Allocates memory for a string to hold the name of a frame or mesh
//----------------------------------------------------------------------
HRESULT AllocateName( LPCTSTR Name, LPTSTR *pNewName )
{
UINT cbLength;
if (Name != NULL)
{
cbLength = lstrlen(Name) + 1;
*pNewName = new TCHAR[cbLength];
if (*pNewName == NULL)
return E_OUTOFMEMORY;
memcpy(*pNewName, Name, cbLength*sizeof(TCHAR));
}
else
{
*pNewName = NULL;
}
return S_OK;
}

• 뼈(프레임)에 이름을 넣어주는 함수임
• 3DsMax 같은데서 디자인 할 때 뼈에 이름을 심어주는데, 그 이름을 인자로 받아와서 새로 들어간

본 구조체 안의 이름에 동적 메모리를 할당해서 이름을 넣어주는 역할

 

CAllocateHierarchy

//-------------------------------------------------------------------

// Name: class CAllocateHierarchy

// Desc: Custom version of ID3DXAllocateHierarchy with custom methods to create

// frames and meshcontainers.

//-------------------------------------------------------------------

class CAllocateHierarchy: public ID3DXAllocateHierarchy

{

public:

STDMETHOD(CreateFrame)(THIS_ LPCTSTR Name, LPD3DXFRAME *ppNewFrame);

STDMETHOD(CreateMeshContainer)(THIS_ LPCTSTR Name, LPD3DXMESHDATA pMeshData,

LPD3DXMATERIAL pMaterials, LPD3DXEFFECTINSTANCE pEffectInstances, DWORD

NumMaterials,

DWORD *pAdjacency, LPD3DXSKININFO pSkinInfo,

LPD3DXMESHCONTAINER *ppNewMeshContainer);

STDMETHOD(DestroyFrame)(THIS_ LPD3DXFRAME pFrameToFree);

STDMETHOD(DestroyMeshContainer)(THIS_ LPD3DXMESHCONTAINER pMeshContainerBase);

CAllocateHierarchy( CPROD3DANIMESH* m_pApp) :m_pObject(pObject) {}

public:

CPROD3DANIMESH* m_pApp;

};

• 이 클래스가 생성될 때 애니메이션 객체의 포인터를 넘겨줘서... 그 포인터를 사용해서 애니메이션 클래스에 접근 을 함
//----------------------------------------------------------------------

// Name: CAllocateHierarchy::CreateFrame()

// Desc: making new frame by got name and pointing new frame pointer

//----------------------------------------------------------------------

HRESULT CAllocateHierarchy::CreateFrame( LPCTSTR Name, LPD3DXFRAME *ppNewFrame )

{

HRESULT hr = S_OK;

D3DXFRAME_DERIVED *pFrame;

*ppNewFrame = NULL;

pFrame = new D3DXFRAME_DERIVED;

if( pFrame == NULL )

{

hr = E_OUTOFMEMORY;

delete pFrame;

return hr;

}

hr = AllocateName( Name, &pFrame->Name ); // 위에 나왔던 AllocateName() 전역함수.

if( FAILED(hr) )

{

delete pFrame;

return hr;

}

// Initialize other data member of the frame

D3DXMatrixIdentity( &pFrame->CombinedTransformationMatrix );

D3DXMatrixIdentity( &pFrame->TransformationMatrix );

pFrame->pMeshContainer = NULL;

pFrame->pFrameFirstChild = NULL;

pFrame->pFrameSibling = NULL;

*ppNewFrame = pFrame;

pFrame = NULL;

return S_OK;

}

 새로운 프레임 - 뼈를 만들어 주는 함수 : 인자로 이름을 받고, 프레임 구조체 형의 더블 포인터를 받아서 새로

메모리를 할당해서프레임을 생성해주고 포인터를 연결해 줌. 그 프레임 안에 있는 각종 값들을 초기화 시킴.
프레임 전체를 트리구조로 꾸미기 위한 준비 작업 (뼈 전체가 트리 구조)

 

CombinedTransformationMatrix

//-----------------------------------------------------------

// Name: struct D3DXFRAME_DERIVED

// Desc: Structure derived from D3DXFRAME so we can add some app-specific

// info that will be stored with each frame

//-----------------------------------------------------------

struct D3DXFRAME_DERIVED: public D3DXFRAME

{

D3DXMATRIXA16 CombinedTransformationMatrix;

};

 CombinedTransformationMatrix : 누적된 행렬 정보
예: 이 프레임이 손 끝의 프레임이라고 한다면, 이 행렬 정보에는 어깨의 회전값,
팔의 회전값, 손목의 회전 값이 누적되어 들어가게 됨 (손 끝의 회전은 손목을 기준으로 돌아가기 때문)

• 이 구조체의 부모 클래스인 D3DXFRAME

D3DXFRAME

 

typedef struct _D3DXFRAME {

LPTSTR Name; // 프레임 이름

D3DXMATRIX TransformationMatrix; // 변환 행렬 정보

LPD3DXMESHCONTAINER pMeshContainer; // 메시 컨테이너 포인터

struct _D3DXFRAME *pFrameSibling; // 형제 프레임의 포인터

struct _D3DXFRAME *pFrameFirstChild; // 자식 프레임의 포인터

} D3DXFRAME, *LPD3DXFRAME;

트리 구조를 위한 형제, 자식의 포인터와 프레임 이름과.. 자신의 변환 행렬 정보..메시 컨테이너의 포인터가 있음
LPD3DXMESHCONTAINER : 메시를 담고 있는 컨테이너 구조체

 

D3DXMESHCONTAINER

typedef struct _D3DXMESHCONTAINER {

LPTSTR Name; // 메시의 이름

D3DXMESHDATA MeshData; // 메시 데이터 구조체

LPD3DXMATERIAL pMaterials; // 메시의 메터리얼 정보 구조체의 포인터

LPD3DXEFFECTINSTANCE pEffects; // EffectInstance 구조체의 포인터

DWORD NumMaterials; // 메터리얼 갯수

DWORD *pAdjacency; // 인접정보를 갖고 있는 DWORD형의 삼각형 하

// 나당 3개의 배열의 포인터.

LPD3DXSKININFO pSkinInfo; // 스킨 정보를 가지고 있는

// ID3DXSKININFO 인터페이스의 포인터

struct _D3DXMESHCONTAINER *pNextMeshContainer; // 링크드 리스트..

} D3DXMESHCONTAINER, *LPD3DXMESHCONTAINER;

 

typedef struct D3DXMESHDATA {

D3DXMESHDATATYPE Type; // 메시의 타입.

union

{

LPD3DXMESH pMesh; // 일반적인 메시의 포인터

LPD3DXMESH pPMesh; // 프로그레시브 메시의 포인터

LPD3DXPATCHMESH pPatchMesh; // 패치 메시(..?)의 포인터

}

} D3DXMESHDATA, *LPD3DXMESHDATA;

typedef enum D3DXMESHDATATYPE {

D3DXMESHTYPE_MESH = 0x001,

D3DXMESHTYPE_PMESH = 0x002,

D3DXMESHTYPE_PATCHMESH = 0x003,

D3DXEDT_FORCE_DWORD = 0x7fffffff

} D3DXMESHDATATYPE;

 

D3DXMESHCONTAINER_DERIVED

//-------------------------------------------------------------------

//Name: struct D3DXMESHCONTAINER_DERIVED

// Desc: Structure derived from D3DXMESHCONTAINER so we can add some app-specific

// info that will be stored with each mesh

//-------------------------------------------------------------------

struct D3DXMESHCONTAINER_DERIVED: public D3DXMESHCONTAINER

{

// array of textures, entries are NULL if no texture specified

  1. LPDIRECT3DTEXTURE9* ppTextures;

 

// SkinMesh info

LPD3DXMESH pOrigMesh; // 원래 메시의 임시 저장을 위한..

LPD3DXATTRIBUTERANGE pAttributeTable; // 메시의 속성 테이블 구조체.

DWORD NumAttributeGroups; // 속성 갯수.

DWORD NumInfl; // 이 디바이스가 최대한 블랜딩 할 수 있는 매트릭스 갯수

LPD3DXBUFFER pBoneCombinationBuf;// 본 콤비네이션 버퍼

D3DXMATRIX** ppBoneMatrixPtrs; // 프레임에 저장되어있는 변환매트릭스들의 배열의 포인.

D3DXMATRIX* pBoneOffsetMatrices;// 본의 위치를 담아둘 매트릭스 배열의 포인터.

DWORD NumPaletteEntries; // 행렬 팔렛트 엔트리.. NonIndexed에선 사용되지 않음.

bool UseSoftwareVP; // 소프트웨어 버텍스 프로세싱 사용?

DWORD iAttributeSW; // used to denote the split between SW and HW if necessary

for non-indexed skinning

// , Mixed 에서 SWHW 버텍스 프로세싱을 사용할 때

// 나누기 위한 정보를 저장하기 위한 변수.

};


* 스킨드 애니메이션을 위해서 좀 더 필요한 정보를 저장하기 위해서 메시컨테이너를
  상속 받아 확장 시킨 구조체
* 속성 : 메시가 나눠지는 것
* 텍스쳐를 기준으로 나눠짐 : 매핑 소스가 1개면 메시가 한 덩어리
* tiny.x는 매핑 소스가 1개 이므로 속성 테이블의 갯수도 1개
 

CreateMeshContainer

HRESULT CAllocateHierarchy::CreateMeshContainer( LPCTSTR Name,
LPD3DXMESHDATA pMeshData,
LPD3DXMATERIAL pMaterials,
LPD3DXEFFECTINSTANCE pEffectInstances, DWORD NumMaterials,
DWORD *pAdjacency, LPD3DXSKININFO pSkinInfo,
LPD3DXMESHCONTAINER *ppNewMeshContainer )

• 메시 컨테이너를 만들어 주는 함수
• 받는 인자들은 모두 D3DXMESHCONTAINER 구조체 속에 모두 있는 것들임

 

HRESULT hr;

D3DXMESHCONTAINER_DERIVED *pMeshContainer = NULL;

// 임시로 사용할 메시 컨테이너의 포인터

UINT NumFaces; // 삼각형 면의 갯수

UINT iMaterial; // 메터리얼 갯수

UINT iBone, cBones; // 본의 갯수, 본 카운트 임시 변수

LPDIRECT3DDEVICE9 pd3dDevice = NULL; // 임시로 받아둘 디바이스의 포인터

LPD3DXMESH pMesh = NULL; // 임시로 사용할 메시

 

*ppNewMeshContainer = NULL;

// this sample does not handle patch mesh, so fail when one is found

if( pMeshData->Type != D3DXMESHTYPE_MESH )

{

// 종료 루틴

SAFE_RELEASE(pd3dDevice);

// call Destroy function to properly clean up the memory allocated

if (pMeshContainer != NULL)

{

DestroyMeshContainer(pMeshContainer);

}

return E_FAIL;

}

• 더블 포인터 형으로 받은 메시 컨테이너의 포인터를 초기화 시키는 작업
• 메시 타입이 D3DXMESHTYPE_MESH (일반적인 메시)가 아니라면 함수를 끝내버리는 일을 함

 

// get the pMesh interface pointer out of the mesh data structure

pMesh = pMeshData->pMesh;

// this sample does not FVF compatible meshes, so fail when one is found

if( pMesh->GetFVF() == 0 )

{

// 종료 루틴 생략

return E_FAIL;

}

// allocate the overloaded structure to return as a D3DXMESHCONTAINER

pMeshContainer = new D3DXMESHCONTAINER_DERIVED;

if( pMeshContainer == NULL )

{

// 종료 루틴 생략

return E_OUTOFMEMORY;

}

memset( pMeshContainer, 0, sizeof(D3DXMESHCONTAINER_DERIVED) );

// make sure and copy the name. All memory as input belongs to caller, interfaces can be addref'd

though

hr = AllocateName( Name, &pMeshContainer->Name );

if( FAILED(hr) )

{

// 종료 루틴 생략

return E_FAIL;

}

 그리고 나서, 임시로 만들었던 pMesh 메시 포인터에 인자로 넘어온 메시 데이터 안에 있는  메시의 포인터를 그대로 물려줌
 FVF가 제대로 있는지 검사를하고 메시 컨테이너를 하나 동적으로 생성
• 생성 못하면 그냥 종료
만들어진 구조체를 초기화 시킨 후 AllocateName() 전역함수로 생성된 메시 컨테이너 안에 인자로 받았던 이름을 달아줌

pMesh->GetDevice( &pd3dDevice );

NumFaces = pMesh->GetNumFaces();

// if no normals are in the mesh, add them

if( !(pMesh->GetFVF() & D3DFVF_NORMAL) )

{

pMeshContainer->MeshData.Type = D3DXMESHTYPE_MESH;

// clone the mesh to make room for the normals;

hr = pMesh->CloneMeshFVF( pMesh->GetOptions(), pMesh->GetFVF() | D3DFVF_NORMAL,

pd3dDevice, &pMeshContainer->MeshData.pMesh );

if( FAILED(hr) )

{

// 종료 루틴 생략

return E_FAIL;

}

// get the new pMesh pointer back out of the mesh container use

// NOTE: we do not release pMesh because we don't have a reference to it yet

pMesh = pMeshContainer->MeshData.pMesh;

// now generate the normal for the pmesh

D3DXComputeNormals( pMesh, NULL );

}

else // if no normals, just add a reference to the mesh for mesh container

{

pMeshContainer->MeshData.pMesh = pMesh;

pMeshContainer->MeshData.Type = D3DXMESHTYPE_MESH;

// Increases the interface's reference count by 1.

pMesh->AddRef();

}

// allocate memory to contain the meterial information. This sample uses

// the D3D9 materials and texture names instead of the EffectInstance style materials

pMeshContainer->NumMaterials = max( 1, NumMaterials );

pMeshContainer->pMaterials = new D3DXMATERIAL[ pMeshContainer->NumMaterials ];

pMeshContainer->ppTextures = new LPDIRECT3DTEXTURE9[ pMeshContainer->NumMaterials ];

pMeshContainer->pAdjacency = new DWORD[ NumFaces*3 ];

if( (pMeshContainer->pAdjacency == NULL) || (pMeshContainer->pMaterials == NULL) )

{

// 종료 루틴 생략.

return E_OUTOFMEMORY;

}

 • pMesh로 인터페이스에 접근을 해서 디바이스를 하나 얻어옴
• 폴리곤 수를 따로 저장
• FVF를 체크하는데, 만약에 NORMAL값이 없는 메시라면 CloneMeshFVF 함수를 사용해서

   빛이 들어간 FVF로 설정된 메시를 새로 복사해서 만들어 냄
• 새로 생성되었던 지금 만들고 있는 메시컨테이너 안의 즉, pMeshContainer->MeshData.pMesh 안에 새로 클론된 메시를 집어 넣음

 

 pMesh->AddRef()

• IUnknown 이라는 클래스는
– AddRef()
– QueryInterface()
– Release() 이 세가지를 멤버 함수로 가지고 있음
– 따라서 다이렉트 X의 모든 인터페이스는 저 3가지 함수를 공통으로 가지고 있습니다.
– Release()는 자주 쓰는 것
– Release()와 반대되는 개념이 AddRef()
– 내부적으로 레퍼런스 카운트라고 있는데
– 레퍼런스 카운트가 0이 되어야만 비로소 진짜 메모리에서 릴리즈가 됨
– Release()는 레퍼런스 카운트를 하나 줄여줌

 

// allocate memory to contain the meterial information. This sample uses

// the D3D9 materials and texture names instead of the EffectInstance style materials

pMeshContainer->NumMaterials = max( 1, NumMaterials );

pMeshContainer->pMaterials = new D3DXMATERIAL[ pMeshContainer->NumMaterials ];

pMeshContainer->ppTextures = new LPDIRECT3DTEXTURE9[ pMeshContainer->NumMaterials ];

pMeshContainer->pAdjacency = new DWORD[ NumFaces*3 ];

if( (pMeshContainer->pAdjacency == NULL) || (pMeshContainer->pMaterials == NULL) )

{

// 종료 루틴 생략.

return E_OUTOFMEMORY;

}

memcpy( pMeshContainer->pAdjacency, pAdjacency, sizeof(DWORD)*NumFaces*3 );

memset( pMeshContainer->ppTextures, 0, sizeof(LPDIRECT3DTEXTURE9)*pMeshContainer->NumMaterials );

// if materials provided, copy them

if( NumMaterials > 0 )

{

memcpy( pMeshContainer->pMaterials, pMaterials, sizeof(D3DXMATERIAL)*NumMaterials );

for( iMaterial = 0; iMaterial < NumMaterials; iMaterial++ )

{

// texture loading..

if( pMeshContainer->pMaterials[ iMaterial ].pTextureFilename != NULL )

{

TCHAR strTexturePath[ MAX_PATH ] = "";

DXUtil_FindMediaFileCb( strTexturePath, sizeof(strTexturePath),

pMeshContainer->pMaterials[ iMaterial ].pTextureFilename );

if( FAILED( D3DXCreateTextureFromFile( pd3dDevice, strTexturePath,

&pMeshContainer->ppTextures[ iMaterial ] ) ) )

pMeshContainer->ppTextures[ iMaterial ] = NULL;

// don't remember a pointer into the dynamic memory, just forget the name after loading

pMeshContainer->pMaterials[ iMaterial ].pTextureFilename = NULL;

}

}

}

else // if no materials provided, use a default one

{

pMeshContainer->pMaterials[ 0 ].pTextureFilename = NULL;

memset( &pMeshContainer->pMaterials[ 0 ].MatD3D, 0, sizeof(D3DMATERIAL9) );

pMeshContainer->pMaterials[ 0 ].MatD3D.Diffuse.r = 0.5f;

pMeshContainer->pMaterials[ 0 ].MatD3D.Diffuse.g = 0.5f;

pMeshContainer->pMaterials[ 0 ].MatD3D.Diffuse.b = 0.5f;

pMeshContainer->pMaterials[ 0 ].MatD3D.Specular = pMeshContainer->pMaterials[ 0 ].MatD3D.Diffuse;

 • 메시 컨테이너 안에서 메터리얼의 갯수와 텍스쳐, 기타 메시에 필요한 정보들의 공간만 new 로 확보해 놓음
• adjacency 정보는 인자로 들어온 값을 그대로 카피
• 텍스쳐 포인터 배열은 0으로 초기화, 매터리얼 갯수가 0이 넘으면 즉, 매터리얼이 존재한다면 인자로 넘어온 메터리얼 구조체를 또 그대로 카피
• 텍스쳐 이름을 체크해서 텍스쳐를 생성해주고 이름이 있던 자리엔 NULL을 넣어줌
• 만약 매터리얼이 없다면 메터리얼에 디폴트 값들을 넣어줌

 

// 스킨 정보가 있을 때 실행
// if there is skinning information, save off the required data and then setup for HW skinning
if( pSkinInfo != NULL )
{
// first save off the skinInfo and original mesh data
pMeshContainer->pSkinInfo = pSkinInfo;
pSkinInfo->AddRef();
pMeshContainer->pOrigMesh = pMesh;
UINT temp = pMesh->AddRef();
// Will need an array of offset matrices to move vertices from the figure space to the bone's space
cBones = pSkinInfo->GetNumBones();
pMeshContainer->pBoneOffsetMatrices = new D3DXMATRIX[ cBones ];
if( pMeshContainer->pBoneOffsetMatrices == NULL )
{
// 종료 루틴 생략.
return E_FAIL;
}
// get each of the bone offset matrices so that we don't need to get them later
for( iBone = 0; iBone < cBones; ++iBone )
{
pMeshContainer->pBoneOffsetMatrices[ iBone ] = *(pMeshContainer->pSkinInfo-
>GetBoneOffsetMatrix(iBone) );
}
// GenerateSkinniedMesh Will take general skinning information and transform it to a HW friendly version
hr = m_pApp->GenerateSkinnedMesh( pMeshContainer );
if( FAILED(hr) )
{
// 종료 루틴 생략.
return E_FAIL;
}
}

- 스킨 정보의 포인터를 복사해 주면서 또 AddRef()로 레퍼런스 카운트를 늘려줌
– pMeshContainer->pSkinInfo 를 나중에 따로 릴리즈 하기 때문
– 메시컨테이너의 오리지날 메시에 pMesh 포인터를 또 복사해주면서 레퍼런스 카운트를 늘려줌
– pMeshContainer->pOrigMesh 를 따로 릴리즈 해주기 때문
– 메시 컨테이너 안에 본 오프셋 매트릭스(프레임의 원래 위치)를 셋팅해주기 위해서 스킨 정보 인터페이스 안에 저장되어있는 뼈의 갯수를 얻어옴
– 뼈의 갯수만큼 for문을 돌면서 뼈의 번호에 따라서 메시 컨테이너 안에 있는 본 오프셋 매트릭스를 저장
– CPROD3DANIMESH* m_pApp포인터를 이용해서 GenerateSkinnedMesh() 함수를 호출
– 메시 컨테이너 안에 있는 메시 데이터 안의 메시를 스킨 정보가 들어간 메시로 바꿔주는 일을 함

 

*ppNewMeshContainer = pMeshContainer;
pMeshContainer = NULL;
SAFE_RELEASE(pd3dDevice);
// call Destroy function to properly clean up the memory allocated
if (pMeshContainer != NULL)
{
DestroyMeshContainer(pMeshContainer);
}
return S_OK;

완성된 메시 컨테이너의 포인터를 인자로 받은 더블 포인터를 사용해 연결시켜 준 다음에 pd3dDevice를 릴리즈

 

//----------------------------------------------------------------------------

// Name: CAllocateHierarchy::DestroyFrame()

// Desc: Destroy Frame

//----------------------------------------------------------------------------

HRESULT CAllocateHierarchy::DestroyFrame( LPD3DXFRAME pFrameToFree )

{

// first, delete string of name. it will be leak the memory, if you do not this work..

SAFE_DELETE_ARRAY( pFrameToFree->Name );

SAFE_DELETE( pFrameToFree );

return S_OK;

}

//----------------------------------------------------------------------------

// Name: CAllocateHierarchy::DestroyMeshContainer()

// Desc: Destroy mesh container

//----------------------------------------------------------------------------

HRESULT CAllocateHierarchy::DestroyMeshContainer( LPD3DXMESHCONTAINER pMeshContainerBase )

{

UINT iMaterial;

D3DXMESHCONTAINER_DERIVED *pMeshContainer = (D3DXMESHCONTAINER_DERIVED *)pMeshContainerBase;

SAFE_DELETE_ARRAY( pMeshContainer->Name );

SAFE_DELETE_ARRAY( pMeshContainer->pAdjacency );

SAFE_DELETE_ARRAY( pMeshContainer->pMaterials );

SAFE_DELETE_ARRAY( pMeshContainer->pBoneOffsetMatrices );

// release all the allocated textures

if( pMeshContainer->ppTextures != NULL )

{

for( iMaterial = 0; iMaterial < pMeshContainer->NumMaterials; ++iMaterial )

{

SAFE_RELEASE( pMeshContainer->ppTextures[ iMaterial ] );

}

}

SAFE_DELETE_ARRAY( pMeshContainer->ppTextures );

SAFE_DELETE_ARRAY( pMeshContainer->ppBoneMatrixPtrs );

SAFE_RELEASE( pMeshContainer->pBoneCombinationBuf );

SAFE_RELEASE( pMeshContainer->MeshData.pMesh );

SAFE_RELEASE( pMeshContainer->pSkinInfo );

SAFE_RELEASE( pMeshContainer->pOrigMesh );

SAFE_DELETE( pMeshContainer );

return S_OK;

}

 

HRESULT CPROD3DANIMESH::LoadSKNMesh(char * szFileName)

{

HRESULT hr;

CAllocateHierarchy Alloc(this);

if(m_ppDevice == NULL)

{

MessageBox(NULL, "NULL Device?", "ERROR", MB_OK);

return FALSE;

}

hr = D3DXLoadMeshHierarchyFromX(szFileName, D3DXMESH_MANAGED,

m_ppDevice, &Alloc, NULL, &m_pFrameRoot, &m_pAnimController);

if(hr == D3DERR_INVALIDCALL)

{

MessageBox(NULL, "The method call is invalid.", "ERROR", MB_OK);

return FALSE;

}

if(hr == E_OUTOFMEMORY)

{

MessageBox(NULL, "not sufficient memory to complete the call.", "ERROR", MB_OK);

return FALSE;

}

hr = SetupBoneMatrixPointers(m_pFrameRoot);

return TRUE;

}

D3DXLoadMeshHierarchyFromX() 함수를 호출

D3DXMESH_MANAGED 라는 것은 메시의 버텍스 버퍼와 인덱스 버퍼의 메모리 풀을 D3DPOOL_MANAGED로 사용

D3DPOOL_MANAGED는 데이타가 시스템 메모리와 비디오 카드 메모리를 왔다갔다하며 알아서 D3D가 관리
D3DPOOL_DEFAULT는 오로지 비디오 카드 메모리에만 데이타가 있겠다고 하는 것임
D3DPOOL_SYSTEMMEM는 오로지 시스템 메모리에만 데이터를 두겠다는 것임
MANAGED는 디바이스를 잃더라도 알아서 관리를 해주기 때문에 편한 반면,

시스템 메모리와 비디오 메모리 간에 왔다갔다 하기 때문에 약간의 속도가 저하
D3DPOOL_DEFAULT는 비디오 메모리에만 있어서 속도는 빠르지만 디바이스를 잃어버리면

프로그래머가 수동적으로 데이터를 Restore 시켜줘야 하는 단점이 있음

SetUpBoneMatrixPointers( m_pFrameRoot ); // 본 매트릭스들을 생성해서 트리로 꾸며진 뼈에 매트릭스들을 달아주는 일을 함

 

GenerateSkinnedMesh()

HRESULT hr = S_OK;

// 스킨 정보가 없으면 리턴

if( pMeshContainer->pSkinInfo == NULL )

return hr;

SAFE_RELEASE( pMeshContainer->MeshData.pMesh );

SAFE_RELEASE( pMeshContainer->pBoneCombinationBuf );

hr = pMeshContainer->pSkinInfo->ConvertToBlendedMesh( pMeshContainer->pOrigMesh,

D3DXMESH_MANAGED |

D3DXMESHOPT_VERTEXCACHE,

pMeshContainer->pAdjacency,

NULL, NULL, NULL,

&pMeshContainer->NumInfl,

&pMeshContainer->NumAttributeGroups,

&pMeshContainer->pBoneCombinationBuf,

&pMeshContainer->MeshData.pMesh );

// 실패하면 바로 리턴.

if( FAILED(hr) )

return hr;

CAllocateMeshContainer::CreateMeshContainer() 함수 안에서 마지막에 호출했던 함수

스킨 정보가 있는지 검사. 없으면 바로 리턴

메시 컨테이너의 메시 데이터 안에 있는 메시를 릴리즈 시키고, 본 콤비네이션 버퍼를 없앰

ConvertToBlendMesh() 오리지날 메시를 받아서 약간의 옵션을 정해준 뒤 메시 컨테이너의 여러 정보들을 채워줌

메시도 새로 생성 : 원래 메시가 있던 자리를 밀어내고 새로 생성되니까.. 메모리 누수가 생기기 전에 미리 릴리즈

본 콤비네이션 : 각각의 Attribute에 맞는 BoneID를 가지고 있는 것(나중에 그릴 때 사용)

 

// 디바이스 캡에 맞지 않은 본 콤비네이션 셋을 찾는다.

for( pMeshContainer->iAttributeSW = 0; pMeshContainer->iAttributeSW < pMeshContainer->NumAttributeGroups;

++pMeshContainer->iAttributeSW )

{

DWORD cInfl = 0;

for( DWORD iInfl = 0; iInfl < pMeshContainer->NumInfl; iInfl++ )

{

if( rgBoneCombinations[ pMeshContainer->iAttributeSW ].BoneId[ iInfl ] != UINT_MAX )

{

++cInfl;

}

}

if( cInfl > m_d3dCaps.MaxVertexBlendMatrices )

break;

}

소프트웨어 버텍스 프로세싱을 해줄 갯수를 정해 주는 것임

tiny.x 에서는 31개의 본 콤비네이션이 있음

 

 

DrawMeshContainer()

// first, check for skinning

if( pMeshContainer->pSkinInfo != NULL )

{

AttribIdPrev = UNUSED32;

pBoneComb = reinterpret_cast<LPD3DXBONECOMBINATION>(pMeshContainer-

>pBoneCombinationBuf->GetBufferPointer());

// Draw using default vertex processing of the device (typically HW)

for( iAttrib = 0; iAttrib < pMeshContainer->NumAttributeGroups; ++iAttrib )

{

NumBlend = 0;

for( DWORD i = 0; i < pMeshContainer->NumInfl; ++i )

// 현재 뼈에서 블랜딩할 매트릭스 갯수를 정확히 지정해주는 것

{

if( pBoneComb[ iAttrib ].BoneId[ i ] != UINT_MAX )

{

NumBlend = i;

}

}

// If necessary, draw parts that HW could not handle using SW

if( pMeshContainer->iAttributeSW < pMeshContainer->NumAttributeGroups )

{

AttribIdPrev = UNUSED32;

m_pd3dDevice->SetSoftwareVertexProcessing( TRUE );

for( iAttrib = pMeshContainer->iAttributeSW; iAttrib < pMeshContainer->NumAttributeGroups; ++iAttrib )

{

NumBlend = 0;

for( DWORD i = 0; i < pMeshContainer->NumInfl; ++i )

{

if( pBoneComb[ iAttrib ].BoneId[ i ] != UINT_MAX )

{

NumBlend = i;

}

}

if( m_d3dCaps.MaxVertexBlendMatrices < NumBlend+1 )

{

// first calculate the world matrices for the current set of blend weights and get the accurate

// count of the number of blends

for( DWORD i = 0; i < pMeshContainer->NumInfl; ++i )

{

if( iMatrixIndex != UINT_MAX )

{

D3DXMatrixMultiply( &matTemp, &pMeshContainer->pBoneOffsetMatrices[ iMatrixIndex ],

pMeshContainer->ppBoneMatrixPtrs[ iMatrixIndex ] );

m_pd3dDevice->SetTransform( D3DTS_WORLDMATRIX( i ), &matTemp );

}

}

m_pd3dDevice->SetRenderState( D3DRS_VERTEXBLEND, NumBlend );

// Lookup the material used this subset of faces

if( (AttribIdPrev != pBoneComb[ iAttrib ].AttribId) || (AttribIdPrev == UNUSED32) )

{

m_pd3dDevice->SetMaterial( &pMeshContainer-

>pMaterials[ pBoneComb[ iAttrib ].AttribId ].MatD3D );

m_pd3dDevice->SetTexture( 0, pMeshContainer->ppTextures[ pBoneComb[ iAttrib ].AttribId ] );

AttribIdPrev = pBoneComb[ iAttrib ].AttribId;

}

// draw the subset now that the correct material and matrices are loaded

pMeshContainer->MeshData.pMesh->DrawSubset( iAttrib );

}

}

m_pd3dDevice->SetSoftwareVertexProcessing( FALSE );

}

//----------------------------------------------------------------------------
// Name: SetupBoneMatrixPointers()
// Desc: Called to setup the pointers for a given bone to its transformation matrix
//----------------------------------------------------------------------------
HRESULT CPROD3DANIMESH::SetupBoneMatrixPointersOnMesh(LPD3DXMESHCONTAINER
pMeshContainerBase)
{
UINT iBone, cBones;
D3DXFRAME_DERIVED *pFrame;
D3DXMESHCONTAINER_DERIVED *pMeshContainer =
(D3DXMESHCONTAINER_DERIVED*)pMeshContainerBase;
// if there is skinned mesh, then setup the bone matrices
if( pMeshContainer->pSkinInfo != NULL )
{
cBones = pMeshContainer->pSkinInfo->GetNumBones();
pMeshContainer->ppBoneMatrixPtrs = new D3DXMATRIX*[ cBones ];
if( pMeshContainer->ppBoneMatrixPtrs == NULL )
return E_OUTOFMEMORY;
for( iBone = 0; iBone < cBones; iBone++ )
{
pFrame = (D3DXFRAME_DERIVED*)D3DXFrameFind( m_pFrameRoot, pMeshContainer-
>pSkinInfo->GetBoneName(iBone) );
if( pFrame == NULL )
return E_FAIL;
pMeshContainer->ppBoneMatrixPtrs[ iBone ] = &pFrame->CombinedTransformationMatrix;
}
}
return S_OK;
}
메시컨테이너에 매트릭스를 달아주는 함수
DrawMeshContainer()에서 그려줄 때 BoneID로 행렬을 찾아줌 : 그 배열을 생성
스킨 정보로 전체 뼈의 갯수를 얻어옴
그 개수대로 매트릭스 배열을 동적 메모리 할당을 해서 만들어 냄
뼈의 갯수대로 루프를 돌며 D3DXFrameFind() 함수를 사용하여 뼈를 이름으로 찾아내고 그 프레임의 최종 변환
행렬의 포인터를 배열에 물려줌
41
//----------------------------------------------------------------------------
// Name: SetupBoneMatrixPointers()
// Desc: Called to setup the pointers for a given bone to its transformation matrix
//----------------------------------------------------------------------------
HRESULT CPROD3DANIMESH::SetupBoneMatrixPointers(LPD3DXFRAME
pFrame)
{
HRESULT hr;
if( pFrame->pMeshContainer != NULL )
{
hr = SetUpBoneMatrixPointersOnMesh( pFrame->pMeshContainer );
if( FAILED(hr) )
return hr;
}
if( pFrame->pFrameSibling != NULL )
{
hr = SetUpBoneMatrixPointers( pFrame->pFrameSibling );
if( FAILED(hr) )
return hr;
}
if( pFrame->pFrameFirstChild != NULL )
{
hr = SetUpBoneMatrixPointers( pFrame->pFrameFirstChild );
if( FAILED(hr) )
return hr;
}
}
트리 구조의 뼈를 돌면서 메시컨테이너에 최종 변환 매트릭스를 할당하고 물려줌
42
void CPROD3DANIMESH::UpdateFrameMatrices(LPD3DXFRAME
pFrameBase, LPD3DXMATRIX pParentMatrix)
{
D3DXFRAME_DERIVED *pFrame = (D3DXFRAME_DERIVED*)pFrameBase;
if( pParentMatrix != NULL )
D3DXMatrixMultiply( &pFrame->CombinedTransformationMatrix, &pFrame->TransformationMatrix, pParentMatrix );
else
pFrame->CombinedTransformationMatrix = pFrame->TransformationMatrix;
if( pFrame->pFrameSibling != NULL )
{
UpdateFrameMatrices( pFrame->pFrameSibling, pParentMatrix );
}
if( pFrame->pFrameFirstChild != NULL )
{
UpdateFrameMatrices( pFrame->pFrameFirstChild, &pFrame->CombinedTransformationMatrix );
}
return S_OK;
}
사람모델인 경우 머리부터 발 끝까지 순서대로 순회를 하며 적절한 애니메이션 타임에 따른 매트
릭스대로 업데이트
m_pd3dDevice->SetRenderState( D3DRS_VERTEXBLEND, 0 );

 

 

 

 

//----------------------------------------------------------------------------

// Name: DrawFrame()

// Desc: Called to render a frame in the hierarchy

//----------------------------------------------------------------------------

HRESULT DrawFrame(LPD3DXFRAME pFrame)

{

LPD3DXMESHCONTAINER pMeshContainer;

pMeshContainer = pFrame->pMeshContainer;

while( pMeshContainer != NULL )

{

DrawMeshContainer( pMeshContainer, pFrame );

pMeshContainer = pMeshContainer->pNextMeshContainer;

}

if( pFrame->pFrameSibling != NULL )

{

DrawFrame( pFrame->pFrameSibling );

}

if( pFrame->pFrameFirstChild != NULL )

{

DrawFrame( pFrame->pFrameFirstChild );

}

return S_OK;

}

 

현재 있는 메시 컨테이너를 모두 그려준 후에, 그 밑에 if 문으로 가서 형제 프레임이있는지 검사
만약 존재한다면 자기 자신을 또 호출. 그런식으로 계속 깊게 깊게 들어가다보면 결국 자신과

동등한 트리 깊이를 가지고 있는 메시 컨테이너들은 다 그리게 됨

 

 

//----------------------------------------------------------------------
// Name: SetupBoneMatrixPointers()
// Desc: Called to setup the pointers for a given bone to its transformation matrix
//----------------------------------------------------------------------
HRESULT CPROD3DANIMESH::SetupBoneMatrixPointersOnMesh(LPD3DXMESHCONTAINER
pMeshContainerBase)
{
UINT iBone, cBones;
D3DXFRAME_DERIVED *pFrame;
D3DXMESHCONTAINER_DERIVED *pMeshContainer =
(D3DXMESHCONTAINER_DERIVED*)pMeshContainerBase;
// if there is skinned mesh, then setup the bone matrices
if( pMeshContainer->pSkinInfo != NULL )
{
cBones = pMeshContainer->pSkinInfo->GetNumBones();
pMeshContainer->ppBoneMatrixPtrs = new D3DXMATRIX*[ cBones ];
if( pMeshContainer->ppBoneMatrixPtrs == NULL )
return E_OUTOFMEMORY;
for( iBone = 0; iBone < cBones; iBone++ )
{
pFrame = (D3DXFRAME_DERIVED*)D3DXFrameFind( m_pFrameRoot, pMeshContainer-
>pSkinInfo->GetBoneName(iBone) );
if( pFrame == NULL )
return E_FAIL;
pMeshContainer->ppBoneMatrixPtrs[ iBone ] = &pFrame->CombinedTransformationMatrix;
}
}
return S_OK;
}

 

메시컨테이너에 매트릭스를 달아주는 함수
DrawMeshContainer()에서 그려줄 때 BoneID로 행렬을 찾아줌 : 그 배열을 생성
스킨 정보로 전체 뼈의 갯수를 얻어옴
그 개수대로 매트릭스 배열을 동적 메모리 할당을 해서 만들어 냄
뼈의 갯수대로 루프를 돌며 D3DXFrameFind() 함수를 사용하여 뼈를 이름으로 찾아내고 그 프레임의 최종 변환 행렬의 포인터를 배열에 물려줌

//----------------------------------------------------------------------------
// Name: SetupBoneMatrixPointers()
// Desc: Called to setup the pointers for a given bone to its transformation matrix
//----------------------------------------------------------------------------
HRESULT CPROD3DANIMESH::SetupBoneMatrixPointers(LPD3DXFRAME
pFrame)
{
HRESULT hr;
if( pFrame->pMeshContainer != NULL )
{
hr = SetUpBoneMatrixPointersOnMesh( pFrame->pMeshContainer );
if( FAILED(hr) )
return hr;
}
if( pFrame->pFrameSibling != NULL )
{
hr = SetUpBoneMatrixPointers( pFrame->pFrameSibling );
if( FAILED(hr) )
return hr;
}
if( pFrame->pFrameFirstChild != NULL )
{
hr = SetUpBoneMatrixPointers( pFrame->pFrameFirstChild );
if( FAILED(hr) )
return hr;
}
}

트리 구조의 뼈를 돌면서 메시컨테이너에 최종 변환 매트릭스를 할당하고 물려줌

 

 

void CPROD3DANIMESH::UpdateFrameMatrices(LPD3DXFRAME
pFrameBase, LPD3DXMATRIX pParentMatrix)
{
D3DXFRAME_DERIVED *pFrame = (D3DXFRAME_DERIVED*)pFrameBase;
if( pParentMatrix != NULL )
D3DXMatrixMultiply( &pFrame->CombinedTransformationMatrix, &pFrame->TransformationMatrix, pParentMatrix );
else
pFrame->CombinedTransformationMatrix = pFrame->TransformationMatrix;
if( pFrame->pFrameSibling != NULL )
{
UpdateFrameMatrices( pFrame->pFrameSibling, pParentMatrix );
}
if( pFrame->pFrameFirstChild != NULL )
{
UpdateFrameMatrices( pFrame->pFrameFirstChild, &pFrame->CombinedTransformationMatrix );
}
return S_OK;
}

사람모델인 경우 머리부터 발 끝까지 순서대로 순회를 하며 적절한 애니메이션 타임에 따른 매트릭스대로 업데이트

 

 

 

출처 - http://sdi1982.springnote.com/

sdi1982 님의 노트에서 발췌

----- sdi1982님의 노트에는 로그인을 하지 않으면

댓글을 쓸 수 없어서 동의를 얻지 못하고 퍼왔습니다.

 문제가 된다면 삭제 하겠습니다.-----