Export to format with limited bone influences per vertex?

I have a format which only allows 3 bones to influence a vertex, Ive written an exporter for the format. However, I have a problem because some models people created have up to 7 influences for some vertices. Right now the exporter only uses the first 3 from the list, but that causes a lot of exported models to look pretty weird when animating.
So is there a better way to do this?

I’m not sure if Blender has a built-in function to handle this situation. I ran into an identical problem and wrote a custom operator to limit the weights.

To use the operator, make the mesh that you want to clean up the Active Object then run the script. You can run it from a text window or you can register it and run it by hitting Space Bar then typing in “Distribute Weights”.

What it does is find vertices that have more than 3 influencing bones and takes the weights from the lowest bone and splits that weight evenly with the remaining bones. It repeats that until the maximum number of supported bones is reached.

I have not run this on a lot of models but in my experience when more than 3 bones affect a model, the top 3 contribute the majority of the weight info and the rest are rounding errors. This is for a biped models, other rigs might have different behavior.

I’m currently using this in 2.62 but I think it worked in 2.56 and 2.59 as well.


import bpy


# Distributes the weighting on the vertices so that at most 'maxInfluence' bones
# affect the vertex.
def distributeWeight(obj, maxInfluence):

    print("--- %s ----" % (obj.name))

    # Access the mesh data.
    mesh = obj.data

    # Run though the vertices
    for v in mesh.vertices:
        
        # Get a list of the non-zero group weightings for the vertex
        nonZero = []
        for g in v.groups:
            
            g.weight = round(g.weight, 4)
            
            if g.weight < .0001:
                continue
            
            nonZero.append(g)

        # Sort them by weight decending
        byWeight = sorted(nonZero, key=lambda group: group.weight)
        byWeight.reverse()

        # As long as there are more than 'maxInfluence' bones, take the lowest influence bone
        # and distribute the weight to the other bones.
        while len(byWeight) > maxInfluence:

            print("Distributing weight for vertex %d" % (v.index))

            # Pop the lowest influence off and compute how much should go to the other bones.
            minInfluence = byWeight.pop()
            distributeWeight = minInfluence.weight / len(byWeight)
            minInfluence.weight = 0

            # Add this amount to the other bones        
            for influence in byWeight:
                influence.weight = influence.weight + distributeWeight

            # Round off the remaining values.
            for influence in byWeight:
                influence.weight = round(influence.weight, 4)

class DistributeWeightOperator(bpy.types.Operator):
    '''Distributes the weights at each vertex so that at most 4 groups are affecting it.'''
    bl_idname = "object.distribute_weight_operator"
    bl_label = "Distribute Weight Operator"

    @classmethod
    def poll(cls, context):
        return len(context.selected_objects) > 0

    def execute(self, context):
        for obj in context.selected_objects:
            distributeWeight(obj, 3)
        return {'FINISHED'}


def register():
    bpy.utils.register_class(DistributeWeightOperator)


def unregister():
    bpy.utils.unregister_class(DistributeWeightOperator)


if __name__ == "__main__":
    register()

    # test call
    bpy.ops.object.distribute_weight_operator()

Nice idea. At first I thought it was bad because some bones would influence the vertex more than the modeller intended, but then I remembered all the bone weights must be equal to 1 so you don’t have much choice.

You have experience in these kind of stuff. Mind answering another similar question?
What if 2 bones in Blender influence a vertex the same amount and both are 1 or their sum is bigger than 1? Because in weight painting mode you can paint the vertex red for both bones. Actually this seems to be the one causing the weird deformation issue more.

What happens with vertex weights that don’t sum up to 1 is dependent on which engine is rendering the model.

I have not debugged Blender but from experimentation with weight painting, I don’t think that it uses the weight for each bone directly. From what I can tell, Blender seems to actually add up all the weights at each vertex to get a total “mass” for the vertex and then uses (bone Weight / total mass) as the actual factor used to influence the vertex.

My evidence for this is the following scenarios:

  1. Create a 1 bone armature and parent it to a cube. Paint the entire cube at 0.5 weight. When you move the bone around, the cube still follows bone as if it was painted at 1.0. In fact, with just 1 bone, the cube will follow the bone perfectly unless you paint each vertex down to 0.0.

  2. Add 2 bones and paint both at 1.0. They then influence the verts the same if they are painted a 0.5 or both at 0.3.

I know that the engine that I export to assumes that the bone weights will sum to 1.0. If I had 2 bones that both had “1.0” for influence then my models would probably look correct in Blender but would fly apart or get weird scaling artifacts in my external engine. Before I export I use the “normalize all” button to get all the weights to sum to 1.0, then I use my “distribute weight” operator to ensure that at most 3 bones affect any given vertex.

You’re right. There must be some other issue. The format doesn’t support bone parenting, so the script needs to disconnect the bones before exporting. Could that be the problem? What to do in this case?

Do you have a screen shot of what the problem is? What external engine are you targeting?

I doubt that the problem is that your engine does not support bone parents. One of the main points of an armature is to capture the parent-child relationship.

What data are you actually exporting from Blender? My engine only replays animations created in Blender; It does not do physics on the bones. Because of this my deform bones each become a single matrix that is exported. I don’t have much experience with trying to export a Blender armature into another format.

I can send you an example blender screenshot/video/blend file and a screenshot/video of the model in the engine while animated, if you don’t mind.

One of the main points of an armature is to capture the parent-child relationship.
I didn’t quite catch this. Do you just mean you can’t think of a reason why disconnecting the bones would change the weights of the vertices?

My engine only replays animations created in Blender; It does not do physics on the bones. Because of this my deform bones each become a single matrix that is exported.
Same here, no physics, joints are just matrices.

