FBX Exporter: Fix space transform on first child, apply to bones and armatures

Authored by Bastien Montagne (mont29) on Jun 18 2014, 1:43 PM.



The current version of the code just shifts the problem I tried to fix one level down the hierarchy: Instead of having the -90 degree rotation on the root node it is on the first child.
This applies the bake throughout the whole hierarchy. Additionally I made it apply to armatures and bones. In my tests in Unity this produces exactly the result I expected, but I can provide example Blender and Unity scenes and a write up as well.

I tried to refactor the matrix calculation code a bit to make clearer what happens:

  • first it calculates the Blender global space matrix
  • then it converts into FBX global space
  • if the local matrix is expected it gets the calculated parent matrix and applies the inverse

(That way it should handle any odd mixed hierarchy)

For the non-baked case I didn't see any changes in my tests, but if there are cases where the result is different please reject this patch and I'll find a solution.

Diff Detail

It may be save to apply to Empties as well:

self.bdata.type in BLENDER_OBJECT_TYPES_MESHLIKE | {'ARMATURE', 'EMPTY'}) or self._tag in {'DP', 'BO'}))

I decided to provide some more information.

This is a YouTube video where I demonstrate the problem with the old non-baked exporter, the problem with the current exporter that applies the bake only to the root, and the effect of this patch:

This video is only unlisted because it will hopefully be outdated in a few days...

And, as mentioned, my test scene:

I guess I missed the deadline for 2.71... Please don't apply it now until I've added a new option to enable or disable this for people who rely on the 2.71 behaviour.

I would never have committed that for 2.71, way too much risky for a last-minute change. And please do not bother about an option here, we already have too much in this exporter, and it would not make much sense (either you want baked transforms, or not).

And I still have to find time to test it & understand those changes, some are making me shiver tbh.

