Blend Shapes Changing Vertex Normals

Unless I’m misunderstanding, the new blend shape normal calculation options don’t have a way to retain the mesh’s base normals. This is particularly problematic when animating meshes with custom vertex normals (for things like toon shading, etc.). The editor tooltip says:

“If Import is selected and the blend shape has no normals, they will be calculated instead”

But could there not be an option for the blend shapes to retain the normals of the mesh in its default state, so that the normals don’t change at all when animating blend shapes? The way it is now, no matter what my setup is, so long as I have custom normals the blend shapes will change the normals to the calculated versions.

For reference, I’m using 3ds Max and the Morpher modifier, and I’ve turned off Smoothing Groups when exporting my FBX because it will average out the vertex normals I’ve set manually, which is undesirable.

Edit: I just checked 2018.2, and this seems to be the behaviour I’m talking about. Another option in the importer for having the blend shapes interpolate normals like this would be greatly appreciated. Something like a Legacy option would be ideal.

2 Likes

We are currently tracking a bug related to the FBX importer calculating normals for blend shapes even when the ‘Blend Shape Normals’ setting is set to ‘None’. You can follow that bug here. However, the ‘None’ setting should be used when the object requires no normal information, no lighting.

Regarding smoothing groups. Although this didn’t seem to have any impact on my test files, our documentation does say that smoothing groups are required for importing blendshape normals:

“Note Importing blendshape normals requires smoothing groups in the FBX file.” - https://docs.unity3d.com/Manual/FBXImporter-Model.html

Regarding your edit: Are you saying that 2018.2 has the behaviour you need or exhibits the issue?

1 Like

Hey Thomh, thanks for the reply.

2018.2 has the behaviour I need. When I change the blendshape values in 2018.2, the normals/smoothing groups retain those of the base mesh. When I try to do the same in 2018.3, they will progressively change to the recalculated normals/smoothing groups.

Basically I want to preserve my custom normals/smoothing group information when my blendshapes are active, but all of the current options always end up recalculating them.

I’ve attached a quick GIF to show the problem. 2018.2 is the desired effect; 2018.3 is undesired.

4335727--391195--FaceNormals.gif

In 2018.3 I was only able to get this kind of behaviour from an FBX exported from blender in ascii mode. I was unable to get this behaviour from any FBX exported from MAX.

Are you using ascii or binary FBX files?

Both have the same effect.

In order to preserve custom normals in MAX while exporting morph targets (blendshapes), I actually have to not export the Smoothing Groups (I’ve shown my export settings in the attached image). Otherwise, MAX will recalculate the normals due to how the Morpher modifier works.

This trick seems to work fine in 2018.2, preserving my custom normals/smoothing groups and retaining them in the blendshapes. Could it be because of the bug you mentioned? I basically want the functionality that was default in 2018.2 (blendshapes not calculating normals at all), and perhaps that’s what the None option is supposed to do, it just doesn’t due to the bug?

There seems to be an issue with some FBX files ending up with calculated blend shape normals when they should be imported. I’ve logged some bugs against these problems but if you’re having trouble right now with blend shape normals getting recalculated (when you don’t want them to be) please take a look at this script editor script for creating a new mesh asset based on any other mesh asset (but with normals copied from the base mesh). Use it like this:

  1. Put this script into your project.
  2. Find the FBX that contains the incorrectly imported blend shape.
  3. Expand the asset to show the mesh, right click on it.
  4. Select ‘Generate Mesh With Fixed Normals’.

Use your fbx model as the basis for your character and then simply drop the exported ‘_fixed’ mesh onto the skinned mesh renderer’s mesh reference. Everything should be preserved apart from the blend shape normals which will be changed.

I hope this helps!

using UnityEngine;
using UnityEditor;

public class FixNormals
{
    /*
     * Set the normals for each blend shape frame to the
     * same normals as the base mesh and export a new mesh
     * asset with a sensible name. Select a mesh, right
     * click and choose 'Generate Mesh With Fixed Normals'.
     */
    [MenuItem("Assets/Generate Mesh With Fixed Normals")]
    private static void FixBlendShapeNormals()
    {

        if(Selection.activeObject.GetType() != typeof(Mesh))
        {
            Debug.LogError("This isn't a mesh.");
            return;
        }

        Debug.Log("let's fix these blendshape normals!");

        Mesh selected = Selection.activeObject as Mesh;
     
        Vector3[] deltaVertices = new Vector3[selected.vertexCount];
        Vector3[] deltaNormals = new Vector3[selected.vertexCount];
        Vector3[] deltaTangents = new Vector3[selected.vertexCount];

        int bsc = selected.blendShapeCount;

        Mesh newMesh = new Mesh();
        newMesh.vertices = selected.vertices;
        newMesh.uv = selected.uv;
        newMesh.normals = selected.normals;
        newMesh.colors = selected.colors;
        newMesh.tangents = selected.tangents;
        newMesh.subMeshCount = selected.subMeshCount;
        //newMesh.triangles = selected.triangles;

        int subMeshes = selected.subMeshCount;
        for (int i = 0; i < subMeshes; i++) {
            int[] tris = selected.GetTriangles(i);
            newMesh.SetIndices(tris, MeshTopology.Triangles, i);
        }

        newMesh.name = selected.name + "_fixed";
        newMesh.boneWeights = selected.boneWeights;
        newMesh.bindposes = selected.bindposes;

        for (int i = 0; i< bsc; i++)
        {
            string name = selected.GetBlendShapeName(i);
            int weightCount = selected.GetBlendShapeFrameCount(i);
            for (int j = 0; j < weightCount; j++)
            {
                float weight = selected.GetBlendShapeFrameWeight(i, j);
                selected.GetBlendShapeFrameVertices(i, j, deltaVertices, deltaNormals, deltaTangents);
                newMesh.AddBlendShapeFrame(name, weight, deltaVertices, selected.normals, deltaTangents);
            }
        }

        string savePath = AssetDatabase.GetAssetPath(selected);
        savePath = savePath.Substring(0, savePath.LastIndexOf('/') + 1);
     
        string newAssetName = savePath + selected.name + "_fixed.asset";

        AssetDatabase.CreateAsset(newMesh, newAssetName);

        AssetDatabase.SaveAssets();

        Debug.Log("Done!");
    }
}
10 Likes

