Hello there,
We are currently using Unreal 5.6 and we found a way to use morph targets with cloth sim at the same time, with the help of a custom DeformerGraph (see Repro steps above) and a small engine fix.
But we want to confirm that the engine modification we used is safe: at first glance, it should not be dangerous to integrate, but who knows.
What do you say ?
Regards,
Thomas
Steps to Reproduce
Duplicate the DG_LinearBlendSkin_Morph_Cloth_RecomputeNormals
Modify the kernel code in the LinearBlendSkin_Morph_Cloth_PositionOnly node (see below)
Also modify the SkeletalRenderGPUSkin.cpp (see below)
Import a skeletal mesh with morph targets
Paint some clothes
Assign the deformer graph
Notice that now the Clothes and the MorphTargets can be used on the same mesh section
-- DG_LinearBlendSkin_Morph_Cloth_RecomputeNormals --
-----------------------------------------------------
if (Index >= ReadNumThreads().x) return;
float3 LocalPosition = ReadPosition(Index);
float3 MorphDelta = ReadMorphDelta(Index);
float3 MorphedPosition = LocalPosition + MorphDelta;
float3x4 WeightedBoneMatrix = ReadWeightedBoneMatrix(Index);
float3 SkinnedPosition = mul(WeightedBoneMatrix, float4(MorphedPosition, 1));
float3 ClothWeight = ReadClothWeight(Index);
float3 ClothPosition = ReadClothPosition(Index);
float3 LocalClothPosition = mul(float4(ClothPosition, 1), ReadClothToLocal()).xyz;
// TA
float3x4 BoneMatrix = ReadBoneMatrix(Index, 0);
LocalClothPosition += mul(BoneMatrix, float4(MorphDelta, 0));
// TA
float3 FinalPosition = lerp(SkinnedPosition, LocalClothPosition.xyz, ClothWeight);
WriteOutPosition(Index, FinalPosition);
-- SkeletalRenderGPUSkin.cpp --
-------------------------------
void FSkeletalMeshObjectGPUSkin::ProcessUpdatedDynamicData(FRHICommandList& RHICmdList, FGPUSkinCache* GPUSkinCache, EGPUSkinCacheEntryMode Mode)
{
...
const bool bSectionUsingCloth = GEnableCloth && ClothVertexFactory != nullptr;
//TA>
// const bool bSectionUsingMorph = Mode == EGPUSkinCacheEntryMode::Raster && MorphVertexBuffer && !bSectionUsingCloth && (bHasExternalMorphs || (bHasWeightedActiveMorphs && DynamicData->SectionIdsUseByActiveMorphTargets.Contains(SectionIdx)));
const bool bSectionUsingMorph = Mode == EGPUSkinCacheEntryMode::Raster && MorphVertexBuffer && (bHasExternalMorphs || (bHasWeightedActiveMorphs && DynamicData->SectionIdsUseByActiveMorphTargets.Contains(SectionIdx)));
//<TA
bool bSectionUsingSkinCache = bAllowAddToSkinCache ? Section.MaxBoneInfluences != 0 : false;
VertexFactory->UpdateMorphState(RHICmdList, bSectionUsingMorph);
...
}
DDeVoe
(DDeVoe_)
March 20, 2026, 4:06pm
3
Hey there,
Just wanted to let you know we’ve reached out to the dev on this one, and they’re on vacation; they’ll be back soon to get you an answer.
Dustin
Jack.Cai
(Jack Cai)
March 25, 2026, 7:13pm
4
Hey, thanks for reach out! The change should be safe for this use case.