FBX binary file format specification

FBX is a popular 3D file format that was originally developed by Kaydara for MotionBuilder, acquired by Autodesk Inc in 2006. It is now one of the main 3D exchange formats as used by many 3D tools. Even though Autodesk publishes “free” SDK for FBX, its license and the software itself remains fully closed. FBX has a text based (ascii) and a binary version.
With no public documentation available on the binary FBX format, Blender Foundation asked Alexander to write up what he has figured out sofar. We hope this will lead to better interoperability of 3D applications in general. Blender”s next release (2.69) will support binary FBX file reading as well.

August 2013, by Alexander Gessler, reviewed by Campbell Barton. Published by Blender Foundation, as public domain information.

This is an incomplete specification for the binary FBX file format.
It has been tested with file versions starting 2011, but it should also work with earlier versions.

This document only describes the encoding of binary FBX files, not the interpretation of the data being encoded.
It should enable you to translate binary FBX files to ASCII text format (or an in-memory representation of it).

Text-Based File Structure

Knowledge of the text-based format is relevant for this document, so here is a quick writeup. The core hierarchical building block (node) of a text-based FBX document is

NodeType: SomeProperty0a, SomeProperty0b, ... , {

 NestedNodeType1 : SomeProperty1a, ...
 NestedNodeType2 : SomeProperty2a, ... , {
 ... Sub-scope
 }

 ...
}

In other words, a document is essentially a nested list of nodes. Each node has…

  • A NodeType identifier (class name)
  • A tuple of properties associated with it, the tuple elements are the usual primitive data types: float, integer, string etc.
  • A list which contains nodes in the same format (recursively).

At global level, there is an “implicit list” (i.e. the curly braces, the property list and the name are omitted) with some standard nodes defined. Each of these standard items consists only of a nested list, so a file might look like this

FBXHeaderExtension: {...}
GlobalSettings: {...}
Documents: {...}
Definitions: {...}
Connections: {...}
...

Applications have to parse the contents of these in order to access FBX geometry.

Binary File Structure

The first 27 bytes contain the header.

  • Bytes 0 - 20: Kaydara FBX Binary  \x00 (file-magic, with 2 spaces at the end, then a NULL terminator).
  • Bytes 21 - 22: [0x1A, 0x00] (unknown but all observed files show these bytes).
  • Bytes 23 - 26: unsigned int, the version number. 7300 for version 7.3 for example.

Directly after this data, there is the top-level object record. Unlike for the text file format, this is not omitted – a full node record with empty name and empty property list is written.

After that record (which recursively contains the entire file information) there is a footer with unknown contents.

Node Record Format

A named node record has the following memory layout:

Size (Bytes) Data Type Name
4 Uint32 EndOffset
4 Uint32 NumProperties
4 Uint32 PropertyListLen
1 Uint8t NameLen
NameLen char Name
? ? Property[n], for n in 0:PropertyListLen
Optional
? ? NestedList
13 uint8[] NULL-record

Where…

  • EndOffset is the distance from the beginning of the file to the end of the node record (i.e. the first byte of whatever comes next). This can be used to easily skip over unknown or not required records.
  • NumProperties is the number of properties in the value tuple associated with the node. A nested list as last element is not counted as property.
  • PropertyListLen is the length of the property list. This is the size required for storing NumProperties properties, which depends on the data type of the properties.
  • NameLen is the length of the object name, in characters. The only case where this is 0 seems to be the lists top-level.
  • Name is the name of the object. There is no zero-termination.
  • Property[n] is the n“th property. For the format, see section Property Record Format. Properties are written sequentially and with no padding.
  • NestedList is the nested list, presence of which is indicated by a NULLrecord at the very end.

Reading a node record up to and including the properties is straightforward. To determine whether a nested list entry exists, check if there is bytes left until the EndOffset is reached. If so, recursively read an object record directly following the last property. Behind that object record, there is 13 zero bytes, which should then match up with the EndOffset. (Note: it is not entirely clear why the NULL entry is required. This strongly hints at some FBX subtlety or format feature that not known to the authors of this document ….)

Property Record Format

A property record has the following memory layout:

Size (Bytes) Data Type Name
1 char TypeCode
? ? Data

where TypeCode can be one of the following character codes, which are ordered in groups that require similar handling.

i) Primitive Types

  • Y: 2 byte signed Integer
  • C: 1 bit boolean (1: true, 0: false) encoded as the LSB of a 1 Byte value.
  • I: 4 byte signed Integer
  • F: 4 byte single-precision IEEE 754 number
  • D: 8 byte double-precision IEEE 754 number
  • L: 8 byte signed Integer

For primitive scalar types the Data in the record is exactly the binary representation of the value, in little-endian byte order.

ii) Array types

  • f: Array of 4 byte single-precision IEEE 754 number
  • d: Array of 8 byte double-precision IEEE 754 number
  • l: Array of 8 byte signed Integer
  • i: Array of 4 byte signed Integer
  • b: Array of 1 byte Booleans (always 0 or 1)

For array types, Data is more complex:

Size (Bytes) Data Type Name
4 Uint32 ArrayLength
4 Uint32 Encoding
4 Uint32 CompressedLength
? ? Contents

If Encoding is 0, the Contents is just ArrayLength times the array data type. If Encoding is 1, the Contents is a deflate/zip-compressed buffer of length CompressedLength bytes. The buffer can for example be decoded using zlib.

