@jwatte @GigiK-K-K Thanks for your thoughtful suggestions.
@jwatte You are correct and I was wrong. VertexTangentZ
does indeed provide the vertex normal. Evidently, the issue I’m having relates to the Blender mesh I was testing as a raycast impact surface.
I have now compared how my Pawn traverses over (1) a sphere from Unreal and (2) a UV sphere from Blender. On the Unreal sphere the vertex normals are reported correctly by VertexTangentZ
and I get smooth traversal using the barycentric coordinates to interpolate between vertex normals. In contrast, on the Blender sphere all vertex normals from VertexTangentZ
point in the same direction, which causes abrupt orientation snapping when traversing between faces on the mesh.
What would you suggest to correctly get geometry from Blender into Unreal? Relevant details are below.
Here are the two spheres side by side:
The Unreal sphere shows different vertex normals for each vertex of a hit face (expected behavior):
The Blender UV sphere shows vertex normals pointing in the same direction (not expected or desired):
I believe that the issue relates to how I am exporting meshes from Blender and/or importing into Unreal. Here are my settings:
Code snippet:
// Get buffers for vertex positions and vertex normals
FTransform ComponentTransform = StaticMeshComp->GetComponentTransform();
FStaticMeshVertexBuffers* VertexBuffers = &StaticMesh->GetRenderData()->LODResources[0].VertexBuffers;
FStaticMeshVertexBuffer* StaticMeshVertexBuffer = &VertexBuffers->StaticMeshVertexBuffer;
FPositionVertexBuffer* PositionVertexBuffer = &VertexBuffers->PositionVertexBuffer;
FIndexArrayView IndexBuffer = StaticMesh->GetRenderData()->LODResources[0].IndexBuffer.GetArrayView();
// Storage for the triangle vertex positions and vertex normals
FVector VertexPositions[3];
FVector VertexNormals[3];
uint32 index0 = IndexBuffer[FaceIndex * 3 + 0];
VertexPositions[0] = FVector(PositionVertexBuffer->VertexPosition(index0));
VertexNormals[0] = FVector(StaticMeshVertexBuffer->VertexTangentZ(index0));
uint32 index1 = IndexBuffer[FaceIndex * 3 + 1];
VertexPositions[1] = FVector(PositionVertexBuffer->VertexPosition(index1));
VertexNormals[1] = FVector(StaticMeshVertexBuffer->VertexTangentZ(index1));
uint32 index2 = IndexBuffer[FaceIndex * 3 + 2];
VertexPositions[2] = FVector(PositionVertexBuffer->VertexPosition(index2));
VertexNormals[2] = FVector(StaticMeshVertexBuffer->VertexTangentZ(index2));
// Transform vertices and vertex normals from local space to world space
VertexPositions[0] = ComponentTransform.TransformPosition(VertexPositions[0]);
VertexNormals[0] = ComponentTransform.TransformVector(VertexNormals[0]);
VertexPositions[1] = ComponentTransform.TransformPosition(VertexPositions[1]);
VertexNormals[1] = ComponentTransform.TransformVector(VertexNormals[1]);
VertexPositions[2] = ComponentTransform.TransformPosition(VertexPositions[2]);
VertexNormals[2] = ComponentTransform.TransformVector(VertexNormals[2]);
// Store normal vector based on barycentric interpolation
FVector BaryNormal;
// Get barycentric coordinates
FVector b = FMath::ComputeBaryCentric2D(ImpactPoint, VertexPositions[0], VertexPositions[1], VertexPositions[2]);
BaryNormal.X = b.X * VertexNormals[0].X + b.Y * VertexNormals[1].X + b.Z * VertexNormals[2].X;
BaryNormal.Y = b.X * VertexNormals[0].Y + b.Y * VertexNormals[1].Y + b.Z * VertexNormals[2].Y;
BaryNormal.Z = b.X * VertexNormals[0].Z + b.Y * VertexNormals[1].Z + b.Z * VertexNormals[2].Z;
// Normalize
BaryNormal.Normalize();
// Set hover height above surface
SetActorLocation(ImpactPoint + BaryNormal * HoverHeight);
// Set rotation to match surface normal
FVector cp = FVector::CrossProduct(ActorUpVector, BaryNormal);
float dp = FVector::DotProduct(ActorUpVector, BaryNormal);
float s = FMath::Sqrt((1.0 + dp) * 2.0);
float rs = 1.0 / s;
FQuat q(cp.X * rs, cp.Y * rs, cp.Z * rs, s * 0.5);
q = q * ActorQuat;
q.Normalize();
SetActorRotation(q);