Skip to content

Commit e40436d

Browse files
committed
Switch LOD generation to use iterative simplification
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.
1 parent b7c5fca commit e40436d

File tree

1 file changed

+31
-29
lines changed

1 file changed

+31
-29
lines changed

scene/resources/3d/importer_mesh.cpp

Lines changed: 31 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -429,7 +429,6 @@ void ImporterMesh::generate_lods(float p_normal_merge_angle, Array p_bone_transf
429429

430430
unsigned int merged_vertex_count = merged_vertices.size();
431431
const Vector3 *merged_vertices_ptr = merged_vertices.ptr();
432-
const int32_t *merged_indices_ptr = merged_indices.ptr();
433432
Vector3 *merged_normals_ptr = merged_normals.ptr();
434433

435434
{
@@ -474,17 +473,22 @@ void ImporterMesh::generate_lods(float p_normal_merge_angle, Array p_bone_transf
474473
}
475474
}
476475

477-
unsigned int index_target = 12; // Start with the smallest target, 4 triangles
478-
unsigned int last_index_count = 0;
476+
print_verbose("LOD Generation: Triangles " + itos(index_count / 3) + ", vertices " + itos(vertex_count) + " (merged " + itos(merged_vertex_count) + ")" + (deformable ? ", deformable" : ""));
479477

480-
const float max_mesh_error = 1.0f; // we only need LODs that can be selected by error threshold
481-
float mesh_error = 0.0f;
478+
const float max_mesh_error = 1.0f; // We only need LODs that can be selected by error threshold.
479+
const unsigned min_target_indices = 12;
480+
481+
LocalVector<int> current_indices = merged_indices;
482+
float current_error = 0.0f;
483+
484+
while (current_indices.size() > min_target_indices * 2) {
485+
unsigned int current_index_count = current_indices.size();
486+
unsigned int target_index_count = MAX(((current_index_count / 3) / 2) * 3, min_target_indices);
482487

483-
while (index_target < index_count) {
484488
PackedInt32Array new_indices;
485-
new_indices.resize(index_count);
489+
new_indices.resize(current_index_count);
486490

487-
int simplify_options = 0;
491+
int simplify_options = SurfaceTool::SIMPLIFY_SPARSE; // Does not change appearance, but speeds up subsequent iterations.
488492

489493
// Lock geometric boundary in case the mesh is composed of multiple material subsets.
490494
simplify_options |= SurfaceTool::SIMPLIFY_LOCK_BORDER;
@@ -494,38 +498,40 @@ void ImporterMesh::generate_lods(float p_normal_merge_angle, Array p_bone_transf
494498
simplify_options |= SurfaceTool::SIMPLIFY_REGULARIZE;
495499
}
496500

501+
float step_error = 0.0f;
497502
size_t new_index_count = SurfaceTool::simplify_with_attrib_func(
498503
(unsigned int *)new_indices.ptrw(),
499-
(const uint32_t *)merged_indices_ptr, index_count,
504+
(const uint32_t *)current_indices.ptr(), current_index_count,
500505
merged_vertices_f32.ptr(), merged_vertex_count,
501506
sizeof(float) * 3, // Vertex stride
502507
merged_attribs_ptr,
503508
sizeof(float) * attrib_count, // Attribute stride
504509
attrib_weights, attrib_count,
505510
nullptr, // Vertex lock
506-
index_target,
511+
target_index_count,
507512
max_mesh_error,
508513
simplify_options,
509-
&mesh_error);
514+
&step_error);
510515

511-
if (new_index_count < last_index_count * 1.5f) {
512-
index_target = index_target * 1.5f;
513-
continue;
514-
}
516+
// Accumulate error over iterations. Usually, it's correct to use step_error as is; however, on coarse LODs, we may start
517+
// getting *smaller* relative error compared to the previous LOD. To make sure the error is monotonic and strictly increasing,
518+
// and to limit the switching (pop) distance, we ensure the error grows by an arbitrary factor each iteration.
519+
current_error = MAX(current_error * 1.5f, step_error);
520+
521+
new_indices.resize(new_index_count);
522+
current_indices = new_indices;
515523

516-
if (new_index_count == 0 || (new_index_count >= (index_count * 0.75f))) {
524+
if (new_index_count == 0 || (new_index_count >= current_index_count * 0.75f)) {
525+
print_verbose(" LOD stop: got " + itos(new_index_count / 3) + " triangles when asking for " + itos(target_index_count / 3));
517526
break;
518527
}
519-
if (new_index_count > 5000000) {
520-
// This limit theoretically shouldn't be needed, but it's here
521-
// as an ad-hoc fix to prevent a crash with complex meshes.
522-
// The crash still happens with limit of 6000000, but 5000000 works.
523-
// In the future, identify what's causing that crash and fix it.
524-
WARN_PRINT("Mesh LOD generation failed for mesh " + get_name() + " surface " + itos(i) + ", mesh is too complex. Some automatic LODs were not generated.");
528+
529+
if (current_error > max_mesh_error) {
530+
print_verbose(" LOD stop: reached " + rtos(current_error) + " cumulative error (step error " + rtos(step_error) + ")");
525531
break;
526532
}
527533

528-
new_indices.resize(new_index_count);
534+
// We need to remap the LOD indices back to the original vertex array; note that we already copied new_indices into current_indices for subsequent iteration.
529535
{
530536
int *ptrw = new_indices.ptrw();
531537
for (unsigned int j = 0; j < new_index_count; j++) {
@@ -534,15 +540,11 @@ void ImporterMesh::generate_lods(float p_normal_merge_angle, Array p_bone_transf
534540
}
535541

536542
Surface::LOD lod;
537-
lod.distance = MAX(mesh_error * scale, CMP_EPSILON2);
543+
lod.distance = MAX(current_error * scale, CMP_EPSILON2);
538544
lod.indices = new_indices;
539545
surfaces.write[i].lods.push_back(lod);
540-
index_target = MAX(new_index_count, index_target) * 2;
541-
last_index_count = new_index_count;
542546

543-
if (mesh_error == 0.0f) {
544-
break;
545-
}
547+
print_verbose(" LOD " + itos(surfaces.write[i].lods.size()) + ": " + itos(new_index_count / 3) + " triangles, error " + rtos(current_error) + " (step error " + rtos(step_error) + ")");
546548
}
547549

548550
surfaces.write[i].lods.sort_custom<Surface::LODComparator>();

0 commit comments

Comments
 (0)