메모장_SMD

@ 2004-08-03 @

  SMD(Half-Life File Format)

# 본 애니는 스티칭 애니와 스키닝 애니 두개로 나눌 수 있는데,
스티칭 애니의 대표적인 파일포멧이 SMD 파일 포맷으로 벨브사가 하프라이프 SDK를 공개하면서 같이 공개된 포멧이다.
스티칭 애니는 점 하나에 본하나가 대응되는 형태로
관절부분이 애니때 접히거나 뽀족하게 될 경우도 많지만, 버텍스하나에 매트릭스 하나이기에 속도는 빠르다.

# SMD 매우 명료한 정보들만 가지는 장점을 가지고 있지만
본의 갯수라던가, 삼각형의 갯수 등 MOD파일의 해더성 정보 부분이 없기 때문에 중복점을 제거하는 버텍스 인덱스화나 같은 텍스쳐를 묶어서 렌더링하는 조작 등의 최적화가 필요하다.

# SMD의 자료구조는 크게 3부분으로 나뉜다.
본노드 / 스켈레톤 정보 / 매쉬 정보

이러한 정보를 SMD는 두가지 파일로 나뉘어 저장하는데
레퍼런스 파일에는 위의 3가지 자료구조를 모두 가지고 있으며,
애니메이션 파일에는 매쉬 정보가 빠진, 스켈레톤 정보가 프레임수만큼 나와있다.
0) 공통구성data : bone의 node구성
1) 레퍼런스 파일; 0프레임의 bone position/rotation data triangle,texture map data
2) 애니메이션 파일; 모든 프레임의 bone position/rotation data

# 섹션별 구성
Header ----------------------------------------
“Version 1” 버전을 표시

nodes ----------------------------------------
Node tree 뼈의 모든 본의 목록을 나타낸다. - 부모가 없는 Objects는 부모의 ID가 -1이다.

<ID> “Bone Name” <Parent ID>

end

Skeleton --------------------------------------
Time 0
<ID><PosX><PosY><PosZ><RotX><RotY<RotZ>

<ID> ® Bone index,
<PosX><PosY><PosZ> ® 부모에서 부터의 Local 위치값
<RotX><RotY<RotZ> ® Local Euler 라디안 회전값,부모의 local 회전값
- 부모로 부터 <PosX><PosY><PosZ> 떨어진 점의(회전이 없는 원래 좌표)
<RotX><RotY><RotZ> 회전값
...

time 1 애니메이션의 경우 time 블록이 추가
...
end

Triangles --------------------------------------
Triangles 리스트

“bitmapname.bmp”
<Parent><PosX><PosY><PosZ><NormX><NormY><NormZ><TexU><TexV>

<Parent> ® vertex의 부모 bone의 ID번호
<PosX><PosY><PosZ> ® 월드 좌표계에서의 vertex위치값
<NormX><NormY><NormZ> ® 스무싱을 위한 vertex normal vector
<TexU><TexV> ® vertex의 texture 좌표값
버텍스 3개씩 기록
...
end

▷ Node의 Hierarchy 구조
Vertex Morphing 방식처럼, 매 프레임마다 삼각형의 모든 정보를 갖는 것이 아니라, 단지 Bone의 위치값과 회전값을 갖는 애니메이션이다. 이를 위해서 모든 본은 하이어라키 구조( 계층구조)로 이루어져 있으며, 자식 본은 부모 본을 따라 움직이게 된다. 즉, 최상위에서 최하위로 이어지는 노드(node)만 알고 있으면 쉽게 에니메이션을 구현할 수 있는데, 자식 본은 3차원 공간상의 부모 본을 중심으로 회전해야 한다.
그러기 위해서는, 임의축 회전을 가능케 하는 Matrix가 필요한데, 이것을 해결해 주는 것이 Half_Life SDK에 포함되어 있는 AngleMatrix 함수이다. 이 함수는 3차원 공간상의 하나의 벡터를 임의의 축에 대해 회전시킬 수 있도록 하는 Matrix를 만들어 준다.

#.회전 Matrix
typedef float vec_t;
typedef vec_t vec3_t[3]; // x,y,z

void AngleMatrix( const vec3_t angles, float (*matrix)[4] )
{
float angle;
float sr, sp, sy, cr, cp, cy;

angle = angles[2] * (Q_PI*2 / 360);
sy = sin(angle);
cy = cos(angle);


angle = angles[1] * (Q_PI*2 / 360);
sp = sin(angle);
cp = cos(angle);


angle = angles[0] * (Q_PI*2 / 360);
sr = sin(angle);
cr = cos(angle);

// matrix = (Z * Y) * X
matrix[0][0] = cp*cy;
matrix[1][0] = cp*sy;
matrix[2][0] = -sp;
matrix[0][1] = sr*sp*cy+cr*-sy;
matrix[1][1] = sr*sp*sy+cr*cy;
matrix[2][1] = sr*cp;
matrix[0][2] = (cr*sp*cy+-sr*-sy);
matrix[1][2] = (cr*sp*sy+-sr*cy);
matrix[2][2] = cr*cp;
matrix[0][3] = 0.0;
matrix[1][3] = 0.0;
matrix[2][3] = 0.0;
}

