Search Unity

Continuous smoothing between GameObjects

Discussion in 'General Graphics' started by ergin3d, Jul 9, 2020.

  1. ergin3d

    ergin3d

    Joined:
    Jul 31, 2015
    Posts:
    30
    Hi,

    I'm working on a new humanoid and I plan to use Blend-shapes / Morph-targets for the facial expressions of this character. I separated the head from the body and created some test targets for the head but the problem with this approach is that the smoothing doesn't continue(it looks cut). Do know a way to continue the smoothing. Or should I apply the blend-shapes to entire body (which I think may have performance drawbacks).



    Thanks
     
  2. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,352
    If the body and head are separate meshes, Unity (and real time render in general) will have no way to keep the normals between them smooth. Even if it was the same mesh and there was a seam, there's still no way to fix it. Real time rendering works on a single vertex at a time with no knowledge of even other vertices in the mesh. This is a content issue that needs to be fixed in the original content.

    Most 3D modelling tools will update a mesh's normals when parts are detached, and this is likely no different. Maya has options for locking the normals so that when you detach parts the original normals are retained. 3ds Max has the option to lock normals too, but it breaks if the mesh is separated into multiple objects. I don't know of a solution for this apart from custom max scripts to store off the normals and reapply them by matching vertex positions. Something like "Normal Thief" may be an option.
     
  3. ergin3d

    ergin3d

    Joined:
    Jul 31, 2015
    Posts:
    30
    Thanks for the reply gbolus! It was very helpful.

    It seems that the Morpher(Blend-shapes) modifier of the 3ds Max (version 2015) breaks the normals/ smoothing information of the edges and the edit normals above Morpher gets ignored during export. I will ask this question at the Autodesk Area forums and if I can find a solution I will post it here. I don't want to spend time on it but I think I can attempt to write a Unity / C# script that copies the normals information of the edges, the way Normal Thief does, if nothing else works.

    Thanks!
     
  4. ergin3d

    ergin3d

    Joined:
    Jul 31, 2015
    Posts:
    30
    No one replied at Area Forums so I wrote the script, to copy normals from overlapping vertices from the other mesh.

    https://github.com/ergin3d/Unify-Normals



    Running the script once is enough, but Meshes / Read Write must be enabled from Import Setting. There is also a debug option to display modified normals.
     
    t_tutiya likes this.
  5. Reanimate_L

    Reanimate_L

    Joined:
    Oct 10, 2009
    Posts:
    2,788
    there's a stitch normal script for 3ds to handle separated mesh normal
     
  6. ergin3d

    ergin3d

    Joined:
    Jul 31, 2015
    Posts:
    30
    Thanks for the reply Reanimate_L,

    modified normals above Morpher is ignored by the fbx exporter, and below is modified by Morpher.(3ds Max 2015) But the problem is solved. Anyone with the same issue can use this script to solve these types of problems:

    Script simply finds overlapping vertices from seperated meshes and unifys their normals.

    https://github.com/ergin3d/Unify-Normals
     
    ikazrima, jimmying, id0 and 2 others like this.
  7. local306

    local306

    Joined:
    Feb 28, 2016
    Posts:
    155
    A year later and I would like to thank you for this script!
     
    t_tutiya likes this.
  8. local306

    local306

    Joined:
    Feb 28, 2016
    Posts:
    155
    @ergin3d is it possible to do the same for the calculated blendshape normal values as well? I'm noticing that using any amount of blendshape breaks the unified normal appearance unfortunately.
     
  9. Michal_Stangel

    Michal_Stangel

    Joined:
    Apr 17, 2017
    Posts:
    151
    Thank you for sharing your script. It helped me to solve this problem, when I separate DAZ mesh in Blender by materials. Resulting figure looked like Frankenstein, but this smoothed out all the transitions between meshes. Something what UMA does, but I don't use it.
     
  10. ergin3d

    ergin3d

    Joined:
    Jul 31, 2015
    Posts:
    30
    No problem. "Any amount of blendshape breaks the unified normal appearance" is unfortunately correct.



    You can try the settings above. The torso mesh in the video is composed of two seperate meshes.
     
  11. Michal_Stangel

    Michal_Stangel

    Joined:
    Apr 17, 2017
    Posts:
    151
    It seems that turning "Bland Shape Normals" in Import Settings from Calculate to None works fine. I didn't notice it before but with default settings it really messes up the transitions between meshes again when applying body blendshapes. Hopefully it has not some downside.
    Anyway, thanks again :)
     
  12. Michal_Stangel

    Michal_Stangel

    Joined:
    Apr 17, 2017
    Posts:
    151
    Well, unfortunately it doesn't work. When I set Normals to "Import" and Blendshapes Normals to "None" in Import Settings and copy Normals from unsplitted mesh to it's parts, it works and hard edges are gone. But every time I reopen Unity, it's back, not sure why.
    And copying Normals during spawning a character is also not an option as it takes several seconds.
     
  13. Michal_Stangel

    Michal_Stangel

    Joined:
    Apr 17, 2017
    Posts:
    151
    Just an update if it can help somebody in the future. A solved it by duplicating FBX meshes and copying normals from complete figure on these (in editor script).
     
  14. ergin3d

    ergin3d

    Joined:
    Jul 31, 2015
    Posts:
    30
    Very good! Thank you very much. if it won't be a big trouble for you, can you please share the script?
     
  15. Michal_Stangel

    Michal_Stangel

    Joined:
    Apr 17, 2017
    Posts:
    151
    Sure! First I find desired FBX in active Project folder and save it's meshes into LOD subfolder:
    Code (CSharp):
    1.  
    2.                     // Find FBX with original meshes
    3.  
    4.                     string activeFolderPath = GetActiveFolderInProjectWindow(); // Get path active in Project window
    5.                    
    6.                     string[] allActiveFolder_GUI = AssetDatabase.FindAssets("t:model", new[] { activeFolderPath });
    7.  
    8.                     Log.L("prefabRecipe.FBX_Name: " + prefabRecipe.FBX_Name);
    9.  
    10.                     GameObject FBXmodel = null;
    11.                     string FBXtoFind = prefabRecipe.FBX_Name + " - FROM BLENDER";
    12.                     foreach (string guid in allActiveFolder_GUI)
    13.                     {
    14.                         // Get path
    15.                         string path = AssetDatabase.GUIDToAssetPath(guid);
    16.  
    17.                         // Get file names only
    18.                         string fileName = ACore_FilesFolders.GetFileNameWithoutExtension(path);
    19.  
    20.                         if (fileName == FBXtoFind)
    21.                         {
    22.                             FBXmodel = (GameObject)AssetDatabase.LoadAssetAtPath(path, typeof(GameObject));                          
    23.                         }                          
    24.                     }
    25.  
    26.                     if (FBXmodel == null)
    27.                     {
    28.                         Log.L("FBX for DAZ CHARACTER NOT FOUND: " + FBXtoFind + ". We cannot duplicated original meshes.", Log.Type.Error);
    29.                     }
    30.                     else
    31.                     {
    32.                         SkinnedMeshRenderer[] renderers = FBXmodel.GetComponentsInChildren<SkinnedMeshRenderer>();
    33.  
    34.                         foreach (SkinnedMeshRenderer renderer in renderers)
    35.                         {
    36.                             //Log.L("renderer name: " + renderer.name, Log.Type.Warning);
    37.  
    38.                             // Clone mesh
    39.                             Mesh sharedMesh = (Mesh)Instantiate(renderer.sharedMesh);
    40.  
    41.                             string filePath = GetActiveFolderInProjectWindow(); // Get path active in Project window
    42.                             filePath += "/LODS";
    43.                             string fileName = sharedMesh.name + "_Quality_Original" + ".asset"; // File name      
    44.  
    45.                             Regex illegalInFileName = new Regex(@"[\\/:*?""<>|]"); // Characters not allowed in Windows in file name
    46.                             fileName = illegalInFileName.Replace(fileName, "");
    47.  
    48.                             filePath += "/" + fileName;
    49.  
    50.                             Log.L("filePath with LODS: " + filePath);
    51.                             // -----------------------
    52.  
    53.                             if (filePath != "")
    54.                             {
    55.                                 AssetDatabase.CreateAsset(sharedMesh, filePath);
    56.                                 AssetDatabase.SaveAssets();
    57.                                 AssetDatabase.Refresh();
    58.                             }
    59.                         }
    60.                     }    
    61.  
    62.         public static string GetActiveFolderInProjectWindow()
    63.         {
    64.             Type projectWindowUtilType = typeof(ProjectWindowUtil);
    65.             MethodInfo getActiveFolderPath = projectWindowUtilType.GetMethod("GetActiveFolderPath", BindingFlags.Static | BindingFlags.NonPublic);
    66.             object obj = getActiveFolderPath.Invoke(null, new object[0]);
    67.             string pathToCurrentFolder = obj.ToString();
    68.             return pathToCurrentFolder;
    69.         }      
    70.  
    And than I load those meshes on my character model prefab with something like this. It's in different method so I paste only important part:
    Code (CSharp):
    1.                         Mesh originalMesh = (Mesh)AssetDatabase.LoadAssetAtPath(originalMesh_Path, typeof(Mesh));
    2.  
    3.                         renderer.sharedMesh = originalMesh;
    4.