Thursday, September 18, 2008

FBX and XNA Part 2 - 3D Models

The general file format of FBX is divided into sections.

They are
  • Header (ignored)
  • Definition (ignored)
  • Objects
  • Relations (ignored)
  • Connections
  • Takes
  • Version5 (ignored)
This is the data about the model.
  • Cameras (ignored) Things like Quicktime will use these to position the view when showing the model. Quicktime is a great way to get a look at models, and "if it works in QuickTime then it should work in my code".
  • Mesh - The actual points that make up the model. More on this later.
  • LimbNode - the Bones of the file. These are definitions of the links between different parts of the mesh and Deformers and Animations. Just like your body has a skin (the mesh) and bones (the LimbNodes).
  • Pose - (ignored) In modeling tools there are pre-done poses for the model. You then animate as changes to the pose. We ignore this since it only applies to tools that will be editing and changing the model.
  • Deformer and SubDeformer - these are attached to the LimbNodes and tell which mesh points should be moved when the LimbNode moves, and by how much. Several Deformers may influence a single mesh point to give smooth bends to the mesh around the joints.
  • GlobalSettings (ignored) If I ever see a model that has these changed I'll have to account for them.
The connections section tells what the tree hierarchy is and which Deformers apply to which LimbNodes. We use this alot when loading the model to get everything stitched together.


Takes are the animations on the model. A single take is a single animation. It has a range of time in some strange units that is an unsigned long. I convert it to seconds by a simple conversion factor until the speed looks right.
The conversion is N / (1539538600.0 * 30.0) . Which may or may not be correct.
In a Take are Keyframes for various values on a LimbNode.
First there are the joints. One section is the Transform where it says Channel: "T" {
Then in the section there is X, Y and Z. There is a default value for each part, and then may or may not be keyframes with the tag Key:
Key: entries are a series of values at given times. The format is Time, Value, U, [s or a(?)], then if it was s there is a start velocity and end velocity (ignored), the the letter n.
So an entry might look like this:
Key: 1539538600,4.30184507369995,U,s,0,0,n,23093079000,4.30184507369995,U,s,0,0,n,46186158000
1539538600 is the start time of the first position, 4.3... is the value, since it is an s then the 0,0 are start and end velocity, and then the letter n.
There is also a KeyCount that I should use in future versions for a more efficient load.

Correction: Not really start and end velocity. Actually the tangent of the velocity curve at the beginning and end of the segment, which is almost the same thing. In the future I will use these to get smoother animations.

The only sections I use are the "T" section for translation, and "R" for rotation.

A note on take: If the take has a Key: entry, then that determines the translation and rotation. If it does not then you use the default values (Default: key). If there is no animation for any LimbNode, than you have to fill in a dummy animation with the LimbNode Lcl Translation and Lcl Rotation as the default values. Another way to do this would be to have the LimbNodes driving the drawing and looking to see if there is a corresponding animation data for that limb. I could do it that way, but my code already is driven by the animations, so I create dummy animations for Limbs that don't have one. After load and building the Mesh, Animation, and Deformers, I throw out all the other model data. This is more compact.

(Does any one know what "liw" stands for?)

The Mesh sections define the surface mesh of points in a model. They are the basis of the model.
the mesh has a Properties60: section that has lots of settings, all ignored. But then there is the Vertices: section. This is a list of X,Y,Z coordinates of all the points in the mesh and is often quite large. We read all these and keep them.

Then is the PolygonVertexIndex: list. This tells the index in Vertices of each point in a polygon on the model surface. Obviously points often are part of several polygons, usually 4 since the vertices are the corners of rectangular patches. The one trick here is that the end-of-polygon indicator is a negative sign on the index minus one. What really happens is a -1 is XOR'd with the index.

We ignore Edges: They are for showing the model as a wire frame which we never do.

Another section is the Normals: section. These are the XYZ normals at the Vertices points. This is used by the graphics processor to get the lighting shade correct when drawing the polygons. There are two flavors, ByPolygonVertex, and ByVertex. If it is ByPolygonVertex then every index in PolygonVertexIndex has a corresponding normal. If it is ByVertex the each point in the Vertices list has a single corresponding normal.

Next is UV: which is the texture coordinates. A UV entry for a given Vertex is the X,Y location in the texture that corresponds to that point. In a triangle of the mesh there will be 3 UV coordinates that form a triangle in the texture that is painted onto the triangle when it is drawn on the screen. UV can also be ByPolygonVertex or ByVertex.
The rest of the Mesh data gets ignored.

When we read in the Mesh we convert it to a flat list of VertexPositionNormalTexture in XNA.
Thus if it is a PerVertex Polygon and UV data it gets expanded out to PerPolygonVertex.

Yikes, that's a lot of info.
Next we talk about the other sections...



The Guy said...

This is very helpful. Thank you for documenting this!

The Guy said...

In the PolygonVertexIndex, the smallest number is 0. The largest is often the number of nodes. This doesn't make sense to me. If the number of nodes is 10,000 and you assign the 1st node an index of 0, the 10,000th node should be 9,999. However this doesn't seem to be the case in the fbx files. Can you explain where I am missing this? Here is a file:

I try to plot faces from an fbx in a program called Scilab and it doesn't look right at all. It looks like I am drawing random triangles.

Jason Nicholson said...
This comment has been removed by the author.
Jason Nicholson said...
This comment has been removed by the author.
Jason Nicholson said...

I figured it out. Triangles are listed like below


The negative signifies the end of a polygon. The last node in a polygon has an absolute value 1 greater than the node it references. The triangles below correspond to the above FBX format.

triangle 1:

Евгений Ли said...

WARNING: If UV type is ByPolygonVertex, then FBX vertex count can't be equals count model vertexes of class XNA "VertexPositionNormalTexture". Because every FBX vertex can have different texture coordinates in different poligons.