X Tutup
Skip to content

Switch LOD generation to use iterative simplification#110027

Merged
clayjohn merged 1 commit intogodotengine:masterfrom
zeux:lod-iter
Sep 25, 2025
Merged

Switch LOD generation to use iterative simplification#110027
clayjohn merged 1 commit intogodotengine:masterfrom
zeux:lod-iter

Conversation

@zeux
Copy link
Contributor

@zeux zeux commented Aug 27, 2025

Instead of simplifying every LOD from the original down to an
increasing number of triangles, we simplify each LOD from the previous
LOD and stop when the simplification can't proceed further.

This has a few benefits:

  • It's significantly faster; using sparse flag helps ensure that
    subsequent simplifications after the first one are increasingly
    cheaper.

  • It results in higher quality attributes on generated LODs; attribute
    quadrics reduce the quality of attribute preservation the more they
    are accumulated, so recomputing them from intermediate geometry helps.

  • It results in monotonic appearance: if a feature is reduced in a
    higher LOD, it will stay reduced or get reduced more significantly in
    lower LODs. This is not a significant problem right now, but can be
    helpful to ensure if the number of LODs increases or some newer
    features get enabled.

After this change, the set of generated LODs will be a little different, as the number
of triangles the simplifier is given is naturally distinct - however, the behavior
in general should be more or less consistent. Now that generating more LODs is
quite cheap, we can explore making LODs a little more frequent (e.g. doing 1.6x steps
instead of 2x steps) in the future.

Just like previously, on some models we end up reducing the LOD 2x and generating
a significantly steeper error, whereas a 1.6x reduction could result in a more reasonable
error. This is something we can explore fixing in the future (for example, if a given simplification
produced an error that's more than 3x the previous error, we can attempt to rerun it with a
smaller target, which is now easy and fairly cheap). To reduce the complexity of this change,
it doesn't do that.

Testing this on a 100-model variant from #93587,
I get 25 seconds to import before this change, and 17 seconds after. This, however, doesn't
tell the full story: before this change, the simplification time during the import is 11 seconds,
and after this change it's just 3!

0.5 out of 3 seconds after this change is spent outside of meshopt_simplify in various hashmap
code. This can be improved in the future / separately; that all will be unnecessary if we switch
to permissive simplification, but that will probably be in 4.7 timeframe.

@zeux
Copy link
Contributor Author

zeux commented Aug 27, 2025

For posterity, I'll note future work here as I've done in prior issues (assuming all 4 PRs in the chain land):

  • Investigate adjusting LOD reduction targets if the next LOD has a significantly higher error, and/or increase LOD frequency a little bit, perhaps adaptively.
  • Further performance cleanup in code that prepares data for LOD generation.

And, these two ideally should wait for meshoptimizer 1.0 as both upstream features are experimental and will see some improvements in the next version:

  • Investigate using permissive mode as a substitute to the existing normal based welding to further improve appearance
  • Investigate using vertex update for lower LODs to improve quality in the distance at a small extra memory cost

@zeux
Copy link
Contributor Author

zeux commented Aug 27, 2025

Here's before-after profile for a 2M triangle .obj file; on this one the performance gains for LOD generation are even stronger - the more triangles you have in individual meshes, the higher the gains.

image image

Copy link
Member

@Calinou Calinou left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Tested locally, it works as expected. Code looks good to me.

Preview

⚠️ Contains flashing images. ⚠️

Before

This is after updating meshoptimizer to 0.25, i.e. the "After" video in #109990. This uses the same MRP as in my review there.

pr.webm

After

pr_iterative.webm

@zeux
Copy link
Contributor Author

zeux commented Aug 28, 2025

The “flashing images” disclaimer will haunt me now 😅 fwiw this is mostly the case on axis aligned tessellated planes that previously got an error that’s exactly zero and now get a small error in 1e-6 neighborhood. Any sufficiently non-zero lod threshold will still select the most coarse lod but small thresholds may select intermediate lods in certain cases. Non axis aligned tessellation has round-off error during internal calculations so that never generated error = 0.

Instead of simplifying every LOD from the original down to an
increasing number of triangles, we simplify each LOD from the previous
LOD and stop when the simplification can't proceed further.

This has a few benefits:

- It's significantly faster; using sparse flag helps ensure that
  subsequent simplifications after the first one are increasingly
  cheaper.

- It results in higher quality attributes on generated LODs; attribute
  quadrics reduce the quality of attribute preservation the more they
  are accumulated, so recomputing them from intermediate geometry helps.

- It results in monotonic appearance: if a feature is reduced in a
  higher LOD, it will stay reduced or get reduced more significantly in
  lower LODs. This is not a significant problem right now, but can be
  helpful to ensure if the number of LODs increases or some newer
  features get enabled.
@clayjohn clayjohn merged commit 6e4e807 into godotengine:master Sep 25, 2025
20 checks passed
@clayjohn
Copy link
Member

Thank you!

@zeux zeux deleted the lod-iter branch September 27, 2025 01:39
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants

X Tutup