"Simplify mesh" very slow in HLOD but fast in "Merge actors" dialog. Why?

I try to generate HLOD proxies by using “simplify mesh” option for hlod level 1 and it takes very long time.

So I tried the following: I took the mesh generated for the hlod 0, and used Merge Actors dialog on that. It is fast, and generates a nice lowpoly mesh without the inside parts as expected.

I thought something is wrong with my parameters, so I used exactly the same parameters and made sure that the cluster for hlod level 1 contains only the lod actor from level 0.

But it still takes ages, stuck at 5% forever.

Is there a way to execute it faster, like “Merge Actors” dialog does?

I did another test, it’s very strange.

I have a house like in the screenshot below, It’s inside an HLOD volume. The house is made from many meshes and many materials.

HLOD is configured like this:

HLOD Level 0 will create 1 mesh with 1 material, but will keep the same polys (simplify mesh is unchceked)

HLOD Level 1 will take the actor created by Level 0 and simplify it (simplify mesh is checked).

“Generate Proxy mesh” for Level 0 takes few seconds. Generate proxy mesh for Level 1 takes like an HOUR or so.

So, my experiment is: I took the proxy mesh generated for level 0 and put it into the level like another actor, and I put the volume around it, like so:

(the new mesh is on the right)

Then I generate HLOD with the same settings. HLOD Level 0 takes same time as before, and HLOD Level 1 now takes only maybe 20 seconds (instead of an hour).

As I understand it HLOD level 1 is supposed to be produced from the same mesh in both cases, so why it takes so long?

Anybody have this problem? Is this a bug?

UPD:

I found the reason of difference in time it takes. HLOD generation will always take base level meshes. For example HLOD1 will not take the meshs generated by HLOD0 but the meshes you have put in the level. I.e. every HLOD level works on the same meshes. This is done by this code in HLODProxy::ExtractComponents:


if (Actor->IsA<ALODActor>())
{
   ExtractStaticMeshComponentsFromLODActor(Cast<ALODActor>(Actor), Components);
} 
else
{
   Actor->GetComponents<UStaticMeshComponent>(Components);
}

This method is called from FHierarchicalLODUtilities::BuildStaticMeshForLODActor, so I replaced the call by a loop with Actor->GetComponents inside.

Now it works much faster (about 20 seconds for each simplify operation). If you have more than 2 HLODsyou have generate each level separately, because of a null pointer somewhere.

Still, I don’t understand why simplify operation on a merged mesh is so much faster. Maybe HLOD generator should merge meshes before running the simplify pass?

I think I found the bad guy: MaterialBakingModule.cpp:208


FMaterialBakingHelpers::PerformUVBorderSmear(CurrentOutput.PropertyData[Property], RenderTarget->GetSurfaceWidth(), RenderTarget->GetSurfaceHeight());

PerformUVBorderSmear is more or less slow depending on the size of the texture, and there is an optional parameter to limit the number of iterations. By default it’s equals to the size of the texture. If you disable it altogether simplify will be very fast but you will have some magenta pixels in produced textures, between uv shells.

I’ve put a small number instead, it seems to work - no magenta - and it’s fast.


FMaterialBakingHelpers::PerformUVBorderSmear(CurrentOutput.PropertyData[Property], RenderTarget->GetSurfaceWidth(), RenderTarget->GetSurfaceHeight(), 8);

I think there are algos to make this smear operation faster, no?

I think I finally understood how simplify works. It’s the baking of materials that takes long time. There is one bake operation including slow “uv smear” per mesh,material and property. So, for example, I have my house built from 100 meshes, each with 2 materials and I want 3 properties (color, normal, roughness) and my result texture is 1024x1024 then there will be 100x2x3 = 600 bake operations each one operating on an array of 1048576 fcolor values = 629145600 fcolor values. They also will be all stored in memory then merged at the end.

On the other hand, merge mesh (without simplify) has a parameter “use vertex colors”, which when disabled allows to greatly reduce the number of bakes, it will be only 1 bake per material (so it’s 16 bakes for my house).

If hlod level 1 could take the result of the previous step, it would be possible to bake all materials to a simple one in hlod level 0, then simplify in hlod level 1. But unfortunately hlod level 1 will not take the result of hlod level 0 as input.

To summarize, if you want merge and simplify many meshes, like a whole sublevel, it will take infinite time and memory.

I’m totally confused about how HLOD is supposed to be used…

Is there any big examples with hlod?

I just start to use HLOD and I like your approach to understand how it works. Sorry to not contribute, I have not the competence. Hope you will bring more answers.

@Metathesus Thanks! I try now to find some working examples of HLOD, but didn’t found anything yet. If you know some from Epic content or others please post.

I found that it’s super sensitive to two things: generated texture size and source poly count. So I managed to use it by specifying small textures (like 512 max) and checking “use all lods”.
I also did some effort to avoid generating textures for the invisible parts, like house interiors. You need your source to be “water tight”, so avoid holes. For big holes I placed some cubes to close them then deleted those cubes later. And you can play with the parameters to close small holes.

If you want to see the example, in this demo Modular Japanese House Demo by Inu Games all houses have an hlod mesh (sometimes 2-3 meshes for big ones) and it’s visible from about half the map size.