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.
This commit is contained in:
Arseny Kapoulkine
2025-05-15 18:10:35 +03:00
parent 428a762e98
commit 9a8348cc2c

View File

@ -314,6 +314,7 @@ void ImporterMesh::generate_lods(float p_normal_merge_angle, Array p_bone_transf
Vector<Vector2> uv2s = surfaces[i].arrays[RS::ARRAY_TEX_UV2];
Vector<int> bones = surfaces[i].arrays[RS::ARRAY_BONES];
Vector<float> weights = surfaces[i].arrays[RS::ARRAY_WEIGHTS];
Vector<Color> 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<float> 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<float> 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<float> 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,