## Morph Animation

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 &amp;&amp; m_DstMesh == dstIndex &amp;&amp; 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 &lt; 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 &lt; 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 &lt; 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 &lt; 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);
}
}
}

2016-06-18 Comments