This blog has moved. Visit Groundswell Games for the latest. Remember to update your bookmarks and RSS feeds.

Friday, November 28, 2008

Morph targets and a disembodied head

It's been a while since I posted something for you to play with.

Well, here you go.

A man's faceIt will take a minute to load (lots of stuff to calculate). Once it does, just play around with the sliders to customize the guy's face. You can click and drag on his face to turn him around. Cool, huh?

Unfortunately, I can't say that I whipped this up after Thanksgiving dinner. Actually I've been working on it for a week or so, trying out different methods of achieving what I wanted -- and some methods of getting nowhere close. Actually, this version is the most complex one I did. The fact that it works is reason enough to post it.

How it works
Now the thrilling explanation. One of the features that Unity lacks at the moment is the ability to handle morph targets (aka blend shapes) imported from 3D tools like Maya or Cheetah. So, the only way to implement them is to code a system by hand. After scouring the Unity forums, I found a script that would handle simple mesh morphs but didn't do anything as complex as multiple blended attributes.

Using the basics of that script, I manged to put together a system that works pretty well. Here's the basic flow of things:

  • To set everything up, I assign the base mesh (3D shape) in Unity, then create a list of attributes that I want to adjust. Each attribute has a reference mesh created by editing the original mesh to get the most extreme version of each facial attribute.
  • When the scene loads, it first stores several pieces of information (this is why it takes a while to load):
    • For each attribute (24 of them in this scene), it builds a list of vertices in the mesh that are affected by that attribute.
    • At the same time it builds a list of offset vectors for each vertex affected by each attribute. This information stores the maximum possible offset for each vertex per attribute. Lots of data building up here...
    • Then we build a list of which attributes affect each vertex. This is the converse of the first list above, and it's necessary to keep the code running smoothly.
  • After all that data has been stored, the default mesh loads along with a bunch of sliders to control the various attributes.
  • Each time a slider is adjusted, we calculate the proper amount to adjust each vertex affected by that attribute. This is done by using a weighted average of all the attributes affecting each vertex in question. By using a weighted average, we can combine attributes like the width and height of the eyes without getting conflicts (this part took me forever to figure out).
  • Once we have the right offset vectors, we adjust the affected vertices by those values and redraw the mesh. Voila!
Optimization
Because there's so much manipulation of individual vertices in this code, it was really hard to optimize. I spent almost the entire day trying to get it to run at an acceptable speed, and I still don't think it's good enough. The main problem is that when a vertex (or group of vertices) is affected by multiple attributes at once, the weighted average becomes harder to figure out and requires more processing juice. I attempted to store offset values rather than recalculating the average every time a vertex is affected, but I couldn't figure it out before dinner. This version runs well enough, so it will have to do.

Oh, and happy (late) Thanksgiving!

EDIT: I posted a new version of the file after getting some serious optimization help from Jamie. It still takes a while to start up (but not as long), and it runs considerably faster once it loads. My code now also morphs the normals as well as the vertex positions, which I had overlooked before.

5 comments:

  1. Ha! This is interesting because this is one of the backend parts of my software. The frontend part allows you to set up complex relationships between the targets, so that if you drive up target_1, you can have the system automatically drive up target_2 and drive down target_3. The backend is not only morph targets but also bone poses and material parameters (animated normal mapped wrinkles), or even a custom backend like a muscle based animation system. If you'll be in town for the holidays I'll show you how it all works (I don't think I've ever really showed any of you guys my software).

    If you send me the source for your script I'll see if I can make it run faster for you. Looking at the script you based it on, it's no wonder that it's slow. :) I've never worked with Unity so just getting to the point where I can easily test my changes to the script will be the hardest part.

    ReplyDelete
  2. I would love a demo of your software when you're in town. Sounds like it has some nice bells and whistles (like the animated normal maps -- that's pretty slick). I'll send you my script too. There are a couple of areas where I think it could be streamlined. But be warned, I tend to program by trial and error, so I sometimes end up with messy code. My script is written in Unity's version of JavaScript. Even if you have some optimization ideas that you could write out in psuedo code, I could probably figure out how to implement them.

    ReplyDelete
  3. Actually, I don't think I could really do anything with the script since I don't have a Mac here in Prague. I can certainly look at it and tell you how to make it faster, and maybe make some changes to it, but I won't be able to run it here at all. :) I can already tell you how to make the one you linked in your blog post faster, but I don't know how you've modified it so I don't know how applicable my changes are to your script. Just email it or something and I'll take a look.

    ReplyDelete
  4. Hello Ben !

    Officially Unity 3D lacks support for vertex morphing/animation or morph targets/blend shapes, respectively; but inofficially, You have added this much sought after capability to Unity 3D via Your 'Morph Target Script' (Unity Forum Thread: http://forum.unity3d.com/viewtopic.php?t=16425). Did You know: Your addition of vertex morphing can play a very important role in the evaluation of web 3D authoring packages.

    So my questions are: Do You
    - still do further development on this 'Morph Target Script' approach in May 2009 ?
    - officially recommend to use this approach ? E.g. in mission-critical gaming site enterprises ? My impression of the provided head morph example was: When starting to play it within Unity, it takes well over one minute before the sliders are displayed ... The same holds true for loading the published web page in Firefox 3 ...

    I'm on a Windows XP SP3 machine, using the recently released Windows version of Unity 2.5.

    Since this is an important topic - at least to me it is - I hope You will respond to this comment !


    Cheers and Tschüss

    Kai (Tischler) from Northrhine-Westfalia in Germany

    ReplyDelete
  5. Kai, thanks for your comment. To answer your questions:

    1. I haven't done any further development on the script. I may do some in the future. Mostly I wanted to see if it was possible.
    2. I don't think I can officially recommend anything, but I do think this general approach could work if it were optimized some more. The amount of time your scene takes to load will depend directly on the number of vertices in your model and the number of different morphs you're trying to do. My example was fairly complex. It might also be possible to figure out a way to stream in morph data so the scene wouldn't take so long to load.

    If you were going to use this script in a character creation system, you could use the morph script to create a character and then "bake" it into your final character mesh by saving the mesh data and using it later.

    The only way this script could handle real-time facial animation would probably be if you worked with a very simple mesh.

    I hope this helps some. I will say that Unity is an amazing tool, and you shouldn't let this one feature make the decision for you. Since Unity does let you manipulate mesh data during runtime, with a little work, you could probably create something more efficient than my script.

    Ben

    ReplyDelete