- Home /
Calculating Tangents (Vector4)
Hello!
I'm writing an importer and I would like to know if there's a way to generate tangents from my vertex,uvs and normals. Because mesh.RecalculateNormals isn't giving me the result I want.
thx!
sanryoga
Hi, In c# I have an error. It says IndexOutOfRangeException in the Vector2 w1 = uv[i1]; Why? Thanks for the help
Answer by Aras · Nov 16, 2009 at 05:28 PM
I assume you know what tangent space is then (in short: it's a surface-relative coordinate system, and consists of tangent, bitangent and normal; mostly used for normal mapped shaders).
Tangents in Unity are Vector4. Three components (xyz) of it store the tangent vector itself, and w component is used to determine "handedness" of the bitangent. Bitangent (sometimes called binormal) is always computed from tangent and normal. In Cg shader code:
float3 binormal = cross (normal, tangent.xyz) * tangent.w;
Now, computing tangents needs a texture coordinate set (UVs). There's actually tons of resources for that, e.g. Lengyel's approach is a good starting point. NVIDIA also has a NVMeshMender library that does it. Calculating tangents can get quite nasty when you want to make it fully robust.
I've gotten some pretty awful results using the various scripts on this page. Aras posted the code Unity uses when importing meshes at https://gist.github.com/aras-p/2843984 but I'm having some trouble converting it to C#. I' not sure for example, what polygonSizes or faceOffsets are, among other things.
Answer by N1nja · May 29, 2010 at 03:47 PM
and the same script in C#..
class TangentSolver { public static void Solve(Mesh mesh) { int triangleCount = mesh.triangles.Length / 3; int vertexCount = mesh.vertices.Length;
Vector3[] tan1 = new Vector3[vertexCount];
Vector3[] tan2 = new Vector3[vertexCount];
Vector4[] tangents = new Vector4[vertexCount];
for(long a = 0; a < triangleCount; a+=3)
{
long i1 = mesh.triangles[a+0];
long i2 = mesh.triangles[a+1];
long i3 = mesh.triangles[a+2];
Vector3 v1 = mesh.vertices[i1];
Vector3 v2 = mesh.vertices[i2];
Vector3 v3 = mesh.vertices[i3];
Vector2 w1 = mesh.uv[i1];
Vector2 w2 = mesh.uv[i2];
Vector2 w3 = mesh.uv[i3];
float x1 = v2.x - v1.x;
float x2 = v3.x - v1.x;
float y1 = v2.y - v1.y;
float y2 = v3.y - v1.y;
float z1 = v2.z - v1.z;
float z2 = v3.z - v1.z;
float s1 = w2.x - w1.x;
float s2 = w3.x - w1.x;
float t1 = w2.y - w1.y;
float t2 = w3.y - w1.y;
float r = 1.0f / (s1 * t2 - s2 * t1);
Vector3 sdir = new Vector3((t2 * x1 - t1 * x2) * r, (t2 * y1 - t1 * y2) * r, (t2 * z1 - t1 * z2) * r);
Vector3 tdir = new Vector3((s1 * x2 - s2 * x1) * r, (s1 * y2 - s2 * y1) * r, (s1 * z2 - s2 * z1) * r);
tan1[i1] += sdir;
tan1[i2] += sdir;
tan1[i3] += sdir;
tan2[i1] += tdir;
tan2[i2] += tdir;
tan2[i3] += tdir;
}
for (long a = 0; a < vertexCount; ++a)
{
Vector3 n = mesh.normals[a];
Vector3 t = tan1[a];
Vector3 tmp = (t - n * Vector3.Dot(n, t)).normalized;
tangents[a] = new Vector4(tmp.x, tmp.y, tmp.z);
tangents[a].w = (Vector3.Dot(Vector3.Cross(n, t), tan2[a]) < 0.0f) ? -1.0f : 1.0f;
}
mesh.tangents = tangents;
}
}
Spent a couple of days trying to find a different script to the c# one above, as it didn't work!!! Simply replace:
int triangleCount = mesh.triangles.Length / 3;
with
int triangleCount = mesh.triangles.Length;
now it will work, dohhh!
Thanks for the code. Gotta wonder why Unity doesn't have this as a function like the RecalculatNormals() function.
Is this code excessively slow for anyone else? I'm trying to use it to calculate tangents for a mesh with 4096 triangles (12,288 verts) and it's taking 1.4 seconds.
I was able to speed the calculation up by a factor of 140 by a combination of suggestions from the related forum post (http://forum.unity3d.com/threads/38984-How-to-Calculate-$$anonymous$$esh-Tangents/page2)
public static void calculate$$anonymous$$eshTangents($$anonymous$$esh mesh)
{
//speed up math by copying the mesh arrays
int[] triangles = mesh.triangles;
Vector3[] vertices = mesh.vertices;
Vector2[] uv = mesh.uv;
Vector3[] normals = mesh.normals;
//variable definitions
int triangleCount = triangles.Length;
int vertexCount = vertices.Length;
Vector3[] tan1 = new Vector3[vertexCount];
Vector3[] tan2 = new Vector3[vertexCount];
Vector4[] tangents = new Vector4[vertexCount];
for (long a = 0; a < triangleCount; a += 3)
{
long i1 = triangles[a + 0];
long i2 = triangles[a + 1];
long i3 = triangles[a + 2];
Vector3 v1 = vertices[i1];
Vector3 v2 = vertices[i2];
Vector3 v3 = vertices[i3];
Vector2 w1 = uv[i1];
Vector2 w2 = uv[i2];
Vector2 w3 = uv[i3];
float x1 = v2.x - v1.x;
float x2 = v3.x - v1.x;
float y1 = v2.y - v1.y;
float y2 = v3.y - v1.y;
float z1 = v2.z - v1.z;
float z2 = v3.z - v1.z;
float s1 = w2.x - w1.x;
float s2 = w3.x - w1.x;
float t1 = w2.y - w1.y;
float t2 = w3.y - w1.y;
float r = 1.0f / (s1 * t2 - s2 * t1);
Vector3 sdir = new Vector3((t2 * x1 - t1 * x2) * r, (t2 * y1 - t1 * y2) * r, (t2 * z1 - t1 * z2) * r);
Vector3 tdir = new Vector3((s1 * x2 - s2 * x1) * r, (s1 * y2 - s2 * y1) * r, (s1 * z2 - s2 * z1) * r);
tan1[i1] += sdir;
tan1[i2] += sdir;
tan1[i3] += sdir;
tan2[i1] += tdir;
tan2[i2] += tdir;
tan2[i3] += tdir;
}
for (long a = 0; a < vertexCount; ++a)
{
Vector3 n = normals[a];
Vector3 t = tan1[a];
//Vector3 tmp = (t - n * Vector3.Dot(n, t)).normalized;
//tangents[a] = new Vector4(tmp.x, tmp.y, tmp.z);
Vector3.OrthoNormalize(ref n, ref t);
tangents[a].x = t.x;
tangents[a].y = t.y;
tangents[a].z = t.z;
tangents[a].w = (Vector3.Dot(Vector3.Cross(n, t), tan2[a]) < 0.0f) ? -1.0f : 1.0f;
}
mesh.tangents = tangents;
}
The above code doesn't work in Unity 3.5. For some reason, tangents[0]=(NaN, NaN, NaN) which messes up the bump map of the first triangle. Rest of the tangents work perfectly.
EDIT: fixed by starting vertices array on 1 ins$$anonymous$$d of 0. I don't know why that is though...
Answer by noontz · Mar 30, 2010 at 06:00 PM
Here is the javascript derived from the link above & developed with help from this thread http://forum.unity3d.com/viewtopic.php?t=41476
/* Derived from Lengyel, Eric. Computing Tangent Space Basis Vectors for an Arbitrary Mesh. Terathon Software 3D Graphics Library, 2001. http://www.terathon.com/code/tangent.html */
class TangentSolver { function TangentSolver(theMesh : Mesh) { vertexCount = theMesh.vertexCount; vertices = theMesh.vertices; normals = theMesh.normals; texcoords = theMesh.uv; triangles = theMesh.triangles; triangleCount = triangles.length/3; tangents = new Vector4[vertexCount]; tan1 = new Vector3[vertexCount]; tan2 = new Vector3[vertexCount]; tri = 0; for ( i = 0; i < (triangleCount); i++) { i1 = triangles[tri]; i2 = triangles[tri+1]; i3 = triangles[tri+2];
v1 = vertices[i1];
v2 = vertices[i2];
v3 = vertices[i3];
w1 = texcoords[i1];
w2 = texcoords[i2];
w3 = texcoords[i3];
x1 = v2.x - v1.x;
x2 = v3.x - v1.x;
y1 = v2.y - v1.y;
y2 = v3.y - v1.y;
z1 = v2.z - v1.z;
z2 = v3.z - v1.z;
s1 = w2.x - w1.x;
s2 = w3.x - w1.x;
t1 = w2.y - w1.y;
t2 = w3.y - w1.y;
r = 1.0 / (s1 * t2 - s2 * t1);
sdir = new Vector3((t2 * x1 - t1 * x2) * r, (t2 * y1 - t1 * y2) * r, (t2 * z1 - t1 * z2) * r);
tdir = new Vector3((s1 * x2 - s2 * x1) * r, (s1 * y2 - s2 * y1) * r, (s1 * z2 - s2 * z1) * r);
tan1[i1] += sdir;
tan1[i2] += sdir;
tan1[i3] += sdir;
tan2[i1] += tdir;
tan2[i2] += tdir;
tan2[i3] += tdir;
tri += 3;
}
for (i = 0; i < (vertexCount); i++)
{
n = normals[i];
t = tan1[i];
// Gram-Schmidt orthogonalize
Vector3.OrthoNormalize( n, t );
tangents[i].x = t.x;
tangents[i].y = t.y;
tangents[i].z = t.z;
// Calculate handedness
tangents[i].w = ( Vector3.Dot(Vector3.Cross(n, t), tan2[i]) < 0.0 ) ? -1.0 : 1.0;
}
theMesh.tangents = tangents;
}
}
Answer by cyble · Jul 07, 2012 at 01:50 PM
In the C# version: to prevent corrupt tangents, make sure you catch the division by zero as it will result in a NaN. You can do this by replacing the calculation of r with something like this:
float div = s1 * t2 - s2 * t1;
float r = div == 0.0f ? 0.0f : 1.0f / div;
aha, thanks, yeah, the script doesn't work properly otherwise
Answer by dvorm · Jul 09, 2019 at 06:45 AM
Thank you for this one. It works fine even today.
Both triangleCount vertexCount and are int while a is long. That seems inconsistent. Can anyone explain?
Your answer
Follow this Question
Related Questions
Unity2D vector sprite sheet is blurry compared to single image with the same resolution. 1 Answer
Problem importing baked normal map from 3ds max 1 Answer
Tangent Space Normal Map to World Space Normal Vector in C# Script 0 Answers
Find tangent points on circle 0 Answers
Normal mapping mirrored UVs? 2 Answers