Creating a custom model format that supports skeletal animation - feedback wanted

Started by
34 comments, last by Gage64 14 years, 6 months ago
I'm trying to create a custom model format that supports skeletal animation. I want something that's relatively easy to work with, but also something that is usable in a "real" project. I want this to be something that can be useful to other people, both for beginners trying to learn more about skeletal animation (so ease of use is important) and possibly to indie developers working on commercial games. It probably won't be "advanced" enough for AAA titles, but I can live with that. [smile] Here's what I came up with so far:
// General Notes:
// This is a binary format.
// All counts (num vertices/indices/etc.) are signed integers.
// Strings are nul-terminated.
// Matrices have 16 floats and are stored row after row.

header - a version number and maybe an ID string

num vertices
for each vertex
    position - 3 floats
    normal - 3 floats
    tex coords - 2 floats
    weights - 3 floats 
    indices - 4 ubytes

// The default values for unused weights are: 1, 0, 0.

num indices
for each index
    index - ushort

num materials
for each material
    ambient - 4 floats
    diffuse - 4 floats
    specular - 4 floats
    emissive - 4 floats
    specular power - float
    texture name - string[256] (just a texture name, no path)

num parts
for each parts
    vertex start - int
    vertex count - int
    index start - int
    index count - int
    material index - int (-1 if no material)

num bone influences - int (0 - 4)

// Zero bone influences means there's no animation data, that is,
// this is a static model

num palette entries
for each entry
    bone name - string[64]
    offset matrix - matrix

num bones
for each bone
    name - string[64]
    local transform - matrix
    sibling index - int
    child index - int

// The first bone in the array is the root.
// The name can be "(Empty Name)" if the name in the original model was "",
// or "(NULL Name)" if the name in the original model was NULL.
// Sibling/child indices are -1 if there's no sibling/child.

num animations
for each animation
    name - string[64]
    duration - float
    
    num bones participating in this animation sequence
    for each bone
        bone name - string[64]
        
        num rotation keyframes
        for each keyframe
            time - float
            rotation quaternion - 4 floats (x, y, z, w)
        
        num scale keyframes
        for each keyframe
            time - float
            scale vector - 3 floats
        
        num translation keyframes
        for each keyframe
            time - float
            translation vector - 3 floats
I tried to come up with a format that is relatively easy to parse, even if it means the actual file is a bit bigger than it could have been. My experience with skeletal animation and model formats is very limited, so I was hoping to get some feedback, as well as suggestions for improvement. Right now I have a few specific questions, but before that, I should probably mention that I'm also working on a converter that converts .X files to this format, and so most of my knowledge and ideas come from working with .X files and the D3DX animation API. I also plan to write a few demos that show how to load, render and do animation with this format, and I hope to release the source code to both the demos and the converter. Now for the questions: - Should there be an offset table specifying the offset to the vertices/indices/etc. from the beginning of the file? - Should each part store its vertices/indices, or should they be stored in one large vertex/index buffer and each part will store offsets/counts? - The material structure seems too basic to do anything interesting. How should I extend it? Store multiple textures? Store the name of an .fx file? Anything else? - Should the data be stored in several files? One file for the rendering data - vertices, indices, materials, parts, the number of bone influences and possibly the offset transforms (since these are unique to each model?). One file for the "skeleton" data - the bone hierarchy and the array of bone names (this array is used to specify the name of the ith bone, which you need to know to correctly build the matrix palette). One file for the animation data - the keyframes. If you have several characters in a game that all use the same model, you probably want to share the rendering data, but not the skeleton data, since each character animates independently from the others. I also think the animation data can be shared between different models. They would have to have a similar shape, but this is not too uncommon. That's all I have so far. I would really appreciate any feedback, both good and bad.
Advertisement
Hey Gage. Some quick comments for you.

The first bytes in a data file should specifiy the format. For consistency, use four unsigned character bytes and use ASCII. Format specific information like version numbers, etc., can come after that. That supports a more universal and quick check if the file being loaded is the format expected.

If this is to support cross-platform use, you should define whether it uses big-endian or little-endian storage, either as part of the spec or as a flag in the file itself. Also, if cross-platform is intended, you need to define all data types in terms of number of bytes. E.g., you didn't define "ushort." With today's advancing computers, double-precision floats are often faster than single-precision. They can be converted during loading, but, in binary format, I still need to know how many bytes to read in.

Probably not important: You should define whether wide-character support is supported or not or just make the flat statement that all characters are ASCII.

Is there a reason to specify all counts as signed integers? In particular, if you specify "signed" for all indices, then you can't take full advantage of 32-bit index buffers.

You need to specify whether the model is in a right-hand or left-hand system. There have been no end of problems with mesh file formats in this regard. SMD and several other formats, for instance, are right-handed. X-files are left-handed (one of the few).

Right-hand and left-hand would apply to how matrices are to be handled, also.

for each vertex
If the intent is to support static meshes, or for quick testing, you should add a color to each vertex.

By specifying "ubyte" for the bone indices, you're restricting the influences to the first 256 bones. That's probably sufficient[ATTENTION] [SMILE] but for a consistent format, the bone index data-type should match the "num bones" data-type.

