Skip to content

Commit 6e4e807

Browse files
authored
Merge pull request #110027 from zeux/lod-iter
Switch LOD generation to use iterative simplification
2 parents 1ce3101 + e40436d commit 6e4e807

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)