NICK FISHER

Normalizing Vertex Group Weights in Blender

Yesterday I published a video on YouTube that covers the basics of bone weights and normalization in Blender.

The key takeaway is that unless bone envelopes are used, the influence of a bone on a vertex is determined only by the vertex group weights for that vertex.

In other words, the distance of a vertex from a bone is irrelevant in bone weight mode.

To see for yourself, start with a cube with a single-bone armature. Duplicate it (which also duplicates the armature modifier) and move the duplicate some distance away.

The duplicate cube shares the same armature modifier but sits far from the bone

The duplicate cube (right) shares the same armature modifier, but is nowhere near the bone

When you translate the bone, the relative translation of each cube will be identical. Likewise, rotating the bone rotates both objects through the same angle. All vertices receive the same influence from the bone, no matter how far they are from the bone itself.

Rotating the bone -52.96° rotates both cubes identically

Rotating the bone: the remote cube rotates identically to the close one

The second lesson is that all non-zero vertex weights for deform bones will be normalized for a single vertex. This means that if a vertex is only influenced by a single deform bone, the weight of that influence is effectively ignored.

To show this, say you want to use a pose bone to add a smile to a facial mesh. You start by positioning a bone on the face of the upper lip:

The UpperLip bone positioned on the face of the upper lip

The UpperLip bone, positioned on the face of the upper lip

How would you assign vertex group weights to these vertices?

For a smile, the vertices on the lip face will travel further (relative to the pose bone) than the vertices underneath the nose. Intuitively, you'd expect the best approach would be to weight the upper lip face 100% to the UpperLip vertex group/bone, then progressively fade out the weight for each edge loop toward the nose:

Weight paint mode showing a smooth red-to-green falloff from the face of the upper lip towards the nose

A smooth falloff from 1.0 at the lip face down to 0.5 near the nose

After applying these weights and posing the bone, though, you'll see that your intuition was not correct:

Posing the UpperLip bone lifts the whole mouth as one rigid block

Posing the bone lifts the entire mouth as one

As we saw with the cube example above, all vertices move exactly the same distance, even though they're weighted differently:

The separated lip mesh with arrows showing the uniform displacement

The upper part of the mouth moves exactly the same distance as the face of the upper lip

The reason is that Blender will normalize a vertex's weights when calculating its deformation.

From armature_deform.cc:


  float contrib = 0.0f;
  ...
  for (const auto &dw : dweights) {
      ...
      contrib += pchan_bone_deform(pchanbone, weight, co, mixer);
  }
  ...

  /*
  * mixer.finalize: 
  *  const float scale_factor = armature_weight / total;
  *  r_delta_co = position_delta * scale_factor;
  *  r_deform_mat = deform * scale_factor;
  */
  mixer.finalize(co, contrib, armature_weight, delta_co, local_deform_mat);

  ...

Each vertex group's contribution is accumulated into a per-vertex total (contrib += pchan_bone_deform(...)), and then the accumulated deformation is scaled by armature_weight / total — i.e. divided by the sum of all the weights.

In other words, the vertex group weights for a given vertex will all be scaled such that they add up to 1.0. If a vertex only has a single bone weight, that weight will be rescaled directly to 1.0; it doesn't matter whether the user-facing value is 0.1 or 0.9:

0.1 / 0.1 = 1.0
0.9 / 0.9 = 1.0

Your hand-painted falloff will be completely ignored!

To preserve it (and to preserve any variation in the weights for one bone across multiple vertices), each vertex must also be weighted to at least one other bone with a constant weight.

For two vertices Va and Vb with UpperLip weight of 0.1 and 0.9 respectively, but both weighted to LowerLip with a value of 1.0, the normalization calculation becomes:

Weight_Va = 0.1 / (0.1 + 1.0) = 0.1 / 1.1 = 0.09
Weight_Vb = 0.9 / (0.9 + 1.0) = 0.9 / 1.9 = 0.47

Now, posing the lip bone will displace the vertices on the lip face ~5x further than the vertices near the nose:

Posing UpperLip: the lip face travels much further than the region near the nose

The lip face moves ~5x further than the area near the nose

So this approach successfully solves the normalization problem.

This isn't a permanent solution though - because you weighted the top of the mouth region to LowerLip, posing that lower lip bone will deform this region, which obviously isn't what you want.

Posing the LowerLip bone drags the upper mouth with it

Posing the lower lip bone moves the top of the mouth

The fix is straightforward - don't use the LowerLip bone for normalization. Instead, use a bone that doesn't deform the lip independently of the head. If the entire facial mesh needs to move with head/neck rotation, you can use the head bone for this. If not, then create an additional bone that isn't intended to move, and exists solely as a "normalization sink" (I generally call this Root or NormalizationRoot):

Outliner showing three bones: LowerLip, UpperLip and the newly added Root

The Root bone exists only as a normalization constant

This bone exists purely as a normalization constant in the denominator for the runtime bone influence calculation to "soak up" the leftover weight.

All vertices selected in edit mode, assigned to the Root vertex group

Every vertex gets a matching weight on Root, which never moves

Now the upper lip weights still fade from 1.0 to 0.5 towards the nose. Moving UpperLip produces a smooth, natural falloff again, but posing LowerLip does nothing, because nothing is assigned to it. This solves the problem.

One final caution: in Blender, this normalization bone must be a deforming bone, i.e. you must have Deform checked in the bone properties panel. If this is unchecked, its value will be ignored when calculating the bone influence:

The Root bone with the Deform checkbox unchecked

Deform unchecked on the Root bone - the weight will be ignored