Skip to content

Commit f0086ff

Browse files
committed
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
1 parent 8dcf5b4 commit f0086ff

1 file changed

Lines changed: 45 additions & 4 deletions

File tree

modules/fbx/fbx_document.cpp

Lines changed: 45 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -611,9 +611,50 @@ Error FBXDocument::_parse_meshes(Ref<FBXState> p_state) {
611611
array[Mesh::ARRAY_VERTEX] = _decode_vertex_attrib_vec3(fbx_mesh->vertex_position, indices);
612612
}
613613

614-
// Normals always exist as they're generated if missing,
615-
// see `ufbx_load_opts.generate_missing_normals`.
616-
Vector<Vector3> normals = _decode_vertex_attrib_vec3(fbx_mesh->vertex_normal, indices);
614+
// Generate normals respecting FBX smoothing groups
615+
Vector<Vector3> normals;
616+
normals.resize(vertex_num);
617+
618+
HashMap<Vector3, Vector3> normal_accum_smooth;
619+
HashMap<Vector3, int> normal_count_smooth;
620+
HashMap<Vector3, Vector3> normal_accum_faceted;
621+
HashMap<Vector3, int> normal_count_faceted;
622+
623+
for (size_t face_idx = 0; face_idx < fbx_mesh->faces.count; face_idx++) {
624+
ufbx_face face = fbx_mesh->faces.data[face_idx];
625+
626+
if (face.num_indices >= 3) {
627+
Vector3 v0 = _as_vec3(fbx_mesh->vertex_position[face.index_begin]);
628+
Vector3 v1 = _as_vec3(fbx_mesh->vertex_position[face.index_begin + 1]);
629+
Vector3 v2 = _as_vec3(fbx_mesh->vertex_position[face.index_begin + 2]);
630+
631+
Vector3 face_normal = Plane(v0, v1, v2).normal;
632+
633+
bool is_smooth = (face_idx < fbx_mesh->face_smoothing.count) ? fbx_mesh->face_smoothing.data[face_idx] : false;
634+
635+
HashMap<Vector3, Vector3> &normal_accum = is_smooth ? normal_accum_smooth : normal_accum_faceted;
636+
HashMap<Vector3, int> &normal_count = is_smooth ? normal_count_smooth : normal_count_faceted;
637+
638+
normal_accum[v0] += face_normal;
639+
normal_accum[v1] += face_normal;
640+
normal_accum[v2] += face_normal;
641+
normal_count[v0]++;
642+
normal_count[v1]++;
643+
normal_count[v2]++;
644+
}
645+
}
646+
647+
for (int i = 0; i < vertex_num; i++) {
648+
Vector3 vertex_pos = _as_vec3(fbx_mesh->vertex_position[indices[i]]);
649+
650+
if (normal_count_smooth.has(vertex_pos) && normal_count_smooth[vertex_pos] > 0) {
651+
normals.write[i] = (normal_accum_smooth[vertex_pos] / normal_count_smooth[vertex_pos]).normalized();
652+
} else if (normal_count_faceted.has(vertex_pos) && normal_count_faceted[vertex_pos] > 0) {
653+
normals.write[i] = (normal_accum_faceted[vertex_pos] / normal_count_faceted[vertex_pos]).normalized();
654+
} else {
655+
normals.write[i] = Vector3(0, 1, 0);
656+
}
657+
}
617658
array[Mesh::ARRAY_NORMAL] = normals;
618659

619660
if (fbx_mesh->vertex_tangent.exists) {
@@ -2053,7 +2094,7 @@ Error FBXDocument::_parse(Ref<FBXState> p_state, const String &p_path, Ref<FileA
20532094
opts.ignore_geometry = true;
20542095
opts.ignore_embedded = true;
20552096
}
2056-
opts.generate_missing_normals = true;
2097+
opts.generate_missing_normals = false;
20572098

20582099
ThreadPoolFBX thread_pool;
20592100
thread_pool.pool = WorkerThreadPool::get_singleton();

0 commit comments

Comments
 (0)