Bad merging of material sections for Nanite fallback mesh

Since updating to UE5.6, we noticed a few of our HLOD meshes had their materials seemingly assigned to the incorrect slots.

We tracked this down to a change in BuildFallbackMesh() where mesh sections are now merged if they have the same material, as part of a change to support Nanite Assemblies. The Nanite Assemblies code path tracks this merging and updates the indices later to match the merged sections but the regular code path doesn’t seem to.

We worked around it by adding a !Remap condition in the MergeSectionArray lambda, since a null is passed in for the non-Nanite Assemblies:

if (!Remap || !Dest.ContainsByPredicate([MaterialIndex](const FMeshDataSection& S) { return S.MaterialIndex == MaterialIndex; })) Is this a safe workaround? Does it need fixing up properly by Epic?

Steps to Reproduce

Hi Ben,

Would you be able to share the HLOD mesh that exhibits the issue? Before my change it would keep the number of mesh sections the same but would also still merge triangles of the same material index. So it therefore had a problem where if two mesh sections had the same material index they’d both be set up with the same triangle ranges, which created other problems with the fallback mesh. It would help me to debug an asset that behaves differently between 5.5 and 5.6 so I can take a closer look in the debugger to better reason about the cause of the materials getting jumbled up and finding the best solution to fix it.

Thanks!

-Jamie

Hey Ben,

I chatted with our resident HLOD expert, and unfortunately we don’t currently have a means of extracting that data from the OFPA file you sent, even though it is embedded in there somewhere. We’re working on adding an option to the HLOD actor panel to be able to extract and save the static mesh and its materials/textures into a content browser folder so we can get that information. Seems until we get that online, there’s not much to glean from this file at the moment. Alternatively, if you can reproduce a small, isolated example of the issue in a simple project and could send me a .zip of the whole project that would also be a huge help.

In either case, I think your change may be partially correct. I think to solve the issue I was referring to with the fallback mesh, you’d also need to change the for loop below it to something like this:

`// Fixup mesh section info with new coarse mesh ranges, while respecting original ordering and keeping materials
// that do not end up with any assigned triangles (due to decimation process).
uint64 HandledMaterials = 0;
for (FMeshDataSection& Section : OutFallbackMeshData.Sections)
{
const uint64 MaterialBit = 1ull << uint64(Section.MaterialIndex);
if ((HandledMaterials & MaterialBit) != 0ull)
{
continue;
}

// For each section info, try to find a matching entry in the coarse version.
const FMeshDataSection* FallbackSection = FallbackSections.FindByPredicate(
[&Section](const FMeshDataSection& CoarseSectionIter)
{
return CoarseSectionIter.MaterialIndex == Section.MaterialIndex;
});

if (FallbackSection != nullptr)
{
// Matching entry found
Section.FirstIndex = FallbackSection->FirstIndex;
Section.NumTriangles = FallbackSection->NumTriangles;
Section.MinVertexIndex = FallbackSection->MinVertexIndex;
Section.MaxVertexIndex = FallbackSection->MaxVertexIndex;
}

HandledMaterials |= MaterialBit;
}`I’ve been testing this change locallly and haven’t seen any particular issues with it yet. Would you be willing to make the same change and make sure your HLOD meshes look okay - both Nanite and fallback - and that generally nothing else in your project seems broken by it?

Thanks!

-Jamie

Hey Ben,

I’ve submitted this fix into ue5-main at CL 44665178, so 5.7 should have it. In the meantime you should be able to continue to use both yours and my change to get equivalent behavior. Thanks for the report!

-Jamie

Hi Ben,

Yeah, I did realize that. However, without the second bit of the change, there would be mesh sections which duplicated the triangles for the fallback (which would have still also been the case in UE 5.5). This would have the effect of issuing two identical draw calls for that material in the base pass when Nanite is disabled. To be clear, I wasn’t expecting you to notice a change by the addition of it. but with a lack of a reproduction of your specific issue, I wanted to make sure nothing obviously broke on your end with it, either.

Thanks again!

-Jamie

Hi Jamie,

I’ve attached the OFPA file containing the HLOD actor. I couldn’t figure out a quick way to extract the mesh from the actor but hopefully you can!

If you turn off Nanite, you’ll see the material assignment as it’s meant to be. If I add the !Remap condition, the materials are correct in the Nanite and Nanite Fallback Mesh.

Thanks,

Ben

Hi Jamie,

I tried adding your change to my !Remap change and it didn’t make any difference to the fix. As with my !Remap change, it leaves a large number of mesh sections with the same materials. Without my !Remap change and with your change, it looks just as broken as before.

I’ve tried to reproduce the issue outside of the HLOD pipeline or our project but I haven’t been able to do so. Sorry!

Ben

Hi Jamie,

I just want to make sure you’re aware that your change didn’t improve anything on my end.

The !Remap change alone gets it back to UE5.5 behavior. The large number of mesh sections remain unmerged with or without your change.

Best regards,

Ben