Values other than 0,1 for Encoding have not been observed.

iii) Special types

  • S: String
  • R: raw binary data

Both of these have the following interpretation:

Size (Bytes) Data Type Name
4 Uint32 Length
Length byte/char Data

The string is not zero-terminated, and may well contain \0 characters (this is actually used in some FBX properties).

20 comments 53,171 Views
  1. Interesting. Lets hope documentation comes soon.

  2. Some old open source Irrlicht extension can read meshes from binary fbx files. See
    http://sourceforge.net/p/irrext/code/91/tree/trunk/extensions/scene/IMeshLoader/fbx/

  3. Updated info (figured out what ‘b’ type properties are),

    @Erwin, ah, I wasn’t aware of other binary loaders, I had a quick look and they did indeed figure this out earlier on.

    I’ve put together a standalone parser in Python (runs without Blender), hopefully will help anyone trying to figure this out in future (also includes example fbx2json converter).

    https://github.com/ideasman42/pyfbx_i42

  4. Straying slightly from topic, but… For my own game development purposes, I have modified the existing python FBX export script to support material textures, including layered colour textures, normal maps, specular maps, etc. I’ve also made a few other fixes here and there. I’m happy to pass it back for evaluation and possible integration into a future blender release, but I’m not sure to whom I should send it. Any thoughts?

  5. @Erlend Sogge Heggen: I checked both links…
    – `fbx-conv` uses Autodesks SDK, so not really interesting as far as being able to load FBX data goes.
    – `openfbx` only reads ASCII files, This is OK, and we had a test ASCII importer in Blender for some years, but parsing large ascii files is slow and applications default to binary.

    Not to speak negatively about either of these projects – but neither of them are doing anything very special as far as reading FBX files is concerned.

  6. Please consider using FBX SDK. You will always be behind regarding new versions and features otherwise, and it would be much easier with the SDK.
    License is no problem if you have fbx code in its own process, and IPC makes it slower but not much.

    Looks at:
    http://blenderartists.org/forum/showthread.php?274687-Another-FBX-Importer-Exporter-addon

    The code is very simple and almost does the job. Why create something complex that will be hell to maintain (reverse engineering every change on FBX updates,..)?

  7. I have been trying to parse an FBX binary file fith those specs and I found some mismatchings.

    1) I did not find any “Empty name top-level object” starting at byte 27. The node located there was full with information like creator, timestamps and so on.
    2) I did not locate any “13 0 bytes at the end of node”. In fact my script went to infinite loop a few times trying to find them. From what I figured out – this “13 empty bytes” are there only if the node has any nested nodes. They mark the end of nested nodes list. All nodes without nested nodes don’t have this 13-byte ender.

    Anyway – thank you for this specs. They were really helpful.

    • Thanks for this feedback.

      1) Think you directly found the FBXHeaderExtension sub-node (top-level node implies all others are its children).
      2) That’s said in doc actually… In any case, better to always trust the ‘EndOffset’ value first.

  8. This was very helpful in writing my lightweight FBX reader/writer: https://github.com/hamish-milne/FbxWriter

    I managed to figure out what the footer contains, and thus I’m able to write fully compliant binary files.

    • Hey Hamish, this sounds rather promising… What’s the license of your code? Could be interested in testing and adding that to our exporter (with proper credit of course), as well as the format info data, after 2.76 is out.

  9. I tried some ways to uncompress the binary chunks
    The only one that worked fine to me is Ionic.Zlib
    This is an extract I implemented from their examples
    (The CopyStream function is there in examples too, very simple)
    if(enc==1)
    {
    MemoryStream mc = new MemoryStream(data, offset, cmp);
    mc.Seek(0, SeekOrigin.Begin);
    MemoryStream mx = new MemoryStream();
    ZlibStream zOut = new ZlibStream(mx, CompressionMode.Decompress, true);
    CopyStream(mc, zOut);
    output = new byte[mx.Length];
    mx.Seek(0, SeekOrigin.Begin);
    mx.Read(output, 0, (Int32)mx.Length);
    }
    From there I read the array of uncompressed data

    In case you do not want to extract from the examples

    static void CopyStream(Stream src, Stream dest)
    {
    byte[] buffer = new byte[1024];
    int len = src.Read(buffer, 0, buffer.Length);
    while (len > 0)
    {
    dest.Write(buffer, 0, len);
    len = src.Read(buffer, 0, buffer.Length);
    }
    dest.Flush();
    }

    • You saved me with this. I spent multiple hours trying to decompress the arrays and then it occurred to me to read the comments…

  10. The document mentions: “Behind that object record, there is 13 zero bytes, which should then match up with the EndOffset”.

    It’s only my guess, but 13 bytes exactly matches the length of an empty record (4+4+4+1 bytes), so it seems that it’s just a marker like “nothing more to read here”.

  11. Thank you for sharing FBX file description.
    Over past few days I wrote simple C++ parser for FBX files.

    Here it is: https://github.com/jskorepa/fbx/tree/master
    (it is MIT-licensed and I plan to add FBX writing as well)

  12. Thanks for this, it helped me a bit to make my FBX importer https://github.com/nem0/OpenFBX

  1. Leave a Reply

    Your email address will not be published. Required fields are marked *

     

share this story on