FSkeletalMeshMerge and MorphTargets

Hello Dear Community,
I would like to create a character system with customization in mind, letting the player choose the clothing. I separated the body mesh in multiple parts, each with a set of morph targets. Textures is already atlased, so each parts uses same material and skeletal merge seems a good way to optimize. Is there a way to preserve each morph target, using them even after merging?
Here’s some coding:

void ATESTCompositeCharacter::Build(UAnimBlueprint *l)
{
	TArray<FStringAssetReference> CharItemToDraw;
	CharItemToDraw.Reset();
	FStreamableManager &Streamable = UGameSingleton::Get().AssetLoader;
	Mesh->SetSkeletalMesh(NULL);
	L = l; //Storing Animation Blueprint taken at Blueprint coding

	CharItemToDraw.AddUnique(CID->GetItemSkeletalMesh(10).ToStringReference());
	CharItemToDraw.AddUnique(CID->GetItemSkeletalMesh(11).ToStringReference());

	Streamable.RequestAsyncLoad(CharItemToDraw, FStreamableDelegate::CreateUObject(this, &ATESTCompositeCharacter::BuilDeferred));
}


void ATESTCompositeCharacter::BuilDeferred()
{

	TArray<USkeletalMesh*> mergeMeshes;
	mergeMeshes.Add(ComBody);
	mergeMeshes.Add(ComHead);
	USkeletalMesh* targetMesh = NewObject<USkeletalMesh>();
	TArray<FSkelMeshMergeSectionMapping> sectionMappings;
	FSkeletalMeshMerge merger(targetMesh, mergeMeshes, sectionMappings, 0);
	const bool mergeStatus = merger.DoMerge();
	check(mergeStatus == true);
	targetMesh->PostProcessAnimBlueprint = L->GetAnimBlueprintGeneratedClass();
	Mesh->SetSkeletalMesh(targetMesh);

}

Thanks for listening!

1 Like

I have also been doing the same thing, but found no way to do it. In 4.16 I was able to manually recreate the morph targets post merge. But in 4.18 this functionally has broken and I’m not sure why.

Here is what the 4.18 code looked like for morph copying. It should be noted that it is recreating the morphs rather than copying as not doing so causes the meshes to dirty. I can’t share the code due to contract agreements but some notes which may be helpful:

I needed to declare some externs and may have also had to edit the engine code for the following, it’s been a while between multiple engine versions so not sure.

	extern ENGINE_API FMorphTargetDelta* GetMorphTargetDelta(int32 LODIndex, int32& OutNumDeltas); 
	extern ENGINE_API bool HasDataForLOD(int32 LODIndex); 

I had to create an array to track the vertex location for each LOD level. I’d loop through the merged meshes and add the NumVertices for each LOD level. It seems the vertices here are ordered by LOD level. So after processing each mesh you need to add the vertices to an array that adds the number of vertices for that LOD, so the next mesh can add that value to the SourceIdx for the morph target.

I looped through each of the piece meshes and grabbed the LOD info, then created a new MorphTargetDelta that copied the information the original but on SourceIDX it added the vertex count we had stored in the array. Then rather than calling RegisterMorphTarget I made a trimmed version it which would simply set the mesh for it add it to MorphTargets and then add it to MorphTargetsIndexMap. If you call register morph target it’s going to keep rebuilding your morph targets and dirtying the mesh every time you add one.

This would set up the morph targets up, and then you could use SetMorphTarget on them post-merge. That worked fine in 4.16. It broke when we moved to 4.18 and I haven’t been able to figure out why yet. But it should help get you on the right track.

Thanks for replying. Is this issue known to devs?

Thanks for all the info. I am able to get it working in 4.21. I just want to add that on top of what you described that need to be done for morph targets, you also want to increment SectionIndices on morptargets for each mesh since are they are combined into one mesh.

Hi, I’m trying to work with this but I’m finding some issues when applying the recreated morph targets…

