Browse Source

Fix jolt_physics soft body vertex normal calculation

The code previously iterated through each face and set all vertices to
that face's normal.  This resulted in each vertex getting the normal
from just one face that it belonged to (whichever face was last in this
array).  This caused weird shading artifacts.

This fixes the code so that the vertex normal is now the average normal
of all faces that it belongs to.  This results in "smooth shading"
behavior for soft body meshes.  This is still somewhat undesirable if
the input mesh was using flat shading, but it looks less bad than the
previous behavior of picking a normal at random from one attached face.

This matches the behavior of GodotPhysicsServer3D.

Fixes #107831.
Adam Simpkins 1 month ago
parent
commit
dd80a3aa19
1 changed files with 17 additions and 3 deletions
  1. 17 3
      modules/jolt_physics/objects/jolt_soft_body_3d.cpp

+ 17 - 3
modules/jolt_physics/objects/jolt_soft_body_3d.cpp

@@ -628,8 +628,13 @@ void JoltSoftBody3D::update_rendering_server(PhysicsServer3DRenderingServerHandl
 
 
 	const int physics_vertex_count = (int)physics_vertices.size();
 	const int physics_vertex_count = (int)physics_vertices.size();
 
 
+	normals.clear();
 	normals.resize(physics_vertex_count);
 	normals.resize(physics_vertex_count);
 
 
+	// Compute vertex normals using smooth-shading:
+	// Each vertex should use the average normal of all faces it is a part of.
+	// Iterate over each face, and add the face normal to each of the face vertices.
+	// By the end of the loop, each vertex normal will be the sum of all face normals it belongs to.
 	for (const SoftBodyFace &physics_face : physics_faces) {
 	for (const SoftBodyFace &physics_face : physics_faces) {
 		// Jolt uses a different winding order, so we swap the indices to account for that.
 		// Jolt uses a different winding order, so we swap the indices to account for that.
 
 
@@ -643,9 +648,18 @@ void JoltSoftBody3D::update_rendering_server(PhysicsServer3DRenderingServerHandl
 
 
 		const Vector3 normal = (v2 - v0).cross(v1 - v0).normalized();
 		const Vector3 normal = (v2 - v0).cross(v1 - v0).normalized();
 
 
-		normals[i0] = normal;
-		normals[i1] = normal;
-		normals[i2] = normal;
+		normals[i0] += normal;
+		normals[i1] += normal;
+		normals[i2] += normal;
+	}
+	// Normalize the vertex normals to have length 1.0
+	for (Vector3 &n : normals) {
+		real_t len = n.length();
+		// Some normals may have length 0 if the face was degenerate,
+		// so don't divide by zero.
+		if (len > CMP_EPSILON) {
+			n /= len;
+		}
 	}
 	}
 
 
 	const int mesh_vertex_count = shared->mesh_to_physics.size();
 	const int mesh_vertex_count = shared->mesh_to_physics.size();