키 프레임 애니메이션

 


시스템 설계
앞서 개발한 프레임워크에 새로운 클래스를 추가했다.

ZTrack :  키 프레임 애니메이션을 보관하고 있다가, 원하는 프레임에서의 애니메이션 행렬을 구해주는 것, ZNode에 추가되며 ZNode의 Animate()함수가 조금 변형될 것이다.

개발자에 따라서 ZTrack에 해당하는 클래스를 ZNode가 아니라 ZBone에 포함시키는 사람도 있다, 이럴 경우 철저하게 뼈대 애니메이션만 지원하는 구조가 된다. 이번에 구현할 애니메이션 엔진은 메시에 직접 키값도 넣는 방식을 지원할 예정이므로 ZNode에 ZTrack클래스를 삽입하였다.


 



실제 구현 - ZTrack

#ifndef _ZTRACK_H_
#define _ZTRACK_H_

#include "define.h"
#include "ZDefine.h"
#include <d3d9.h>
#include <d3dx9.h>

/// 애니메이션 키값을 보관하기 위한 클래스
class ZTrack
{
public:
enum INTERPOL { INTP_LINEAR, INTP_SPLINE, INTP_HERMIT }; // 현재는 LINEAR만 지원

protected:
ZKeys m_keys; /// 키값들
D3DXMATRIXA16 m_matAni; /// 최종적으로 구한 애니메이션 행렬

float _GetAlpha( float f1, float f2, float frame )
{
return ( (frame-f1) / (f2-f1) );
}

void _Get2Keys( float frame, vector<ZKeyPos>& keys, ZKeyPos*& k1, ZKeyPos*& k2 );
void _Get2Keys( float frame, vector<ZKeyRot>& keys, ZKeyRot*& k1, ZKeyRot*& k2 );
void _Get2Keys( float frame, vector<ZKeyScl>& keys, ZKeyScl*& k1, ZKeyScl*& k2 );
BOOL _GetPosKey( float frame, D3DXVECTOR3* pv );
BOOL _GetRotKey( float frame, D3DXQUATERNION* pq );
BOOL _GetSclKey( float frame, D3DXVECTOR3* pv );
public:
ZTrack( Track& keys );
~ZTrack();
D3DXMATRIXA16* Animate( float frame );
};

#endif // _ZTRACK_H_

ZTrack클래스의 선언부를 보도록 하자. ZTrack 클래스는 m_keys에 키값을 보관하고 있다가 Animate(float frame) 함수로부터 전달된 frame 위치의 키값을 찾아서 반환하는 일을 한다. 현재 키는 위치, 회전, 크기의 세가지 종류를 지원한다.

먼저 생성자를 보도록 합시다.

ZTrack::ZTrack( Track& keys )
{
int i;

if( keys.pos.size() )
{
m_keys.pos.resize( keys.pos.size() );
for( i = 0 ; i < m_keys.pos.size() ; i++ )
{
m_keys.pos[i].frame = keys.pos[i].frame;
memcpy( &m_keys.pos[i].value, &keys.pos[i].value, sizeof D3DXVECTOR3 );
}
}

if( keys.rot.size() )
{
D3DXQUATERNION q = D3DXQUATERNION( 0, 0, 0, 1 );
m_keys.rot.resize( keys.rot.size() );
m_keys.rot[0].frame = keys.rot[0].frame;
m_keys.rot[0].value = q;
for( i = 1 ; i < m_keys.rot.size() ; i++ )
{
m_keys.rot[i].frame = keys.rot[i].frame;
if( keys.rot[i].frame <= keys.start ) // 애니메이션이 시작되는 프레임보다 작은 키값들은 전부 단위 쿼터니온으로!
q = D3DXQUATERNION( 0, 0, 0, 1 );
else
q = D3DXQUATERNION( keys.rot[i].value.x, keys.rot[i].value.y, keys.rot[i].value.z, keys.rot[i].value.w );

D3DXQuaternionMultiply( &m_keys.rot[i].value, &m_keys.rot[i-1].value, &q );
}
}

if( keys.scl.size() )
{
m_keys.scl.resize( keys.scl.size() );
for( i = 0 ; i < m_keys.scl.size() ; i++ )
{
m_keys.scl[i].frame = keys.scl[i].frame;
memcpy( &m_keys.scl[i].value, &keys.scl[i].value, sizeof D3DXVECTOR3 );
}
}

}

