<!-- /* Font Definitions */ @font-face {font-family:宋体; panose-1:2 1 6 0 3 1 1 1 1 1; mso-font-alt:SimSun; mso-font-charset:134; mso-generic-font-family:auto; mso-font-pitch:variable; mso-font-signature:3 135135232 16 0 262145 0;} @font-face {font-family:"/@宋体"; panose-1:2 1 6 0 3 1 1 1 1 1; mso-font-charset:134; mso-generic-font-family:auto; mso-font-pitch:variable; mso-font-signature:3 135135232 16 0 262145 0;} /* Style Definitions */ p.MsoNormal, li.MsoNormal, div.MsoNormal {mso-style-parent:""; margin:0cm; margin-bottom:.0001pt; mso-pagination:none; font-size:10.5pt; mso-bidi-font-size:12.0pt; font-family:"Times New Roman"; mso-fareast-font-family:宋体; mso-font-kerning:1.0pt;} p.MsoBodyText, li.MsoBodyText, div.MsoBodyText {margin-top:0cm; margin-right:0cm; margin-bottom:6.0pt; margin-left:0cm; mso-pagination:none; font-size:10.5pt; mso-bidi-font-size:12.0pt; font-family:"Times New Roman"; mso-fareast-font-family:宋体; mso-font-kerning:1.0pt;} p.MsoBodyTextFirstIndent, li.MsoBodyTextFirstIndent, div.MsoBodyTextFirstIndent {mso-style-update:auto; mso-style-parent:正文文本; mso-style-link:" Char Char"; margin:0cm; margin-bottom:.0001pt; text-indent:19.85pt; mso-pagination:none; font-size:10.5pt; font-family:"Times New Roman"; mso-fareast-font-family:宋体; mso-ansi-language:ZH-CN;} p.a, li.a, div.a {mso-style-name:正文(首行不缩进); margin:0cm; margin-bottom:.0001pt; line-height:150%; mso-pagination:none; mso-layout-grid-align:none; text-autospace:none; font-size:10.5pt; mso-bidi-font-size:10.0pt; font-family:"Times New Roman"; mso-fareast-font-family:宋体;} span.CharChar {mso-style-name:" Char Char"; mso-style-locked:yes; mso-style-link:正文首行缩进; mso-ansi-font-size:10.5pt; mso-bidi-font-size:10.5pt; font-family:宋体; mso-fareast-font-family:宋体; mso-ansi-language:ZH-CN; mso-fareast-language:ZH-CN; mso-bidi-language:AR-SA;} /* Page Definitions */ @page {mso-page-border-surround-header:no; mso-page-border-surround-footer:no;} @page Section1 {size:612.0pt 792.0pt; margin:72.0pt 90.0pt 72.0pt 90.0pt; mso-header-margin:36.0pt; mso-footer-margin:36.0pt; mso-paper-source:0;} div.Section1 {page:Section1;} -->
md2文件是 Quake2使用的模型文件,如图 Quake使用的 mdl文件一样。一个单独的 md2文件通常包含了模型的几何信息、帧信息、皮肤文件名和纹理坐标等。而对应的纹理贴图文件等则单独存在,供程序调用时使用。
md2 文件的文件头结构如下
typedef struct
{
int magic; // This is used to identify the file
int version; // The version number of the file (Must be 8)
int skinWidth; // The skin width in pixels
int skinHeight; // The skin height in pixels
int frameSize; // The size in bytes the frames are
int numSkins; // The number of skins associated with the model
int numVertices; // The number of vertices (constant for each frame)
int numTexCoords; // The number of texture coordinates
int numTriangles; // The number of faces (polygons)
int numGlCommands; // The number of gl commands
int numFrames; // The number of animation frames
int offsetSkins; // The offset in the file for the skin data
int offsetTexCoords; // The offset in the file for the texture data
int offsetTriangles; // The offset in the file for the face data
int offsetFrames; // The offset in the file for the frames data
int offsetGlCommands; // The offset in the file for the gl commands data
int offsetEnd; // The end of the file offset
}md2_header_t;
其中 magic 用于标识 md2 文件 ,共四个字节, 取值是 0x32504449 , 它其实是字符串 “ IDP2 ” (id polygon 2) 。 version是 md2文件的版本号,必须是 8 。 skinWidth和 skinHeight分别表示皮肤的宽度和高度,以像素为单位。 frameSize表示每一帧的大小,单位是字节。
numVertices表示每一帧的顶点数。
offsetSkins、 offsetTexCoords、 offsetTriangles、 offsetFrames、 offsetGlCommands分别表示是皮肤名、纹理坐标数据、三角形数据、帧列表数据、 gl命令列表数据在文件中的的偏移量。
offsetEnd表示从文件头到文件结尾的偏移量,实际上就是文件的大小。
<!-- /* Font Definitions */ @font-face {font-family:宋体; panose-1:2 1 6 0 3 1 1 1 1 1; mso-font-alt:SimSun; mso-font-charset:134; mso-generic-font-family:auto; mso-font-pitch:variable; mso-font-signature:3 135135232 16 0 262145 0;} @font-face {font-family:黑体; panose-1:2 1 6 0 3 1 1 1 1 1; mso-font-alt:SimHei; mso-font-charset:134; mso-generic-font-family:auto; mso-font-pitch:variable; mso-font-signature:1 135135232 16 0 262144 0;} @font-face {font-family:"/@宋体"; panose-1:2 1 6 0 3 1 1 1 1 1; mso-font-charset:134; mso-generic-font-family:auto; mso-font-pitch:variable; mso-font-signature:3 135135232 16 0 262145 0;} @font-face {font-family:"/@黑体"; panose-1:2 1 6 0 3 1 1 1 1 1; mso-font-charset:134; mso-generic-font-family:auto; mso-font-pitch:variable; mso-font-signature:1 135135232 16 0 262144 0;} /* Style Definitions */ p.MsoNormal, li.MsoNormal, div.MsoNormal {mso-style-parent:""; margin:0cm; margin-bottom:.0001pt; mso-pagination:none; font-size:10.5pt; mso-bidi-font-size:12.0pt; font-family:"Times New Roman"; mso-fareast-font-family:宋体; mso-font-kerning:1.0pt;} h1 {mso-style-parent:""; mso-style-next:"标题 2"; margin-top:12.0pt; margin-right:0cm; margin-bottom:12.0pt; margin-left:21.6pt; text-align:justify; text-justify:inter-ideograph; text-indent:-21.6pt; mso-pagination:widow-orphan; page-break-after:avoid; mso-outline-level:1; mso-list:l0 level1 lfo1; tab-stops:list 21.6pt; font-size:16.0pt; font-family:Arial; mso-fareast-font-family:黑体; mso-bidi-font-family:"Times New Roman"; mso-font-kerning:0pt; mso-bidi-font-weight:normal;} h2 {mso-style-parent:""; mso-style-next:正文; margin-top:12.0pt; margin-right:0cm; margin-bottom:12.0pt; margin-left:28.8pt; text-align:justify; text-justify:inter-ideograph; text-indent:-28.8pt; mso-pagination:widow-orphan; page-break-after:avoid; mso-outline-level:2; mso-list:l0 level2 lfo1; tab-stops:list 28.8pt; font-size:12.0pt; font-family:Arial; mso-fareast-font-family:黑体; mso-bidi-font-family:"Times New Roman"; font-weight:normal;} h3 {mso-style-next:正文; margin-top:13.0pt; margin-right:0cm; margin-bottom:13.0pt; margin-left:36.0pt; text-indent:-36.0pt; line-height:173%; mso-pagination:lines-together; page-break-after:avoid; mso-outline-level:3; mso-list:l0 level3 lfo1; tab-stops:list 36.0pt; font-size:12.0pt; mso-bidi-font-size:16.0pt; font-family:"Times New Roman"; mso-fareast-font-family:黑体; mso-font-kerning:1.0pt; font-weight:normal; mso-bidi-font-weight:bold;} p.MsoBodyText, li.MsoBodyText, div.MsoBodyText {margin-top:0cm; margin-right:0cm; margin-bottom:6.0pt; margin-left:0cm; mso-pagination:none; font-size:10.5pt; mso-bidi-font-size:12.0pt; font-family:"Times New Roman"; mso-fareast-font-family:宋体; mso-font-kerning:1.0pt;} p.MsoBodyTextFirstIndent, li.MsoBodyTextFirstIndent, div.MsoBodyTextFirstIndent {mso-style-update:auto; mso-style-parent:正文文本; mso-style-link:" Char Char"; margin:0cm; margin-bottom:.0001pt; text-indent:19.85pt; mso-pagination:none; font-size:10.5pt; font-family:"Times New Roman"; mso-fareast-font-family:宋体; mso-ansi-language:ZH-CN;} a:link, span.MsoHyperlink {color:blue; text-decoration:underline; text-underline:single;} a:visited, span.MsoHyperlinkFollowed {color:purple; text-decoration:underline; text-underline:single;} p.a, li.a, div.a {mso-style-name:正文(首行不缩进); margin:0cm; margin-bottom:.0001pt; line-height:150%; mso-pagination:none; mso-layout-grid-align:none; text-autospace:none; font-size:10.5pt; mso-bidi-font-size:10.0pt; font-family:"Times New Roman"; mso-fareast-font-family:宋体;} span.CharChar {mso-style-name:" Char Char"; mso-style-locked:yes; mso-style-link:正文首行缩进; mso-ansi-font-size:10.5pt; mso-bidi-font-size:10.5pt; font-family:宋体; mso-fareast-font-family:宋体; mso-ansi-language:ZH-CN; mso-fareast-language:ZH-CN; mso-bidi-language:AR-SA;} /* Page Definitions */ @page {mso-page-border-surround-header:no; mso-page-border-surround-footer:no;} @page Section1 {size:612.0pt 792.0pt; margin:72.0pt 90.0pt 72.0pt 90.0pt; mso-header-margin:36.0pt; mso-footer-margin:36.0pt; mso-paper-source:0;} div.Section1 {page:Section1;} /* List Definitions */ @list l0 {mso-list-id:1666475049; mso-list-template-ids:-28945502;} @list l0:level1 {mso-level-style-link:"标题 1"; mso-level-text:%1; mso-level-tab-stop:21.6pt; mso-level-number-position:left; margin-left:21.6pt; text-indent:-21.6pt;} @list l0:level2 {mso-level-style-link:"标题 2"; mso-level-text:"%1/.%2"; mso-level-tab-stop:28.8pt; mso-level-number-position:left; margin-left:28.8pt; text-indent:-28.8pt;} @list l0:level3 {mso-level-style-link:"标题 3"; mso-level-text:"%1/.%2/.%3"; mso-level-tab-stop:36.0pt; mso-level-number-position:left; margin-left:36.0pt; text-indent:-36.0pt;} @list l0:level4 {mso-level-tab-stop:1.0cm; mso-level-number-position:left; margin-left:46.8pt; text-indent:-34.0pt;} @list l0:level5 {mso-level-text:%5); mso-level-tab-stop:1.0cm; mso-level-number-position:left; margin-left:46.8pt; text-indent:-34.0pt;} @list l0:level6 {mso-level-number-format:alpha-lower; mso-level-text:%6); mso-level-tab-stop:1.0cm; mso-level-number-position:left; margin-left:46.8pt; text-indent:-34.0pt;} @list l0:level7 {mso-level-number-format:roman-lower; mso-level-text:%7; mso-level-tab-stop:1.0cm; mso-level-number-position:left; margin-left:46.8pt; text-indent:-34.0pt;} @list l0:level8 {mso-level-text:"%1/.%2/.%3/.%4/.%5/.%6/.%7/.%8"; mso-level-tab-stop:72.0pt; mso-level-number-position:left; margin-left:72.0pt; text-indent:-72.0pt;} @list l0:level9 {mso-level-text:"%1/.%2/.%3/.%4/.%5/.%6/.%7/.%8/.%9"; mso-level-tab-stop:79.2pt; mso-level-number-position:left; margin-left:79.2pt; text-indent:-79.2pt;} ol {margin-bottom:0cm;} ul {margin-bottom:0cm;} -->
1.1.1 帧
在 Quake2和 Quake中,模型是由三角形组成的,每个三角形是由三个顶点组成,而每一帧就包含了这些顶点在 3D空间中的位置。
typdef struct
{
byte vertex[3];
byte lightNormalIndex;
}triangleVertex_t;
vertex[3]表示顶点的 x, y, z坐标,这里的坐标不是真实的坐标,而是一个经过处理的值,目的是让每一个值能够压缩在一个字节中。这是 id Software为了减少模型占用空间的一种做法。要把这些坐标还原成真实坐标,需要乘以 frame_t结构中的对应的比例因子 scale,再加上 frame_t结构中的对应的平移量 tranlation,得到的结果就是该顶点相对于模型原点的坐标值。模型的原点是 (0, 0, 0)。 lightNormalIndex表示光法向量索引,索引表由 Quake2提供。
下面是帧的结构,不同 md2 文件的帧结构大小是不定的,但在同一个 md2 文件中,帧结构大小相同。
typedef struct
{
float scale[3];
float translate[3];
char name[16];
triangleVertex_t vertices[1];
}frame_t;
float scale[3] 用于对 triangleVertex_t 结构中的 vertex 进行比例调整。 float translate[3] 用于对 triangleVertex_t 结构中的 vertex 进行平移变换。 char name[16]表示帧的名字。 而 triangleVertex_t vertices[1]表示顶点坐标,但是需要经过变换才是真实的坐标值。
1.1.2 三角形
md2文件从偏移量 offsetTriangles处开始就是三角形数据,每一个三角形的结构如下。
typedef struct
{
short vertexIndices[3];
short textureIndices[3];
} triangle_t;
vertexIndices是顶点索引,指向每帧的顶点数据。换句话说,在一个 md2文件中三角形的数目是固定的,每一个三角形一直是由帧中的某三个顶点组成的。所以,在每一帧,三角形自身保持不变,仅仅顶点在移动。 textureIndices表示纹理坐标,和vertexIndices类似。
1.1.3 纹理
在 md2 文件偏移量为 offsetSkins 处保存的是 numSkins 个皮肤数据,每一个皮肤名为 64 个字节的字符串,它所在的路径是相对于 Quake2 主程序路径的相对路径,对“标准的”的 Quake2 来说,其路径为 baseq2 ,纹理文件都是 pcx 文件。 纹理需要使用纹理坐标才能映射到模型上,纹理坐标的结构如下。
typedef struct
{
short s, t;
} textureCoordinate_t;
和 glCommandVertex 中的 s 和 t 不同,这里的 s 和 t 用于将顶点映射到皮肤上,其中 s 是水平方向上值, t 是垂直方向上的值。 0 <= s < skinWidth , 0 <= s < skinHeight。
1.1.4 GL 命令
在 md2 文件的偏移量 offsetGlCommands处,存储的是 gl命令列表。 gl命令列表由一些包含整数、浮点数的组组成,每一个组开始于一个整数,如果该整数是正的,那么接下来就是一些 glCommandVertex_t结构,组成一个三角形链 triangle strip(三角形链是一些三角形连在一起,相邻的两个三角形共用一个边,即 n+2个节点可以表示 n个三角形);如果该整数为负数,则接下来的一些 glCommandVertex_t结构组成一个三角扇形 triangle fan(如果有一个点为所有的三角形共用就形成了三角扇形共用点为扇形的圆心。所以,如果用三角形链或三角扇形来确定物体的一个表面就可以节约节点的数目);如果该整数为 0则表示到了命令列表的的结尾。 gl命令列表是为了在使用 OpenGL进行渲染时进行优化,提高效率。 命令列表的结构如下。
typedef struct
{
float s, t;
int vertexIndex;
} glCommandVertex_t;
和 textureCoordinate_t 结构中的 s 、 t 不同的是 , 这里的 s 、 t 取值范围是 0.0 ~ 1.0 , 并且是浮点数 , 便于 Quake2 向 OpenGL 中传递参数。 vertexIndex表示每一帧中顶点的索引。
在 Quake2中对于组成模型的元素有限制,其中每一个模型最多支持三角形 4096个,顶点是 2048个,纹理坐标 2048个,帧 512,皮肤 32个。