Note: The guide below is a good one-off solution if your game does not rely heavily on this feature. If you plan on having many customization options or many uniquely dressed avatars on screen, learning a tool like UMA will be a better, more optimized option for you.
A common feature in many games is the ability to switch out equipment or cosmetic objects on the player’s character. A common example of this is clothing or armor. But how is it done?
First we will need to understand skinned meshes. Think about how when you set one game object as child of another, moving and rotating the parent will move and rotate the child as well. In fact, the child’s position and rotation will remain constant relative to the parent object. This is because the child’s position and the positions of all its vertices are being multiplied by the transform of the child itself as well as that of the parent, and its parent, and so on. If you are not familiar with the math, go check out this tutorial. 3Blue1Brown also has a phenomenal series on linear algebra on his YouTube channel.
Anyway, just like how a child can be “coupled” to a parent by matrix multiplication, a vertex can be “coupled” to a bone by matrix multiplication. Only skinned meshes take things one step further; multiple matrices can weigh in on where a vertex ends up. That is, each transform matrix which affects a vertex will be given a scalar weight which affects the degree to which the vertex is “coupled” to that transform. These weights are the ones “painted” by the artist who rigged the model. Because all of these transformations happen for every vertex, and because the operation involves a lot of matrix multiplication, it is typically done in a vertex shader. Here is a more in-depth description and example of this concept.
One optimization that may be assumed from this is that each skinned mesh should only use bones that actually have weight on them. Otherwise, extra bone transforms are eating up bandwidth to the graphics card. Performance gains from this will be highly dependent on the target platform and how many skinned meshes you use. Since the bones are in a nice array, moving this data in a nice solid block, there is not the same overhead as moving them all individually. Your vertex shader will still be doing a bunch of matrix multiplications that just end up being multiplied by zero if you do not trim the extra bones though.
Back to our problem: we need to make sure that the clothing deforms along with the skinned mesh of the character. Just tossing the clothing in the scene with its own copy of the rig would result in the constant need to synchronize animation playback on multiple sets of bones. Therefore, we need to make sure that all skinned meshes attached to the character use the same bone objects.
Assuming you use the same rig for the clothing, this is very simple. Unity’s SkinnedMeshRenderer has an array called “bones” which stores references to the Transforms of all of the bone GameObjects which it is linked to. The weights painted on the skinned mesh refer to the bones by their index. So if you use the same rig across all of your clothing options and your base model, all you have to do is set the bone array of the clothing skinned meshes to the same as that of the base model skinned mesh. Here is an extension method that does just that.
public static class SkinnedMeshExtension { public static void CopyBonesFrom (this SkinnedMeshRenderer target, SkinnedMeshRenderer source) { target.bones = source.bones; } }
As an extension method, it is sparse to the extent of pointless. Let’s make it a little more robust to handle less clean cases. If your bone structures are the similar enough (such as two with the same layout, but different indices or names; or the case where one rig is a subset of the other) you will be able to remap to the target rig with relative ease. This will require that you iterate though the rigs however and perform some name matching.
The following method will use the bone names from the new mesh (clothing) to find existing bones of the same name from the base mesh.
public static void CopyBonesFrom (this SkinnedMeshRenderer target, SkinnedMeshRenderer source) { //create a dictionary referencing source bones by name Dictionary<string, Transform> boneMap = new Dictionary<string, Transform> (); foreach (Transform bone in source.bones) { boneMap [bone.name] = bone; } //match each bone name of the target to a bone from the source for (int i = 0; i < target.bones.Length; ++i) { string boneName = target.bones[i].name; if (!boneMap.TryGetValue (boneName, out target.bones [i])) { Debug.LogError (target.name + " failed to get the bone, " + boneName + ", from " + source.name); Debug.Break (); } } }
If something went wrong in the art creation process and the bone names do not match between assets, the following method could be used to match bones using a custom name array rather than the one associated with the target mesh (clothing in our example).
public static void CopyBonesFrom (this SkinnedMeshRenderer target, SkinnedMeshRenderer source, string [] boneNames) { //create a dictionary referencing source bones by name Dictionary<string, Transform> boneMap = new Dictionary<string, Transform> (); foreach (Transform bone in source.bones) { boneMap [bone.name] = bone; } //match each bone name to a bone from the source for (int i = 0; i < target.bones.Length; ++i) { string boneName = boneNames [i]; if (!boneMap.TryGetValue (boneName, out target.bones [i])) { Debug.LogError (target.name + " failed to get the bone, " + boneName + ", from " + source.name); Debug.Break (); } } }
If your rigs are too different, more complicated remapping will be involved and undesired distortion of the mesh may occur. Future works could include matching bones by structure by walking the hierarchy and identifying common features.