위치(pos)키값이 있으면 위치키값을 복사하고, 회전(rot)키값이 있으면 회전키를 복사한다.
물론 크기(scl)키의 값도 마찬가지다. 그러나 이때 회전키 값을 복사하는 부분을 주의해서 봐두기 바랍니다.
아래의 행이 아주 중요한 역할을 하게 됩니다.

D3DXQuaternionMultiply( &m_keys.rot[i].value, &m_keys.rot[i-1].value, &q );

이 소스에 대해 해석을 해보면, '직전 프레임의 회전키 값과 현재 프레임의 회전키 값을 곱해서 누적된 키 값을 현재 프레임의 키값으로 보간하라' 라는 의미가 된다. 이렇게 하는 이유는 맥스로부터 얻어진 회전키 값은 항상 직전 프레임의 키값을 기준으로 얼마만큼 변했는가 라는 변화값(delta)이기 때문이다. 이 때문에, 우리처럼 애니메이션을 실시간으로 해야 할 경우 미리 누적시킨 키값을 갖고 있는것이 나중에 계산하기 편리하다. 또한 실제 애니메이션에서는 사용되지 않으면서도 키값을 차지하고 있는 경우가 종종있다. 특히 모션캡쳐데이터의 경우 이런 값들이 특히 많은데, 이런 값들은 전부 단위사원수값ㄷ으로 환산해버려야 애니메이션 키값 누적시에 오차가 발생하지 않는다.
다른 부분에 비해 회전키 값 복사 부분의 소스가 좀 복잡한 이유는 바로 요런이유이다.

가장 중요한 역할을 하는 Animate() 함수를 살펴보자.

D3DXMATRIXA16* ZTrack::Animate( float frame )
{
D3DXVECTOR3 v;
D3DXQUATERNION q;

D3DXMatrixIdentity( &m_matAni );

if( _GetRotKey( frame, &q ) )
D3DXMatrixRotationQuaternion( &m_matAni, &q );

if( _GetPosKey( frame, &v ) )
{
m_matAni._41 = v.x;
m_matAni._42 = v.y;
m_matAni._43 = v.z;
}
if( _GetSclKey( frame, &v ) )
{
m_matAni._11 *= v.x;
m_matAni._22 *= v.y;
m_matAni._33 *= v.z;
}

return &m_matAni;
}

먼저 키값 보관용으로 사용될 행렬을 단위행렬로 만든 뒤 회전 키값을 얻어온다, 이 때 얻어온 값은 회전행렬값이 아니라 사원수 값이므로 얻어온 사원수를 D3DXMatrixRotationQuaternion() 함수를 사용해서 회전행렬로 변환한다. 그리고 위치키 값을 얻어서 위치키를 회전행렬에 넣는다, 마지막으로, 크기 키 값을 얻어서 회전행렬에 넣으면 최종행렬이 완성된다. 요고를 수식으로 표현하자면

Mani = Mrot * Mpos * Mscl (회전, 위치, 크기)

각각의 회전, 위치, 크기변환 행렬을 얻어서 한꺼번에 곱한 값을 얻어도 되지만, 이들 연산을 직접 해보면 결국 위치키 값이 회전행렬의 _41, _42, _43에 등록되어 있으면 크기변환 행렬은 _11, _22, _33에 곱해지게 되어있다. 그렇기 때문에 결과적으로는 같은 연산이 된다.

      [ _11, _12, _13, _14 ]
      [ _21, _22, _23, _24 ]
M = [ _31, _32, _33, _34 ]
      [ _41, _42, _43, _44 ]

이제 회전키 값을 얻어오는 함수를 보자

BOOL ZTrack::_GetRotKey( float frame, D3DXQUATERNION* pq )
{
float a;
ZKeyRot *p1,*p2;

_Get2Keys( frame, m_keys.rot, p1, p2 );

if( !p1 || !p2 ) return FALSE;

if( p1 == p2 )
{
pq->x = p2->value.x;
pq->y = p2->value.y;
pq->z = p2->value.z;
pq->w = p2->value.w;
}
else
{
a = _GetAlpha( p1->frame, p2->frame, frame );
D3DXQuaternionSlerp( pq, &p1->value, &p2->value, a );
}

return TRUE;
}

원하는 프레임의 좌우에 위치한 키값 2개를 얻어온다, 즉, 실제로 존재하는 회전키 값이 다음과 같다고 가정하자