USkeletalMesh *FinalMesh = NewObject<USkeletalMesh>(this, USkeletalMesh::StaticClass(), FName("MergedMesh"), RF_Transient);
    TArray<FSkelMeshMergeSectionMapping> SectionMappings;
    FSkeletalMeshMerge Merger(FinalMesh, MeshArray, SectionMappings, 0);
    //Merger.GenerateLODModel<int>(0);

    const bool MergeStatus = Merger.DoMerge();
    check(MergeStatus == true);

    SetSkeletalMesh(FinalMesh);

    //We need to add the number of vertices per LOD
    //so we can recreate the morph target on the newly merged mesh
    for(int i = 0; i < MeshArray.Num(); ++i)
    {
      for(int j = 0; j < MeshArray[i]->MorphTargets.Num(); ++j)
      {
        if(MeshArray[i]->MorphTargets[j]->HasDataForLOD(0) == true)
        {
          int32 NumDeltas = 0;
          MeshArray[i]->MorphTargets[j]->GetMorphTargetDelta(0, NumDeltas);
          NumVerticesPerLOD[0] += NumDeltas;
          UE_LOG(LogTemp, Log, TEXT("This morph target has this many deltas : %d"), NumDeltas);
        }
      }
    }

    for(int i = 0; i < MeshArray.Num(); ++i)
    {
      for(int j = 0; j < MeshArray[i]->MorphTargets.Num(); ++j)
      {
        if(MeshArray[i]->MorphTargets[j]->HasDataForLOD(0) == true)
        {
          UMorphTarget* FinalMeshMorphTarget = NewObject<UMorphTarget>(this, UMorphTarget::StaticClass());
          TArray<FMorphTargetDelta> NewMorphTargetDeltas;
          int32 NumDeltas = 0;
          FMorphTargetDelta *MorphTargetDeltaArray = MeshArray[i]->MorphTargets[j]->GetMorphTargetDelta(0, NumDeltas);
          for(int z = 0; z < NumDeltas; ++z)
          {
            FMorphTargetDelta NewTargetDelta(MorphTargetDeltaArray[z]);
            NewTargetDelta.SourceIdx += NumVerticesPerLOD[0];
            NewMorphTargetDeltas.Add(NewTargetDelta);
          }

          #if WITH_EDITOR
            TArray<FSkelMeshSection> Sections;
            for (int w = 0; w < MeshArray[i]->GetImportedModel()->LODModels[0].Sections.Num(); ++w)
            {
                Sections.Add(MeshArray[i]->GetImportedModel()->LODModels[0].Sections[w]);
                Sections.Last().BaseIndex += NumVerticesPerLOD[0];
            }
            FinalMeshMorphTarget->PopulateDeltas(NewMorphTargetDeltas, 0, Sections);
          #endif
          FinalMeshMorphTarget->BaseSkelMesh = FinalMesh;
          FinalMesh->MorphTargets.Add(FinalMeshMorphTarget);
          FinalMesh->MorphTargetIndexMap.Add(TPair<FName, int32>(FName("NewMorphTarget"), 0));
          //UE_LOG(LogTemp, Log, TEXT("The source idx of the new morph target is : %d | The old one was : %d"), NewMorphTarget.SourceIdx, MorphTargetInfo->SourceIdx);
        }
      }
    }

    NumVerticesPerLOD[0] = 0;

I think the issue is with the vertex buffers. I know I’m missing something but can’t quite figure out what yet. Any help would be appreciated! I’m on UE4 4.24

Hi there. I’ve been trying to do the same thing for weeks now.
Have you made any progress with this?

also looking for an answer to this

Could you please post a code example? I can’t get it to work in 4.26 :frowning:

Does somebody have a working solution? All I achieved is a wrong morph vertex positions and vertex count in merged mesh :)) (using @algababr code)
PS Maybe this will be usefull for somebody, idk: Skeletal mesh merge - Gamedev Guide

So, after a full day of digging into the engine, I managed to run morph targeting on the merged meshes. In my case, the morph targets are always on the first mesh, so I just copy them without changes.

for (TObjectPtr<UMorphTarget> MorphTarget : MorphTargets)
	{
		UMorphTarget* NewMorphTarget = NewObject<UMorphTarget>(CreatedMesh, MorphTarget->GetFName());
		for (FMorphTargetLODModel MorphTargetLODModel : MorphTarget->GetMorphLODModels())
		{
			NewMorphTarget->GetMorphLODModels().Add(MorphTargetLODModel);
		}
		CreatedMesh->RegisterMorphTarget(NewMorphTarget, false);
	}
	CreatedMesh->InitMorphTargets();

	CreatedMesh->GetResourceForRendering()->ReleaseResources();
	CreatedMesh->GetResourceForRendering()->InitResources(true, CreatedMesh->GetMorphTargets(), CreatedMesh);
2 Likes

@Dangetsu-PK where do you put this code?
I am using the SkeletalMerging plugin.