번역: 전병관 ( 2007년 9월 1일 ) - 계속 수정중입니다.
참조: http://tfc.duke.free.fr/
MD2 file format
(Quake 2's models)
Written by David Henry, 19th December of 2004
소개
MD2 model file format 은 id 소프트웨어가 Quake2를 1997년 11월에 출품하면서 소개 되었다. 그것은 이해하기에 정말 쉬운 파일 포멧이다. MD2 모델의 케릭터는 :
- Model의 기하학적 도형 데이타(삼각형) ( Model's geometric data (triangles) )
- 연결된 프레임의 활동만화 ( Frame-by-frame animations )
- 구조화된 데이타 ( GL_TRIANGLE_FAN, GL_TRIANGLE_STRIP 원시데이타 를 그린다 : OpenGL 명령어라고 불린다 )
모델의 표면입히기(texture)는 분리된 파일로 존재한다. 한개의 MD2 모델은 동시에 단 하나의 표면입히기(texture)를 갖는다.
MD2 모델 파일의 확장자는 “md2”이다. MD2 파일은 이진파일의 두 부분으로 나뉜다; header, data. 헤더(머리)는 데이타를 조작하고 사용하기에 필요한 모든 정보를 포함한다.
변수 크기
변수 유형과 크기:
- char: 1 byte
- short: 2 bytes
- int: 4 bytes
- float: 4 bytes
위 자료형은 C언어의 x86 구조와 일치한다. 다른 곳에서 컴파일 할 때 자료형의 크기에 신경을 써야 한다. OS 및 언어별 또는 버젼에 따라서 지원되는 자료형의 크기가 다르기 때문이다.
Endianess issues
MD2파일이 이진 포멧이어서, 반드시 엔디안(endianess)을 다뤄야만 한다. MD2 파일은 리틀-엔디안(x86)로 저장된다. 만약 빅-엔디안 구조물(PowerPC, SPARC, ...)을 타켓으로 한다거나, 또는 간단하게 이식성있는 프로그램을 만들고 싶다면, 파일을 읽을 때 각 단어 또는 더블(2배)-단어에 대해서 적절한 전환을 수행해야만 한다.
The header
헤더는 파일의 시작점에 있는 구조이다.:
/* MD2 header */
struct md2_header_t
{
int ident; /* magic number: "IDP2" */
int version; /* version: must be 8 */
int skinwidth; /* texture width */
int skinheight; /* texture height */
int framesize; /* size in bytes of a frame */
int num_skins; /* number of skins */
int num_vertices; /* number of vertices per frame(프레임별 꼭지점들)*/
int num_st; /* number of 표면입히기(texture) coordinates */
int num_tris; /* number of 삼격형(triangles) */
int num_glcmds; /* number of opengl commands */
int num_frames; /* number of frames(활동만화 동작들) */
int offset_skins; /* offset skin data */
int offset_st; /* offset texture coordinate data */
int offset_tris; /* offset triangle data */
int offset_frames; /* offset frame data */
int offset_glcmds; /* offset OpenGL command data */
int offset_end; /* offset end of file */
};
ident(식별)은 파일의 마법의 숫자이다.
파일 유형을 식별하는데 사용한다. ident(식별)은 반드시
844121161 과 동일하거나 또는 문자열 “IDP2”와 동일해야 한다. 우리는 (('2'<<24) + ('P'<<16) + ('D'<<8) + 'I') 표현식을 사용하면 얻을 수 있다
.
버젼은 파일 포멧 버젼 숫자이며 틀림없이 8이어야 한다.
.
skinwidth
와 skinheight는 각 모델의 표면입히기(
texture) 넓이와 높이이다.
framesize는 프레임과 그 모든 데이타의 바이트(bytes) 크기이다.
num_skins은 모델에 조합된 표면입히기(texture)의 숫자이다.
num_vertices
은 하나의 프레임에 대한 꼭지점의 숫자이다.num_st
은 표면입히기(texture) 좌표계의 숫자이다.num_tris
는 삼각형(triangles)의 숫자이다.num_glcmds
은 OpenGL명령어의 숫자이다.num_frames
은 모델이 갖는 프레임의 숫자이다.
offset_skins 은 파일 시작에서 표면입히기 데이타에 연결하는 것을 바이트로 위치를 지시한다.
offset_st 는 표면입히기(texture) 좌표계 데이타의 위치를 가리킨다.
offset_tris
는 삼각형(triangle) 데이타의 위치를 가리킨다.offset_frames
는 프레임 데이타의 위치를 기리킨다.offset_glcmds
는 OpenGL 명령들의 위치를 가리킨다.offset_end
는 파일의 끝 위치를 가리킨다.
자료형
Vector
벡터, 실수형 좌표계(x, y, z)로 구성된다:
/* Vector */
typedef float vec3_t[3];
표면입히기(Texture) 정보
Texture 정보는 모델을 연결하는 표면입히기 이름의 리스트이다:
/* 표면입히기(Texture) 이름 */
struct md2_skin_t
{
char name[64]; /* texture file name */
};
표면입히기 좌표계(Texture coordinates)
Texture 좌표계는 short 정수형으로 구조화되어 저장된다. 올바른 표면입히기(texture)좌표계를 획득하기 위해서, 반다시 skinwidth 나누기 s, skinheight 나누기 t를 해줘야만 한다:
/* 표면입히기 좌표계(Texture coords) */
struct md2_texCoord_t
{
short s;
short t;
};
삼각형(Triangles)
각 삼각형은 꼭지점을 지시하는 배열과 표면입히기(texture) 좌표계를 지시하는 배열을 갖는다.
/* 삼각형 정보(Triangle info) */
struct md2_triangle_t
{
unsigned short vertex[3]; /* 삼각형의 꼭지점 인덱스 */
unsigned short st[3]; /* tex. coord. indices : 표면입히기. 좌표계. 인덱스 */
};
꼭지점(Vertices)
꼭지점은 “압축된” 3D 좌표계로 구성되어진다, 그것은 1 바이트 좌표로 저장되고, 보통 벡터 인덱스로 관리된다. 보통 벡터 배열은 Quake 2의 anorms.h 파일에 저장되고 실수형 좌표(3 float)의 162 개의 벡터 를 갖는다.
/* Compressed vertex */
struct md2_vertex_t
{
unsigned char v[3]; /* position */
unsigned char normalIndex; /* normal vector index */
};
Frames
프레임들은 그자신과 꼭지점 좌표 리스트에 대한 구체적인 정보들을 갖는데. 정보는 압축된 꼭지점을 풀고, 실좌표를 얻는데 사용되어 진다.
/* Model frame */
struct md2_frame_t
{
vec3_t scale; /* scale factor */
vec3_t translate; /* translation vector */
char name[16]; /* frame name */
struct md2_vertex_t *verts; /* list of frame's vertices */
};
꼭지점 좌표계의 압축을 풀기 위해서, 크기로 각 컴포넌트를 곱하고 각각의 이동 컴포넌트를 더할 필요가 있다:
vec3_t v; /* real vertex coords. */
struct md2_vertex_t vtx; /* compressed vertex */
struct md2_frame_t frame; /* a model frame */
v[i] = (vtx.v[i] * frame.scale[i]) + frame.translate[i];
OpenGL 명령어들
OpenGL 명령어는 정수형(int) 배열에 저장된다. 그것은 문서의 말미에 다룰 것이다.
MD2 파일 렌더링(그리기)
md2_model_t이 모든 당신의 모델 데이타(md2_model_t 객체에 붙어 있는 포인터 *mdl)를 잡고 있는 구조라고 가정하자, 이 코드는 MD2 모델 파일을 어떻게 읽어 오는지를 보여준다:
int
ReadMD2Model (const char *filename, struct md2_model_t *mdl)
{
FILE *fp;
int i;
fp = fopen (filename, "rb");
if (!fp)
{
fprintf (stderr, "Error: couldn't open \"%s\"!\n", filename);
return 0;
}
/* Read header */
fread (&mdl->header, 1, sizeof (struct md2_header_t), fp);
if ((mdl->header.ident != 844121161) ||
(mdl->header.version != 8))
{
/* Error! */
fprintf (stderr, "Error: bad version or identifier\n");
fclose (fp);
return 0;
}
/* Memory allocations */
mdl->skins = (struct md2_skin_t *)
malloc (sizeof (struct md2_skin_t) * mdl->header.num_skins);
mdl->texcoords = (struct md2_texCoord_t *)
malloc (sizeof (struct md2_texCoord_t) * mdl->header.num_st);
mdl->triangles = (struct md2_triangle_t *)
malloc (sizeof (struct md2_triangle_t) * mdl->header.num_tris);
mdl->frames = (struct md2_frame_t *)
malloc (sizeof (struct md2_frame_t) * mdl->header.num_frames);
mdl->glcmds = (int *)malloc (sizeof (int) * mdl->header.num_glcmds);
/* Read model data */
fseek (fp, mdl->header.offset_skins, SEEK_SET);
fread (mdl->skins, sizeof (struct md2_skin_t),
mdl->header.num_skins, fp);
fseek (fp, mdl->header.offset_st, SEEK_SET);
fread (mdl->texcoords, sizeof (struct md2_texCoord_t),
mdl->header.num_st, fp);
fseek (fp, mdl->header.offset_tris, SEEK_SET);
fread (mdl->triangles, sizeof (struct md2_triangle_t),
mdl->header.num_tris, fp);
fseek (fp, mdl->header.offset_glcmds, SEEK_SET);
fread (mdl->glcmds, sizeof (int), mdl->header.num_glcmds, fp);
/* Read frames */
fseek (fp, mdl->header.offset_frames, SEEK_SET);
for (i = 0; i < mdl->header.num_frames; ++i)
{
/* Memory allocation for vertices of this frame */
mdl->frames[i].verts = (struct md2_vertex_t *)
malloc (sizeof (struct md2_vertex_t) * mdl->header.num_vertices);
/* Read frame data */
fread (mdl->frames[i].scale, sizeof (vec3_t), 1, fp);
fread (mdl->frames[i].translate, sizeof (vec3_t), 1, fp);
fread (mdl->frames[i].name, sizeof (char), 16, fp);
fread (mdl->frames[i].verts, sizeof (struct md2_vertex_t),
mdl->header.num_vertices, fp);
}
fclose (fp);
return 1;
}
모델 렌더링(그리기)
여기, 모델 mdl의 프레임 n을 어떻게 그릴 것인가 하는 예제가 있다.:
void
RenderFrame (int n, const struct md2_model_t *mdl)
{
int i, j;
GLfloat s, t;
vec3_t v;
struct md2_frame_t *pframe;
struct md2_vertex_t *pvert;
/* n이 가능 영역에 있는지 확인한다. */
if ((n < 0) || (n > mdl->header.num_frames - 1))
return;
/* 모델의 표면입히기(texture)를 가능케 한다. */
glBindTexture (GL_TEXTURE_2D, mdl->tex_id);
/* 모델을 그린다. */
glBegin (GL_TRIANGLES);
/* 각 삼각형(triangle)을 그린다. */
for (i = 0; i < mdl->header.num_tris; ++i)
{
/* 각 꼭지점을 그린다. */
for (j = 0; j < 3; ++j)
{
pframe = &mdl->frames[n];
pvert = &pframe->verts[mdl->triangles[i].vertex[j]];
/* 표면입히기(texture) 좌표계를 계산한다. */
s = (GLfloat)mdl->texcoords[mdl->triangles[i].st[j]].s / mdl->header.skinwidth;
t = (GLfloat)mdl->texcoords[mdl->triangles[i].st[j]].t / mdl->header.skinheight;
/* OpenGL에 표면입히기(texture)좌표계를 통과시킨다. */
glTexCoord2f (s, t);
/* Normal vector */
glNormal3fv (anorms_table[pvert->normalIndex]);
/* 꼭지점의 실좌표를 계산한다. */
v[0] = (pframe->scale[0] * pvert->v[0]) + pframe->translate[0];
v[1] = (pframe->scale[1] * pvert->v[1]) + pframe->translate[1];
v[2] = (pframe->scale[2] * pvert->v[2]) + pframe->translate[2];
glVertex3fv (v);
}
}
glEnd ();
}
Animation(활동만화)
MD2 모델은 프레임에서 프레임으로 연사되어 애니메이션화 된다. 한 프레임은 한 애니메이션의 스크린샷이다. 덜덜거리고 추한 애니메이션을 회피하려면, 두개의 연속된 프레임의 꼭지점 좌표 사이에 선형 보간법(linear interplocation)을 사용한다(우리가 그리는 현재 프레임은 다음에 그리는 프레임이다). 표준 벡터를 위해 같은 것을 한다:
struct md2_frame_t *pframe1, *pframe2;
struct md2_vertex_t *pvert1, *pvert2;
vec3_t v_curr, v_next, v;
for (/* ... */)
{
pframe1 = &mdl->frames[current];
pframe2 = &mdl->frames[current + 1];
pvert1 = &pframe1->verts[mdl->triangles[i].vertex[j]];
pvert2 = &pframe2->verts[mdl->triangles[i].vertex[j]];
/* ... */
v_curr[0] = (pframe1->scale[0] * pvert1->v[0]) + pframe1->translate[0];
v_curr[1] = (pframe1->scale[1] * pvert1->v[1]) + pframe1->translate[1];
v_curr[2] = (pframe1->scale[2] * pvert1->v[2]) + pframe1->translate[2];
v_next[0] = (pframe2->scale[0] * pvert2->v[0]) + pframe2->translate[0];
v_next[1] = (pframe2->scale[1] * pvert2->v[1]) + pframe2->translate[1];
v_next[2] = (pframe2->scale[2] * pvert2->v[2]) + pframe2->translate[2];
v[0] = v_curr[0] + interp * (v_next[0] - v_curr[0]);
v[1] = v_curr[1] + interp * (v_next[1] - v_curr[1]);
v[2] = v_curr[2] + interp * (v_next[2] - v_curr[2]);
/* ... */
}
v
는 그리기 위한 마지막 꼭지점이다. interp 는 두 프레임 사이에 통계-보간(interpolation:중간값 삽입) 백분율이다.
그것은 0.0~1.0 구간의 float
이다. 그것이 1.0 과 동일 할 때, 현재의 값은 1씩 증가하고 interp 는 0.0으로 다시 초기화
된다. 표면입히기(texture) 좌표계에 삽입할 필요가 없는데 이유는 그것들이 모든 모델 프레임들이 동일 하기 때문이다.
interp가 초당 렌더링되는 프레임의 프로그램의 수(fps)와 관련되어지는 것이 더 바람직하다.
void
Animate (int start, int end, int *frame, float *interp)
{
if ((*frame < start) || (*frame > end))
*frame = start;
if (*interp >= 1.0f)
{
/* Move to next frame */
*interp = 0.0f;
(*frame)++;
if (*frame >= end)
*frame = start;
}
}
OpenGL 명령어 사용하기
OpenGL 명령어는 단지 GL_TRIANGLE_FAN and GL_TRIANGLE_STRIP
primitives를 사용하는 모델을 위한 구조화된 데이타이다. 그것은 꾸러미내에서 읽혀지는 정수(int)형 배열이다:
- 처음 정수형은 신규 primitive를 그리는 꼭지점들의 숫자이다. 그것이 양수의 값이면, primitive는 GL_TRIANGLE_STRIP이다. 그렇지 않으면
GL_TRIANGLE_FAN 이다
. - 다음 정수형들은 거기에 그리기 위한 많은 꼭지점들을 3개의 꾸러미로 가져갈 수 있다. 두 개의 처음은 실수형 좌표에 존재하는 표면입히기(texture) 좌표계이고 세번째는 그리기 위한 꼭지점 인덱스이다.
- 그리기 위한 꼭지점들의 수가 0 일 때, 모델을 그리기는 끝난다.
우리는 그 데이타들의 꾸러미를 구조화해서 생성할 수 있다:
/* GL 명령 꾸러미들 */
struct md2_glcmd_t
{
float s; /* s texture coord. */
float t; /* t texture coord. */
int index; /* vertex index */
};
여기 렌더링(그리기) 알고리즘을 사용하는 것은 옛날 기능보다 낳은 프레임율을 포함한다. 이유는 우리가 GL_TRIANGLES primitieves를 사용않고 GL_TRIANGLE_FAN
과 GL_TRIANGLE_STRIP
primitives 를 사용하고(어떤 것은 GPU 시간보다 덜 사용한다) 표면입히기(texture) 좌표계는 더이상
계산되지 않개 때문이다(skinwidth와 skinheight로 나눌 필요가 없다). 여기에 그것들을 사용하는 예제가 있다:
void
RenderFrameWithGLCmds (int n, const struct md2_model_t *mdl)
{
int i, *pglcmds;
vec3_t v;
struct md2_frame_t *pframe;
struct md2_vertex_t *pvert;
struct md2_glcmd_t *packet;
/* n이 가능 영역에 있는지 확인한다. */
if ((n < 0) || (n > mdl->header.num_frames - 1))
return;
/* 모델의 표면입히기(texture)를 가능케 한다. */
glBindTexture (GL_TEXTURE_2D, mdl->tex_id);
/* 명령 리스트의 시적점 pglcmds 좌표들 */
pglcmds = mdl->glcmds;
/* 모델을 그린다. */
while ((i = *(pglcmds++)) != 0)
{
if (i < 0)
{
glBegin (GL_TRIANGLE_FAN);
i = -i;
}
else
{
glBegin (GL_TRIANGLE_STRIP);
}
/* 이 그룹의 각 꼭지점을 그린다. */
for (/* Nothing */; i > 0; --i, pglcmds += 3)
{
packet = (struct md2_glcmd_t *)pglcmds;
pframe = &mdl->frames[n];
pvert = &pframe->verts[packet->index];
/* OpenGL의 표면입히기(texture) 좌표계를 통과 시킨다. */
glTexCoord2f (packet->s, packet->t);
/* Normal vector */
glNormal3fv (anorms_table[pvert->normalIndex]);
/* 꼭지점의 실좌표를 계산한다.( Calculate vertex real position ) */
v[0] = (pframe->scale[0] * pvert->v[0]) + pframe->translate[0];
v[1] = (pframe->scale[1] * pvert->v[1]) + pframe->translate[1];
v[2] = (pframe->scale[2] * pvert->v[2]) + pframe->translate[2];
glVertex3fv (v);
}
glEnd ();
}
}
상수
여기 최대 치수들을 정의하는 상수값이 있다:
- Maximum number of triangles: 4096
- Maximum number of vertices: 2048
- Maximum number of texture coordinates: 2048
- Maximum number of frames: 512
- Maximum number of skins: 32
- Number of precalculated normal vectors: 162
Sample code: md2.c (16 KB), anorms.h (6.7 KB). No texture mapping.
This document is available under the terms of the GNU Free Documentation License (GFDL)
© David Henry – contact : tfc_duke (AT) club-internet (DOT) fr