EDIT: OK, I think I can explain the problem in a simple way:
Basically there are some vertices which seem to always be in their default position during animation (in the other program), which leads me to believe they have no weights. At the same time I don’t see the same issue in Blender while posing: when I move the corresponding bones, all vertices move (both when the bones are connected and disconnected).

Correct. Changing your bones from connected to disconnected should not affect your weight at all.

EDIT: OK, I think I can explain the problem in a simple way:
Basically there are some vertices which seem to always be in their default position during animation (in the other program), which leads me to believe they have no weights. At the same time I don’t see the same issue in Blender while posing: when I move the corresponding bones, all vertices move (both when the bones are connected and disconnected).

That sounds like the case where Blender is handling the weights differently than your external engine. Did you try doing:

  • “Normalize All” the weights in your mesh
  • Use the “Distribute Weights” script I posted
  • Export

It sounds like there are some verts in blender that only have 1 bone affecting them and have a low weight assigned to them. In this case, they will follow the bone in Blender but will probably look like they are frozen in an external game engine.

That sounds like the case where Blender is handling the weights differently than your external engine.
I think so to. The question is: what is different between the two.

Did you try doing:

  • “Normalize All” the weights in your mesh
  • Use the “Distribute Weights” script I posted
  • Export
    Well, I ported the “distribute weights” script functionality to my exporter. As for the “Normalize All” button… I’m currently finishing the Blender 2.4 version of the script, and I don’t think it has a button like that. If you could tell me what it basically does, I could write the code for that too. let me guess, it makes sure sum of all weights is always 1.0?

It sounds like there are some verts in blender that only have 1 bone affecting them and have a low weight assigned to them.
What will happen in that case in Blender? I just made a test and the vertex seems to follow the bone as expected.

Thanks for the help so far.

Yes, “normalize all” goes though all the verts and ensures that the bone weights sum to 1.0. If I was going to attempt to reproduce this code without reading the Blender source I would do the following:

  • Sum all the weights for the vertex and call this ‘mass’
  • For each bone affecting the vert, boneWeight = boneWeight / mass

That’s just an educated guess. If it does not work then you will have to go get the 2.62 source and see what the ‘normalize all’ button is really doing.

What will happen in that case in Blender? I just made a test and the vertex seems to follow the bone as expected.
Thanks for the help so far.

If there is a single bone bone affecting a vert with a non-zero weight, then in Blender I would expect that vert to behave as if it had a weight of “1.0” for that bone. i.e. the vert would follow the bone around.

If there is a single bone bone affecting a vert with a non-zero weight, then in Blender I would expect that vert to behave as if it had a weight of “1.0” for that bone. i.e. the vert would follow the bone around.
Hm, I could have sworn I just made a test without the same result, but now I get the same result as you describe.

Well then I could check if there is only one bone and write 1.0 to the file instead. Oh, the normalize function I’ll write should fix that too, huh? :slight_smile:

I would have never guessed all this, thanks. I’ll make the changes to my script and let you know if it worked.

EDIT: animates perfectly now :wink:
I’ll need to test some more files to be really sure though.

Great! And I got some of my theories about how Blender works tested out for free. :smiley:

Okay, I tested few more models, and there seems to be the same problem for 1-2 vertices for some models.
Got anymore ideas? I think maybe the bone heat might leave some vertices not assigned to any bone at all, but I think I would noticed that in blender.

Here’s the normalization code:


# 'boneinfluences' is a list containing bone name and bone weight, or sublists which contain those

if type(boneinfluences[0]) != list: # single bone
    boneinfluences[1] = 1 # no need to even check if is already 1.0
else:
    currentsum = 0
    for x in boneinfluences:
        currentsum += x[1]
    if currentsum < 1:
        remaining = 1 - currentsum
        for x in boneinfluences:
            x[1] += remaining/len(boneinfluences)

That’s weird. If they had no bone weight assignment at all then I would expect them to be stuck in Blender as well. I’m out of ideas on those. I started using Blender with 2.5 so I’m not sure how different they might be for 2.49.

Nah, that was just my wrong assumption, you are right that in that case they wouldn’t move in Blender too.

I found the issue with the few models I had. Turns out Blender can not only have the weights less than 1, but more than 1, so in your weight normalization code you need to also evenly subtract from the weights in that case.

Hey guys,

I’m writing a patch for the c code that edits armature weights. It will allow the animator to select the limit of skinning bones. Right now it just orders the weights and then cuts off the tail of the weights array beyond the skinning bones limit. I’d like to know if there will be any issues like what vida_vida mentioned in the first post. Could i get some of the blend files where down-skinning (reducing the bones limit) causes problems.

From some searching, it seems like 4 is more than enough in almost all cases, except for the face or when using mocap data.

I’d also like some testers for when the patch is submitted.

kestion

Cutting the values off might be a problem. I expect that most of the time people want to limit the number of bones so that the models work with an external engine. It is common for these engines to have a limit on the number of bones and also require that the bone weights sum to 1.0. If you just clip the weights then a separate step to normalize the values might be required.

Yeh, i think we’ll want to do a normalize after culling the bones. What if you have a mixture of bone armatures and other modifiers such as hair or particles. Will your script correctly select only the bone weights for limiting (culling), or does it treat all weights as if they are the same?

That is a good question. All my models only use the weights for controlling armatures; I’ve never done anything with particles or soft bodies or anything. It appears that my script would mess up these weight values.

I apologize for reviving this topic but it’s still an issue when working in old engines that only support 3 bones and there’s no way to get a smooth result (That I know of)

Is there anyone that could update the script?