Skip to content

[rmodels] Fix and improve GenMeshTangents #4937

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
May 13, 2025
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
137 changes: 96 additions & 41 deletions src/rmodels.c
Original file line number Diff line number Diff line change
Expand Up @@ -3608,109 +3608,164 @@ BoundingBox GetMeshBoundingBox(Mesh mesh)
}

// Compute mesh tangents
// NOTE: To calculate mesh tangents and binormals we need mesh vertex positions and texture coordinates
// Implementation based on: https://answers.unity.com/questions/7789/calculating-tangents-vector4.html
void GenMeshTangents(Mesh *mesh)
{
if ((mesh->vertices == NULL) || (mesh->texcoords == NULL))
// Check if input mesh data is useful
if ((mesh == NULL) || (mesh->vertices == NULL) || (mesh->texcoords == NULL) || (mesh->normals == NULL))
{
TRACELOG(LOG_WARNING, "MESH: Tangents generation requires texcoord vertex attribute data");
TRACELOG(LOG_WARNING, "MESH: Tangents generation requires vertices, texcoords and normals vertex attribute data");
return;
}

// Allocate or reallocate tangents data
if (mesh->tangents == NULL) mesh->tangents = (float *)RL_MALLOC(mesh->vertexCount*4*sizeof(float));
else
{
RL_FREE(mesh->tangents);
mesh->tangents = (float *)RL_MALLOC(mesh->vertexCount*4*sizeof(float));
}

Vector3 *tan1 = (Vector3 *)RL_MALLOC(mesh->vertexCount*sizeof(Vector3));
Vector3 *tan2 = (Vector3 *)RL_MALLOC(mesh->vertexCount*sizeof(Vector3));
// Allocate temporary arrays for tangents calculation
Vector3 *tan1 = (Vector3 *)RL_CALLOC(mesh->vertexCount, sizeof(Vector3));
Vector3 *tan2 = (Vector3 *)RL_CALLOC(mesh->vertexCount, sizeof(Vector3));

if (mesh->vertexCount % 3 != 0)
if (tan1 == NULL || tan2 == NULL)
{
TRACELOG(LOG_WARNING, "MESH: vertexCount expected to be a multiple of 3. Expect uninitialized values.");
TRACELOG(LOG_WARNING, "MESH: Failed to allocate temporary memory for tangent calculation");
if (tan1) RL_FREE(tan1);
if (tan2) RL_FREE(tan2);
return;
}

for (int i = 0; i <= mesh->vertexCount - 3; i += 3)
// Process all triangles of the mesh
// 'triangleCount' must be always valid
for (int t = 0; t < mesh->triangleCount; t++)
{
// Get triangle vertices
Vector3 v1 = { mesh->vertices[(i + 0)*3 + 0], mesh->vertices[(i + 0)*3 + 1], mesh->vertices[(i + 0)*3 + 2] };
Vector3 v2 = { mesh->vertices[(i + 1)*3 + 0], mesh->vertices[(i + 1)*3 + 1], mesh->vertices[(i + 1)*3 + 2] };
Vector3 v3 = { mesh->vertices[(i + 2)*3 + 0], mesh->vertices[(i + 2)*3 + 1], mesh->vertices[(i + 2)*3 + 2] };
// Get triangle vertex indices
int i0, i1, i2;

if (mesh->indices != NULL)
{
// Use indices if available
i0 = mesh->indices[t*3 + 0];
i1 = mesh->indices[t*3 + 1];
i2 = mesh->indices[t*3 + 2];
}
else
{
// Sequential access for non-indexed mesh
i0 = t*3 + 0;
i1 = t*3 + 1;
i2 = t*3 + 2;
}

// Get triangle vertices position
Vector3 v1 = { mesh->vertices[i0*3 + 0], mesh->vertices[i0*3 + 1], mesh->vertices[i0*3 + 2] };
Vector3 v2 = { mesh->vertices[i1*3 + 0], mesh->vertices[i1*3 + 1], mesh->vertices[i1*3 + 2] };
Vector3 v3 = { mesh->vertices[i2*3 + 0], mesh->vertices[i2*3 + 1], mesh->vertices[i2*3 + 2] };

// Get triangle texcoords
Vector2 uv1 = { mesh->texcoords[(i + 0)*2 + 0], mesh->texcoords[(i + 0)*2 + 1] };
Vector2 uv2 = { mesh->texcoords[(i + 1)*2 + 0], mesh->texcoords[(i + 1)*2 + 1] };
Vector2 uv3 = { mesh->texcoords[(i + 2)*2 + 0], mesh->texcoords[(i + 2)*2 + 1] };
Vector2 uv1 = { mesh->texcoords[i0*2 + 0], mesh->texcoords[i0*2 + 1] };
Vector2 uv2 = { mesh->texcoords[i1*2 + 0], mesh->texcoords[i1*2 + 1] };
Vector2 uv3 = { mesh->texcoords[i2*2 + 0], mesh->texcoords[i2*2 + 1] };

// Calculate triangle edges
float x1 = v2.x - v1.x;
float y1 = v2.y - v1.y;
float z1 = v2.z - v1.z;
float x2 = v3.x - v1.x;
float y2 = v3.y - v1.y;
float z2 = v3.z - v1.z;

// Calculate texture coordinate differences
float s1 = uv2.x - uv1.x;
float t1 = uv2.y - uv1.y;
float s2 = uv3.x - uv1.x;
float t2 = uv3.y - uv1.y;

// Calculate denominator and check for degenerate UV
float div = s1*t2 - s2*t1;
float r = (div == 0.0f)? 0.0f : 1.0f/div;
float r = (fabsf(div) < 0.0001f)? 0.0f : 1.0f/div;

// Calculate tangent and bitangent directions
Vector3 sdir = { (t2*x1 - t1*x2)*r, (t2*y1 - t1*y2)*r, (t2*z1 - t1*z2)*r };
Vector3 tdir = { (s1*x2 - s2*x1)*r, (s1*y2 - s2*y1)*r, (s1*z2 - s2*z1)*r };

tan1[i + 0] = sdir;
tan1[i + 1] = sdir;
tan1[i + 2] = sdir;
// Accumulate tangents and bitangents for each vertex of the triangle
tan1[i0] = Vector3Add(tan1[i0], sdir);
tan1[i1] = Vector3Add(tan1[i1], sdir);
tan1[i2] = Vector3Add(tan1[i2], sdir);

tan2[i + 0] = tdir;
tan2[i + 1] = tdir;
tan2[i + 2] = tdir;
tan2[i0] = Vector3Add(tan2[i0], tdir);
tan2[i1] = Vector3Add(tan2[i1], tdir);
tan2[i2] = Vector3Add(tan2[i2], tdir);
}

// Compute tangents considering normals
// Calculate final tangents for each vertex
for (int i = 0; i < mesh->vertexCount; i++)
{
Vector3 normal = { mesh->normals[i*3 + 0], mesh->normals[i*3 + 1], mesh->normals[i*3 + 2] };
Vector3 tangent = tan1[i];

// TODO: Review, not sure if tangent computation is right, just used reference proposed maths...
#if defined(COMPUTE_TANGENTS_METHOD_01)
Vector3 tmp = Vector3Subtract(tangent, Vector3Scale(normal, Vector3DotProduct(normal, tangent)));
tmp = Vector3Normalize(tmp);
mesh->tangents[i*4 + 0] = tmp.x;
mesh->tangents[i*4 + 1] = tmp.y;
mesh->tangents[i*4 + 2] = tmp.z;
mesh->tangents[i*4 + 3] = 1.0f;
#else
Vector3OrthoNormalize(&normal, &tangent);
mesh->tangents[i*4 + 0] = tangent.x;
mesh->tangents[i*4 + 1] = tangent.y;
mesh->tangents[i*4 + 2] = tangent.z;
mesh->tangents[i*4 + 3] = (Vector3DotProduct(Vector3CrossProduct(normal, tangent), tan2[i]) < 0.0f)? -1.0f : 1.0f;
#endif
// Handle zero tangent (can happen with degenerate UVs)
if (Vector3Length(tangent) < 0.0001f)
{
// Create a tangent perpendicular to the normal
if (fabsf(normal.z) > 0.707f) tangent = (Vector3){ 1.0f, 0.0f, 0.0f };
else tangent = Vector3Normalize((Vector3){ -normal.y, normal.x, 0.0f });

mesh->tangents[i*4 + 0] = tangent.x;
mesh->tangents[i*4 + 1] = tangent.y;
mesh->tangents[i*4 + 2] = tangent.z;
mesh->tangents[i*4 + 3] = 1.0f;
continue;
}

// Gram-Schmidt orthogonalization to make tangent orthogonal to normal
// T_prime = T - N * dot(N, T)
Vector3 orthogonalized = Vector3Subtract(tangent, Vector3Scale(normal, Vector3DotProduct(normal, tangent)));

// Handle cases where orthogonalized vector is too small
if (Vector3Length(orthogonalized) < 0.0001f)
{
// Create a tangent perpendicular to the normal
if (fabsf(normal.z) > 0.707f) orthogonalized = (Vector3){ 1.0f, 0.0f, 0.0f };
else orthogonalized = Vector3Normalize((Vector3){ -normal.y, normal.x, 0.0f });
}
else
{
// Normalize the orthogonalized tangent
orthogonalized = Vector3Normalize(orthogonalized);
}

// Store the calculated tangent
mesh->tangents[i*4 + 0] = orthogonalized.x;
mesh->tangents[i*4 + 1] = orthogonalized.y;
mesh->tangents[i*4 + 2] = orthogonalized.z;

// Calculate the handedness (w component)
mesh->tangents[i*4 + 3] = (Vector3DotProduct(Vector3CrossProduct(normal, orthogonalized), tan2[i]) < 0.0f)? -1.0f : 1.0f;
}

// Free temporary arrays
RL_FREE(tan1);
RL_FREE(tan2);

// Update vertex buffers if available
if (mesh->vboId != NULL)
{
if (mesh->vboId[SHADER_LOC_VERTEX_TANGENT] != 0)
{
// Update existing vertex buffer
// Update existing tangent vertex buffer
rlUpdateVertexBuffer(mesh->vboId[SHADER_LOC_VERTEX_TANGENT], mesh->tangents, mesh->vertexCount*4*sizeof(float), 0);
}
else
{
// Load a new tangent attributes buffer
// Create new tangent vertex buffer
mesh->vboId[SHADER_LOC_VERTEX_TANGENT] = rlLoadVertexBuffer(mesh->tangents, mesh->vertexCount*4*sizeof(float), false);
}

// Set up vertex attributes for shader
rlEnableVertexArray(mesh->vaoId);
rlSetVertexAttribute(RL_DEFAULT_SHADER_ATTRIB_LOCATION_TANGENT, 4, RL_FLOAT, 0, 0, 0);
rlEnableVertexAttribute(RL_DEFAULT_SHADER_ATTRIB_LOCATION_TANGENT);
Expand Down
Loading