Friday, August 31, 2012

Procedural Skinned Mesh In Unity

This is an old post migrated from my previous blog (08/31/2012). I apologize if any info seems outdated.
In a current project I am faced with the challenge of needing to make tons of characters available to players. All the characters are of the same rig but are very complex with several systems such as: leg animator, gestures, animation groups, different sensory systems, ‘anchor transforms’ for weapons/etc…tons of stuff. So making a change to the smallest thing can be a real pain in the pipeline if there is a prefab for each character model variation.
So the ideal solution is have one character prefab and load in different models…easy right? Well it’s not so straight forward with systems that have to be baked on the fbx object and sometimes abstracting those systems to a higher level game object can be nearly impossible.
There are a couple ways to tackle this one. But I’m going to post some code particular to a procedural method of creating a character. It could use some optimization but it gets the point across :)
When you import an fbx with bones it should have a SkinnedMeshRenderer component on it. As far as coding goes there is a “sharedmesh” member — typeof(Mesh). This is the mesh with all the info that constructs it. The members of mesh we are concerned about are: vertices, uv, triangles, boneWeights, and bindposes. Unity actually has an example constructing an object from scratch that will help shed some light on what these are – EXAMPLE.
Here is my version of this process with a character. Please be aware at the end I use the bones from the source SkinnedMeshRenderer. You’ll want to copy and instantiate new bones and bind to those for a completely separate character…I just skipped that for my demo purposes because drawing the mesh on those bones was okay for me :)
Here is the code:

using System.Collections;
using System.Collections.Generic;
using System.Linq;
//add this script to a gameobject...then be sure to set the source in inspector
public class Test : MonoBehaviour {
    //source models skinnedMeshRenderer
    public SkinnedMeshRenderer s;
    void Start()
        {
        //gameObject.AddComponent<animation>(); //using sources animation to test.
             gameObject.AddComponent<skinnedmeshrenderer>();
        SkinnedMeshRenderer renderer = GetComponent<skinnedmeshrenderer>();
              Mesh mesh = new Mesh();
        ////////////VERTS
        List<vector3> verts = new List<vector3>();
        for(int i = 0i &lts.sharedMesh.vertices.Lengthi++)
        {
            Vector3 v = s.sharedMesh.vertices[i];
            verts.Add(new Vector3(v.xv.yv.z));
        }
             mesh.vertices = verts.ToArray();
        ////////////UVS
        List<vector2> uvs = new List<vector2>();
        for(int i = 0i &lts.sharedMesh.uv.Lengthi++)
        {
            Vector2 uv = s.sharedMesh.uv[i];
            uvs.Add(new Vector2(uv.xuv.y));
        }
         mesh.uv = uvs.ToArray();
        ////////////TRIS
        List<int> tris = new List<int>();
        for(int i = 0i &lts.sharedMesh.triangles.Lengthi++)
        {
            int t = s.sharedMesh.triangles[i];
            tris.Add(t);
        }
        mesh.triangles = tris.ToArray();
        mesh.RecalculateNormals();
         renderer.material = new Material(Shader.Find(" Diffuse"));
        //BONE WEIGHTS
        List<boneweight> weights = new List<boneweight>();
        for(int i = 0i &lts.sharedMesh.boneWeights.Lengthi++)
        {
            BoneWeight b = new BoneWeight();
            b.boneIndex0 = s.sharedMesh.boneWeights[i].boneIndex0;
            b.weight0 = s.sharedMesh.boneWeights[i].weight0;
            b.boneIndex1 = s.sharedMesh.boneWeights[i].boneIndex1;
            b.weight1 = s.sharedMesh.boneWeights[i].weight1;
            b.boneIndex2 = s.sharedMesh.boneWeights[i].boneIndex2;
            b.weight2 = s.sharedMesh.boneWeights[i].weight2;
            b.boneIndex3 = s.sharedMesh.boneWeights[i].boneIndex3;
            b.weight3 = s.sharedMesh.boneWeights[i].weight3;
            weights.Add(b);
        }
        mesh.boneWeights = weights.ToArray();
             //BINDPOSES
        List<matrix4x4> bindPoses = new List<matrix4x4>();
        for(int i = 0i &lts.sharedMesh.bindposes.Lengthi++)
        {
            Matrix4x4 m = new Matrix4x4();
            Matrix4x4 mm = s.sharedMesh.bindposes[i];
            m.m00 = mm.m00;     m.m01 = mm.m01;     m.m02 = mm.m02;     m.m03 = mm.m03;
            m.m10 = mm.m10;     m.m11 = mm.m11;     m.m12 = mm.m12;     m.m13 = mm.m13
            m.m20 = mm.m20;     m.m21 = mm.m21;     m.m22 = mm.m22;     m.m23 = mm.m23;     
            m.m30 = mm.m30;     m.m31 = mm.m31;     m.m32 = mm.m32;     m.m33 = mm.m33
            bindPoses.Add(m);
        }
        mesh.bindposes = bindPoses.ToArray();
              //using sources bones...could make a copy here instead.
             renderer.bones = s.bones;
             renderer.sharedMesh = mesh;
        //play an animation here to test
        transform.parent.animation.Play("Walk");
    }
}