It appears that you're limiting the max bone influences per vertex to 4. That's probably sufficient but not flexible. See next comment.

You need something to indicate the number of bone influences for a vertex, even if you require the storage space for unused influences. Otherwise, it will require unnecessary testing or looping. If you intend that bone weights will be 0 to flag "non-influences," then either the loading code will have to count the number of bone weights > 0 to determine the number of bone influences, or the animation loop will have to access bone matrices it's not going to use and do a lot of multiplications by 0.

num indices
See comment above about matching data-types. "num indices" is signed int, actual indices are ushort. "num indices" and actual indices should probably be unsigned 32bit if you want to support 32bit index buffers.

num parts
Are these subsets of the mesh? If so:

If you intend that the format supports indexed meshes, then you shouldn't specify start index and count for vertices and indices. This forces duplicate vertices which might be a good thing to avoid. Many vertices will be shared between subsets in an indexed mesh.

A subset need only be defined by vertex indices and a material. Those indices will not be in serial order.

Maybe:
for each part: number of indices, list of indices

num bone influences
Why not just "0" or "1" as a flag? The number of influences is per-vertex anyway.

num palette entries and num bones
Any reason to have these separate?

If they're separate, it'll require the loading code to do string searches to match up offsets with locals, etc. In general, the format should support minimizing loading code burden. The file is created once but it may be loaded many times.

How about adding a "parent" index for each bone? That will allow more flexibility in the loading and animation code. I.e., if I want to determine the combined matrix of a bone, I would otherwise have to search all bones to find the bone that has that bone as a child. That, also, relieves some of the burden from the application.

animation frames
You separate all the quats from the vectors, etc. It's true that supports a minimization of storage but requires bookkeeping on the part of the loading app to keep track of 3 different lerps that may or may not have the same time key. Putting all the information for each time-step in one structure requires more storage but simplifies the animation code.

Quote:Should there be an offset table specifying the offset to the vertices/indices/etc. from the beginning of the file?
I wouldn't think that would be useful. It'll just put more burden on the loading code and allow additional errors.
Quote:Should each part store its vertices/indices, or should they be stored in one large vertex/index buffer and each part will store offsets/counts?
Indices and materials are all that's required to specify a subset. Let the application decide how to store the vertices and indices. Sometimes one big vertex buffer will be the way to go. Sometimes it may be better to use multiple buffers with index offsets, etc. Don't require duplicated vertices for each subset.
Quote:material structure .. Store the name of an .fx file?
Interesting. Perhaps not a name but some sort of recommended processing so the user could apply just pipeline if desired, or a default vertex shader. If you get into effects and shaders, then you have to think about what shader versions are supported, individual machine capabilities,etc.
Quote:the offset transforms (since these are unique to each model?)
Offset transforms are unique to a bone hierachy, not a mesh. The bone influences for a vertex are sufficient and specify which bone offset will be used.
Quote:Should the data be stored in several files?
You might want to consider having an "animation-only" flag for the file so animations could be stored and loaded separately. Animations can be applied to different meshes (provided the meshes were developed with the same bone hierarchy, which is common).
Quote:you probably want to share the rendering data, but not the skeleton data, since each character animates independently from the others.
I would expect it to be the other way 'round. Unique meshes for different types of characters but many characters having similar animations ("run", "walk", etc.).

Again, I emphasize the specification of a right-hand versus left-hand statement. That will support conversion of various modeling formats correctly.

Whew.

Please don't PM me with questions. Post them in the forums for everyone's benefit, and I can embarrass myself publicly.

You don't forget how to play when you grow old; you grow old when you forget how to play.

For the material part: don't include any material information in the mesh itself. Textures, shaders, diffuse/ambient/emissive/specular colours, powers, and so on are all material-specific and not mesh-specific, and you should be able to apply any material to any mesh. Perhaps the most you should include material-wise is a default material name which the mesh should use if not assigned any material manually. This material name could then correspond to a material you've created via other means (such as from a script).
Quote:Textures (etc.) .. are all material-specific and not mesh-specific, and you should be able to apply any material to any mesh
I'd have to disagree. Materials are very mesh-specific. E.g., vertex texture coordinates usually apply to a specific texture.

@Gage64-
EDIT: Rats! I wasn't thinking through the problem of duplicate vertices in subsets correctly. It does look like you'll have to have duplicate vertices for vertex locations that separate subsets because each vertex has just one set of texture coordinates. Unless you want to specify an array of tex coords for each vertex, a set of tex coords for each subset the vertex is used in.


[Edited by - Buckeye on August 19, 2009 12:54:00 PM]

Please don't PM me with questions. Post them in the forums for everyone's benefit, and I can embarrass myself publicly.

You don't forget how to play when you grow old; you grow old when you forget how to play.

Buckeye: Thanks! This is just the sort of feedback I was hoping to get - and there's so much of it!

You made a lot of great points and suggestions. I have some questions about some of them, but I'm kind of tired so they will have to wait until tomorrow or maybe Friday.