프레임                                       회전키 (사원수)
   0                                            (0.0, 0.0, 0.0, 1.0)
   9                                            (0.200308, 0.0, 0.0, 0.979733)
   30                                          (0.167842, 0.0, 0.0, 0.985814)
   50                                          (-0.260492, 0.0, 0.0, 0.965476)
   60                                          (-0.119429, 0.0, 0.0, 0.992843)

요때, 만약에 프레임값으로 35가 주어졌다면, 우리는 30번째 프레임과 50번째 프레임의 사원수값을 기반으로 35번째 키값을 만들어내야한다. 또한, 이때 30번째, 프레임과 50번째 프레임의 키값을 찾아주는 역할을 하는 것이 _Get2Keys()라는 함수다. 만약 정상적으로 2개의 키값이 얻어졌다면, 우리는 이 값을 기준으로 사원수 선형 보간을 하게되는데, 이때 사용되는 함수는 D3DXQuaternionSlerp()이다. 이 함수는 2개의 키값과 키값 사이의 거리를 나타내는 알파값을 필요로 한다, 그 알파값을 구해주는게 _GetAlpha() 함수다.

_Get2Keys()함수는 간단한 2진검색으로 가장 가까운 2개의 키값을 찾는 함수이므로 더이상의 설명은 안하구, 만들어진 ZTrack 클래스를 활용해보자


변경사항 - ZNode

class ZNode
{
public:
enum NODETYPE { NODE_NODE, NODE_BONE, NODE_MESH }; // 현재 지원하는 노드의 종류
protected:

int m_nNodeType; /// 노드의 종류
int m_nObjectID; /// 오브젝트 ID
int m_nParentID; /// 부모의ID
int m_nMaterialID; /// 재질의 ID
D3DXVECTOR3 m_bboxMax; /// 바운딩 박스의 최대값(max)
D3DXVECTOR3 m_bboxMin; /// 바운딩 박스의 최소값(min)
D3DXMATRIXA16 m_matLocal; /// local TM 행렬(불변)

/// m_matTM = m_matLocal * m_matAni * (mom's m_matTM) * (grandmom's m_matTM) * ...
D3DXMATRIXA16 m_matTM; /// 최종 TM 행렬(변화)
ZTrack* m_pTrack; /// 애니메이션 트랙정보

LPDIRECT3DDEVICE9 m_pDev; /// D3D디바이스
public:
ZNode( LPDIRECT3DDEVICE9 pDev, ZCMesh* pMesh );
~ZNode();

/// 노드의 종류
int GetNodeType() { return m_nNodeType; }

/// 메시의 ID값을 얻는다
int GetObjectID() { return m_nObjectID; }

/// 부모메시의 ID값을 얻는다
int GetParentID() { return m_nParentID; }

int GetMaterialID() { return m_nMaterialID; }

/// 현재의 TM값을 얻는다
D3DXMATRIXA16* GetMatrixTM() { return &m_matTM; }

/// 경계상자의 값을 얻는다
void GetBBox( D3DXVECTOR3* pMin, D3DXVECTOR3* pMax )
{ *pMin = m_bboxMin; *pMax = m_bboxMax; }

/// ZTrack으로부터 m_matAni를 구해서 m_matTM을 만든다
D3DXMATRIXA16* Animate( float fFrame, D3DXMATRIXA16* pParentTM );

/// 노드를 그린다.
virtual int Draw() { return 1; }

/// 경계상자를 그린다
virtual int DrawBBox();
};

ZNode 클래스에서 변화된 부분이 몇군데 있다.
가장 중요한 ZTrack변수가 선언되었다.

ZTrack*  m_pTrack;

그리고 GetBBox()와 DrawBBox() 함수가 선언되어 있다.
이 함수들은 경계상자를 그려주고 그 값을 얻어내는 함수들이다.

ZNode 클래스의 생성자의 맨 끝에는 요고 한줄 추가된다.

ZNode::ZNode( LPDIRECT3DDEVICE9 pDev, ZCMesh* pMesh )
{
                                    :
                                    :
m_pTrack = new ZTrack( pMesh->m_track );
}

그리고 이렇게 추가된 ZTrack 객체를 기반으로 키값을 얻어내는 Animate() 함수가 크게 변형되었다.

