Thanks for attaching the repro project; that’s helped track down what’s going wrong here. The reason we’re crashing is that Mutable is changing the number of sections on the output mesh, which the groom component can’t cope with, since it needs to be identical to the target mesh in the binding asset. There are a couple of different things at play here. The first is that we also ran into the same crash in development of 5.8 and have made a fix for. The CL with the change is 52376990, but it’s just a one-liner in AddHairStrandInitMeshSamplesPass if you’re able to make source modifications:
Previously:
SectionDatas.Init(FSectionData(), TotalSectionCount);New:
SectionDatas.Init(FSectionData(), FMath::Max(uint32(MeshData.Sections.Num()), TotalSectionCount));That will at least prevent the crash, but the groom will still be invalid as the sections on the generated mesh don’t match the binding asset. The reason for that is this property in the customizable object:
There’s some information about that property in the docs
here. It causes the lower LODs to be generated automatically, resulting in different section information. To prevent that, you can switch it to manual, and then wire up all the section data from the input mesh. That should give all the same sections on the output, which should mean the groom binding is now valid.
Having said that, there’s a final issue that I then ran into. As you mentioned originally, MH uses the LODSync component to keep the groom LODs synchronized with the mesh LODs, and it turns out that MH characters only use LOD1, 3, 5 and 7. There is some code that strips the geometry from the groom’s other LODs during assembly to make those assets more efficient. Since Mutable doesn’t have anything similar to the LODSync component, you’ll see the groom LODs that don’t contain any geometry being used as the LOD changes in game, so the groom will disappear.
I’m going to talk with the MH team about this because it feels like the better approach here would be to consolidate the optimized groom so that the LODs are contiguous, instead of only using 1, 3, 5 and 7. That would mean you would be able to use the optimized groom with your Mutable setup. But in the short to medium term, the only option here is to make a code change to remove the optimization step and leave the original groom intact.
The code in question is here in ProcessSkeletalMeshes in MetaHumanGroomEditorPipeline.cpp:
// Need to optimize the Groom before downsizing to have final Groom data and avoid creating textures that will not be used later on
if (GroomQuality != EMetaHumanQualityLevel::Cinematic)
{
OptimizeGroom(NewGroom, GroomQuality); // REMOVE THIS LINE
DownsizeGroomTextures(NewGroom, TargetPlatform, OuterForGeneratedObjects, GroomBuiltData);
}
// Need to call PostEditChangeProperty for the hair cards after any changes
FPropertyChangedEvent HairGroupCardsChanged(UGroomAsset::StaticClass()->FindPropertyByName(UGroomAsset::GetHairGroupsCardsMemberName()));
NewGroom->PostEditChangeProperty(HairGroupCardsChanged);