Interpolating skinning weights may be the most tricky, and painful thing. If you do not do it properly, in the best case scenario you just see some holes in your model.
I linearly interpolated skinning weights for my Precomputed Ambient Occlusion for Animated Meshes project, and in the first trial because of not being careful, I got problematic results. The funny thing, when it is easy to find lots of information about skinning, it is almost impossible to find some information about how you can interpolate skinning weights. Also, this is the reason why I am adding this page into my web site.
During the interpolation, you should be sure about one simple rule. If one of the weights of the vertex is referring to the same bone in the other vertex, you should interpolate them together. Otherwise, you should interpolate this vertex alone.
For example if we have the vertices, and weights below,
1 2 3 4 5 6 7 |
v1 v2 index | weight index | weight ------+-------- ------+-------- 0 | 0.2 2 | 0.1 1 | 0.5 3 | 0.6 2 | 0.1 4 | 0.2 3 | 0.2 5 | 0.1 |
we should consider bones (indices) 0, 1, 4, and 5 alone; however, bones 2, and 3 together with two vertices:
1 2 3 4 5 6 7 8 |
index | weight ------+-------- 0 | 0.2 / 2 = 0.1 1 | 0.5 / 2 = 0.25 2 | (0.1 + 0.1) / 2 = 0.1 3 | (0.2 + 0.6) / 2 = 0.4 4 | 0.2 / 2 = 0.1 5 | 0.1 / 2 = 0.05 |
After we get the interpolated weights, we can consider two different approaches. We can sort resultant weights, and pick the largest ones (if we specifically need to use 4 weights for each bone) or we can use all of them (In my project, I am using the second approach):
1 2 3 4 5 6 7 8 |
index | weight ------+-------- 3 | 0.4 * 1 | 0.25 * 0 | 0.1 * 2 | 0.1 * 4 | 0.1 5 | 0.05 |
After we get our weights, we have to be sure about the most important rule of the skinning for weights. We have to be sure that sum of the all weights is 1. For being sure about that, we simply sum our weights, and check whether the sum is 1 or not. If it is below or above 1, we simply divide each weight with the sum.
For example, if we select the weights with “*”s above, these weights are sum to 0.85. So divide the weights by 0.85 to get the final weights and indices as below:
1 2 3 4 5 6 |
index | weight ------+-------- 3 | 0.47 1 | 0.29 0 | 0.12 2 | 0.12 |
Algorithm
For implementing this approach, I used a data structure named BoneNIndex simply defined with an int index, and a float weight member. I used 3 std::vectors typed BoneNIndex for keeping all indices and weights of two vertices, and my resultants.
- Put bone indices and weights of the vertices into the data structure (Let’s call them as std::vector v1, and std::vector v2)
- For each member of v1, search the same index in v2.
- If you found the same index, interpolate the weight using v1.weight, and v2.weight.
- Otherwise, interpolate v1.weight alone
- If there are remaining (uninterpolated) weights in v2, interpolate them alone
- Sum the weights and be sure the summation is equal to 1
- Assign interpolated new weights, and indices to the new vertex
For implementation, instead of using BoneNIndex structure, you can also use C++ map (dictionary).