Search Unity

DIY Skinned Mesh Rendering

Discussion in 'Scripting' started by ecurtz, Mar 1, 2012.

  1. ecurtz

    ecurtz

    Joined:
    May 13, 2009
    Posts:
    640
    I'm starting to play around with some alternate character skinning methods and I thought this might save somebody some of the teeth gnashing I went through trying to set it up.

    The two big gotchas are that the sharedMesh doesn't "do what it says on the box," and there's information in the prefab SkinnedMeshRenderer that can't be recreated from the mesh and transforms.

    To try it out replace a SkinnedMeshRenderer with the script, a MeshFilter and a MeshRenderer, then give the script the magic link to the prefab version of the SkinnedMeshRenderer. Obviously there's no reason to use this instead of the SkinnedMeshRenderer if you want high performance.

    Code (csharp):
    1.  
    2. using UnityEngine;
    3. using System.Collections;
    4.  
    5. public class LinearBlendSkin : MonoBehaviour {
    6.     private MeshFilter filter;
    7.     private Vector3[] originalVertices;
    8.     private Transform[] bones;
    9.    
    10.     public SkinnedMeshRenderer prefabRenderer;
    11.    
    12.     // Use this for initialization
    13.     void Start () {
    14.         // The SkinnedMeshRenderer that is created with
    15.         // the prefab magically knows what order the bones
    16.         // are in, but it is impossible to get this information
    17.         // through any public methods, so we'll have to
    18.         // use an instance to look them up.
    19.         bones = new Transform[prefabRenderer.bones.Length];
    20.         Transform[] transforms = transform.root.GetComponentsInChildren<Transform>();
    21.         int boneCount = bones.Length;
    22.         int transCount = transforms.Length;
    23.         for (int bone = 0; bone < boneCount; bone++) {
    24.             string boneName = prefabRenderer.bones[bone].name;
    25.             for (int trans = 0; trans < transCount; trans++) {
    26.                 if(transforms[trans].name == boneName) {
    27.                     bones[bone] = transforms[trans];
    28.                     break;
    29.                 }
    30.             }
    31.         }
    32.        
    33.         filter = GetComponent<MeshFilter>();
    34.        
    35.         // The sharedMesh reference gets broken
    36.         // as soon as you modify the mesh, which
    37.         // makes absolutely no sense, but that's
    38.         // how Unity has decided it should work.
    39.         originalVertices = filter.sharedMesh.vertices;
    40.        
    41.         // SkinnedMeshRenderer seems to use its
    42.         // own transform only for the bounds, but
    43.         // MeshRenderer uses it while rendering?
    44.         // Should this be built into the binds?
    45.         transform.localScale = new Vector3(1f, 1f, 1f);
    46.         transform.localPosition = new Vector3();
    47.         transform.localRotation = new Quaternion();
    48.     }
    49.    
    50.     // Animations are updated between Update and LateUpdate
    51.     void LateUpdate () {
    52.         Mesh mesh = filter.mesh;
    53.  
    54.         Matrix4x4[] binds = mesh.bindposes;
    55.        
    56.         Vector3[] vertices = mesh.vertices;
    57.         BoneWeight[] weights = mesh.boneWeights;
    58.         int vertexCount = mesh.vertexCount;
    59.         for (int vert = 0; vert < vertexCount; vert++) {
    60.             int index0 = weights[vert].boneIndex0;
    61.             float weight0 = weights[vert].weight0;
    62.             vertices[vert] = bones[index0].localToWorldMatrix.MultiplyPoint3x4(binds[index0].MultiplyPoint3x4(originalVertices[vert])) * weight0;
    63.            
    64.             // This is ignoring the quality setting and
    65.             // just using all 4 bone weights for now
    66.             int index1 = weights[vert].boneIndex1;
    67.             float weight1 = weights[vert].weight1;
    68.             if (weight1 > 0f)
    69.                 vertices[vert] += bones[index1].localToWorldMatrix.MultiplyPoint3x4(binds[index1].MultiplyPoint3x4(originalVertices[vert])) * weight1;
    70.            
    71.             int index2 = weights[vert].boneIndex2;
    72.             float weight2 = weights[vert].weight2;
    73.             if (weight2 > 0f)
    74.                 vertices[vert] += bones[index2].localToWorldMatrix.MultiplyPoint3x4(binds[index2].MultiplyPoint3x4(originalVertices[vert])) * weight2;
    75.            
    76.             int index3 = weights[vert].boneIndex3;
    77.             float weight3 = weights[vert].weight3;
    78.             if (weight3 > 0f)
    79.                 vertices[vert] += bones[index3].localToWorldMatrix.MultiplyPoint3x4(binds[index3].MultiplyPoint3x4(originalVertices[vert])) * weight3;
    80.         }
    81.        
    82.         mesh.vertices = vertices;
    83.     }
    84. }
    85.  
     
  2. KRGraphics

    KRGraphics

    Joined:
    Jan 5, 2010
    Posts:
    4,467
    What is the speed of this particular script? If this is what i think it is, you have just saved my bacon... I need to do cloth sim on some of my characters and this will help immensely...
     
  3. ecurtz

    ecurtz

    Joined:
    May 13, 2009
    Posts:
    640
    It should be fine for rendering a small number of characters, but I wouldn't use it for a crowd. I see from the other thread that you're doing a fighting game, I don't think it would be a very large hit there.

    As far as the collider goes, I haven't played with it much, but I think there is a rather large overhead for replacing the mesh (I assume it's doing a bunch of one time calculations) so that might hurt your framerate. You could try a lower resolution mesh on the same skeleton for collision where you concentrated the verts in the areas most likely to intersect the cloth.

    I actually didn't do much testing on this, just playing around, so I'm not sure about putting it onto your prefab - try it! It does require a link to the SkinnedMeshRenderer that Unity creates on import because that has magic information we can't access about which transform corresponds to which bone. Then just turn off or remove the SkinnedMeshRenderer in your instance and add a regular MeshRenderer.

    Also, as you can see it uses all four bone weights and ignores the quality setting, if you're using fewer you could just edit out the extra calculations.
     
    Last edited: Mar 20, 2012
  4. ChristmasEve

    ChristmasEve

    Joined:
    Apr 21, 2015
    Posts:
    45
    I know this is an old post but, how is this script supposed to work?
    The mesh.bindposes and mesh.boneWeights you're referencing are on the STATIC mesh, not the Skinned Mesh. Static meshes do not have bone weights or bind poses. I fixed those two errors but the resulting mesh still didn't work. I got a jumbled ball for a mesh. I'm trying to use this for a different purpose. I'm trying to morph just a single static mesh into the pose that a SkinnedMeshRenderer is currently in (both are humans with same bones). I'm not trying to continue animating it. But the idea is the same. There are no weights/binds on the static mesh that was created.
     
  5. ecurtz

    ecurtz

    Joined:
    May 13, 2009
    Posts:
    640
    Those values come from the "original" mesh as stored in the asset which is why it uses the static mesh. As far as I know there's no way to determine the order of the transforms used for skinning without referencing back to the original asset, at least there wasn't at the time.
     
  6. ecurtz

    ecurtz

    Joined:
    May 13, 2009
    Posts:
    640