D3DXMATRIXA16* ZNode::Animate( float fFrame, D3DXMATRIXA16* pParentTM )
{
D3DXMATRIXA16* pmatAni;

// 애니메이션 행렬을 만든다.
pmatAni = m_pTrack->Animate( fFrame );

m_matTM = m_matLocal * *pmatAni;

// 만약 pos키값이 없으면 local TM의 좌표를 사용한다
if( pmatAni->_41 == 0.0f && pmatAni->_42 == 0.0f && pmatAni->_43 == 0.0f )
{
m_matTM._41 = m_matLocal._41;
m_matTM._42 = m_matLocal._42;
m_matTM._43 = m_matLocal._43;
}
else // pos키값을 좌표값으로 적용한다(이렇게 하지 않으면 TM의 pos성분이 두번적용된다)
{
m_matTM._41 = pmatAni->_41;
m_matTM._42 = pmatAni->_42;
m_matTM._43 = pmatAni->_43;
}
m_matTM = m_matTM * *pParentTM;
return &m_matTM;
}

계층구조의 기본 행렬 공식은 다음과 같다.

Vworld = Vlocal * Mlocal * Mparent

여기에 애니메이션 행렬을 추가하면 다음과 같이 된다

Vworld = Vlocal * Mlocal * Mani *  Mparent

이것을 소스상에서 구현해보면 다음과 같다.

1. 먼저 애니메이션 행렬을 얻어낸다 == pmatAni = m_pTrack->Animate( fFrame );
2. Mtransform = Mlocal * Mani를 만든다. == m_matTM = m_matLocal * *pmatAni;
3. Mtransform = Mtransform * Mparent == m_matTM = m_matLocal * *pmatAni;

단, 이 과정에서 위치키 값은 Mlocal과 Mani의 값이 두번 적용되지 않게 주의한다.

Animate()함수를 이렇게 변형한것 만으로도 이제 이 클래스는 애니메이션을 처리할 수 있게되었다. ZRigidMesh::Draw() 함수는 전혀 고칠 필요가 없다.
이제, 경계상자를 그려주는 함수를 추가하는 것으로 ZNode에 대한 변경을 마무리하자

int ZNode::DrawBBox()
{
BOXVERTEX vtx[8];
vtx[0] = BOXVERTEX( m_bboxMin.x, m_bboxMax.y, m_bboxMax.z, 0xffff0000 );
vtx[1] = BOXVERTEX( m_bboxMax.x, m_bboxMax.y, m_bboxMax.z, 0xffff0000 );
vtx[2] = BOXVERTEX( m_bboxMax.x, m_bboxMax.y, m_bboxMin.z, 0xffff0000 );
vtx[3] = BOXVERTEX( m_bboxMin.x, m_bboxMax.y, m_bboxMin.z, 0xffff0000 );
vtx[4] = BOXVERTEX( m_bboxMin.x, m_bboxMin.y, m_bboxMax.z, 0xffff0000 );
vtx[5] = BOXVERTEX( m_bboxMax.x, m_bboxMin.y, m_bboxMax.z, 0xffff0000 );
vtx[6] = BOXVERTEX( m_bboxMax.x, m_bboxMin.y, m_bboxMin.z, 0xffff0000 );
vtx[7] = BOXVERTEX( m_bboxMin.x, m_bboxMin.y, m_bboxMin.z, 0xffff0000 );

Index3w idx[12] = 
{ 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 } /// 뒷면
};

m_pDev->MultiplyTransform( D3DTS_WORLD, &m_matTM );
m_pDev->SetFVF( BOXVERTEX::FVF );
m_pDev->DrawIndexedPrimitiveUP( D3DPT_TRIANGLELIST, 0, 8, 12, idx, D3DFMT_INDEX16, vtx, sizeof BOXVERTEX );

return 1;
}


변경사항 - ZNodeMgr

