Combining Skinned Meshes

Hello Unity Answers! :slight_smile: I’m having a lot of difficulty over the subject of skinned meshes, and haven’t found a suitable answer anywhere yet.

My situation is:

  • I’m putting together a character
    customization system with different
    head, body and clothes meshes;
  • This will be animated using a common
    skeleton;
  • I want this to remain in one
    drawcall;

What I’ve done so far:

  • I’ve managed to do this on static (non-skinned) meshes; that is, I have successfully combined the 3 meshes if they are not skinned;
  • I’ve managed to create a custom atlas and change the UVs accordingly;
  • I’ve tried to use SkinnedMeshCombiner ([available at the wiki here][1]) but I can’t make it work correctly;

My problems with SkinnedMeshCombiner:

  • the script seems to add every bone (in all 3 exported meshes’ rigs) to the finished combined mesh. I have an AnimatorController that moves one of those rigs (avatars), so only parts of the combined SkinnedMesh move;
  • if I try to edit the script around to add only one set of bones to the combined SkinnedMesh, the skin weights are all over the place and the body does not distort properly. (see [this image for the results][2] and my code below, SkinnedCharacterCombiner.cs)

Can anyone shed some light on this? Basically what I want is:

In runtime, load 3 different meshes that share the same skeleton, combine them in one SkinnedMeshRenderer, then apply animation coming from the shared rig, so that the whole character remains one drawcall.

Thanks!


SkinnedCharacterCombiner.cs

using UnityEngine;
using System.Collections.Generic;

public class SkinnedCharacterCombiner : MonoBehaviour {
	
	void Start() {

		Vector3 originalPosition = this.transform.position;
		this.transform.position = Vector3.zero;
		
		SkinnedMeshRenderer[] smRenderers = GetComponentsInChildren<SkinnedMeshRenderer>();
		int numSubs = 0;

		List<Transform> bones = new List<Transform>();

		List<BoneWeight> boneWeights = new List<BoneWeight>();        
		List<CombineInstance> combineInstances = new List<CombineInstance>();


		foreach(SkinnedMeshRenderer smr in smRenderers)
			numSubs += smr.sharedMesh.subMeshCount;
		
		//int boneOffset = 0;

		SkinnedMeshRenderer baseMR = smRenderers[0];

		for( int s = 0; s < smRenderers.Length; s++ ) {
			SkinnedMeshRenderer smr = smRenderers~~;~~ 

~~ BoneWeight meshBoneweight = smr.sharedMesh.boneWeights;~~

~~ // May want to modify this if the renderer shares bones as unnecessary bones will get added.~~
~~ foreach( BoneWeight bw in meshBoneweight ) {~~

~~ BoneWeight bWeight = bw;~~

~~ //bWeight.boneIndex0 += boneOffset;~~
~~ //bWeight.boneIndex1 += boneOffset;~~
~~ //bWeight.boneIndex2 += boneOffset;~~
~~ //bWeight.boneIndex3 += boneOffset;~~

~~ boneWeights.Add( bWeight );~~
~~ }~~
~~ //boneOffset += smr.bones.Length;~~

~~ CombineInstance ci = new CombineInstance();~~
~~ ci.mesh = smr.sharedMesh;~~

~~ ci.transform = smr.transform.localToWorldMatrix;~~
~~ combineInstances.Add(ci);~~

~~ Object.Destroy(smr.gameObject);~~
~~ }~~

~~ Transform meshBones = baseMR.bones;~~
~~ foreach( Transform bone in meshBones )~~
~~ bones.Add( bone );~~

~~ List bindposes = new List();~~

~~ for(int b = 0; b < bones.Count; b++) {~~
~~ bindposes.Add(bones__.worldToLocalMatrix * transform.worldToLocalMatrix);__~~
** }**

** SkinnedMeshRenderer r = gameObject.AddComponent();**
** r.sharedMesh = new Mesh();**
** r.sharedMesh.CombineMeshes(combineInstances.ToArray(), true, true);**

** Material combinedMat = new Material( Shader.Find( “Diffuse” ) );**

** r.sharedMaterial = combinedMat;**

** r.rootBone = bones[0];**
** r.bones = bones.ToArray();**
** r.sharedMesh.boneWeights = boneWeights.ToArray();**
** r.sharedMesh.bindposes = bindposes.ToArray();**

** r.sharedMesh.RecalculateBounds();**

** this.transform.position = originalPosition;**
** }**
}
~~[1]: http://wiki.unity3d.com/index.php?title=SkinnedMeshCombiner**~~
~~
[2]: http://dl.dropbox.com/s/di82wsw2gk7u4xm/mesh.jpg**~~

