i’ve got a problem. almost every mesh i have (concave, closed space ships) is kind of symmetric. now the texture on one side (right) is the same as on the other (left). calculating the tangents / bitangents, they will point to the back of the ship (let’s assume that just for now). that means, the normal on one side points outwards as it should, but inwards on the other …

what can i do to once and for all fix this error ???

maybe flipping the tangent / bitangent for faces that lead to an inward-pointing normal, but how can i determine if a normal points inward for a given face ?

Align the mesh so that the mirror plane is x=0. Multiply the normal by the sign (±1) of the vertex’ X coordinate.

That will work unless you have a sharp seam at the mirror plane (where the sign of X is zero). If the join is smooth, then the normal’s X component will be zero at the mirror plane.

A symmetric mesh will have the handedness of the TBN coordinate system flipped (i.e. negated determinant) on one half. Calculating N=T×B will always result in the matrix whose columns are [T B N] having a positive determinant (specifically, det(TBN) = N·N = ||N||^{2}).

If you want TBN to be right-handed everywhere but with outward-pointing normals, you need to negate either T or B on one half. If the texture contains text, you probably want to do that anyhow so that the text isn’t mirrored (likewise if it contains an image which shouldn’t be flipped).

thank you, i fixed it by flipping tangents for face whose normals (smoothed) point in the opposite direction of TxB, now it works quite good …

another question i have:
consider 2 faces, connected at 1 vertex “P”, should my TxB normal be exctly the smoothed normal of that vertex? or must the TxB normal be at 90° to its face?

here’s what i’m currently doing:
(i’m calculating tangents per-face, not per-vertex … is that correct?)
(meshes that dont have a bump map will use a 1-texel-texture with: { 0x80, 0x80, 0xFF })

void Graphics::CalculateTangentSpace(Mesh& mesh)
{
for (uint lod = 0; lod < 4; lod++)
{
for (auto& face : mesh.at(lod).Faces)
{
vec3 A = mesh.at(lod).Vertices.at(face.x).Position;
vec3 B = mesh.at(lod).Vertices.at(face.y).Position;
vec3 C = mesh.at(lod).Vertices.at(face.z).Position;
vec2 uvA = mesh.at(lod).Vertices.at(face.x).Texcoord;
vec2 uvB = mesh.at(lod).Vertices.at(face.y).Texcoord;
vec2 uvC = mesh.at(lod).Vertices.at(face.z).Texcoord;
vec3 dAB = B - A;
vec3 dAC = C - A;
vec2 duvAB = uvB - uvA;
vec2 duvAC = uvC - uvA;
// WARNING: divisor can be 0 !!!
float r = 1.0f / (duvAB.x * duvAC.y - duvAB.y * duvAC.x);
vec3 tangent = r * (dAB * duvAC.y - dAC * duvAB.y);
vec3 bitangent = r * (dAC * duvAB.x - dAB * duvAC.x);
// flip tangent space if normal would point inwards
vec3 test_normal = mesh.at(lod).Vertices.at(face.x).Normal; // ... ???
if (dot(cross(test_normal, tangent), bitangent) < 0)
tangent = -tangent;
mesh.at(lod).Vertices.at(face.x).Tangent = tangent;
mesh.at(lod).Vertices.at(face.y).Tangent = tangent;
mesh.at(lod).Vertices.at(face.z).Tangent = tangent;
mesh.at(lod).Vertices.at(face.x).Bitangent = bitangent;
mesh.at(lod).Vertices.at(face.y).Bitangent = bitangent;
mesh.at(lod).Vertices.at(face.z).Bitangent = bitangent;
}
}
}

It should be the smoothed normal. Using a normal map where every texel is logically (0,0,1) (i.e. (0.5,0.5,1) if you’re using an unsigned format) should be indistinguishable from not using a normal map.

Ideally they should be calculated per-vertex and interpolated.

ok, i found a little problem with my meshes …
there are single faces (triangles) on which each vertex has the same texture coords … the normals are good

what advice for these “special cases” can you give me here ??

Other than that: if you first clamp r to some fixed limit then scale it by length(duvXX), the tangents generated for triangles which are small in texture space will be small. Then, if you average the tangents at each vertex without normalising the per-face tangents, you’ll get a valid result so long as at least one face sharing a vertex has valid tangents.

If all of the faces sharing a vertex have a degenerate texture mapping, you won’t be able to get valid tangents at that vertex.