Unity Character Dress Up Implementation

Recently, I have developed a certain interest in the way and principle of Unity’s dressing, so I went to the Internet to search for some docs and realized a simple version of the dressing system. Here is a brief summary.

There are a few concepts we need to understand before we can formally understand how to do a makeover.

Skeleton, Skin and Animation

There are two commonly used animations in game development: vertex animation and skin animation

  1. Vertex animation
    It is realized by directly modifying the position of the mesh vertices in the animation frame. It is usually used when the number of mesh vertices is small and the animation is simple, such as the swing of grass, the swing of trees, the fluctuation of water, etc

  2. Skin animation
    By directly modifying the position of the bone in the animation, the vertex of the mesh changes with the change of the bone, which is usually used for humanoid animation, such as the running and jumping of characters

What is bone?

When we pour in the Model with bones, we can find a nested GameObject in it. This GamoeObject and all its child GameObjects have only one property, which is the coordinate information of the transforms, which make up the skeleton information of the model.

What is skin?

We know that the mesh is composed of vertices and faces. If the skin data is not bound, it is called a static mesh, which does not have animation effects, such as houses, floors, bridges, roads, etc. in the game;

For the mesh of binding skin, we call it SkinMesh. In SkinMesh, the vertex of each mesh will be affected by several bones and matched with a certain weight ratio;

Just like our real people, the first support and determine the position and movement is a group of bones, head + body + limbs, and the muscles on the body are affected by the bones to produce movement, and the movement of each muscle may be affected by multiple The influence of bones;

Data needed in skins

In unity, the calculation of skin animation is mainly realized through the SkinnedMeshRenderer component

Data required to calculate skinning animation:

SkinnedMeshRenderer.bones: a list of all references to bone, note that the order is determined, the index of bone in the BoneWeight of subsequent vertices is based on the index of this array order
SkinnedMeshRenderer.sharedMesh: Mesh data required for rendering, note that compared to the vertex and surface data required by ordinary MeshRender, there will be some additional computational skinning related data
Mesh.boneWeights: The index and weight of which bones are affected by each vertex (each vertex is affected by up to four bones, see the definition of structure BoneWeight for details)
Mesh.bindposes: The transformation matrix of each bone from mesh space to its own bone space, that is, the Inverse Matrix of the transformation matrix from bone space to mesh space of the predefined bone. Note that the transformation made by the vertex affected by bone is based on Transformation done in bone space

According to the Unity doc, the algorithm of BindPose in Unity is as follows:

OneBoneBindPose = bone.worldToLocalMatrix * transform.localToWorldMatrix;
The world-to-local coordinate system matrix of the skeleton multiplied by the local-to-world matrix of the mesh

Note: Art is generally in the binding skin, the bones will be put into a Tpose style, this time the bone transform into a matrix that is bindpose, all bone animation is relatively transformed on this basis, and eventually as the mesh itself The static data is saved.

SkinnedMeshRenderer is a different mesh
This information comes with the import of the model, and the modeling tool will determine how each point is affected by the information of each bone, that is, how the corresponding mesh changes when the position of a certain point in the bone changes.

Two ways to dress up

How to replace SkinnedMeshRender

Mainly suitable for clothes, pants, hairstyles, etc

Replacement steps:

1: Generally, according to whether a separate part can be changed, a single part or a whole set of models can be made into a Prefab.

Load prefab and instantiate it as a GameObject

3: Find the original SkinnedMeshRender corresponding to the newly instantiated SkinnedMeshRender.

4: Replace bones

5: Replace the mesh

6: Replacement of materials

7: Replace completed, destroy the newly instantiated Prefab

How nodes are mounted

Mainly suitable for weapons, wings, tails, etc

Replacement steps:

1: Generally, a single set of models is made into a Prefab

Load prefab and instantiate it as a GameObject

3: Find hangpoints (for example, weapons are usually mounted on the skeleton node of the hand)

4: Destroy the original equipment

5: Set the parent node of the instantiated GameObject as a hang point

6: Set the offset, scaling, rotation of the GameObject (usually 0)

Specific implementation examples

The first is a simple way to take out the SkinnedMeshRenderer of all clothes and add them all to the bone root node

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
SkinnedMeshRenderer CombineSuitsToSkeleton(GameObject skeleton, SkinnedMeshRenderer[] meshes)
{
var transforms = new List<Transform>(skeleton.GetComponentsInChildren<Transform>(true));

var materials = new List<Material>();

var combineInstances = new List<CombineInstance>();

var bones = new List<Transform>();

foreach (SkinnedMeshRenderer smr in meshes)
{
materials.AddRange(smr.materials);
for (int sub = 0; sub < smr.sharedMesh.subMeshCount; sub++)
{
CombineInstance ci = new CombineInstance();
ci.mesh = smr.sharedMesh;
ci.subMeshIndex = sub;
combineInstances.Add(ci);
}
foreach (Transform b in smr.bones)
{
var t = transforms.Find(t => b.name.Equals(t.name));
if (t != null)
{
bones.Add(t);
}
}
}

var r = skeleton.AddComponent<SkinnedMeshRenderer>() ?? skeleton.GetComponent<SkinnedMeshRenderer>();
r.bones = bones.ToArray();
r.sharedMesh = new Mesh();
r.sharedMesh.CombineMeshes(combineInstances.ToArray(), true, false);
r.materials = materials.ToArray();

return r;
}