class ZNodeMgr
{
protected:
ZObjectInfo m_info; /// 현재 노드전체에 대한 정보
vector<ZMaterial> m_materials; /// 재질값 배열
vector<ZNode*> m_nodes; /// 노드들
LPDIRECT3DDEVICE9 m_pDev; /// D3D디바이스
D3DXMATRIXA16 m_matTM; /// 자식 노드 전체에 적용될 TM

BOOL _IsBone( int n )
{
for( int i = 0 ; i < m_info.BoneTable.size() ; i++ )
if( m_info.BoneTable[i] == n ) return TRUE;

return FALSE;
}

public:
ZNodeMgr( LPDIRECT3DDEVICE9 pDev, ZCParsedData* pData );
~ZNodeMgr();

/// 정보구조체의 포인터를 반환한다
ZObjectInfo* GetInfo() { return &m_info; }

/// TM을 얻어온다
D3DXMATRIXA16* GetTM() { &m_matTM; }

/// TM을 셋팅한다
void SetTM( D3DXMATRIXA16* pTM ) { m_matTM = *pTM; }

/// 애니메이션 행렬을 만든다
int Animate( float fFrame );

/// 노드전체를 그린다.
int Draw( int nNodeType );
int DrawBBox( int nNodeType );
};

_IsBone()이라는 함수가 추가되었다.
이 함수는 현재노드가 뼈에 해당하는지를 판단한다.
이런 함수가 필요한 이유는 현재 우리가 전체 노드 구조를 트리구조로 만들지 않고 단순 리스트 형태로 다루고 있기 때문이다. 맥스스크립트에서 데이터를 추출해 내면 최상위 부모노드로부터 자식노드의 순서로 추출된다.
그러나 이때 스키닝이 적용된 경우 애니메이션 키값이 들어가 있는 뼈대보다, 메시가 먼저 출력될 수 있다. 그 이유는 스키닝이 적용된 메시는 최상위 노드에 속하기 때문이다. 그렇게되면 메시는 애니메이션이 처리되지 않은 뼈대의 변환행렬을 사용하게 되므로, 결과적으로 애니메이션이 제대로 작동하지 않을 수 있다.
그러한 현상을 방지하기 위해 먼저 뼈대에 해당하는 노드들을 애니메이션시켜 변환행렬들을 구해놓은 뒤 메시에 해당하는 노드들을 애니메이션 시키는 것이다.

생성자 추가내용
for( i = 0 ; i < pData->m_meshList.size() ; i++ )
{
if( !_IsBone( i ) )
m_info.MeshTable.push_back( i );
}

뼈대가 아닌 노드들은 전부 MeshTable이라는 배열에 넣어두는 것이다.
물론 이 변수의 정확한 명칭은 NotBoneTable쯤으로 하는게 명확하겠지만

가장 중요한 Animate()함수를 보자

int ZNodeMgr::Animate( float fFrame )
{
int i;
int id;
D3DXMATRIXA16* pTM;

if( fFrame > m_info.fAnimationEnd ) fFrame = m_info.fAnimationEnd;
if( fFrame < m_info.fAnimationStart ) fFrame = m_info.fAnimationStart;

// 먼저 뼈대(bone)들의 애니메이션 행렬을 만들어 둔다
for( i = 0 ; i < m_info.BoneTable.size(); i++ )
{
id =  m_nodes[m_info.BoneTable[i]]->GetParentID();
pTM = m_nodes[id]->GetMatrixTM();
m_nodes[m_info.BoneTable[i]]->Animate( fFrame, pTM );
}

// 뼈대가 아닌 것들의 애니메이션 행렬을 만든다
for( i = 0 ; i < m_info.MeshTable.size() ; i++ )
{
id =  m_nodes[m_info.MeshTable[i]]->GetParentID();
pTM = m_nodes[id]->GetMatrixTM();
m_nodes[m_info.MeshTable[i]]->Animate( fFrame, pTM );
}

return 1;
}

먼저 뼈대에 해당하는 노드들을 애니메이션시켜 변환행렬을 만든다, 이때, 반드시 부모의 변환행렬을 동반해서 ZNode::Animate() 함수에 넘기고 있다는것을 알아두자. 뼈대에 대한 애니메이션이 끝나면 메시에 대한 애니메이션 행렬을 만든다. 이게 끝 이제 그려보자

