Ilya Seletsky - Skinned Animations

The video!!!

Note: if the video doesn't work, try using VLC or some other video player.

Skinning

Using asset importer I was able to make a model file converter to a custom format for my engine. I can take various formats like Md5, Collada, etc and convert them to my format. The animation video shows some Doom 3 Md5 models converted to load with my engine.

You can download the code on GitHub

Here are the formats for the files as requested.

.illmesh

This is the mesh data file. It's in a nice conventient format to read directly into the VBO and IBO of your application. It's also in ASCII format similar to md5 because it's easier to store float values. Here's the format description:

First comes the magic string at the top of the file:

ILLMESH0

Load this in first and make sure this is the string at the top. If not, the mesh file is probably invalid or a newer version of the illmesh format.

After that comes a set of booleans saying whether or not the mesh contains certain data. If it does, make sure your application accounts for that.

boolean: Has Positions? (most likely always true)
boolean: Has Normals?
boolean: Has Tangents? (this means it also has bitangents)
boolean: Has Texture Coordinates?
boolean: Has Blend Data? (the bone blend indices and blend weights)
boolean: Has Colors?

Then the number of verteces in the Vertex Buffer Object and number of indices in the Index Buffer Object. Use these numbers to allocate the needed memory.

unsigned int: VBO Size
unsigned int: IBO Size (This should be a multiple of 3 since every 3 indices make up a triangle)

The next block just contains the VBO data to read in. It's important to know the order of the data. The data is interleaved to improve Vertex Shader Cache performance and all that technical stuff. I'd recommend you keep your application's VBO in the same format.

For Each Vertex:
  float vec3: position (If has positions)
  vec3: normal (If has normals)
  float vec3: tangent (If has tangents)
  float vec3: bitangent (If has bitangents)
  float vec2: texture coordinate (If has texture coordinates)
  float vec4: bone indices (If has blend data)
  float vec4: blend weights (If has blend data)
  float vec4: color (If has colors>

So if blend data is present for skinning, each vertex allows itself to be affected by up to 4 bones. This is all you'll need in most cases. Doom 3 models typically have up to 2 bones affecting each vertex on average. The bone index corresponds to the bone index in the skeleton that will be described later. The blend weight is how much the corresponding bone's transform relative to the bind pose affects this vertex. The blend weights should all be normalized or else strange results will happen. The nice thing about this format is that no bones or skinning are necessary to render the mesh even if there is blend data. It'll just be in its bind pose.

After this comes the Vertex Index Array. As mentioned earlier, every 3 indices makes up a triangle. This data can go directly into the index buffer object.

For Each Index:
  unsigned int: vertex index

.illskel

This is the skeleton file. The indices of the bones in here should correspond to bone indices in the vertex buffer object of the mesh.

First comes the magic string at the top of the file just like with the mesh file. Make sure this is the first string to make sure the file is correct.

ILLSKEL0

Next comes the number of bones.

unsigned int: number of bones

Next come all of the 4x4 matrices that make up the bind pose for each bone in row major order. (I think) Just make sure the for loop that reads it is in this form if reading into a glm::mat4.

for(unsigned int matRow = 0; matRow < 4; matRow++) {
  for(unsigned int matCol = 0; matCol < 4; matCol++) {
    (*openFile) >> m_bones[bone].m_transform[matCol][matRow];
  }
}

For Each Bone:
  float16: transform matrix

After that comes the bone heirarchy.

For Each Bone:
  int: parent bone index (If -1 or its own index, this is the root bone.)

After that come the bone names which are needed to cross reference which bone is animated from the animation file. Bone names are also really useful for controlling certain things in the game itself, like attaching props that characters hold in their hands.

For Each Bone:
  string: Bone Name

.illanim

This is the animation file.

First comes the magic string at the top of the file just like with the mesh file. Make sure this is the first string to make sure the file is correct.

ILLANIM0

Then come the frame rate, number of frames, and number of animated bones in this file. The number of bones animated may not necessarily correspond to the number of bones in the skeleton. Use the bone names to reference the correct bone in the skeleton.

unsigned int: frame rate
unsigned int: number of frames
unsigned int: number of bones animated

Then come the keyframes for each bone in the form of the position, quaternion, and scale. These are the absolute transforms, not the transforms relative to the bind pose. They are still transforms relative to the parent though so you still need to traverse the bone heirarchy.

For Each Bone:
  string: Bone Name

  For Each keyframe:
    float vec3: position transform
    float quat: rotation quaternion
    float vec3: sclae

Interpolate these in between the keyframes to get smooth animations. Then create the final transform by applying them in this order: position * rotation * scale.