Skip to content

WTF Is Up Skinned Mesh Renderers

Keenan Woodall edited this page Jun 5, 2021 · 5 revisions

One of the most common questions I get asked is, "Does Deform support skinned mesh renderers?" My answer is always "yes, but not how you want."

This is due to how Unity updates skinned meshes. There is no hook that allows us to performantly modify a snapshot of the skinned mesh before it is rendered. The only thing we have access to is the unanimated shared mesh. This means that the Deformable has to modify the mesh before the skinned vertices are moved by their joints.

So what does that look like?

Well, it's not always great. Here's an example:

Notice how the squash and stretch effect looks fine at first, but as soon as I rotate the arms away from their default poses it completely breaks. This is because the Deformable is modifying the mesh before the joints affect it. So in this example the model gets squashed and then the arm joints rotate the arms upwards.

pre skinning


So is it possible to correct the order of operations, and have Deform run on the resulting skinned mesh?

Technically yes, but be warned this is very slow and hacky. (it really is so slow that I hesitate to share this workaround 😅)

While we don't have access to the final skinned mesh that is rendered, Unity allows us to bake out a snapshot to our own mesh using skinnedMeshRenderer.Bake(outputMesh)

This means we could theoretically write a script that bakes out a skinned mesh every frame and sends it off to a "normal" Deformable with a Mesh Filter for deformation and rendering. Reminder: this is incredibly expensive and weird, but sometimes ya gotta do weird shit.

I've gone ahead and written a simple script that should handle interfacing between a plain skinned mesh renderer and a deformable on a Mesh Filter. Point it at a Deformable and a disabled SkinnedMeshRenderer and it will take care of baking out the skinned mesh and sending it to the Deformable.

Here's how my hierarchy is set up with Liam, my test mesh

hierarchy

and here's the component:

using Deform;
using UnityEngine;

[ExecuteAlways]
public class SkinnedMeshBaker : MonoBehaviour
{
    public SkinnedMeshRenderer source;
    public Deformable target;

    private Mesh bakedMesh;

    private void OnEnable()
    {
        if (target == null)
            return;
        target.UpdateMode = UpdateMode.Custom;
    }

    private void OnDisable()
    {
        if (target != null)
            target.Complete();
    }

    private void Update()
    {
        if (source == null || target == null)
            return;
        if (bakedMesh == null)
            bakedMesh = new Mesh();
        // Bake the skinned mesh
        source.BakeMesh(bakedMesh);
        // Send it to the Deformable
        target.ChangeMesh(bakedMesh);
        // Schedule the deformable's calculations
        target.PreSchedule();
        target.Schedule ();
    }

    private void LateUpdate()
    {
        if (target == null)
            return;
        // Complete the deformable calculations
        target.Complete();
        // And apply the new data
        target.ApplyData();
    }
}

Once setup, it looks like this. The order of operations is correct now...but at what cost? (⊙_⊙;)

post skinning

Clone this wiki locally