Sometimes we need only a subset of the vertices in a mesh to be animated without a full skeleton, such as a set of mouth shapes or face vertices for facial animation. A easy way to do this is using Morph Target Animation. In this animation we blend vertices instead of bones, this is why it’s also called per-vertex animation. The animation is stored as a series of deformed versions of the original mesh vertices. The deformed version is called morph target while the original is called base. We can use a weights array to blend between a base and morph targets:

\[
vertex = base + \sum_{i=1}^n ( w_i * ( target_i – base ) )
\]

The formula above can be used for vertex position, normal, UV, etc.

By using a jd file format we can do interpolation between pos0 and pos1, normal0 and normal1

The following example from Unity wiki simply use Lerp to blend vertices between 2 meshes:

using UnityEngine;
 
/// REALLY IMPORTANT NOTE.
/// When using the mesh morpher you should absolutely make sure that you turn
/// off generate normals automatically in the importer, or set the normal angle to 180 degrees.
/// When importing a mesh Unity automatically splits vertices based on normal creases.
/// However the mesh morpher requires that you use the same amount of vertices for each mesh and that
/// those vertices are laid out in the exact same way. Thus it wont work if unity autosplits vertices based on normals.
[RequireComponent(typeof(MeshFilter))]
public class MeshMorpher : MonoBehaviour
{
   public Mesh[] m_Meshes;
   public bool m_AnimateAutomatically = true;
   public float m_OneLoopLength = 1.0F; /// The time it takes for one loop to complete
   public WrapMode m_WrapMode = WrapMode.Loop;
   private float m_AutomaticTime = 0;
 
   private int m_SrcMesh = -1;
   private int m_DstMesh = -1;
   private float m_Weight = -1;
   private Mesh m_Mesh;
 
   /// Set the current morph in    
   public void SetComplexMorph(int srcIndex, int dstIndex, float t)
   {
      if (m_SrcMesh == srcIndex && m_DstMesh == dstIndex && Mathf.Approximately(m_Weight, t))
         return;
 
      Vector3[] v0 = m_Meshes[srcIndex].vertices;
      Vector3[] v1 = m_Meshes[dstIndex].vertices;
      Vector3[] vdst = new Vector3[m_Mesh.vertexCount];
      for (int i = 0; i < vdst.Length; i++)
         vdst[i] = Vector3.Lerp(v0[i], v1[i], t);
 
      m_Mesh.vertices = vdst;
      m_Mesh.RecalculateBounds();
   }
 
   /// t is between 0 and m_Meshes.Length - 1.
   /// 0 means the first mesh, m_Meshes.Length - 1 means the last mesh.
   /// 0.5 means half of the first mesh and half of the second mesh.
   public void SetMorph(float t)
   {
      int floor = (int)t;
      floor = Mathf.Clamp(floor, 0, m_Meshes.Length - 2);
      float fraction = t - floor;
      fraction = Mathf.Clamp(t - floor, 0.0F, 1.0F);
      SetComplexMorph(floor, floor + 1, fraction);
   }
 
   void Awake()
   {
      enabled = m_AnimateAutomatically;
      MeshFilter filter = GetComponent(typeof(MeshFilter)) as MeshFilter;
 
      // Make sure all meshes are assigned!
      for (int i = 0; i < m_Meshes.Length; i++)
      {
         if (m_Meshes[i] == null)
         {
            Debug.Log("MeshMorpher mesh  " + i + " has not been setup correctly");
            m_AnimateAutomatically = false;
            return;
         }
      }
 
      //  At least two meshes
      if (m_Meshes.Length < 2)
      {
         Debug.Log("The mesh morpher needs at least 2 source meshes");
         m_AnimateAutomatically = false;
         return;
      }
 
      filter.sharedMesh = m_Meshes[0];
      m_Mesh = filter.mesh;
      int vertexCount = m_Mesh.vertexCount;
      for (int i = 0; i < m_Meshes.Length; i++)
      {
         if (m_Meshes[i].vertexCount != vertexCount)
         {
            Debug.Log("Mesh " + i + " doesn't have the same number of vertices as the first mesh");
            m_AnimateAutomatically = false;
            return;
         }
      }
   }
 
   void Update()
   {
      if (m_AnimateAutomatically)
      {
         float deltaTime = Time.deltaTime * (m_Meshes.Length - 1) / m_OneLoopLength;
         m_AutomaticTime += deltaTime;
         float time;
         if (m_WrapMode == WrapMode.Loop)
            time = Mathf.Repeat(m_AutomaticTime, m_Meshes.Length - 1);
         else if (m_WrapMode == WrapMode.PingPong)
            time = Mathf.PingPong(m_AutomaticTime, m_Meshes.Length - 1);
         else
            time = Mathf.Clamp(m_AutomaticTime, 0, m_Meshes.Length - 1);
 
         SetMorph(time);
      }
   }
}

 

 

Leave a Reply

Your email address will not be published. Required fields are marked *