Search Unity

  1. Unity 2019.2 is now released.
    Dismiss Notice

AssetPostprocessor skinned mesh renderer bone index reorder [solved]

Discussion in 'Scripting' started by hippocoder, May 3, 2016.

  1. hippocoder

    hippocoder

    Digital Ape Moderator

    Joined:
    Apr 11, 2010
    Posts:
    25,828
    Edit: the code now works perfectly, so you can feel free to use it in your own projects.

    Hi all,

    I've made an AssetPostprocessor that indexes skinned mesh renderer bones alphabetically. The purpose of this is to create consistent bone ordering at import time since the FBX file format / exporter will shuffle these. The reason I do this is so that replacing skinned mesh renderer sharedMesh is a trivial swap during the game, where all meshes use an identical rig. This has a lot of useful purposes.

    Some background information can be found here, especially @superpig 's comments, and I've tried to implement his approach without any success - the resultant model is a deformed mess of polygons. I've checked the code for a couple of days and tried everything but I can't see what I am missing so please help out if you can - you may find it useful enough to add to the wiki too.

    Here's the code:

    Code (CSharp):
    1. using UnityEngine;
    2. using UnityEditor;
    3. using System.Collections.Generic;
    4. using System.Linq;
    5.  
    6. //sorts transform bone indexes in skinned mesh renderers so that we can swap skinned meshes at runtime
    7. public class AssetPostProcessorReorderBones : AssetPostprocessor
    8. {
    9.     void OnPostprocessModel(GameObject g)
    10.     {
    11.         Process(g);
    12.     }
    13.  
    14.     void Process(GameObject g)
    15.     {
    16.         SkinnedMeshRenderer rend = g.GetComponentInChildren<SkinnedMeshRenderer>();
    17.         if (rend == null)
    18.         {
    19.             Debug.LogWarning("Unable to find Renderer" + rend.name);
    20.             return;
    21.         }
    22.  
    23.         //list of bones
    24.         List<Transform> tList = rend.bones.ToList();
    25.  
    26.         //sort alphabetically
    27.         tList.Sort(CompareTransform);
    28.  
    29.         //record bone index mappings (richardf advice)
    30.         //build a Dictionary<int, int> that records the old bone index => new bone index mappings,
    31.         //then run through every vertex and just do boneIndexN = dict[boneIndexN] for each weight on each vertex.
    32.         Dictionary<int, int> remap = new Dictionary<int, int>();
    33.         for (int i = 0; i < rend.bones.Length; i++)
    34.         {
    35.             remap[i] = tList.IndexOf(rend.bones[i]);
    36.         }
    37.  
    38.         //remap bone weight indexes
    39.         BoneWeight[] bw = rend.sharedMesh.boneWeights;
    40.         for (int i = 0; i < bw.Length; i++)
    41.         {
    42.             bw[i].boneIndex0 = remap[bw[i].boneIndex0];
    43.             bw[i].boneIndex1 = remap[bw[i].boneIndex1];
    44.             bw[i].boneIndex2 = remap[bw[i].boneIndex2];
    45.             bw[i].boneIndex3 = remap[bw[i].boneIndex3];
    46.         }
    47.  
    48.         //remap bindposes
    49.         Matrix4x4[] bp = new Matrix4x4[rend.sharedMesh.bindposes.Length];
    50.         for (int i = 0; i<bp.Length; i++)
    51.         {
    52.             bp[remap[i]] = rend.sharedMesh.bindposes[i];
    53.         }
    54.  
    55.         //assign new data
    56.         rend.bones = tList.ToArray();
    57.         rend.sharedMesh.boneWeights = bw;
    58.         rend.sharedMesh.bindposes = bp;
    59.     }
    60.  
    61.     private static int CompareTransform(Transform A, Transform B)
    62.     {
    63.         return A.name.CompareTo(B.name);
    64.     }
    65. }
    66.  
    Please don't hesitate to ask questions and thanks for any help you can offer.
     
    theANMATOR2b likes this.
  2. Voxel-Busters

    Voxel-Busters

    Joined:
    Feb 25, 2015
    Posts:
    810
    Could you please share the model to test out?
     
  3. hippocoder

    hippocoder

    Digital Ape Moderator

    Joined:
    Apr 11, 2010
    Posts:
    25,828
    Any skinned mesh valid model will work including Unity's I believe, due to it just reordering it's own bones, reimporting in the project window will allow this script to run and you can check it via the preview panel. I can't supply this particular model due to NDA - if you don't have one I will try and prepare one tomorrow. Thanks for replying!
     
  4. Voxel-Busters

    Voxel-Busters

    Joined:
    Feb 25, 2015
    Posts:
    810
    I tried recalculating the bind poses and it seems to work. Not sure why it gave the issue, still finding it out.

    Code (CSharp):
    1. bp[i] = tList[i].worldToLocalMatrix * g.transform.localToWorldMatrix;
     
  5. gurayg

    gurayg

    Joined:
    Nov 28, 2013
    Posts:
    226
    I'm not not good at scripting in general but It looks like to me, you're assuming all the vertices have 4 bones assigned.
    I think it is possible to have less then 4 bones assigned to a vert.
    That might cause a problem.
     
  6. hippocoder

    hippocoder

    Digital Ape Moderator

    Joined:
    Apr 11, 2010
    Posts:
    25,828
    Does it animate correctly? When I tried this, it was misaligned or did not animate...

    They will sum to 1 from FBX import as far as I know (hope!)
     
  7. gurayg

    gurayg

    Joined:
    Nov 28, 2013
    Posts:
    226
    this might help to see comparing before and after mesh data.
     
  8. Voxel-Busters

    Voxel-Busters

    Joined:
    Feb 25, 2015
    Posts:
    810
    @hippocoder Yes, it worked fine.
    I used unity's third person controller character to test (ethan.fbx)
     
  9. hippocoder

    hippocoder

    Digital Ape Moderator

    Joined:
    Apr 11, 2010
    Posts:
    25,828
    I get this result from Ethan.fbx:
    upload_2016-5-3_15-54-38.png

    With code modification to bindposes, I find there is no animation and it is of a massive scale, lying on it's side :/
     
  10. Voxel-Busters

    Voxel-Busters

    Joined:
    Feb 25, 2015
    Posts:
    810
    Thats very strange then!
    Have a look at the attached gif. On Unity 5.3.3
     
  11. hippocoder

    hippocoder

    Digital Ape Moderator

    Joined:
    Apr 11, 2010
    Posts:
    25,828
    Yep - I get that, but when it is animated, it rotates the entire body as if there is no animation and only derives pelvis animation. Trying to figure out why. I'm using Generic if that helps try to narrow it down. I really appreciate the help so far, thank you :)
     
  12. hippocoder

    hippocoder

    Digital Ape Moderator

    Joined:
    Apr 11, 2010
    Posts:
    25,828
    Code (CSharp):
    1.         //remap bindposes
    2.         Matrix4x4[] bp = new Matrix4x4[rend.sharedMesh.bindposes.Length];
    3.         for (int i = 0; i<bp.Length; i++)
    4.         {
    5.             bp[remap[i]] = rend.sharedMesh.bindposes[i];
    6.         }
    Swapping what's remapped seems to fix it for the same model. I think I'm getting more and more confused at this rate.

    Thanks for help everyone. I've amended the line in the original post so if someone wants to enjoy this code they can.

    People who responded helped me think it through though, so thanks guys! And thanks to Richard Fine who helped out on slack too!
     
    Last edited: May 3, 2016
    theANMATOR2b likes this.
  13. SidarVasco

    SidarVasco

    Joined:
    Feb 9, 2015
    Posts:
    163
    I just tried the script but it deforms the mesh completely. Any reason why this might happen?

    They are all humanoid setups.
     
    Last edited: Sep 14, 2016
  14. GlitchyKitty

    GlitchyKitty

    Joined:
    Sep 29, 2013
    Posts:
    1
    Awesome. This really saved us a lot of time. thank you!

    I guess Unity should do that by default...
     
  15. RobinBlancOSG

    RobinBlancOSG

    Joined:
    Mar 25, 2016
    Posts:
    5
    I was about to dig into this procedure, but a quick search has led me to this topic. Your script does the job pretty well, thank you very much for sharing it. This saves me a lot of time for a custom equipment system. I've slightly edited it to ensure it works with multiple skinned mesh renderers in the imported model's hierarchy.
     
    hippocoder likes this.