From 9a8348cc2c309061b24fc85328e6733d5d79ea58 Mon Sep 17 00:00:00 2001 From: Arseny Kapoulkine Date: Thu, 15 May 2025 18:10:35 +0300 Subject: [PATCH] Use vertex colors (if present) as attributes during simplification When importing meshes with vertex colors, we will now supply them to the simplifier as attributes with weight=1 (in addition to normals). This will guide the simplification to preserve regions where vertex colors change. For this change to take full effect, it is also necessary to respect the full error when selecting LODs; this change does not do that yet, so there are going to still be cases where vertex colors change abruptly during LOD switch in a visible manner. --- scene/resources/3d/importer_mesh.cpp | 53 +++++++++++++++++++++------- 1 file changed, 41 insertions(+), 12 deletions(-) diff --git a/scene/resources/3d/importer_mesh.cpp b/scene/resources/3d/importer_mesh.cpp index ecd401c3e32..7820f09e7be 100644 --- a/scene/resources/3d/importer_mesh.cpp +++ b/scene/resources/3d/importer_mesh.cpp @@ -314,6 +314,7 @@ void ImporterMesh::generate_lods(float p_normal_merge_angle, Array p_bone_transf Vector uv2s = surfaces[i].arrays[RS::ARRAY_TEX_UV2]; Vector bones = surfaces[i].arrays[RS::ARRAY_BONES]; Vector weights = surfaces[i].arrays[RS::ARRAY_WEIGHTS]; + Vector colors = surfaces[i].arrays[RS::ARRAY_COLOR]; unsigned int index_count = indices.size(); unsigned int vertex_count = vertices.size(); @@ -368,6 +369,7 @@ void ImporterMesh::generate_lods(float p_normal_merge_angle, Array p_bone_transf const Vector2 *uvs_ptr = uvs.ptr(); const Vector2 *uv2s_ptr = uv2s.ptr(); const float *tangents_ptr = tangents.ptr(); + const Color *colors_ptr = colors.ptr(); for (unsigned int j = 0; j < vertex_count; j++) { const Vector3 &v = vertices_ptr[j]; @@ -385,7 +387,8 @@ void ImporterMesh::generate_lods(float p_normal_merge_angle, Array p_bone_transf bool is_tang_aligned = !tangents_ptr || (tangents_ptr[j * 4 + 3] < 0) == (tangents_ptr[idx.second * 4 + 3] < 0); ERR_FAIL_INDEX(idx.second, normals.size()); bool is_normals_close = normals[idx.second].dot(n) > normal_merge_threshold; - if (is_uvs_close && is_uv2s_close && is_normals_close && is_tang_aligned) { + bool is_col_close = (!colors_ptr || colors_ptr[j].is_equal_approx(colors_ptr[idx.second])); + if (is_uvs_close && is_uv2s_close && is_normals_close && is_tang_aligned && is_col_close) { vertex_remap.push_back(idx.first); merged_normals[idx.first] += normals[idx.second]; merged_normals_counts[idx.first]++; @@ -424,23 +427,50 @@ void ImporterMesh::generate_lods(float p_normal_merge_angle, Array p_bone_transf unsigned int merged_vertex_count = merged_vertices.size(); const Vector3 *merged_vertices_ptr = merged_vertices.ptr(); const int32_t *merged_indices_ptr = merged_indices.ptr(); + Vector3 *merged_normals_ptr = merged_normals.ptr(); { const int *counts_ptr = merged_normals_counts.ptr(); - Vector3 *merged_normals_ptrw = merged_normals.ptr(); for (unsigned int j = 0; j < merged_vertex_count; j++) { - merged_normals_ptrw[j] /= counts_ptr[j]; + merged_normals_ptr[j] /= counts_ptr[j]; } } - const float normal_weights[3] = { - // Give some weight to normal preservation, may be worth exposing as an import setting - 2.0f, 2.0f, 2.0f - }; - Vector merged_vertices_f32 = vector3_to_float32_array(merged_vertices_ptr, merged_vertex_count); float scale = SurfaceTool::simplify_scale_func(merged_vertices_f32.ptr(), merged_vertex_count, sizeof(float) * 3); + const size_t attrib_count = 6; // 3 for normal + 3 for color (if present) + + float attrib_weights[attrib_count] = {}; + + // Give more weight to normal preservation + attrib_weights[0] = attrib_weights[1] = attrib_weights[2] = 2.0f; + + // Give some weight to colors but only if present to avoid redundant computations during simplification + if (colors_ptr) { + attrib_weights[3] = attrib_weights[4] = attrib_weights[5] = 1.0f; + } + + LocalVector merged_attribs; + merged_attribs.resize(merged_vertex_count * attrib_count); + float *merged_attribs_ptr = merged_attribs.ptr(); + + memset(merged_attribs_ptr, 0, merged_attribs.size() * sizeof(float)); + + for (unsigned int j = 0; j < merged_vertex_count; ++j) { + merged_attribs_ptr[j * attrib_count + 0] = merged_normals_ptr[j].x; + merged_attribs_ptr[j * attrib_count + 1] = merged_normals_ptr[j].y; + merged_attribs_ptr[j * attrib_count + 2] = merged_normals_ptr[j].z; + + if (colors_ptr) { + unsigned int rj = vertex_inverse_remap[j]; + + merged_attribs_ptr[j * attrib_count + 3] = colors_ptr[rj].r; + merged_attribs_ptr[j * attrib_count + 4] = colors_ptr[rj].g; + merged_attribs_ptr[j * attrib_count + 5] = colors_ptr[rj].b; + } + } + unsigned int index_target = 12; // Start with the smallest target, 4 triangles unsigned int last_index_count = 0; @@ -451,7 +481,6 @@ void ImporterMesh::generate_lods(float p_normal_merge_angle, Array p_bone_transf PackedInt32Array new_indices; new_indices.resize(index_count); - Vector merged_normals_f32 = vector3_to_float32_array(merged_normals.ptr(), merged_normals.size()); const int simplify_options = SurfaceTool::SIMPLIFY_LOCK_BORDER; size_t new_index_count = SurfaceTool::simplify_with_attrib_func( @@ -459,9 +488,9 @@ void ImporterMesh::generate_lods(float p_normal_merge_angle, Array p_bone_transf (const uint32_t *)merged_indices_ptr, index_count, merged_vertices_f32.ptr(), merged_vertex_count, sizeof(float) * 3, // Vertex stride - merged_normals_f32.ptr(), - sizeof(float) * 3, // Attribute stride - normal_weights, 3, + merged_attribs_ptr, + sizeof(float) * attrib_count, // Attribute stride + attrib_weights, attrib_count, nullptr, // Vertex lock index_target, max_mesh_error,