Morph Target Script

Hi all,

Seems like folks ask fairly often about morph targets with Unity:

http://forum.unity3d.com/viewtopic.php?t=9103
http://forum.unity3d.com/viewtopic.php?t=6199
http://forum.unity3d.com/viewtopic.php?t=14235
http://forum.unity3d.com/viewtopic.php?t=9076
etc…

Well, inspired by Eric’s MeshMorper script on the wiki, and with some help from a friend who works with morph targets for a living, I managed to create a simple script (attached) that handles mesh morphing between multiple weighted targets.

Here’s a sample where I used it to set up some face customization using sliders to control the different morph targets: http://breccasfall.com/unity/createaface.html (It takes a few seconds to load. :))

Like the MeshMorpher script, which was the starting point for mine, the target for each morph “attribute” comes from a separate mesh, and each target mesh must have the same number of vertices laid out in the same order as the source mesh.

This script could easily be used for basic facial animations. With some more work, it could also probably be extended to work with skeletal animations as well as mesh morphs.

Enjoy!

-B

Edit: Attached a package showing how my scene is set up.

106116–4051–$morphtargets_191.js (4.39 KB)
106116–4061–$morphtargets_384.unitypackage (3.58 MB)

Just want to say it’s not my script, I only translated the C# script which I think was by Joachim. :slight_smile:

–Eric

My bad; thanks for correcting me. Just saw your name next to the JavaScript version and assumed you wrote the original.

hi,
would you be able to publish the project file with your script inlcuded?
I couldnt figure out how it works and whats the big difference to erics/joachims scripts.
We have problems with the meshs, since they need to be sewed and after sewing the uv-texture doesnt fit anymore :frowning:

cheers
d

I am at work now, but I can publish the project file later today.

The original MeshMorpher script lets you morph between several versions of an entire mesh. While there can be any number of targets, only one of them can be in play at a time. It won’t let you combine the results of multiple morph targets.

My script registers which vertices in the mesh are affected by each morph target. So when you adjust how much weight a target has (like with a GUI slider), the script will morph the proper vertices for that target, but it will also take into account all the other targets that affect those vertices. As a result, you can create more complex morphing effects.

To get it to work, you need to set up the meshes and call the SetMorph() function from another script. In my example, I call SetMorph() from the GUI sliders when any of them are changed.

Hope that helps. I’ll post the project file later.

Excellent work, this works fantastically (at least from your example). I’ll have to give this a try sometimes.

Uploaded a package with my sample scene to the original post.

Ooh, thanks a heap for this! Doing facial anims with bones is just too much hassle for me so this is great news :slight_smile:

hieeee guys
well i havent seen ur code yet but surly go through it.
i have manage to get the Morphing of Face on IPhone its working fine but it i still want to increase the speed for the Morphing.I have loaded a single Obj Mesh morphing some special parts of face such as chin, nose, lips eyes, ears forehead i have subdivided this parts im doing calculations on only one part which ever is selected. can anyone guide me to increase the speed of Morphing???

The model which im working on is Having approx 900 faces.
im doing calculations on per-vertex base so its obvious tht it will go slow when the face count is more

the thing i would like to know is tht is there any option for scaling / translating Set of Triangles 2gether rather than going through the set of Vertices present in tht Triangles Set???

I’m trying to figure out how to set up my character in Max.

I can’t read the Cheetah .jas file to see how you set up the mesh and there is only one .jas file in the meshes folder. I was rather surprised not to see several .jas files in the meshes folder.

How did you set up your mesh in your modeler where there is only one .jas file?

So in Max I need to create a mesh for each morph?

I have clothing which will be turned off and on, so those will be on the master mesh and the face change morphers will be as you set up?

The .jas file contains all the different meshes. When the Cheetah file imports, all the meshes import with it, and they can then be used by the morph script.

You do need one mesh for each morph – it shouldn’t matter whether they’re in the same file or different ones. In my case I just created them all in the same file.

Hope that helps.

-B

I just wanted to add that I think this is a great project. Good work.

Finally we have been able to get this to work (from Max) thanks trypho for such a useful tool.

Although it is a bit slow to load, its effective.
In max we have a avatar and we made morphs from this original one, so all the verts are the same. We had vocal, eyebrows, emo, quite a bit of them.
Exported this file as an fbx.
In unity played around with the script,
Made sure (like the script requires) check “calculate normals” and set angle to 180.
some of the meshes do get a little deformed, not sure why but looks fixable (I hope)
Ran the script.(modified by our programmers )

Now on to blending this with custom layered animation…