int ZNodeMgr::Draw( int nNodeType )
{
int i;

if( nNodeType == ZNode::NODE_MESH )
{
for( i = 0 ; i < m_info.MeshTable.size() ; i++ )
{
m_pDev->SetMaterial( &m_materials[m_nodes[m_info.MeshTable[i]]->GetMaterialID()].material );
m_pDev->SetTexture( 0, m_materials[m_nodes[m_info.MeshTable[i]]->GetMaterialID()].pTex[0] ); 
m_pDev->SetTransform( D3DTS_WORLD, &m_matTM );
m_nodes[m_info.MeshTable[i]]->Draw();
}
}
else
if( nNodeType == ZNode::NODE_BONE )
{
for( i = 0 ; i < m_info.BoneTable.size() ; i++ )
{
m_pDev->SetMaterial( &m_materials[m_nodes[m_info.BoneTable[i]]->GetMaterialID()].material );
m_pDev->SetTexture( 0, m_materials[m_nodes[m_info.BoneTable[i]]->GetMaterialID()].pTex[0] ); 
m_pDev->SetTransform( D3DTS_WORLD, &m_matTM );
m_nodes[m_info.BoneTable[i]]->Draw();
}
}
else // NODE_NODE
{
for( i = 0 ; i < m_nodes.size() ; i++ )
{
m_pDev->SetMaterial( &m_materials[m_nodes[i]->GetMaterialID()].material );
m_pDev->SetTexture( 0, m_materials[m_nodes[i]->GetMaterialID()].pTex[0] ); 
m_pDev->SetTransform( D3DTS_WORLD, &m_matTM );
m_nodes[i]->Draw();
}
}

return 1;
}

원래는 훨씬 간단하게 10줄 내외로 만들수도 있었지만, 이 클래스의 개발 목적은 실무용이 아닌 학습용이기 때문에 정확한 if와 for문으로 만들었다. 출력하는 내용이 뼈대인지, 메시인지, 노드전체인지를 판단해서 출력한다.

int ZNodeMgr::DrawBBox( int nNodeType )
{
int i;

m_pDev->SetRenderState( D3DRS_FILLMODE, D3DFILL_WIREFRAME );
for( i = 0 ; i < m_nodes.size() ; i++ )
{
m_pDev->SetTransform( D3DTS_WORLD, &m_matTM );
if( nNodeType == ZNode::NODE_NODE ) m_nodes[i]->DrawBBox();
else if( nNodeType == ZNode::NODE_BONE ) { if( m_nodes[i]->GetNodeType() == ZNode::NODE_BONE ) m_nodes[i]->DrawBBox(); }
else if( nNodeType == ZNode::NODE_MESH ) { if( m_nodes[i]->GetNodeType() == ZNode::NODE_MESH ) m_nodes[i]->DrawBBox(); }
}
m_pDev->SetRenderState( D3DRS_FILLMODE, D3DFILL_SOLID );

return 1;
}


변경사항 - ZBone
ZBone은 Draw() 함수가 변경되었다. 우너래 뼈대를 그린다는 것은 의미도 없고 가능할수도 없지만, 개발중에는 뼈대가 제대로 연산되는지 디버깅용으로 출력할 때가 있다, 이를 위해 만들어진 함수가 Draw() 이다.

int ZBone::Draw()
{

BOXVERTEX vtx[8];
vtx[0] = BOXVERTEX( -1,  1,  1 , 0xffff0000 ); /// v0
vtx[1] = BOXVERTEX(  1,  1,  1 , 0xffff0000 ); /// v1
vtx[2] = BOXVERTEX(  1,  1, -1 , 0xffff0000 ); /// v2
vtx[3] = BOXVERTEX( -1,  1, -1 , 0xffff0000 ); /// v3
vtx[4] = BOXVERTEX( -1, -1,  1 , 0xffff0000 ); /// v4
vtx[5] = BOXVERTEX(  1, -1,  1 , 0xffff0000 ); /// v5
vtx[6] = BOXVERTEX(  1, -1, -1 , 0xffff0000 ); /// v6
vtx[7] = BOXVERTEX( -1, -1, -1 , 0xffff0000 ); /// v7

Index3w idx[12] = 
{ 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 } /// 뒷면
};

m_pDev->MultiplyTransform( D3DTS_WORLD, &m_matTM );
m_pDev->SetFVF( BOXVERTEX::FVF );
m_pDev->DrawIndexedPrimitiveUP( D3DPT_TRIANGLELIST, 0, 8, 12, idx, D3DFMT_INDEX16, vtx, sizeof BOXVERTEX );

// ZNode::DrawBBox();
return 1;
}


'Graphics > DirectX' 카테고리의 다른 글

Direct X - 스키닝  (0) 2014.10.21
Direct X - 계층구조 2  (0) 2014.09.30
Direct X - 계층구조  (0) 2014.09.16
Direct X - 스크립트와 XML파서  (0) 2014.09.05
Direct X - 애니메이션 기법  (0) 2014.09.03
Posted by 긍정왕오킹