The problem with fixes like this is always that there are people why rely on the old behaviour, especially if there is some time until the release of 2.72. It would've been almost better to hide the option in the UI so that people don't create content with it until it is working properly.
I had it often enough in commercial development that some senior people will demand the old behaviour back if you fix a long-standing bug, because your fix breaks their workarounds. :(

Even the current "bake" should not be an option but the default, but because people have been using it like this for the last few years (and complained about the problems it causes) we can't change it now without breaking a lot of people's existing assets.

No, bake will never be default, this is not sane. It is a workaround/helper we offer (afaik, no other app does it?), it does not even fixes anything, just make them "nicer" in importing app… That’s candy, not real hard feature.

As for guys using bugs as features (and there is no bug here, only limitations), well, this is an old issue indeed… Not sure re-introducing buggy behavior just because it turned out to be a feature is a good way to go, better to develop a real feature if it’s really needed (like e.g. the 'save buffer' render option, theorically only used by full OSA, that was abused by compositors as a cache one – we just clearly and cleanly separated both things, so that you get a pure volatile OSA buffer, and real, manageable render cache).

But I really doubt current 'bake transform' behavior could be seen as a feature, it’s WIP state really (so yes, maybe hiding it in release would have been wiser… but tip warns about it being highly experimental, iirc).

AFAIK the official FBX SDK does it if you use the built-in code to change the up axis. But don't quote me on that, I don't have it installed at the moment.

It is not really candy for Unity developers (or game developers in general), where the -90 rotation on root causes all kinds of headaches when it comes to instancing. Without this the bake people will have to export models laying on the side to the get correct orientation when using them for terrain. Other cases are for example people implementing IK or script driven transforms.

I'll be happy if it ends up in 2.72, though. I'll try to test this earlier this time so there is a chance to catch and fix problems like this.

Bastien Montagne (mont29) requested changes to this revision.Jun 30 2014, 12:39 PM

Ok, so finally took time to check this. All in all, I’m rather happy with the simplification of fbx_object_matrix, I think it makes sense and could not find any potential issue in new 'algo'.


breaks here. In fact, I think bake transform should not be applied to bones. We can apply it to meshes, because we can tweak geometry itself (vertex coordinates) to counter-balance the object rotation.

But there is no such things with bones, since in FBX they are 'empties' objects, i.e. have no geometry, only object data (unlike in Blender). Imho, what should be done here to support armature 'baking' would be:

  • Handle armature objects themselves like we do for meshes' objects;
  • Handle bones objects like we do for meshes' geometries.

Thank you, I'll look at this right away.

By the way, I went through the code to see where (and why) the "local_space" parameter to fbx_object_matrix is used, and it seems to be only used in the old version of the matrix calculation. I'll drop it in the next revision of this patch unless there is a good reason to keep it.

I think the math would work out the same way if you treat the armature as a "meshes object", and the bones as "mesh", or if you treat the whole hierarchy as transforms. It worked fine in my test cases, but I'll have to understand more about Blender and this broken scene to see if that is right.

Can you just confirm what exact problem you see and in which program?

I see a problem in Unity if I import the fbx as an unskinned model, but I just want to confirm that we see the same thing:

It works fine as a skinned model:

I have an idea what the problem is and how to fix it...

I’m using Blender's FBX importer (from WIP branch).

Here is the result I get once linked to imported action:

I enlit the biggest (more visible) issues, but many more bones are broken.

For ref, here is the org “thing” at the same frame:

PS: might be working OK on Unity because FBX file is exported in its target axis system (while when re-importing into Blender, we have to re-rotate things again of course), but this means 'bake' is not done correctly nevertheless…

Treating the whole hierarchy only as transform can't work imho, since you 'remove' needed rotation at one level (object), you have to apply it at another level (geometry for meshes, bones for armature), I can’t see how you would do otherwise.

And bone's matrices are a very touchy subject in FBX, since they are used (including in global space) in the skinning data itself… That’s why I did not dare to try to apply 'bake' to armatures/bones, I found it way too hairy for me (but I’m not really at ease with complex matrices handling, tbh).

I just got the latest import script from the fbx branch on git, and I was able to reproduce this. It looks like you see the inverse of the problem that Unity people see when they import without bake, but I'll have to check the math in the importer. The version I got from the master branch earlier didn't have skinning import.
I'll try to get it fixed by tomorrow.

About the bones: You are right, you remove the transform from the children. But child transforms and bones are calculated in world space (in fbx_object_matrix) and then moved into the parent's local space, so it evens out in the end.

You are right, though, bones are a touchy subject, not only because of skinning, but because of IK set ups as well. I'll try more test cases before I submit the next patch.

After a lot of tests I think you are right about not applying the bake to bones. I don't know if that is because of the bake process or because bones need a similar local transform like lights or cameras, but the end result without the bake looks correct for bones.
I did some tests with the official FBX SDK, and unfortunately it doesn't apply the bake. I'd like to do some more tests with Max and Maya, but I don't have access to licenses for them at the moment.

I revised the patch a bit with more comments, and changing the naming from "world" to "global" to be more in line with Blender's conventions. I removed the local_space parameter as well because nothing else seemed to use it, and I can't find a sensible reason to keep it.

I found an actual bug as well! The problem shown in http://www.restemeier.org/import_unskinned.png is actually a bug in the exporter! If you get the current nightly build and re-import a model right after saving it you get the same problem in Blender:

The reason for that is that in Blender the mesh is a child of the armature, while in FBX it isn't. In export_fbx_bin.py:2084 the parent is not connected for armature/mesh relations:


# Meshes parented to armature are handled separately, yet we want the 'no parent' connection (0).
              if par_obj and ob_obj.has_valid_parent(objects) and (par_obj, ob_obj) not in arm_parents:
                  connections.append((b"OO", ob_obj.fbx_uuid, par_obj.fbx_uuid, None))
                  connections.append((b"OO", ob_obj.fbx_uuid, 0, None))

This must be considered when calculating an object matrix as well. In my patch I check for MESH child and ARMATURE parent, but it may be better to pass arm_parents to the function to keep both pieces of code in sync.
The same fix needs to be done to the importer, though I'm not sure how it is supposed to work. This is for the new importer code from the WIP branch:

arm_mat_back = bl_adata.matrix_basis.copy()
        for ob_me, (amat, mmat) in matrices.items():
            # bring global armature & mesh matrices into *Blender* global space.
            amat = global_matrix * amat
            mmat = global_matrix * mmat

            bl_adata.matrix_basis = amat
            #me_mat_back = ob_me.matrix_basis.copy()
            ob_me.matrix_basis = amat.inverted() * mmat # the armature is the parent of the mesh, so the matrix needs to be relative to the armature

            mod = ob_me.modifiers.new(elem_name_utf8, 'ARMATURE')
            mod.object = bl_adata

            ob_me.parent = bl_adata
            #ob_me.matrix_basis = me_mat_back

After making the mesh a child of the armature the mesh matrix needs to be relative to the armature matrix. This works, but I'm not sure why you need to back up and restore the object matrix.

Do I just attach a new version of the patch?

Update to patch:

  • remove bones from the bake process, because it results in different bone orientation in other applications
  • removed "local_space" argument, because it is not required by any other code
  • changed naming from "world" to "global" to be more in line with Blender conventions
  • made sure the correct parent is chosen for mesh nodes under an armature node, to be in sync with the code that writes out connections (see comments, the same fix needs to be done to the importer! )

Arf… tested it, and again got odd results… Anyway, I really do not think we should bake if we can't 'counter-bake' at a sub-level (i.e. mesh data, or perhaps bones, but this is over-complex for me). This means, no bake for empties, and no bake currently for armatures (until we can counter-bake at bone level).

Anyway, made some changes in WIP branch transform code, including just the 'bake children too' part of your patch for now.

I still have to check that 'mesh/armature parenting' issue, do not really understand the issue of hand, but tbh I’m rather sick of FBX for a few days (spent last two days trying to get bone-correction working, was an epic failure :( ).

I understand that feeling, when I heard that Blender still didn't produce the expected result for Unity I had to build courage for a few days before looking into the problem. (I was using my own modified version of the Ascii exporter, so I didn't see the problems. I now changed to the binary exporter for my stuff.)

First about the Armature/Mesh thing, because that is actually a bug, and the old Ascii exporter seems to do it correctly:

In Blender you've got the mesh as a child of the armature:

 + [Mesh]

Both the mesh and the armature can have a transform, so when the mesh is displayed the global transform is armature transform + mesh transform. In FBX the mesh is not a child of the armature, both are children of the scene root:


I have no idea if that is part of the FBX "standard" or a convention, but this is how the exporter writes the scene. Now if you import the model without using the armature, for example in the old version of the Blender FBX importer, or in Unity3D with animation disabled, you get a global transform that is just the mesh transform. This was not visible in the new version of the importer because it just made the mesh a child of the armature again without adjusting the matrix, so the bugs cancel each other out.
In terms of the FBX exporter, the code that writes out connections changes the scene hierarchy. So fbx_object_matrix has to use the Blender hierarchy when building the global matrix, and it has to use the FBX hierarchy when building the local matrix that is written out.

About counter baking children:

That actually happens automatically! All matrices taken from Blender and written to FBX go through fbx_object_matrix (or fbx_object_tx which goes through fbx_object_matrix). All of them are first converted into Blender global space, then FBX global space, (get the optional bake applied), and then get put into FBX parent space. Example, imagine a test scene using only empties:

+ [Child]

What happens without the bake is that both parent and child get transformed into fbx global space, then the child gets put into the parent's local space. The way it works is that I could apply any silly transform to the parent, but because fbx_object_matrix uses the inverse of the parent to put the child into local space it will still end up in the same global space as before.
Or better, in the case of Empties or Armatures, all children of them are transforms themselves, so the bake is applied automatically just by going through fbx_object_matrix.

If you can send me some example scenes I can have a look what is causing the problems. I can have a look at your bone adjustment as well if you want to.

Mesh/Armature parenting issue: Ok, makes sense indeed! Never hit this one since I did not import without armature (and also probably since most models I tried were on 'NULL' transform). Will add this to Master/WIP asap.

About main part of the patch, there is one issue on 'design' level: it can make export noticeably slower (due to the fact it systematically calls parent's matrix func for local transforms, when you have a bunch of bones and a bunch of keyframes to evaluate during anim baking…), in this file it adds over 30% of export time:

It was also in this file I noticed some issues with bones, irrc - but tbh I'd need to recheck that.

As for the bone orientation correction, in fact it might be related to that 'baking' issue (as in, same kind of problem). This file

has been exported from an AD program, if you import it with current WIP code, you'll see that even though pose and skinning looks good, bones are not oriented as expected. In current WIP branch I let my attempt to correct this, just using an identity matrix instead of real correction one (MAT_CONVERT_BONE, in fbx_utils.py) to disable the correction. This correction code gives good result for restpose (the binding one, written as global matrices and read from global matrices), but the LCL_ transform values (i.e. local matrix) are completely insane once corrected… I just can't understand why really, since same code works nice (afaik) for lamps and cameras, even when parented… :/

For the records, committed fix for rigged meshes issue as rBA905f58de. Thanks again for noting that issue.

This revision now requires changes to proceed.Jul 20 2014, 8:59 PM

This revision is probably the smallest version that gives the correct result for models required for game development. It doesn't handle all edge cases that the my more complicated version does, but that would probably be too hard to maintain.

This revision now requires changes to proceed.Aug 14 2014, 12:20 PM

Yes, I know. That model uses Empties, and from what I understand you don't like the bake to be applied to empties because you removed it several times.

A working fix would be

def use_bake_space_transform(self, scene_data):
     # NOTE: Only applies to object types supporting this!!! Currently, only meshes and the like...
     # TODO: Check whether this can work for bones too...
     return (scene_data.settings.bake_space_transform and self._tag in {'OB', 'DP'} and
             self.bdata.type in BLENDER_OBJECT_TYPES_MESHLIKE | {'EMPTY'})

This patch is really the most stripped down code that does the job for most people, without becoming too complicated to maintain or too slow.

Yeah, but then parenting to cameras or lamp fails, see

… :'(

Not I tried re-enabling bake for everything, but then quite obviously data-less objects like cameras and lamps are wrong (and so are bones, even though functionally OK). This is really a can of worms…

Note that I’m not opposed to applying bake to empties (in fact, in that case, it does not have much importance), but applying it to data like armatures, cameras, etc. really seems hairy to me, we up till now never managed to get that working.

I think I’m going to keep current code for now, afaik it works in all cases, and just add empty in 'bake'-enabled obj types.

The current code as it is now doesn't work for me at all. Children are off by 90 degree.
Parenting to lights or cameras isn't going to work either, baked or not baked, because you apply MAT_CONVERT_* to them and don't correct in children.

I fixed it so many times, both in the old Ascii exporter and in this. I'm kind-of fed up with working on proper fixes for this, testing it for ages, and then a few commits down the line some unrelated change will have broken it again. That's why I submitted this change that works for most use cases without being hard to maintain.

I could offer this change, which unfortunately is on top of D735: FBX Exporter: (Optionally) add leaf bones and change bone orientation. It handles more cases, but may be easier to break by changes.

Example File:

(I'm not sure why it is so big...)
Screenshot in Unity:

By the way, I noticed that children to bones in Blender are not attached to the bone node in FBX. It is nothing I need to have fixed, though.

Time to close this I think…