다이렉트 X 9.0 SDK 에서 제공하는 Skinned Animation 1
애니메이션 3 차원그래픽에서애니메이션이란메시나텍스처등의오브젝트를 3 차원공간에서움직이게하는것을통칭하는말이다. 일반적으로많이사용하는애니메이션기법 1. 버텍스애니메이션 (vertex animation) 2. 계층형애니메이션 (hierarchical animation) 3. 뼈대애니메이션 (bone based animation, skeletal animation) 4. 스키닝 (skinning) 5. 역운동학 (inverse kinematics) 키프레임애니메이션전체애니메이션중에서중요한몇개의프레임에애니메이션키값을등록하고, 나머지값들을자동생성하는애니메이션방식을말한다. 여기서의프레임이란애니메이션에서출력될장면한장한장을가리킨다고생각하면된다. 2
키프레임애니메이션 y 좌표 (-10,0,0) 좌표 (10,0,0) x z 1번재키프레임 보간생성된프레임 2번째키프레임 (Interpolated Frame) 1 번프레임 2 번프레임 3 번프레임 4 번프레임 5 번프레임 6 번프레임 좌표 (-10,0,0) 좌표 (-6,0,0) 좌표 (-2,0,0) 좌표 (2,0,0) 좌표 (6,0,0) 좌표 (10,0,0) 3
계층구조구현방법 계층구조는간단하게생각하면부모자식관계를구현하는것이다. 부모자식관계를 3D로구현한다는것은결국 3D적인표현수단을사용한다는것이고, 우리가알고있는 3D적인표현수단은행렬이있다. 정점의변환 V = V M world local transform TM = M transform 일때, Vworld = Vlocal TM 정점이계층구조일때, V world = Vlocal TM child TM parent 부모에해당하는행렬이여러개라면해당하는개수만큼곱해주면된다. 만약부모에해당하는행렬이 n개라면다음과같이처리한다. V = V TM TM TM L TM world local child parent ( n) parent ( n 1) parent (1) 4
계층구조의구현예 인간형태로모델링된메시 인간모델의트리구조 5
계층구조의실제구현 자식메시 구성요소 행렬명 기본변환 TM child 부모메시 Z 축회전부모행렬 R child M parent 부모최종행렬 M = R TM parent parent parent 구성요소 행렬명 기본변환 Y 축회전 TM parent R parent 자식최종행렬 M child = R = R child child TM TM child child M parent ( R TM ) parent parent 6
스키닝 과거의게임들은여러개의분할메시로되어있을경우메시와메시의연결부위가자연스럽게붙지않는관절끊김현상이있었다. 이러한관절끊김을막기위해서메시를단일한하나의메시로만들어버린것이그이후의방식이다. 그러나이방식은관절의끊김은막을수있었으나, 관절이자연스럽게이어지도록만들수는없었다. 그래서나온방법이가중치 (weight) 를사용한기법이다. 7
스키닝 애니메이션결과값비교 가중치없음 v = v M A 분할메시 A의 TM= B의 TM = M B 의가중치 = 의가중치 = M A M B M A W A W B 단일메시 & 가중치없음 A v B v 단일메시 & 가중치사용 가중치사용 v = v M W + v M W A A B B v 8
예제 소프트웨어스키닝 NonIndexed 스키닝 Indexed 스키닝적용 NoneIndexed 스키닝을가지고클래스를설계구현한예제분석 9
CPROD3DANIMESH 이라는클래스 : 만들어진스킨드메시를가지고애니메이션을제어하는클래스 CAllocateHierarchy 클래스 : 실제적으로애니메이션을셋팅하는클래스 10
CAllocateHierarchy 클래스는 CPROD3DANIMESH 클래스가생성될때한번, 소멸될때한번생성되어서본을셋팅하고메시컨테이너를셋팅하고소멸시키는역할을함 DXSDK 9.0 개발문서 원래 CAllocateHierarchy 는 ID3DXAllocateHierarchy 으로이미제공됨 CAllocateHierarchy 는 ID3DXAllocateHierarchy 을상속받아서새로만든자식클래스 원래 D3DXLoadMeshHierarchyFromX() 함수를호출할때인자로넘겨주는인터페이스임 11
D3DXLoadMeshHierarchyFromX - 상속받아서새로만들어서쓰는이유 D3DXLoadMeshHierarchyFromX loads the animation data and frame hierarchy from an.x file. It scans the.x file and builds a frame hierarchy and animation controller according to the ID3DXAllocateHierarchy-derived object passed to it through palloc. Loading the data requires several steps as follows: Derive ID3DXAllocateHierarchy, implementing each method. This controls how frames and meshes are allocated and freed. Derive ID3DXLoadUserData, implementing each method. If your.x file has no embedded user-defined data, or if you do not need it, you can skip this part. Create an object of your ID3DXAllocateHierarchy class, and optionally of your LoadUserData class. You do not need to call any methods of these objects yourself. Call D3DXLoadMeshHierarchyFromX, passing in your ID3DXAllocateHierarchy object and your ID3DXLoadUserData object (or NULL) to create the frame hierarchy and animation controller. All the animation sets and frames are automatically registered to the animation controller. 12
D3DXLoadMeshHierarchyFromX 원래상속받아서쓰는인터페이스임 즉, 원형만만들어놓은추상클래스임 프로그래머가상속받아서이용 스키닝방법이나기타모든것에자유를주면서, 일정한형식을갖추도록한것임 그목적은 ID3DXAnimationController 라는인터페이스와데이터가묶이도록하기위해서임 즉, CAllocateHierarchy 클래스를제대로만들면애니메이션을다구현했다는것을의미 *.X 파일을읽어오는 D3DXLoadMeshHierarchyFromX() 가이클래스로만든객체의포인터를받아서실제로프레임정보와메시컨테이너를만들때 CAllocateHierarchy 에있는함수를사용하기때문에사용 지울때도 CAllocateHierarchy 에있는 DestroyFrame() 함수와 DestroyMeshContainer() 함수를사용함 13
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 같은데서디자인할때뼈에이름을심어주는데, 그이름을인자로받아와서새로들어간본구조체안의이름에동적메모리를할당해서이름을넣어주는역할 14
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; ; public: CPROD3DANIMESH* m_papp; 이클래스가생성될때애니메이션객체의포인터를넘겨줘서... 그포인터를사용해서애니메이션클래스에접근을함 15
//----------------------------------------------------------------------------- // 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; 새로운프레임 - 뼈를만들어주는함수 : 인자로이름을받고, 프레임구조체형의더블포인터를받아서새로메모리를할당해서프레임을생성해주고포인터를연결해줌. 그프레임안에있는각종값들을초기화시킴. 프레임전체를트리구조로꾸미기위한준비작업 ( 뼈전체가트리구조 ) 16
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 : 누적된행렬정보 예 : 이프레임이손끝의프레임이라고한다면, 이행렬정보에는어깨의회전값, 팔의회전값, 손목의회전값이누적되어들어가게됨 ( 손끝의회전은손목을기준으로돌아가기때문 ) 17
D3DXFRAME 이구조체의부모클래스인 D3DXFRAME typedef struct _D3DXFRAME LPTSTR Name; // 프레임이름 D3DXMATRIX TransformationMatrix; // 변환행렬정보 LPD3DXMESHCONTAINER pmeshcontainer; // 메시컨테이너포인터 struct _D3DXFRAME *pframesibling; // 형제프레임의포인터 struct _D3DXFRAME *pframefirstchild; // 자식프레임의포인터 D3DXFRAME, *LPD3DXFRAME; 트리구조를위한형제, 자식의포인터와프레임이름과.. 자신의변환행렬정보.. 메시컨테이너의포인터가있음 LPD3DXMESHCONTAINER : 메시를담고있는컨테이너구조체 18
LPD3DXMESHCONTAINER LP : long pointer (4 바이트 ) LPD3DXMESHCONTAINER pmeshcontainer; // 메시컨테이너포인터라고선언되었듯이 D3DXMESHCONTAINER 구조체의포인터형임 19
D3DXMESHCONTAINER Structure Encapsulates a mesh object in a transformation frame hierarchy. 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; 20
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; 21
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 LPDIRECT3DTEXTURE9* pptextures; // array of textures, entries are NULL if no texture specified // 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 에서 SW와 HW 버텍스프로세싱을사용할때 // 나누기위한정보를저장하기위한변수. ; 스킨드애니메이션을위해서좀더필요한정보를저장하기위해서메시컨테이너를상속받아확장시킨구조체 속성 : 메시가나눠지는것 텍스쳐를기준으로나눠짐 : 매핑소스가 1개면메시가한덩어리 22 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 구조체속에모두있는것들임 23
HRESULT hr; D3DXMESHCONTAINER_DERIVED *pmeshcontainer = NULL; // 임시로사용할메시컨테이너의포인터 UINT NumFaces; // 삼각형면의갯수 UINT imaterial; // 메터리얼갯수 UINT ibone, cbones; // 본의갯수, 본카운트임시변수 LPDIRECT3DDEVICE9 pd3ddevice = NULL; // 임시로받아둘디바이스의포인터 LPD3DXMESH pmesh = NULL; // 임시로사용할메시 24
*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 ( 일반적인메시 ) 가아니라면함수를끝내버리는일을함 25
// 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() 전역함수로생성된메시컨테이너안에인자로받았던이름을달아줌 26
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; else // 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 ); // 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 안에새로클론된메시를집어넣음 27
pmesh->addref() IUnknown 이라는클래스는 AddRef() QueryInterface() Release() 이세가지를멤버함수로가지고있음 따라서다이렉트 X 의모든인터페이스는저 3 가지함수를공통으로가지고있습니다. Release() 는자주쓰는것 Release() 와반대되는개념이 AddRef() 내부적으로레퍼런스카운트라고있는데 레퍼런스카운트가 0 이되어야만비로소진짜메모리에서릴리즈가됨 Release() 는레퍼런스카운트를하나줄여줌 28
// 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을넣어줌 만약매터리얼이없다면메터리얼에디폴트값들을넣어줌 29
// 스킨정보가있을때실행 // 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() 함수를호출 메시컨테이너안에있는메시데이터안의메시를스킨정보가들어간메시로바꿔주는일을함 30
*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 를릴리즈 31
//----------------------------------------------------------------------------- // 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; 32
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 ); // 본매트릭스들을생성해서트리로꾸며진뼈에매트릭스들을달아주는일을함 33
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 를가지고있는것 ( 나중에그릴때사용 ) 34
// 디바이스캡에맞지않은본콤비네이션셋을찾는다. 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개의본콤비네이션이있음 35
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; 36
// 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 ); m_pd3ddevice->setrenderstate( D3DRS_VERTEXBLEND, 0 ); 37
//----------------------------------------------------------------------------- // 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 문으로가서형제프레임이있는지검사만약존재한다면자기자신을또호출. 그런식으로계속깊게깊게들어가다보면결국자신과동등한트리깊이를가지고있는메시컨테이너들은다그리게됨 38
FrameMove - 애니메이션컨트롤러 : 시간을받아옴. 이시간은애니메이션되는정도를의미 39
//----------------------------------------------------------------------------- // 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 로행렬을찾아줌 : 그배열을생성 스킨정보로전체뼈의갯수를얻어옴그개수대로매트릭스배열을동적메모리할당을해서만들어냄 40 뼈의갯수대로루프를돌며 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; 트리구조의뼈를돌면서메시컨테이너에최종변환매트릭스를할당하고물려줌 41
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; 사람모델인경우머리부터발끝까지순서대로순회를하며적절한애니메이션타임에따른매트릭스대로업데이트 42