LODs memory footprint

We conducted a memory footprint analysis on static meshes and their Levels of Detail in both Unreal 4.27 and 5.3 and have observed some unexpected results that we’re trying to understand. Our data comes from cooked assets and correlates with in-engine size maps.

To isolate the variables, we tested four distinct scenarios with the same base mesh.

  • Case 1: Standard Mesh. A single static mesh with only LOD0 and 4 million triangles, resulting in a memory footprint of 430.7 MB. This serves as our baseline.
  • Case 2: LOD0 and LOD1. The same mesh imported with both LOD0 and LOD1. Each has 4 million triangles, and the total memory footprint is 663.5 MB.
  • Case 3: minLOD Set to 1. The same mesh as in Case 2, but we’ve set the minLOD property to 1 inside the mesh asset. This results in a memory footprint of 430.7 MB, identical to our baseline.
  • Case 4: LOD0 Reduced. The same mesh as in Case 2, but we imported the main LOD0 with a triangle count reduced to only 1% of the original. The LOD1 retains its original 4 million triangles. This yields a surprising total memory footprint of 236.2 MB, including a small overhead for the reduced LOD0.

Our findings have led to two main questions we’re hoping to get some insight on:

  1. Why does the LOD0 in our baseline (Case 1) consume nearly double the memory of the LOD1 in Case 4, even though they have the exact same number of triangles (4 million)? This significant difference suggests that an LOD’s memory footprint is not solely determined by its triangle count. What additional data or overhead is associated with a mesh being designated as LOD0 that isn’t present when it’s just LOD1?
  2. Why doesn’t the min LOD setting strip out all of the data from LOD0? In our second test case, we expected setting the minLOD to 1 would completely eliminate the memory used by LOD0. However, our data indicates that some of the LOD0 data persists, even though it’s never meant to be displayed. What’s the reason for this behavior? Is there a reason why some minimal LOD0 data is always retained, regardless of the minLOD setting?

We suspect this behavior is related to how the engine internally handles and references mesh data, and perhaps the LOD0 requires some default data or structures to function (that possibly scales with the number of triangles?), but we can’t find clear documentation on this.

Steps to Reproduce

Hey there! I did some initial testing on my side using SM_Boulder from the Stack O Bot sample project, it’s got 546,482 Nanite Vertices, and 108,380 fallback vertecies, and only one material slot. I also made sure that I’d set the Collision to BlockAll/Simple And Complex, with one convex hull body, and disabled “Customized Collision”

  1. 10.8mb - SM_Nanite is set up what I’d call “nominally”: Nanite Enabled, LODGroup set to LargeProp (4 LODs, with 50/25/12.5 reductions)
  2. 31.2mb - SM_LOD0 has Nanite Disabled
  3. 48.1mb - SM_LOD0_LOD1 has Nanite Disabled
  4. 45.2mb - SM_LOD0_MinLOD1 has Nanite Disabled

All of which is pretty much what I’d expect from the setup. The Nanite-enabled version is a good deal smaller since the Nanite data format is quite a bit more efficient, and its fallback meshes are substantially smaller than the base mesh. If I set the collision to Use Simple As Complex, I save about 12mb off each of the non-Nanite meshes. You’re right that there’s some additional data that gets stored with the mesh, much of which is based off LOD0.

As for why MinLOD doesn’t seem to have an effect, that’s a cook-time optimization and you’ll really only see the benefits of that in a MemoryInsights trace. The engine is going to be holding that LOD0 in memory. It’s not quite the same as setting the Max Texture Size on a texture (in that situation, the max resolution is still Mip0).

Is this more about trying to understand the system, or are you looking for targeted ways to reduce the memory overhead your meshes?

Hi, first of all thank you very much this makes lot of sense. The data we gathered were from cooked assetsas well, that’s why we were puzzled to see these discrepancies. But, as you said, It turns out it was just keeping the complex collisions from LOD0 even with MinLOD=1 while forcing simple collision usage seem to strip all the data from LOD0.

Thanks again for the clarification!