id Software published no specification for MD5. The format is defined by the loader in
the Doom 3 source code, released under the GPL in 2011 (Model_md5.cpp),
which this page follows. An .md5mesh file holds the skeleton in its bind
pose and one or more meshes skinned to it.
Tokens
The file is text, read by the engine’s general tokenizer. Tokens are separated by any
white space, so line breaks and indentation carry no meaning. Names and paths are in
double quotes. Numbers are written in decimal. // starts a comment to the
end of the line and /* … */ encloses one. Vectors are numbers in
parentheses, such as ( 0 1.5 -2 ). Keywords must appear in exactly the
order below; the engine stops with an error at the first unexpected token.
Header
| Keyword | Value | Meaning |
|---|---|---|
MD5Version | integer | Always 10; any other version is refused |
commandline | string | The exporter options used to make the file; ignored when loading |
numJoints | integer | Joints in the joints block |
numMeshes | integer | Number of mesh blocks |
Joints
joints {, then one line per joint, then }. Each joint
is:
"name" parent ( x y z ) ( qx qy qz )
| Field | Type | Meaning |
|---|---|---|
| name | string | Joint name, such as "origin" or "Lknee" |
| parent | integer | Number of the parent joint, counted from 0, or −1 for the root. A parent must come earlier in the list |
( x y z ) | 3 floats | Position of the joint in model space |
( qx qy qz ) | 3 floats | Orientation in model space: the x, y and z parts of a unit quaternion |
The fourth part, w, is not stored. Doom 3 computes it as
w = √|1 − qx² − qy² − qz²|, never negative. Loaders written with other
maths libraries often use the negative root instead, to match their own quaternion
convention. Note that mesh-file joints are
in model space, while the joints in an animation
file are relative to their parents.
Meshes
Each mesh is mesh { … } with these parts in order:
| Keyword | Value | Meaning |
|---|---|---|
name | string | Optional mesh name |
shader | string | The material, a Doom 3 material name such as "models/monsters/imp/imp" |
numverts | integer | Vertices that follow |
vert | see below | One per vertex |
numtris | integer | Triangles that follow |
tri | see below | One per triangle |
numweights | integer | Weights that follow |
weight | see below | One per weight |
Vertex
vert index ( s t ) startWeight countWeight
| Field | Type | Meaning |
|---|---|---|
| index | integer | Vertex number; read and ignored, since vertices are taken in order |
( s t ) | 2 floats | Texture coordinates, 0 to 1 across the image |
| startWeight | integer | Number of the vertex’s first weight |
| countWeight | integer | How many consecutive weights it uses, at least 1 |
Triangle
tri index v0 v1 v2
The index is read and ignored; v0, v1 and v2 are
vertex numbers in this mesh.
Weight
weight index joint bias ( x y z )
| Field | Type | Meaning |
|---|---|---|
| index | integer | Weight number; read and ignored |
| joint | integer | Joint number, 0 to numJoints − 1 |
| bias | float | How much this weight counts; a vertex’s biases add up to 1 |
( x y z ) | 3 floats | Position in the joint’s own space |
Computing the vertices
A vertex’s position is the sum over its weights of the weight’s position carried into model space by its joint:
position = Σ bias × ( joint.position + joint.orientation · weight.position )
countWeight weights from startWeight.With the joints of the bind pose this gives the model at rest; with the joints of an animation frame it gives the animated model. The file has no normals: Doom 3 computes smooth normals and tangents from the triangles.
Worked example: a vertex with one weight on joint 0, bias 1, at
( 0 0 8 ), with joint 0 at ( 0 0 32 ) and orientation
( 0 0 0 ) (w = 1, no rotation), sits at (0, 0, 40). Two weights of 0.5 on
joints at heights 0 and 10, each with position ( 0 0 0 ), put a vertex at
height 5.