{"id":476,"date":"2016-06-18T16:00:14","date_gmt":"2016-06-18T08:00:14","guid":{"rendered":"http:\/\/www.cgdev.net\/blog\/?p=476"},"modified":"2023-04-21T08:37:12","modified_gmt":"2023-04-21T00:37:12","slug":"morph-animation","status":"publish","type":"post","link":"https:\/\/www.cgdev.net\/blog\/476.html","title":{"rendered":"Morph Animation"},"content":{"rendered":"<p>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\u00a0or 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&#8217;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:<\/p>\n<p>\\[<br \/>\nvertex = base + \\sum_{i=1}^n ( w_i * ( target_i &#8211; base ) )<br \/>\n\\]<\/p>\n<p>The formula above can be used for vertex position, normal, UV, etc.<\/p>\n<p>By using a jd file format we can do interpolation between pos0 and pos1, normal0 and normal1<\/p>\n<p>The following example from Unity wiki simply use Lerp to blend vertices between 2 meshes:<\/p>\n<pre lang='a_c'>\r\nusing UnityEngine;\r\n\r\n\/\/\/ REALLY IMPORTANT NOTE.\r\n\/\/\/ When using the mesh morpher you should absolutely make sure that you turn\r\n\/\/\/ off generate normals automatically in the importer, or set the normal angle to 180 degrees.\r\n\/\/\/ When importing a mesh Unity automatically splits vertices based on normal creases.\r\n\/\/\/ However the mesh morpher requires that you use the same amount of vertices for each mesh and that\r\n\/\/\/ those vertices are laid out in the exact same way. Thus it wont work if unity autosplits vertices based on normals.\r\n[RequireComponent(typeof(MeshFilter))]\r\npublic class MeshMorpher : MonoBehaviour\r\n{\r\n\u00a0\u00a0 public Mesh[] m_Meshes;\r\n\u00a0\u00a0 public bool m_AnimateAutomatically = true;\r\n\u00a0\u00a0 public float m_OneLoopLength = 1.0F; \/\/\/ The time it takes for one loop to complete\r\n\u00a0\u00a0 public WrapMode m_WrapMode = WrapMode.Loop;\r\n\u00a0\u00a0 private float m_AutomaticTime = 0;\r\n\r\n\u00a0\u00a0 private int m_SrcMesh = -1;\r\n\u00a0\u00a0 private int m_DstMesh = -1;\r\n\u00a0\u00a0 private float m_Weight = -1;\r\n\u00a0\u00a0 private Mesh m_Mesh;\r\n\r\n\u00a0\u00a0 \/\/\/ Set the current morph in\u00a0\u00a0 \u00a0\r\n\u00a0\u00a0 public void SetComplexMorph(int srcIndex, int dstIndex, float t)\r\n\u00a0\u00a0 {\r\n\u00a0\u00a0\u00a0\u00a0\u00a0 if (m_SrcMesh == srcIndex &amp;&amp; m_DstMesh == dstIndex &amp;&amp; Mathf.Approximately(m_Weight, t))\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 return;\r\n\r\n\u00a0\u00a0\u00a0\u00a0\u00a0 Vector3[] v0 = m_Meshes[srcIndex].vertices;\r\n\u00a0\u00a0\u00a0\u00a0\u00a0 Vector3[] v1 = m_Meshes[dstIndex].vertices;\r\n\u00a0\u00a0\u00a0\u00a0\u00a0 Vector3[] vdst = new Vector3[m_Mesh.vertexCount];\r\n\u00a0\u00a0\u00a0\u00a0\u00a0 for (int i = 0; i &lt; vdst.Length; i++)\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 vdst[i] = Vector3.Lerp(v0[i], v1[i], t);\r\n\r\n\u00a0\u00a0\u00a0\u00a0\u00a0 m_Mesh.vertices = vdst;\r\n\u00a0\u00a0\u00a0\u00a0\u00a0 m_Mesh.RecalculateBounds();\r\n\u00a0\u00a0 }\r\n\r\n\u00a0\u00a0 \/\/\/ t is between 0 and m_Meshes.Length - 1.\r\n\u00a0\u00a0 \/\/\/ 0 means the first mesh, m_Meshes.Length - 1 means the last mesh.\r\n\u00a0\u00a0 \/\/\/ 0.5 means half of the first mesh and half of the second mesh.\r\n\u00a0\u00a0 public void SetMorph(float t)\r\n\u00a0\u00a0 {\r\n\u00a0\u00a0\u00a0\u00a0\u00a0 int floor = (int)t;\r\n\u00a0\u00a0\u00a0\u00a0\u00a0 floor = Mathf.Clamp(floor, 0, m_Meshes.Length - 2);\r\n\u00a0\u00a0\u00a0\u00a0\u00a0 float fraction = t - floor;\r\n\u00a0\u00a0\u00a0\u00a0\u00a0 fraction = Mathf.Clamp(t - floor, 0.0F, 1.0F);\r\n\u00a0\u00a0\u00a0\u00a0\u00a0 SetComplexMorph(floor, floor + 1, fraction);\r\n\u00a0\u00a0 }\r\n\r\n\u00a0\u00a0 void Awake()\r\n\u00a0\u00a0 {\r\n\u00a0\u00a0\u00a0\u00a0\u00a0 enabled = m_AnimateAutomatically;\r\n\u00a0\u00a0\u00a0\u00a0\u00a0 MeshFilter filter = GetComponent(typeof(MeshFilter)) as MeshFilter;\r\n\r\n\u00a0\u00a0\u00a0\u00a0\u00a0 \/\/ Make sure all meshes are assigned!\r\n\u00a0\u00a0\u00a0\u00a0\u00a0 for (int i = 0; i &lt; m_Meshes.Length; i++)\r\n\u00a0\u00a0\u00a0\u00a0\u00a0 {\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 if (m_Meshes[i] == null)\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 {\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 Debug.Log(\"MeshMorpher mesh\u00a0 \" + i + \" has not been setup correctly\");\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 m_AnimateAutomatically = false;\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 return;\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 }\r\n\u00a0\u00a0\u00a0\u00a0\u00a0 }\r\n\r\n\u00a0\u00a0\u00a0\u00a0\u00a0 \/\/\u00a0 At least two meshes\r\n\u00a0\u00a0\u00a0\u00a0\u00a0 if (m_Meshes.Length &lt; 2)\r\n\u00a0\u00a0\u00a0\u00a0\u00a0 {\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 Debug.Log(\"The mesh morpher needs at least 2 source meshes\");\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 m_AnimateAutomatically = false;\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 return;\r\n\u00a0\u00a0\u00a0\u00a0\u00a0 }\r\n\r\n\u00a0\u00a0\u00a0\u00a0\u00a0 filter.sharedMesh = m_Meshes[0];\r\n\u00a0\u00a0\u00a0\u00a0\u00a0 m_Mesh = filter.mesh;\r\n\u00a0\u00a0\u00a0\u00a0\u00a0 int vertexCount = m_Mesh.vertexCount;\r\n\u00a0\u00a0\u00a0\u00a0\u00a0 for (int i = 0; i &lt; m_Meshes.Length; i++)\r\n\u00a0\u00a0\u00a0\u00a0\u00a0 {\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 if (m_Meshes[i].vertexCount != vertexCount)\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 {\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 Debug.Log(\"Mesh \" + i + \" doesn't have the same number of vertices as the first mesh\");\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 m_AnimateAutomatically = false;\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 return;\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 }\r\n\u00a0\u00a0\u00a0\u00a0\u00a0 }\r\n\u00a0\u00a0 }\r\n\r\n\u00a0\u00a0 void Update()\r\n\u00a0\u00a0 {\r\n\u00a0\u00a0\u00a0\u00a0\u00a0 if (m_AnimateAutomatically)\r\n\u00a0\u00a0\u00a0\u00a0\u00a0 {\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 float deltaTime = Time.deltaTime * (m_Meshes.Length - 1) \/ m_OneLoopLength;\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 m_AutomaticTime += deltaTime;\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 float time;\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 if (m_WrapMode == WrapMode.Loop)\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 time = Mathf.Repeat(m_AutomaticTime, m_Meshes.Length - 1);\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 else if (m_WrapMode == WrapMode.PingPong)\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 time = Mathf.PingPong(m_AutomaticTime, m_Meshes.Length - 1);\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 else\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 time = Mathf.Clamp(m_AutomaticTime, 0, m_Meshes.Length - 1);\r\n\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 SetMorph(time);\r\n\u00a0\u00a0\u00a0\u00a0\u00a0 }\r\n\u00a0\u00a0 }\r\n}<\/pre>\n<p>&nbsp;<\/p>\n<p>&nbsp;<\/p>\n","protected":false},"excerpt":{"rendered":"<p>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\u00a0or 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&#8217;s [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[8,23],"tags":[16],"class_list":["post-476","post","type-post","status-publish","format-standard","hentry","category-graphics","category-unity","tag-animation"],"_links":{"self":[{"href":"https:\/\/www.cgdev.net\/blog\/wp-json\/wp\/v2\/posts\/476","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.cgdev.net\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.cgdev.net\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.cgdev.net\/blog\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/www.cgdev.net\/blog\/wp-json\/wp\/v2\/comments?post=476"}],"version-history":[{"count":0,"href":"https:\/\/www.cgdev.net\/blog\/wp-json\/wp\/v2\/posts\/476\/revisions"}],"wp:attachment":[{"href":"https:\/\/www.cgdev.net\/blog\/wp-json\/wp\/v2\/media?parent=476"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.cgdev.net\/blog\/wp-json\/wp\/v2\/categories?post=476"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.cgdev.net\/blog\/wp-json\/wp\/v2\/tags?post=476"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}