In the above way, we will add multiple Materials to the root node of the bone, so that the performance is not optimal, we can optimize it, merge all the materials of the clothes, and then add them to the bone as a whole.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
SkinnedMeshRenderer CombineSuitsToSkeleton(GameObject skeleton, SkinnedMeshRenderer[] meshes)
{
var transforms = new List<Transform>(skeleton.GetComponentsInChildren<Transform>(true));

var materials = new List<Material>();

var combineInstances = new List<CombineInstance>();

var bones = new List<Transform>();

foreach (SkinnedMeshRenderer smr in meshes)
{
materials.AddRange(smr.materials);
for (int sub = 0; sub < smr.sharedMesh.subMeshCount; sub++)
{
CombineInstance ci = new CombineInstance();
ci.mesh = smr.sharedMesh;
ci.subMeshIndex = sub;
combineInstances.Add(ci);
}
foreach (Transform b in smr.bones)
{
var t = transforms.Find(t => b.name.Equals(t.name));
if (t != null)
{
bones.Add(t);
}
}
}

var newMaterial = new Material(Shader.Find("Standard"));
var oldUV = new List<Vector2[]>();
// merge the texture
var Textures = new List<Texture2D>();
foreach (Material m in materials)
{
Textures.Add(m.GetTexture("_MainTex") as Texture2D);
}

Texture2D newDiffuseTex = new Texture2D(1024, 1024, TextureFormat.RGBA32, true);
Rect[] uvs = newDiffuseTex.PackTextures(Textures.ToArray(), 0);
newMaterial.mainTexture = newDiffuseTex;
newMaterial.EnableKeyword("_SPECULARHIGHLIGHTS_OFF");
newMaterial.SetFloat("_SpecularHighlights", 0f);

// reset uv
Vector2[] uva, uvb;
for (int j = 0; j < combineInstances.Count; j++)
{
uva = (Vector2[])(combineInstances[j].mesh.uv);
uvb = new Vector2[uva.Length];
for (int k = 0; k < uva.Length; k++)
{
uvb[k] = new Vector2((uva[k].x * uvs[j].width) + uvs[j].x, (uva[k].y * uvs[j].height) + uvs[j].y);
}
oldUV.Add(combineInstances[j].mesh.uv);
combineInstances[j].mesh.uv = uvb;
}

var r = skeleton.AddComponent<SkinnedMeshRenderer>() ?? skeleton.GetComponent<SkinnedMeshRenderer>();
r.bones = bones.ToArray();
r.sharedMesh = new Mesh();
r.sharedMesh.CombineMeshes(combineInstances.ToArray(), true, false);
r.material = newMaterial;

return r;
}

Code process analysis

  • first get all the bones information (bones are actually an array of transforms): var transforms = new List < Transform > (skeleton. GetComponentsInChildren < Transform > (true));
    Declare a new array of materials: var materials = new List < Material > ();
  • 声明一个CombineInstance(Struct used to describe meshes to be combined using Mesh.CombineMeshes)的数组:var combineInstances = new List();
    Declare an array of all bones: var bones = new List < Transform > ();
  • Then there is a loop to the meshs array, the role is to save the material of each mesh into the materials array, then save the information of each mesh into the combineInstances array, and finally obtain the same name as the mesh’s bone information from the bone information of the body according to the name Save the bones with the same name.
  • In this loop, the material information of the clothes is saved in combineInstances, and the bone information is saved in bones.
  • Get the SkinnedMeshRenderer of the character body, bind its bone information to all bones, and its mesh to a new mesh
  • Create a new material with its shader as the default standard shader
  • Save old UV information: var oldUV = new List < Vector2 [] > ();
  • Save old sticker information:
1
2
3
4
5
var Textures = new List<Texture2D>();
foreach (Material m in materials)
{
Textures.Add(m.GetTexture("_MainTex") as Texture2D);
}
  • Create new texture: Texture2D newDiffuseTex = new Texture2D (1024, 1024, TextureFormat. RGBA32, true);
  • Generate UV information for the new texture based on the old texture array: Rect [] uvs = newDiffuseTex. PackTextures (Textures. ToArray (), 0);
  • Set the texture of the new material as the new texture.
  • Recalculate the UV information of the new texture after merging the clothes map based on the new UV information just generated.

Reference article:

https://zhuanlan.zhihu.com/p/87583171
https://zhuanlan.zhihu.com/p/94134459
https://gameinstitute.qq.com/community/detail/126729