I found if you export models with normals using fbx (from packages that support it) then unity won’t need to recalculate them and won’t add extra points to your model, which is very important when trying to morph them.

Hey, this project has been a huge help for me, but I wanted to ask if anyone who might have done something similar had a more elegant solution to scripting these to trigger off keyboard input. Here’s the script I’ve got triggering them, there’s 20 morphs but I’ve only bound the top row of keys to them, as this is more of just a proof of concept.

var speed = 0.1;
var faceSpeed = new Array(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0);
var morphObject : MorphTargets;
var morphspeed = 0.04;

function Update () {

   x= Input.GetAxis ("Horizontal") * speed;
   // Rotate around works in radians
   transform.RotateAround (Vector3.up, -x);
   if (Input.GetKey (KeyCode.Q)) 
   {
	faceSpeed[0] = morphspeed ;
   }
   else if (Input.GetKey (KeyCode.W)) 
   {
	faceSpeed[1] = morphspeed ;
   }
   else if (Input.GetKey (KeyCode.E)) 
   {
	faceSpeed[2] = morphspeed ;
   }
   else if (Input.GetKey (KeyCode.R)) 
   {
	faceSpeed[3] = morphspeed ;
   }
   else if (Input.GetKey (KeyCode.T)) 
   {
	faceSpeed[4] = morphspeed ;
   }
   else if (Input.GetKey (KeyCode.Y)) 
   {
	faceSpeed[5] = morphspeed ;
   }
   else if (Input.GetKey (KeyCode.U)) 
   {
	faceSpeed[6] = morphspeed ;
   }
   else if (Input.GetKey (KeyCode.I)) 
   {
	faceSpeed[7] = morphspeed ;
   }
   else if (Input.GetKey (KeyCode.O)) 
   {
	faceSpeed[8] = morphspeed ;
   }
   else if (Input.GetKey (KeyCode.P)) 
   {
	faceSpeed[9] = morphspeed ;
   }
	else if (Input.GetKey (KeyCode.Alpha2)) 
   {
    for(var i = 0; i<20; i++)
		{
			morphObject.attributeProgress[i] = 0;
		}
		morphObject.SetMorph();
	}
	else if (Input.GetKey (KeyCode.UpArrow)){
		faceSpeed += 0.01;
	}
	else if (Input.GetKey (KeyCode.DownArrow)){
		faceSpeed -= 0.01;
	}
	updateTalk();
}

function updateTalk()
{
	for(var i = 0; i<20; i++)
	{
		morphObject.attributeProgress[i] += faceSpeed[i];
		if(morphObject.attributeProgress[i] >= 1)
			faceSpeed[i] -= faceSpeed[i]*2;
		if(morphObject.attributeProgress[i] <= 0)
		{
			morphObject.attributeProgress[i] = 0;
			faceSpeed[i] = 0;
		}
	}
	morphObject.SetMorph();
}

EDIT: Also, I’m thinking I’m going to need to attach this to a skinned mesh renderer instead of just a mesh renderer, I’m pretty new to all of this, does this sound feasible?

Mostly we have been able to use this code to import “morphs” into unity with success. We are having an issue with some elements.
We turn all the normals to 180 before exporting the fbx from max.
Import into Unity.

We don’t have to “calculate normals” on ALL models because some of them work on the fly.
We have to calculate normals when unity says that all the vertices do not match. Although all the morphs are taken from the main mesh. So they should be all the same amount of verts…

Anyone know of a solution?

Hi,

I somehow felt, that the performance of the JavaScript was to weak, so I wrote a C# version, which improves my fps from 7 to 400 (in my special case).

/**
 * Just a 20min port of trypho's JS script to C# by skahlert
 * In my experience the performance improved significantly (about 57x the speed of the JavaScript).
 * Have fun with it!!
 * 
 * If you find a nicer way of compensating the lack of dynamic arrays (line 81 and following) it would be nice to hear!
 * 
 */


using System;
using UnityEngine;

class MyMeshMorpher : MonoBehaviour
{
    internal class BlendShapeVertex
    {
        public int originalIndex;
        public Vector3 position;
        public Vector3 normal;
    }

    internal class BlendShape
    {
        public BlendShapeVertex[] vertices;// = new Array();
    }

    public String[] attributes; //Names for the attributes to be morphed
    public Mesh sourceMesh; //The original mesh
    public Mesh[] attributeMeshes; //The destination meshes for each attribute.
    public float[] attributeProgress;

    private BlendShape[] blendShapes;
    private Mesh workingMesh;