*Ok… I couldn’t edit it in an easy way but here is the code that did it for me.

From the 3d model, I had to create a “base” mesh which sole purpose was to bring from the rig all the bones - so that “base” mesh has some skin weights from all the bones in the rig, so that I can read all of the bones included in its skin modifier, and then delete it and use that bone information for the other meshes.

I apologize for not editing it into something that completely solves the problem (I’m sure I’m missing something), but I’m heading off to vacation and didn’t want to leave you empty-handed.

I hope the script below helps. You won’t be able to use it as is, but you can read through it and try to make the same functionality as I did.

Good luck! :)*


using UnityEngine;
using System.Collections;
using System.Collections.Generic;


public class CharacterAssembler : MonoBehaviour {

	//3d 
	public string headResource = "Heads/BaseHead";

	//TODO remove this method and call ConstructCharacter() from the script that instantiates this object
	void Start() {

		ConstructCharacter();
	}


	/// <summary>
	/// Character construction method.
	/// </summary>
	void ConstructCharacter() {

	// 1. reset object to zero-position to avoid issues with world/local during combine
		Vector3 originalPosition = this.transform.position;
		this.transform.position = Vector3.zero;


	// 2. instantiate head
		GameObject headGO = Instantiate(Resources.Load(headResource)) as GameObject;
		headGO.transform.parent = this.transform;


	// 3. get base mesh by name 
		SkinnedMeshRenderer[] smRenderers = GetComponentsInChildren<SkinnedMeshRenderer>();

		SkinnedMeshRenderer smrBase = FindByName(smRenderers, "base");

		if(smrBase != null)
			Debug.Log("Base mesh successfully loaded with " + smrBase.bones.Length + " bones.");
		else
			Debug.LogError("Base mesh is invalid.");

	// 4. keep a list of objects to destroy (at the end of the script)
		List<SkinnedMeshRenderer> toDestroy = new List<SkinnedMeshRenderer>();


	// 5. get bone information from base and destroy it
		List<Transform> bones = new List<Transform>();		
		Hashtable bonesByHash = new Hashtable();
		List<BoneWeight> boneWeights = new List<BoneWeight>();        

		//keep bone info
		int boneIndex = 0;
		foreach(Transform bone in smrBase.bones) {
			
			bones.Add(bone);
			bonesByHash.Add(bone.name, boneIndex);
			boneIndex++;
		}

		//keep bindposes 
		List<Matrix4x4> bindposes = new List<Matrix4x4>();
		
		for(int b = 0; b < bones.Count; b++)
			bindposes.Add(bones__.worldToLocalMatrix * transform.worldToLocalMatrix);__

** //destroy**
** toDestroy.Add(smrBase);**

** // 6. Keep a list of combine instances as we start digging into objects.**

** List combineInstances = new List();**

** // 7. get body smr, alter UVs, insert into combine, delete**

** Vector2 uvs;**
** List totalUVs = new List();**

** SkinnedMeshRenderer smrBody = FindByName(smRenderers, “body”);**

** uvs = smrBody.sharedMesh.uv;**

** for(int n = 0; n < uvs.Length; n++)**
__ uvs[n] = new Vector2(uvs[n].x * 0.5f, uvs[n].y);__

** //smrBody.sharedMesh.uv = uvs;**
** totalUVs.AddRange(uvs);**

** InsertSMRToCombine(smrBody, bonesByHash, boneWeights, combineInstances);**

** toDestroy.Add(smrBody);**

** // 8. get head, alter UVs, insert into combine, delete**

** SkinnedMeshRenderer smrHead = FindByName(smRenderers, “cabeca”); //TODO fbx should contain ‘head’**

** uvs = smrHead.sharedMesh.uv;**

** for(int n = 0; n < uvs.Length; n++)**
__ uvs[n] = new Vector2(uvs[n].x * 0.5f, uvs[n].y);__

** //smrHead.sharedMesh.uv = uvs;**
** totalUVs.AddRange(uvs);**

** InsertSMRToCombine(smrHead, bonesByHash, boneWeights, combineInstances);**

** toDestroy.Add(smrHead);**

** // 9. get uniform smr, alter UVs, insert into combine, delete**

** SkinnedMeshRenderer smrUniform = FindByName(smRenderers, “uniform”);**

** uvs = smrUniform.sharedMesh.uv;**

** for(int n = 0; n < uvs.Length; n++)**
__ uvs[n] = new Vector2(0.5f + uvs[n].x * 0.5f, uvs[n].y);__

** //smrUniform.sharedMesh.uv = uvs;**
** totalUVs.AddRange(uvs);**

** InsertSMRToCombine(smrUniform, bonesByHash, boneWeights, combineInstances);**

** toDestroy.Add(smrUniform);**

** // 10. get shirt smr, alter UVs, insert into combine, delete**

** SkinnedMeshRenderer smrShirt = FindByName(smRenderers, “shirt”);**

** uvs = smrShirt.sharedMesh.uv;**

** for(int n = 0; n < uvs.Length; n++)**
__ uvs[n] = new Vector2(0.5f + uvs[n].x * 0.5f, uvs[n].y);__

** //smrShirt.sharedMesh.uv = uvs;**
** totalUVs.AddRange(uvs);**

** InsertSMRToCombine(smrShirt, bonesByHash, boneWeights, combineInstances);**

** toDestroy.Add(smrShirt);**

** //combine**
** //add an empty skinned mesh renderer, and combine meshes into it**
** SkinnedMeshRenderer r = gameObject.AddComponent();**

** r.sharedMesh = new Mesh();**
** r.sharedMesh.CombineMeshes(combineInstances.ToArray());**
** r.sharedMesh.uv = totalUVs.ToArray();**

** r.bones = bones.ToArray();**
** r.rootBone = bones[0]; // TODO we can search bonehash for the name of the root node**
** r.sharedMesh.boneWeights = boneWeights.ToArray();**
** r.sharedMesh.bindposes = bindposes.ToArray();**

** //make shadermanager texture**

** //apply**

** //late destroy all skinnedmeshrenderers**

** //special destroy for head to get rid of the extra bip**
** Object.Destroy(smrHead.transform.parent.gameObject);**

** //then all smrs**
** foreach (SkinnedMeshRenderer t in toDestroy) {**

** // TODO destroy unnecessary bips**
// Transform bipRoot = t.gameObject.transform.FindChild(“Bip001”);
//
// if(bipRoot != null)
// Object.Destroy(bipRoot);

** Object.Destroy(t.gameObject);**
** }**

** //material**
** r.material = ShaderManager.Instance.GetCharacterMaterial(**
** teamString,**
** uniformIndex,**
** faceTextureName,**
** uniformNumber);**

** //recalculate bounds and return to original position**
** r.sharedMesh.RecalculateBounds();**
** this.transform.position = originalPosition;**
** }**

** #region extra methods**

** private void InsertSMRToCombine (SkinnedMeshRenderer smr, Hashtable boneHash,**
** List boneWeights, List combineInstances) {**

** BoneWeight meshBoneweight = smr.sharedMesh.boneWeights;**

** // remap bone weight bone indexes to the hashtable obtained from base object**
** foreach(BoneWeight bw in meshBoneweight) {**

** BoneWeight bWeight = bw;**

** bWeight.boneIndex0 = (int)boneHash[smr.bones[bw.boneIndex0].name];**
** bWeight.boneIndex1 = (int)boneHash[smr.bones[bw.boneIndex1].name];**
** bWeight.boneIndex2 = (int)boneHash[smr.bones[bw.boneIndex2].name];**
** bWeight.boneIndex3 = (int)boneHash[smr.bones[bw.boneIndex3].name];**

** boneWeights.Add(bWeight);**
** }**

** //add the smr to the combine list; also add to destroy list**
** CombineInstance ci = new CombineInstance();**
** ci.mesh = smr.sharedMesh;**

** ci.transform = smr.transform.localToWorldMatrix;**
** combineInstances.Add(ci);**
** }**

** ///

**
** /// Finds a SkinnedMeshRenderer in the list.**
** ///
**
** /// Found SMR.**
** /// Source array to search.**
** /// Name of the SMR to be searched.**
** private SkinnedMeshRenderer FindByName(SkinnedMeshRenderer source, string name) {**

** SkinnedMeshRenderer target = null;**

** foreach(SkinnedMeshRenderer s in source) {**

** if(s.name.Contains(name)) {**

** target = s;**
** break;**
** }**
** }**

** if(target == null)**
** Debug.LogError(“SkinnedMeshRenderer " + name + " not found.”);**

** return target;**
** }**

** #endregion**
}

i am having the same problems with this script and i cant figure out what is happening…
i will post my my setup to make the issue as clear as possible…

i will keep digging and i let u know if i find something that can help us…

It looks like the issue has already been resolved in this topic, but for anyone reading this in the future, you might consider using the “Skinned Mesh Combiner MT” asset. It is a very simple to use asset that has the function of combining Skinned meshes without afect animations. You can combine the meshes both at Runtime and in the Editor. You can see it in the Asset Store through this link: Skinned Mesh Combiner MT - Character Mesh Merge, Atlasing Support & More | Game Toolkits | Unity Asset Store