#.월드 좌표 결정
이제, 위의 함수로 만든 Matrix를 통해 자식 본을 부모 본을 중심으로 회전시킨 후, 부모 본으로부터의 상대적 위치값을 부모 본의 월드 좌표에 더하여 자식 본의 월드 좌표를 결정한다. 이렇게 더하는 이유는 SMD File의 Skeleton으로부터 단지 부모와 자식사이의 상대적 위치값과 회전값만이 Export되기 때문이다.( SMD는 2개로 Export된다. 하나는 Reference, 하나는 Skeleton. Reference로부터 노드, 삼각형, 기준 본에 관한 정보를 얻고, Skeleton으로부터 본 에니메이션에 관한 정보를 얻는다. )
그 다음에, 자식 본은 그것에 연결된 또다른 본의 부모 본이 되고, 이런 식으로 노드를 따라 최하위 본까지의 월드 좌표를 만들게 된다.

#.Data 구조
MAX에서 SMD File을 Export할 때, 모든 vertex들은 World 좌표로 저장된다. 그러나,모든 Bone은 부모에 대한 상대 좌표로 저장되어 있으므로 이를 같도록 해야 한다.
AngleIMatrix함수는 vertex를 그것이 속한 본에 대한 좌표로 만들어 주는 역할을 한다. 이렇게 함으로써 모든 좌표는 매 프레임마다 항상 그것이 속한 본을 따라 움직이게 된다.
이와 같이 한번만 Vertex를 본에 귀속시키면, 모든 프레임 마다 Vertex들에 대한 정보를 알 필요가 없게 되고, 단지 프레임 마다 에니메이션에 따라 변하는 Bone의 위치값 및 회전값에 대한 정보만 필요할 뿐이다. Vertex Morphing과 비교해 Data량이 상당히 줄어 든다는 것을 알 수 있을 것이다.
==> Max에서 Export되기 때문에 내부적으로 OpenGL 방식으로 처리하고
D3D방식으로 출력할 때는 최종 좌표에서 y,z를 바꿔준다.

#.참고 함수
*.D3DXMatrixRotationYawPitchRoll 함수 ; 요, 피치, 및 롤을 지정해 행렬을 생성 한다.
D3DXMATRIX *D3DXMatrixRotationYawPitchRoll(
D3DXMATRIX *pOut, - [in, out] 연산 결과인 D3DXMATRIX 구조체의 포인터.
FLOAT Yaw, - in y 축을 중심으로 하는 요 (라디안 단위).
FLOAT Pitch, - in x 축을 중심으로 하는 피치 (라디안 단위).
FLOAT Roll ); - in z 축을 중심으로 하는 롤 (라디안 단위).
반환값 ; 지정된 요·피치·롤을 가지는 D3DXMATRIX 구조체의 포인터.

*.D3DXMatrixInverse 함수 ; 행렬의 역행열을 계산한다.
D3DXMATRIX *D3DXMatrixInverse(
D3DXMATRIX *pOut, - [in, out] 연산 결과인 D3DXMATRIX 구조체의 포인터.
FLOAT *pDeterminant, - [in, out] 행렬의 행렬식을 포함한 FLOAT 값의 포인터.
행렬식이 불요의 경우는, 이 파라미터에 NULL 를 설정한다.
CONST D3DXMATRIX *pM ); - in 처리의 기본으로 되는 D3DXMATRIX 구조체의 포인터.
반환값 ; 행렬의 역행열인 D3DXMATRIX 구조체의 포인터.
역행열의 계산이 실패했을 경우는, NULL 를 돌려준다.
이 함수의 반환값은,pOut 파라미터의 반환값과 같다. 따라서,D3DXMatrixInverse 함수를 다른 함수의 인수로서 사용할 수 있다.

*.D3DXMatrixTranspose 함수 ; 행렬의 전치행렬을 돌려준다.
D3DXMATRIX *D3DXMatrixTranspose(
D3DXMATRIX *pOut, - [in, out] 연산 결과인 D3DXMATRIX 구조체의 포인터.
CONST D3DXMATRIX *pM ); - 처리의 기본으로 되는 D3DXMATRIX 구조체의 포인터.
반환값 ; 행렬의 전치행렬인 D3DXMATRIX 구조체의 포인터.

*.D3DXVec3Transform 함수 ; 지정된 행렬에 의해 벡터 (x, y, z, 1)를 변환 한다.
D3DXVECTOR4 *WINAPI D3DXVec3Transform(
D3DXVECTOR4 *pOut, - pOut [in, out] 연산 결과인 D3DXVECTOR4 구조체의 포인터.
CONST D3DXVECTOR3 *pV, - in 처리의 기본으로 되는 D3DXVECTOR3 구조체의 포인터.
CONST D3DXMATRIX *pM ); - in 처리의 기본으로 되는 D3DXMATRIX 구조체의 포인터.
반환값 ; 변환 된 벡터의 D3DXVECTOR4 구조체의 포인터.

*.3DXMatrixMultiply 함수 ; 2 개의 행렬의 적을 계산한다.
D3DXMATRIX *D3DXMatrixMultiply(
D3DXMATRIX *pOut, - [in, out] 연산 결과인 D3DXMATRIX 구조체의 포인터.
CONST D3DXMATRIX *pM1, - in 처리의 기본으로 되는 D3DXMATRIX 구조체의 포인터.
CONST D3DXMATRIX *pM2 ); - in 처리의 기본으로 되는 D3DXMATRIX 구조체의 포인터.
반환값 ; 2 개의 행렬의 적인 D3DXMATRIX 구조체의 포인터.

D3DXMatrixRotationYawPitchRoll 함수는 회전으로 행렬을 만들 때,
Roll (Z) * Pitch(X) * Yaw(Y) 순서로 계산
▷ 오른손 좌표계에서는
Yaw는 Z축에 대한 반시계방향 회전
Pitch는 Y축에 대한 반시계방향 회전
Roll은 X축에 대한 반시계방향 회전
고로, Z * Y * X 순으로 곱하여 회전 행렬식을 만든다.

{_r} 메모장으로 돌아가기