But I will definitely follow up on this! Your comments make me believe that with enough work, I might actually create something that will be useful to other people. Working on this has been frustrating at times because .X files and the D3DX animation API keep throwing new surprises at me, but with this kind of feedback I might actually produce something descent.


Quote:
Quote:Textures (etc.) .. are all material-specific and not mesh-specific, and you should be able to apply any material to any mesh

I'd have to disagree. Materials are very mesh-specific. E.g., vertex texture coordinates usually apply to a specific texture.


The problem is that some of the models I've worked with don't specify material information very well. For example, tiny_4anim.x from the SDK has 1 subset but 2 materials, and only the second one contains a texture name. The two materials have different lighting properties, but AFAIK there's nothing in the file that states which material should be applied.
Yeah, you just may be able to create a useable format. Stranger things have happened. [SMILE]

I don't have tiny4_anim.x. But, if it contains a MeshMaterialList, that's the table that specifices which vertices (or faces? subsets? I can't remember) each material applies to.

Also, we posted simultaneously. I edited my previous comment with regard to vertices that are "shared" between subsets.

EDIT: It depends on the loader, but, if a MeshMaterialList is not present, it may just default to material 0 for all subsets.

EDIT2: There doesn't seem to be a reason to specify a binary-only format. The file header, after the file-type signature (first 4 bytes), could contain a "binary" or "text" flag, also in ASCII.

Having a text format would have several benefits.

Most important: during testing phases, you can create a text file much more easily than a binary file!

In addition, text file loading and parsing, though a lot more complicated, allows for differences in machine floating point precision without having to know how many bytes "float" is on any particular machine (a cross-platform thing). That is, I can read the text of "3.14159" without worrying about how many bytes it's supposed to be or whether it's big-endian or little-endian.

If you haven't guessed, I've been down the road of loading and converting a lot of different file formats!


[Edited by - Buckeye on August 19, 2009 12:20:22 PM]

Please don't PM me with questions. Post them in the forums for everyone's benefit, and I can embarrass myself publicly.

You don't forget how to play when you grow old; you grow old when you forget how to play.

Quote:Original post by Buckeye
... Unless you want to specify an array of tex coords for each vertex, a set of tex coords for each subset the vertex is used in.


That sounds like it would only complicate the loading code, and the only advantage I see is that it will reduce the file size (probably not by much). So I think I'll just stick to using duplicate vertices.

Also, I don't actually duplicate any vertices in my conversion code. I just copy the vertices straight from the vertex buffer of the mesh, so any duplicate vertices are already there.
Quote:That sounds like it would only complicate the loading code, and the only advantage I see is that it will reduce the file size (probably not by much). So I think I'll just stick to using duplicate vertices.

Sorry about that. My comment on tex coord array was sort of a joke as it would, indeed, complicate the bookkeeping!

Cross-posted again: I added some comments about text-optional vs binary-only format in my previous post.

EDIT: Again about duplicate vertices. If you load an x-file with multiple subsets and then use D3DXMeshSaveToFile (or whatever it is), it seems to just blindly output a complete vertex set for each subset, whether that subset has a different material or not! There's usually a long list of duplicate vertices.

Vertices, even if in different subsets, need not be duplicated if every subset they're used in has the same material.

Subsets may have to be created to limit the number of bones-per-subset for shader capability reasons. Those subsets may still use the same material, however.

NOTE: The duplicate vertices comments are just my engineering way of thinking. When something's going to be created once (a file) but used many times, the burden of processing should be on the file creator, not the loader. Duplicate vertices require more memory, possibly multiple (unnecessary) vertex buffer swaps each frame, etc. You can ignore it all if you want. [WINK]

Please don't PM me with questions. Post them in the forums for everyone's benefit, and I can embarrass myself publicly.

You don't forget how to play when you grow old; you grow old when you forget how to play.

Quote:Original post by Buckeye
Quote:Textures (etc.) .. are all material-specific and not mesh-specific, and you should be able to apply any material to any mesh
I'd have to disagree. Materials are very mesh-specific. E.g., vertex texture coordinates usually apply to a specific texture.


Correct, but I think the material itself should be separate from the mesh. For example, take the way Ogre handles this:
// this texture unit is found in a material called "some_material"texture_unit{    texture some_texture.tga    // this texture applies to texture coordinate set 1    tex_coord_set 1}


Then, the mesh would simply have a default material name "some_material" without any further material information, which is contained within the material itself.
Quote:I think the material itself should be separate from the mesh.

Maybe I don't understand what your intent is or what you mean by "mesh." Do you mean in the same file, or stored with the mesh data itself?

It appears you mean storing texture information in a separate file and specifying only a reference to a texture in the "main" file which would be "dereferenced" to load the texture. Is that correct?

If so, that could result in a lot of headaches. If I change tex coords in my model using one texture, it will not render correctly if a different texture is used. If that's not the intent, why not specify the texture directly?

Am I missing your point entirely?

Please don't PM me with questions. Post them in the forums for everyone's benefit, and I can embarrass myself publicly.

You don't forget how to play when you grow old; you grow old when you forget how to play.

This topic is closed to new replies.

Advertisement