From f0086ff2e37e1c22ee5a6ffc5d8e4eb4efe922ba Mon Sep 17 00:00:00 2001 From: "K. S. Ernest (iFire) Lee" Date: Thu, 8 Jan 2026 11:21:34 -0800 Subject: [PATCH 1/2] Fix FBX import normals to respect smoothing groups Generate normals based on FBX face_smoothing data: - Smooth faces share averaged normals across vertices - Faceted faces maintain hard edges - Preserves artist-defined smoothing behavior --- modules/fbx/fbx_document.cpp | 49 +++++++++++++++++++++++++++++++++--- 1 file changed, 45 insertions(+), 4 deletions(-) diff --git a/modules/fbx/fbx_document.cpp b/modules/fbx/fbx_document.cpp index 7ad89d1cbc11..6a829464a1aa 100644 --- a/modules/fbx/fbx_document.cpp +++ b/modules/fbx/fbx_document.cpp @@ -611,9 +611,50 @@ Error FBXDocument::_parse_meshes(Ref p_state) { array[Mesh::ARRAY_VERTEX] = _decode_vertex_attrib_vec3(fbx_mesh->vertex_position, indices); } - // Normals always exist as they're generated if missing, - // see `ufbx_load_opts.generate_missing_normals`. - Vector normals = _decode_vertex_attrib_vec3(fbx_mesh->vertex_normal, indices); + // Generate normals respecting FBX smoothing groups + Vector normals; + normals.resize(vertex_num); + + HashMap normal_accum_smooth; + HashMap normal_count_smooth; + HashMap normal_accum_faceted; + HashMap normal_count_faceted; + + for (size_t face_idx = 0; face_idx < fbx_mesh->faces.count; face_idx++) { + ufbx_face face = fbx_mesh->faces.data[face_idx]; + + if (face.num_indices >= 3) { + Vector3 v0 = _as_vec3(fbx_mesh->vertex_position[face.index_begin]); + Vector3 v1 = _as_vec3(fbx_mesh->vertex_position[face.index_begin + 1]); + Vector3 v2 = _as_vec3(fbx_mesh->vertex_position[face.index_begin + 2]); + + Vector3 face_normal = Plane(v0, v1, v2).normal; + + bool is_smooth = (face_idx < fbx_mesh->face_smoothing.count) ? fbx_mesh->face_smoothing.data[face_idx] : false; + + HashMap &normal_accum = is_smooth ? normal_accum_smooth : normal_accum_faceted; + HashMap &normal_count = is_smooth ? normal_count_smooth : normal_count_faceted; + + normal_accum[v0] += face_normal; + normal_accum[v1] += face_normal; + normal_accum[v2] += face_normal; + normal_count[v0]++; + normal_count[v1]++; + normal_count[v2]++; + } + } + + for (int i = 0; i < vertex_num; i++) { + Vector3 vertex_pos = _as_vec3(fbx_mesh->vertex_position[indices[i]]); + + if (normal_count_smooth.has(vertex_pos) && normal_count_smooth[vertex_pos] > 0) { + normals.write[i] = (normal_accum_smooth[vertex_pos] / normal_count_smooth[vertex_pos]).normalized(); + } else if (normal_count_faceted.has(vertex_pos) && normal_count_faceted[vertex_pos] > 0) { + normals.write[i] = (normal_accum_faceted[vertex_pos] / normal_count_faceted[vertex_pos]).normalized(); + } else { + normals.write[i] = Vector3(0, 1, 0); + } + } array[Mesh::ARRAY_NORMAL] = normals; if (fbx_mesh->vertex_tangent.exists) { @@ -2053,7 +2094,7 @@ Error FBXDocument::_parse(Ref p_state, const String &p_path, Ref Date: Fri, 9 Jan 2026 11:11:59 -0800 Subject: [PATCH 2/2] Update modules/fbx/fbx_document.cpp Co-authored-by: A Thousand Ships <96648715+AThousandShips@users.noreply.github.com> --- modules/fbx/fbx_document.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/fbx/fbx_document.cpp b/modules/fbx/fbx_document.cpp index 6a829464a1aa..81eb2b91a2a4 100644 --- a/modules/fbx/fbx_document.cpp +++ b/modules/fbx/fbx_document.cpp @@ -630,7 +630,7 @@ Error FBXDocument::_parse_meshes(Ref p_state) { Vector3 face_normal = Plane(v0, v1, v2).normal; - bool is_smooth = (face_idx < fbx_mesh->face_smoothing.count) ? fbx_mesh->face_smoothing.data[face_idx] : false; + bool is_smooth = (face_idx < fbx_mesh->face_smoothing.count) && fbx_mesh->face_smoothing.data[face_idx]; HashMap &normal_accum = is_smooth ? normal_accum_smooth : normal_accum_faceted; HashMap &normal_count = is_smooth ? normal_count_smooth : normal_count_faceted;