just used this script and it WORKS!!! kinda…
the eye pupils of my 3d model get distored see pic

edit: nvm fixed the issue by setting the submeshcount to the num
ber of submeshes my mesh had


Line 38 should read:

newMesh.subMeshCount = selected.subMeshCount;

Then it will work for all submesh counts.


it seems still has problem,the vertex normal change with blender weight…

Hi dreamerflyer,

Try this version:

using UnityEngine;
using UnityEditor;

public class FixNormals
{
    /*
     * Set the normals for each blend shape frame to the
     * same normals as the base mesh and export a new mesh
     * asset with a sensible name. Select a mesh, right
     * click and choose 'Generate Mesh With Fixed Normals'.
     */
    [MenuItem("Assets/Generate Mesh With Fixed Normals")]
    private static void FixBlendShapeNormals()
    {

        if(Selection.activeObject.GetType() != typeof(Mesh))
        {
            Debug.LogError("This isn't a mesh.");
            return;
        }

        Debug.Log("let's fix these blendshape normals!");

        Mesh selected = Selection.activeObject as Mesh;
      
        Vector3[] deltaVertices = new Vector3[selected.vertexCount];
        Vector3[] deltaNormals = new Vector3[selected.vertexCount];
        Vector3[] deltaTangents = new Vector3[selected.vertexCount];

        int bsc = selected.blendShapeCount;

        Mesh newMesh = new Mesh();
        newMesh.vertices = selected.vertices;
        newMesh.uv = selected.uv;
        newMesh.normals = selected.normals;
        newMesh.colors = selected.colors;
        newMesh.tangents = selected.tangents;
        newMesh.subMeshCount = selected.subMeshCount;
        //newMesh.triangles = selected.triangles;

        int subMeshes = selected.subMeshCount;
        for (int i = 0; i < subMeshes; i++) {
            int[] tris = selected.GetTriangles(i);
            newMesh.SetIndices(tris, MeshTopology.Triangles, i);
        }

        newMesh.name = selected.name + "_fixed";
        newMesh.boneWeights = selected.boneWeights;
        newMesh.bindposes = selected.bindposes;

        Vector3[] zero = new Vector3[selected.vertexCount];
        for (int i = 0; i < zero.Length; i++)
            zero[i] = Vector3.zero;

        for (int i = 0; i< bsc; i++)
        {
            string name = selected.GetBlendShapeName(i);
            int weightCount = selected.GetBlendShapeFrameCount(i);
            for (int j = 0; j < weightCount; j++)
            {
                float weight = selected.GetBlendShapeFrameWeight(i, j);
                selected.GetBlendShapeFrameVertices(i, j, deltaVertices, deltaNormals, deltaTangents);
                newMesh.AddBlendShapeFrame(name, weight, deltaVertices, zero, deltaTangents);
            }
        }

        string savePath = AssetDatabase.GetAssetPath(selected);
        savePath = savePath.Substring(0, savePath.LastIndexOf('/') + 1);
      
        string newAssetName = savePath + selected.name + "_fixed.asset";

        AssetDatabase.CreateAsset(newMesh, newAssetName);

        AssetDatabase.SaveAssets();

        Debug.Log("Done!");
    }
}
2 Likes

Hi @thomh_unity is there going to be a built in way to get the old behaviour back? Or do we have to use this script in order to have the blendshape normals be the same as the base normals?

Also, does that mean that now normals get blended regardless and therefore the whole operation is more expensive?

Hi AcidArrow,

I’m fairly sure the current behaviour is not correct. I’ve logged a bug relating to this. It should get looked at in the near future.

4 Likes

Hi thomh, I just wanted to say that the script you provided has literally saved my day!! Thank you so much!! Our project has encountered the exact same problem and this will be our temporary solution. Hopefully it will be fixed soon in 2018.3x since we will not be able to migrate our entire project to 2019.x…

2 Likes

This appears to be fixed in 2019.1.4f1 by setting the “Blend Shape Normals” option in the model importer settings to “None”. Thanks a ton for all your help @thomh_unity !

EDIT: Looks like they also added a “Legacy Blend Shapes” option that arguably works even better! Great work guys, thank you so much.

2 Likes

I stumbled upon this by accident after all our blend shapes broke in the update. Thanks for verifying!

I am unsure if this is the same issue I am having but when I import my mesh into Unity and look at the animation preview I get strange smoothing or normal issues happening to my mesh. I check in Max and my mesh isn’t showing this. Is it something with my blend shapes or is there a export or import setting I am doing wrong?

For me, using the “import” option as opposed to “calculate” worked for me! :smile:

1 Like

Hello. I’m sorry to up this topic, but I’m using unity 2019.2 with Maya 2019 and still can’t import blend shape normals from Maya to Unity.
Here you can see Maya’s source before export and unity’s import :frowning:

1 Like

Thank you so much for this!
It’s been a huge help!

1 Like

I set my blendshape normals to None and it fixed the issue, my model still has shadows. You said “However, the ‘None’ setting should be used when the object requires no normal information, no lighting” but I havent seen any issues so far.

Do you know how to make this script update the current mesh instead of making a new one?