    void Awake()
    {
        for (int i = 0; i < attributeMeshes.Length; i++)
        {
            if (attributeMeshes[i] == null)
            {
                Debug.Log("Attribute " + i + " has not been assigned.");
                return;
            }
        }

        //Populate the working mesh
        MeshFilter filter = gameObject.GetComponent(typeof(MeshFilter)) as MeshFilter;
        filter.sharedMesh = sourceMesh;
        workingMesh = filter.mesh;

        //Check attribute meshes to be sure vertex count is the same.
        int vertexCount = sourceMesh.vertexCount;
        //Debug.Log("Vertex Count Source:"+vertexCount);
        for (int i = 0; i < attributeMeshes.Length; i++)
        {
            //Debug.Log("Vertex Mesh "+i+":"+attributeMeshes[i].vertexCount); 
            if (attributeMeshes[i].vertexCount != vertexCount)
            {

                Debug.Log("Mesh " + i + " doesn't have the same number of vertices as the first mesh");
                return;
            }
        }

        //Build blend shapes
        BuildBlendShapes();
    }

    void BuildBlendShapes()
    {

        blendShapes = new BlendShape[attributes.Length];

        //For each attribute figure out which vertices are affected, then store their info in the blend shape object.
        for (int i = 0; i < attributes.Length; i++)
        {
            //Populate blendShapes array with new blend shapes
            blendShapes[i] = new BlendShape();

            /** TODO: Make this a little more stylish!
             *  UGLY hack to compensate the lack of dynamic arrays in C#. Feel free to improve!
             */
            int blendShapeCounter = 0;
            for (int j = 0; j < workingMesh.vertexCount; j++)
            {
                
                if (workingMesh.vertices[j] != attributeMeshes[i].vertices[j])
                {
                    blendShapeCounter++;
                }
            }
            
            blendShapes[i].vertices = new BlendShapeVertex[blendShapeCounter];
            blendShapeCounter = 0;
            for (int j = 0; j < workingMesh.vertexCount; j++)
            {
                //If the vertex is affected, populate a blend shape vertex with that info
                if (workingMesh.vertices[j] != attributeMeshes[i].vertices[j])
                {
                    //Create a blend shape vertex and populate its data.
                    
                    BlendShapeVertex blendShapeVertex = new BlendShapeVertex();
                    blendShapeVertex.originalIndex = j;
                    blendShapeVertex.position = attributeMeshes[i].vertices[j] - workingMesh.vertices[j];
                    blendShapeVertex.normal = attributeMeshes[i].normals[j] - workingMesh.normals[j];

                    //Add new blend shape vertex to blendShape object.
                    blendShapes[i].vertices[blendShapeCounter]=blendShapeVertex;
                    blendShapeCounter++;
                }
            }

            //Convert blendShapes.vertices to builtin array
            //blendShapes[i].vertices = blendShapes[i].vertices.ToBuiltin(BlendShapeVertex);
        }
    }


    public void SetMorph()
    {

        //Set up working data to store mesh offset information.
        Vector3[] morphedVertices = sourceMesh.vertices;
        Vector3[] morphedNormals = sourceMesh.normals;

        //For each attribute...
        for (int j = 0; j < attributes.Length; j++)
        {
            //If the weight of this attribute isn't 0	
            if (!Mathf.Approximately(attributeProgress[j], 0))
            {
                //For each vertex in this attribute's blend shape...
                for (int i = 0; i < blendShapes[j].vertices.Length; i++)
                {
                    //...adjust the mesh according to the offset value and weight
                    morphedVertices[blendShapes[j].vertices[i].originalIndex] += blendShapes[j].vertices[i].position * attributeProgress[j];
                    //Adjust normals as well
                    morphedNormals[blendShapes[j].vertices[i].originalIndex] += blendShapes[j].vertices[i].normal * attributeProgress[j];
                }
            }
        }

        //Update the actual mesh with new vertex and normal information, then recalculate the mesh bounds.		
        workingMesh.vertices = morphedVertices;
        workingMesh.normals = morphedNormals;
        workingMesh.RecalculateBounds();
    }



}

Hope it helps!

Cheers,
Stefan

Oops,

I forgot the RequireComponents… Here’s the updated script.

203858–7510–$morphtargets_198.cs (5.53 KB)

I downloaded the file : morphtargets.unityPackage but when i try to import it into Unity 3D (2.5 Pro) i get this error message : Error while importing Package: Couldn’t decompress package.

Any ideas ? :sweat_smile: :sweat_smile: :sweat_smile:

i have used this script of yours and it works perfectly for my needs.

i have also made an editor script that allows you to better control the scene where you use the morphs (specially in crowded scenes that need model updates)

find it here:

http://forum.unity3d.com/viewtopic.php?p=241386#241386

and… thankz!