# How to Calculate Mesh Tangents

Discussion in 'Scripting' started by amasinton, Jan 14, 2010.

1. ### amasinton

Joined:
Aug 12, 2006
Posts:
125
I am creating a simple mesh from 3d coordinates at runtime.

I can build the mesh fine, but I need to calculate the mesh tangents in order to use materials with normal maps on the mesh I build.

According to the Mesh.tangents documentation, mesh tangents are an array of Vector4s
So, if I have a mesh composed of four vertices and two triangles (a square or rectangle, for instance), would my tangents array have four Vector4s (one for each vertex in the mesh)? Would each Vector4 be composed of the 3d coordinates for the appropriate vertex and then w? The documentation says that w is either 1 or -1. How do I know which to use?

-- Anthony

2. ### noontz

Joined:
Nov 7, 2009
Posts:
16

This answer by Aras is the closest I could get, when researching the same problem, but one thing springs to mind:

If the tangents are calculated using normals and uv's, then provided these are in place, why not have a simple MakeTangents function in the mesh class taking the normals specific UVs as arguments :?:

I mean it must be there somewhere under the hood as it's available on import of meshes, and in general Unity is doing a really good job in NOT being rocket science :wink:

### Volunteer ModeratorModerator

Joined:
Jul 19, 2006
Posts:
31,843
Yes, it's there somewhere for the editor, but it's probably not something available at runtime (there are many editor-only functions). I definitely agree that a RecalculateTangents() function, which would work along the same lines as RecalculateNormals() does, would be nice. In the meantime, one of the links Aras provided ( http://www.terathon.com/code/tangent.html ) has code which can be converted to Javascript or C#. I managed to find that on my own a while ago so I'm glad to hear he thinks it's a good method; I used it in my ObjReader utility.

--Eric

4. ### noontz

Joined:
Nov 7, 2009
Posts:
16
@ Eric
Thanks for the insight!

If I ever get that far I'll post the code in this thread for comments on optimizing, but in this case I would guess the 50% "script inpact" on performance is a bitch compared to a direct call or??

Any threads hinting on how I would go around implementing the other link by Aras (NVMeshMender) would it have any effect on performance compared to the scripted version what so ever??

Thx.

### Volunteer ModeratorModerator

Joined:
Jul 19, 2006
Posts:
31,843
With that kind of code I doubt it's as low as 50%, and unless your object has 10 million vertices I also doubt you'd notice any speed difference. Considering the 65K vertex per object limit it's a moot point anyway.

Not sure, haven't looked at it, but considering the scripted equivalent of RecalculateTangents is only going to run once, does it really matter if it's .002 seconds instead of .001 seconds?

--Eric

6. ### noontz

Joined:
Nov 7, 2009
Posts:
16
@ Eric
NOPE.. not at all..

If this is the amount of time we are talking about on a 65K mesh it's completely irrelevant, and just a matter of convenience..

### Volunteer ModeratorModerator

Joined:
Jul 19, 2006
Posts:
31,843
Well, for a 65K mesh it might be .02 seconds but most meshes aren't that big.

--Eric

8. ### noontz

Joined:
Nov 7, 2009
Posts:
16
Hi again..

So far I think I'm closing in on a plausable javascript translation I would love to share BUT:

When I try testing it I run into the following problems that may be due my misunderstanding of how Unityscript works.

I have a gameobject with a meshfilter meshrender component. I attach my SolverScript another script that calls it. I'm using the Mesh as argument and this causes problems at runtime ( compiles fine ) :
"ApplicationException: Argument is not enumerable" pointing at the mesh as an argument?

Code for calling:
Code (csharp):
1. function Start ()
2. {
3. var mesh : Mesh = GetComponent(MeshFilter).mesh;
4. var tangentssolver = GetComponent("SolverScript");
5. tangentssolver.BuildTangents(mesh);
6. }
function in SolverScript script ( #pragma strict ):
Code (csharp):
1. function BuildTangents(theMesh : Mesh){ /*solve the tangents an write to mesh*/ }
2.
Hopefully I'm making an obvious mistake, but the following questions come to mind:
Is there a restriction on what type of arguments I can use?
Is the SolverScript placed wrong.
Is it possible to call the SolverScript without attaching it to a gameobject?
THX...

### Volunteer ModeratorModerator

Joined:
Jul 19, 2006
Posts:
31,843
Nothing wrong I can see; the error suggests something about trying to use a coroutine when you shouldn't, possibly.

--Eric

10. ### noontz

Joined:
Nov 7, 2009
Posts:
16
I was iterating something that couldn't be ( Learning by doing )...

Here is the finished script thanks for the feedback on this one Eric!

ATM it solves a 65K mesh at 30 FPS @ i7 920 2.67.

If this can be optimized further or there is any "worse practice coding" please comment. If not, I'll append it to the UnityAnswers thread above.

Code (csharp):
1. /*
2. Derived from
3. Lengyel, Eric. “Computing Tangent Space Basis Vectors for an Arbitrary Mesh”. Terathon Software 3D Graphics Library, 2001.
4. [url]http://www.terathon.com/code/tangent.html[/url]
5. noontz 2010
6. */
7.
8. #pragma strict
9.
10. class TangentSolver
11. {
12.     function TangentSolver(theMesh : Mesh)
13.     {
14.         var vertexCount : int = theMesh.vertexCount;
15.         var vertices : Vector3[] = theMesh.vertices;
16.         var normals : Vector3[] = theMesh.normals;
17.         var texcoords : Vector2[] = theMesh.uv;
18.         var triangles : int[] = theMesh.triangles;
19.         var triangleCount : int = triangles.length/3;
20.         var tangents : Vector4[] = new Vector4[vertexCount];
21.         var tan1 : Vector3[] = new Vector3[vertexCount];
22.         var tan2 : Vector3[] = new Vector3[vertexCount];
23.         var tri : int = 0;
24.         for ( i = 0; i <= (triangleCount-1); i++)
25.         {
26.             var i1 : int = triangles[tri];
27.             var i2 : int = triangles[tri+1];
28.             var i3 : int = triangles[tri+2];
29.
30.             var v1 : Vector3 = vertices[i1];
31.             var v2 : Vector3 = vertices[i2];
32.             var v3 : Vector3 = vertices[i3];
33.
34.             var w1 : Vector2 = texcoords[i1];
35.             var w2 : Vector2 = texcoords[i2];
36.             var w3 : Vector2 = texcoords[i3];
37.
38.             var x1 : float = v2.x - v1.x;
39.             var x2 : float = v3.x - v1.x;
40.             var y1 : float = v2.y - v1.y;
41.             var y2 : float = v3.y - v1.y;
42.             var z1 : float = v2.z - v1.z;
43.             var z2 : float = v3.z - v1.z;
44.
45.             var s1 : float = w2.x - w1.x;
46.             var s2 : float = w3.x - w1.x;
47.             var t1 : float = w2.y - w1.y;
48.             var t2 : float = w3.y - w1.y;
49.
50.             var r : float = 1.0 / (s1 * t2 - s2 * t1);
51.             var sdir : Vector3 = Vector3((t2 * x1 - t1 * x2) * r, (t2 * y1 - t1 * y2) * r, (t2 * z1 - t1 * z2) * r);
52.             var tdir : Vector3 = Vector3((s1 * x2 - s2 * x1) * r, (s1 * y2 - s2 * y1) * r, (s1 * z2 - s2 * z1) * r);
53.
54.             tan1[i1] += sdir;
55.             tan1[i2] += sdir;
56.             tan1[i3] += sdir;
57.
58.             tan2[i1] += tdir;
59.             tan2[i2] += tdir;
60.             tan2[i3] += tdir;
61.
62.             tri += 3;
63.         }
64.
65.         for (i = 0; i <= (vertexCount-1); i++)
66.         {
67.             var n : Vector3 = normals[i];
68.             var t : Vector3 = tan1[i];
69.
70.             // Gram-Schmidt orthogonalize
71.             Vector3.OrthoNormalize( n, t );
72.
73.             tangents[i].x  = t.x;
74.             tangents[i].y  = t.y;
75.             tangents[i].z  = t.z;
76.
77.             // Calculate handedness
78.             var tW : int = ( Vector3.Dot(Vector3.Cross(n, t), tan2[i]) < 0 ) ? -1 : 1;
79.
80.             tangents[i].w  = tW;
81.         }
82.         theMesh.tangents = tangents;
83.     }
84. }

### Volunteer ModeratorModerator

Joined:
Jul 19, 2006
Posts:
31,843
Looks good, though this part:

Code (csharp):
1.             var tW : int = ( Vector3.Dot(Vector3.Cross(n, t), tan2[i]) < 0 ) ? -1 : 1;
2.
3.             tangents[i].w  = tW;
would be better like

Code (csharp):
1.             tangents[i].w = ( Vector3.Dot(Vector3.Cross(n, t), tan2[i]) < 0.0 ) ? -1.0 : 1.0;
because otherwise you're spending time converting ints to floats for no real reason. Also declaring a variable you don't really need, trivial though that may be. You can take out the type declarations if you want, not that they hurt anything. (Personally I don't find that "var tan1 : Vector3[] = new Vector3[vertexCount];" adds anything except redundancy, when it's still obvious "var tan1 = new Vector3[vertexCount];" is a Vector3 array.)

--Eric

### Unity Technologies

Joined:
Aug 11, 2006
Posts:
3,346
Eric is right that in this case, the type mismatch makes the explicit intermediate variable less efficient, but in any case where the types match properly (such as without the explicit int typing above), any compiler worth its salt (including the one Unity uses) will use registers for intermediate values as necessary, whether they are named or not. This means that you shouldn't try to fit everything on one line if it's easier to read with named intermediate variables.

13. ### noontz

Joined:
Nov 7, 2009
Posts:
16
Hi.. Thanks again for the insight: I'm adjusting accordingly.. I'm performance testing the changes and I was trying Destroy(this) in the end to see if I would get some benefit, but it does not compile? As I understand javascripts inherit Destroy?? How can I implement this? Thx..

### Volunteer ModeratorModerator

Joined:
Jul 19, 2006
Posts:
31,843
I would have thought that, except there's a measurable difference between using one line and two, even when the types match (one line is faster).

It should compile (and work), but I'm not sure what benefit you'd get from that.

--Eric

### Unity Technologies

Joined:
Aug 11, 2006
Posts:
3,346
That's frustrating. I wonder what the byte code looks like.

16. ### noontz

Joined:
Nov 7, 2009
Posts:
16
Eric @
It doesn't compile? I'm inserting
Code (csharp):
1. Destroy(this);
just after
Code (csharp):
1. theMesh.tangents = tangents;
My idea was to free memory, but please bare with my knowledgelevel :wink:
Anyway for pure insight I would love to know what I'm doing wrong.

Here is the (EDIT: ALMOST) final code:

Code (csharp):
1. /*
2. Derived from
3. Lengyel, Eric. “Computing Tangent Space Basis Vectors for an Arbitrary Mesh”. Terathon Software 3D Graphics Library, 2001.
4. [url]http://www.terathon.com/code/tangent.html[/url]
5. noontz 2010
6. */
7.
8. #pragma strict
9.
10. class TangentSolver
11. {
12.     function TangentSolver(theMesh : Mesh)
13.     {
14.         var vertexCount = theMesh.vertexCount;
15.         var vertices = theMesh.vertices;
16.         var normals = theMesh.normals;
17.         var texcoords = theMesh.uv;
18.         var triangles = theMesh.triangles;
19.         var triangleCount = triangles.length/3;
20.         var tangents = new Vector4[vertexCount];
21.         var tan1 = new Vector3[vertexCount];
22.         var tan2 = new Vector3[vertexCount];
23.         var tri = 0;
24.         for ( i = 0; i <= (triangleCount-1); i++)
25.         {
26.             var i1 : int = triangles[tri];
27.             var i2 : int = triangles[tri+1];
28.             var i3 : int = triangles[tri+2];
29.
30.             var v1 : Vector3 = vertices[i1];
31.             var v2 : Vector3 = vertices[i2];
32.             var v3 : Vector3 = vertices[i3];
33.
34.             var w1 : Vector2 = texcoords[i1];
35.             var w2 : Vector2 = texcoords[i2];
36.             var w3 : Vector2 = texcoords[i3];
37.
38.             var x1 : float = v2.x - v1.x;
39.             var x2 : float = v3.x - v1.x;
40.             var y1 : float = v2.y - v1.y;
41.             var y2 : float = v3.y - v1.y;
42.             var z1 : float = v2.z - v1.z;
43.             var z2 : float = v3.z - v1.z;
44.
45.             var s1 : float = w2.x - w1.x;
46.             var s2 : float = w3.x - w1.x;
47.             var t1 : float = w2.y - w1.y;
48.             var t2 : float = w3.y - w1.y;
49.
50.             var r : float = 1.0 / (s1 * t2 - s2 * t1);
51.             var sdir : Vector3 = Vector3((t2 * x1 - t1 * x2) * r, (t2 * y1 - t1 * y2) * r, (t2 * z1 - t1 * z2) * r);
52.             var tdir : Vector3 = Vector3((s1 * x2 - s2 * x1) * r, (s1 * y2 - s2 * y1) * r, (s1 * z2 - s2 * z1) * r);
53.
54.             tan1[i1] += sdir;
55.             tan1[i2] += sdir;
56.             tan1[i3] += sdir;
57.
58.             tan2[i1] += tdir;
59.             tan2[i2] += tdir;
60.             tan2[i3] += tdir;
61.
62.             tri += 3;
63.         }
64.
65.         for (i = 0; i <= (vertexCount-1); i++)
66.         {
67.             var n = normals[i];
68.             var t = tan1[i];
69.
70.             // Gram-Schmidt orthogonalize
71.             Vector3.OrthoNormalize( n, t );
72.
73.             tangents[i].x  = t.x;
74.             tangents[i].y  = t.y;
75.             tangents[i].z  = t.z;
76.
77.             // Calculate handedness
78.             tangents[i].w = ( Vector3.Dot(Vector3.Cross(n, t), tan2[i]) < 0 ) ? -1 : 1;
79.         }
80.         theMesh.tangents = tangents;
81.     }
82. }

### Volunteer ModeratorModerator

Joined:
Jul 19, 2006
Posts:
31,843
Oh, I see...you can't Destroy a class. You could do something like

Code (csharp):
1. tangents = null;
Also,

Code (csharp):
1. tangents.w = ( Vector3.Dot(Vector3.Cross(Vector3.one, Vector3.zero), Vector3.one) < 0 ) ? -1 : 1;
is a little faster like

Code (csharp):
1. tangents.w = ( Vector3.Dot(Vector3.Cross(Vector3.one, Vector3.zero), Vector3.one) < 0.0 ) ? -1.0 : 1.0;
That way you're not converting ints to floats unnecessarily. (Since tangents.w is a float and Vector3.Dot returns a float.)

--Eric

18. ### noontz

Joined:
Nov 7, 2009
Posts:
16
Thanks.. Updated below.

hmmm... It's not possible to destroy an instance of a class?

I can compile this:
Code (csharp):
1. function Update ()
2. {
3.     var theMesh : Mesh = GetComponent(MeshFilter).mesh;
4.     var a = new TangentSolver(theMesh);
5.     Object.Destroy(a);
6. }
but it throws an error at runtime, so I guess this is the reason?

Final code:

Code (csharp):
1. /*
2. Derived from
3. Lengyel, Eric. “Computing Tangent Space Basis Vectors for an Arbitrary Mesh”. Terathon Software 3D Graphics Library, 2001.
4. [url]http://www.terathon.com/code/tangent.html[/url]
5. */
6.
7. class TangentSolver
8. {
9.     function TangentSolver(theMesh : Mesh)
10.     {
11.         vertexCount = theMesh.vertexCount;
12.         vertices = theMesh.vertices;
13.         normals = theMesh.normals;
14.         texcoords = theMesh.uv;
15.         triangles = theMesh.triangles;
16.         triangleCount = triangles.length/3;
17.         tangents = new Vector4[vertexCount];
18.         tan1 = new Vector3[vertexCount];
19.         tan2 = new Vector3[vertexCount];
20.         tri = 0;
21.         for ( i = 0; i < (triangleCount); i++)
22.         {
23.             i1 = triangles[tri];
24.             i2 = triangles[tri+1];
25.             i3 = triangles[tri+2];
26.
27.             v1 = vertices[i1];
28.             v2 = vertices[i2];
29.             v3 = vertices[i3];
30.
31.             w1 = texcoords[i1];
32.             w2 = texcoords[i2];
33.             w3 = texcoords[i3];
34.
35.             x1 = v2.x - v1.x;
36.             x2 = v3.x - v1.x;
37.             y1 = v2.y - v1.y;
38.             y2 = v3.y - v1.y;
39.             z1 = v2.z - v1.z;
40.             z2 = v3.z - v1.z;
41.
42.             s1 = w2.x - w1.x;
43.             s2 = w3.x - w1.x;
44.             t1 = w2.y - w1.y;
45.             t2 = w3.y - w1.y;
46.
47.             r = 1.0 / (s1 * t2 - s2 * t1);
48.             sdir = new Vector3((t2 * x1 - t1 * x2) * r, (t2 * y1 - t1 * y2) * r, (t2 * z1 - t1 * z2) * r);
49.             tdir = new Vector3((s1 * x2 - s2 * x1) * r, (s1 * y2 - s2 * y1) * r, (s1 * z2 - s2 * z1) * r);
50.
51.             tan1[i1] += sdir;
52.             tan1[i2] += sdir;
53.             tan1[i3] += sdir;
54.
55.             tan2[i1] += tdir;
56.             tan2[i2] += tdir;
57.             tan2[i3] += tdir;
58.
59.             tri += 3;
60.         }
61.
62.         for (i = 0; i < (vertexCount); i++)
63.         {
64.             n = normals[i];
65.             t = tan1[i];
66.
67.             // Gram-Schmidt orthogonalize
68.             Vector3.OrthoNormalize( n, t );
69.
70.             tangents[i].x  = t.x;
71.             tangents[i].y  = t.y;
72.             tangents[i].z  = t.z;
73.
74.             // Calculate handedness
75.             tangents[i].w = ( Vector3.Dot(Vector3.Cross(n, t), tan2[i]) < 0.0 ) ? -1.0 : 1.0;
76.         }
77.         theMesh.tangents = tangents;
78.     }
79. }

19. ### amasinton

Joined:
Aug 12, 2006
Posts:
125
This is a *very* belated reply, my apologies.

Noontz' final code above works perfectly for my purposes. It really should go on the wiki, as it works so nicely - just copied it straight into my meshbuilding code.

It's been a while since I had a chance to work on this again - other work priorities, but I'm getting back to this project now.

This code has helped us to finally be able to read some medieval music which no one has heard for the past six hundred years. Really. It's a long story that once the project is done I'll share, as it's quite interesting.

Many thanks again, for the awesome work - to Eric5h5 too!

Joined:
Nov 7, 2009
Posts:
16
21. ### tonyoakden

Joined:
Nov 13, 2009
Posts:
135
I found a tangent solver in J++ (sorry but I can't remember where I found it and I recoded it in c#. Seems to work. But it's pretty slow when you have big meshes to recompute. I'm tempted to try producing a c version and putting it in a dll to see if that makes a difference.

Code (csharp):
1. class TangentSolver
2. {
3.     public static void Solve(Mesh mesh)
4.     {
5.         int triangleCount = mesh.triangles.Length / 3;
6.         int vertexCount = mesh.vertices.Length;
7.
8.         Vector3[] tan1 = new Vector3[vertexCount];
9.         Vector3[] tan2 = new Vector3[vertexCount];
10.         Vector4[] tangents = new Vector4[vertexCount];
11.         int a = 0;
12.         while(a < triangleCount)
13.         {
14.             long i1 = mesh.triangles[a++];
15.             long i2 = mesh.triangles[a++];
16.             long i3 = mesh.triangles[a++];
17.
18.             Vector3 v1 = mesh.vertices[i1];
19.             Vector3 v2 = mesh.vertices[i2];
20.             Vector3 v3 = mesh.vertices[i3];
21.
22.             Vector2 w1 = mesh.uv[i1];
23.             Vector2 w2 = mesh.uv[i2];
24.             Vector2 w3 = mesh.uv[i3];
25.
26.             float x1 = v2.x - v1.x;
27.             float x2 = v3.x - v1.x;
28.             float y1 = v2.y - v1.y;
29.             float y2 = v3.y - v1.y;
30.             float z1 = v2.z - v1.z;
31.             float z2 = v3.z - v1.z;
32.
33.             float s1 = w2.x - w1.x;
34.             float s2 = w3.x - w1.x;
35.             float t1 = w2.y - w1.y;
36.             float t2 = w3.y - w1.y;
37.
38.             float r = 1.0f / (s1 * t2 - s2 * t1);
39.
40.             Vector3 sdir = new Vector3((t2 * x1 - t1 * x2) * r, (t2 * y1 - t1 * y2) * r, (t2 * z1 - t1 * z2) * r);
41.             Vector3 tdir = new Vector3((s1 * x2 - s2 * x1) * r, (s1 * y2 - s2 * y1) * r, (s1 * z2 - s2 * z1) * r);
42.
43.             tan1[i1] += sdir;
44.             tan1[i2] += sdir;
45.             tan1[i3] += sdir;
46.
47.             tan2[i1] += tdir;
48.             tan2[i2] += tdir;
49.             tan2[i3] += tdir;
50.         }
51.         for (a = 0; a < vertexCount; a++)
52.         {
53.             Vector3 n = mesh.normals[a];
54.             Vector3 t = tan1[a];
55.             tangents[a] = (t - n * Vector3.Dot(n, t)).normalized;
56.             tangents[a].w = (Vector3.Dot(Vector3.Cross(n, t), tan2[a]) < 0.0f) ? -1.0f : 1.0f;
57.         }
58.         mesh.tangents = tangents;
59.     }
60. }

### Volunteer ModeratorModerator

Joined:
Jul 19, 2006
Posts:
31,843
I'd recommend using the code posted by Noontz instead. It's much much faster. I suspect the main difference is that you're reading mesh.triangles, mesh.vertices etc. constantly, instead of assigning those to an array once.

--Eric

23. ### windexglow

Joined:
Jun 18, 2010
Posts:
378
Stolen from the ProcedualMesh tutorial

Code (csharp):
1. vertex = Vector3 (x+MainTerrain.TerrainPixelSize  - block, pixelHeight, y+ MainTerrain.TerrainPixelSize - block);
2.             vertices[y* block + x] = Vector3.Scale(sizeScale, vertex);
3.
4.             uv[y* block + x] = Vector2.Scale(Vector2 (x + offset.x, y + offset.y), uvScale );
5.             vertexL = Vector3( x-1, MainTerrain.PerlinTex.GetPixel(x + offset.x -1, y).r , y );
6.             vertexR = Vector3( x+1, MainTerrain.PerlinTex.GetPixel(y + offset.y + 1, y).r , y );
7.             tan = Vector3.Scale( sizeScale, vertexR - vertexL ).normalized;
8.             tangents[y*block + x] = Vector4( tan.x, tan.y, tan.z, -1.0 );

24. ### tonyoakden

Joined:
Nov 13, 2009
Posts:
135
Thanks Eric,

wow that is much faster. about 20 times faster actually. I think the speed increase is due to the use of this function:

The only significant difference I can see is the fast routine does this:

// Gram-Schmidt orthogonalize
Vector3.OrthoNormalize( ref n, ref t );
tangents.x = t.x;
tangents.y = t.y;
tangents.z = t.z;

Compared to the old routine doing this:
tangents = (t - n * Vector3.Dot(n, t)).normalized;

Amazing! anyway it's a reasonably efficient solution for me now.

It would still be really nice to have a built in function to create the tangents for a mesh though.

regards,

Tony

### Volunteer ModeratorModerator

Joined:
Jul 19, 2006
Posts:
31,843
Actually no, I just tested it and while the OrthoNormalize thing is faster, the vast majority of the slowness was because you were using the mesh directly instead of copying the vertices/tris/etc. to arrays first. (When you assign any of the mesh.triangles and so on to arrays, they are actually copied; it's not a reference.)

--Eric

26. ### jed500

Joined:
Aug 8, 2008
Posts:
27
Thanks Noontz,

Fantastic code, and many thanks!!!
I required it to make my ammendments to OBJ importer code complete, however struggled for a day with the c# code you posted on unity answers, as I thought it was not working due to my code flipping normals/mirroring mesh, however the correction is obvious and due to the many amalgamations, alterations and reversioning of the code:-

Code (csharp):
1. int triangleCount = mesh.triangles.Length / 3;
with

Code (csharp):
1. int triangleCount = mesh.triangles.Length;
or totally remove that line and replace:-

Code (csharp):
1. for(long a = 0; a < triangleCount; a+=3)
with

Code (csharp):
1. for(long a = 0; a < mesh.triangles.Length; a+=3)
Many thanks again, I just wanted to save other people any issues.

27. ### davidmaggot

Joined:
Feb 8, 2011
Posts:
13
Hi guys,

I'm using the TangentSolver posted before but I'm having some troubles with the result.

The thing is that I'm adding this code to my create mesh code in a character customization scene where you can morph the character face, when you leave the scene I'm saving the morphed mesh to use it later.

The problem shows up when I use TangentSolver I get this line in the middle of the character face... something like this:

...and also the mesh looks really dark and it doesn't have to anything to be with the light.

So, I was trying to find my way around the TangentSolver code and I found that if I chance the line:

Code (csharp):
1. // Calculate handedness
2. tangents[i].w = ( Vector3.Dot(Vector3.Cross(n, t), tan2[i]) < 0.0 ) ? -1.0 : 1.0;
and chance it for:

Code (csharp):
1. tangents[i].w = ( Vector3.Dot(Vector3.Cross(n, t), tan2[i]) < 0.0 ) ? 1.0 : -1.0;
I get something like this... but the line it's still there however the face looks a littler lighter:

I guess that the direcctions of the tangents are not correct, but I really don't have a clue where to start in the TangentSolver calculations....

Please anyone can help to find out what I'm doing wrong ?

Last edited: Jun 14, 2011
28. ### DaveA

Joined:
Apr 15, 2009
Posts:
280
You guys should post this on the Script Wiki no?

29. ### col000r

Joined:
Mar 27, 2008
Posts:
617
Thanks for this script!

I took the liberty to translate it to C#:

Code (csharp):
1. /*
2. Derived from
3. Lengyel, Eric. "Computing Tangent Space Basis Vectors for an Arbitrary Mesh". Terathon Software 3D Graphics Library, 2001.
4. [url]http://www.terathon.com/code/tangent.html[/url]
5. */
6.
7. using UnityEngine;
8. using System.Collections;
9.
10. public class TangentSolver : MonoBehaviour {
11.
12.     public void Solve(Mesh theMesh) {
13.
14.         int vertexCount = theMesh.vertexCount;
15.         Vector3[] vertices = theMesh.vertices;
16.         Vector3[] normals = theMesh.normals;
17.         Vector2[] texcoords = theMesh.uv;
18.         int[] triangles = theMesh.triangles;
19.         int triangleCount = triangles.Length/3;
20.
21.         Vector4[] tangents = new Vector4[vertexCount];
22.         Vector3[] tan1 = new Vector3[vertexCount];
23.         Vector3[] tan2 = new Vector3[vertexCount];
24.
25.         int tri = 0;
26.
27.         for (int i = 0; i < (triangleCount); i++) {
28.
29.             int i1 = triangles[tri];
30.             int i2 = triangles[tri+1];
31.             int i3 = triangles[tri+2];
32.
33.             Vector3 v1 = vertices[i1];
34.             Vector3 v2 = vertices[i2];
35.             Vector3 v3 = vertices[i3];
36.
37.             Vector2 w1 = texcoords[i1];
38.             Vector2 w2 = texcoords[i2];
39.             Vector2 w3 = texcoords[i3];
40.
41.             float x1 = v2.x - v1.x;
42.             float x2 = v3.x - v1.x;
43.             float y1 = v2.y - v1.y;
44.             float y2 = v3.y - v1.y;
45.             float z1 = v2.z - v1.z;
46.             float z2 = v3.z - v1.z;
47.
48.             float s1 = w2.x - w1.x;
49.             float s2 = w3.x - w1.x;
50.             float t1 = w2.y - w1.y;
51.             float t2 = w3.y - w1.y;
52.
53.             float r = 1.0f / (s1 * t2 - s2 * t1);
54.             Vector3 sdir = new Vector3((t2 * x1 - t1 * x2) * r, (t2 * y1 - t1 * y2) * r, (t2 * z1 - t1 * z2) * r);
55.             Vector3 tdir = new Vector3((s1 * x2 - s2 * x1) * r, (s1 * y2 - s2 * y1) * r, (s1 * z2 - s2 * z1) * r);
56.
57.             tan1[i1] += sdir;
58.             tan1[i2] += sdir;
59.             tan1[i3] += sdir;
60.
61.             tan2[i1] += tdir;
62.             tan2[i2] += tdir;
63.             tan2[i3] += tdir;
64.
65.             tri += 3;
66.
67.         }
68.
69.
70.
71.         for (int i = 0; i < (vertexCount); i++) {
72.
73.             Vector3 n = normals[i];
74.             Vector3 t = tan1[i];
75.
76.             // Gram-Schmidt orthogonalize
77.             Vector3.OrthoNormalize(ref n, ref t);
78.
79.             tangents[i].x  = t.x;
80.             tangents[i].y  = t.y;
81.             tangents[i].z  = t.z;
82.
83.             // Calculate handedness
84.             tangents[i].w = ( Vector3.Dot(Vector3.Cross(n, t), tan2[i]) < 0.0f ) ? -1.0f : 1.0f;
85.
86.         }
87.
88.         theMesh.tangents = tangents;
89.
90.     }
91.
92. }

30. ### HonoraryBob

Joined:
May 26, 2011
Posts:
1,001
I've confirmed that the tangents are wrong (they make an absolute mess of cubemap-based reflections) . Someone should have tested the code thoroughly first....

31. ### DaveA

Joined:
Apr 15, 2009
Posts:
280
If you have a fix, can you post it here?

32. ### HonoraryBob

Joined:
May 26, 2011
Posts:
1,001
I wish I had a fix, since my project is at an impasse until I do.
The odd thing is that bumpmapping seems to work fine, but cubemapping doesn't. The reflections vary enormously from one facet to the next (I'm testing it on a cylinder with 20 facets around the diameter).
Does anyone know what the problem would be?

Last edited: Jan 29, 2013

Joined:
Sep 7, 2013
Posts:
10
34. ### blaher

Joined:
Oct 21, 2013
Posts:
52
Thanks so much for this. I'm having an issue using this for an animated mesh. It works great until I add a mesh animation. Any idea how why?

35. ### imphenzia

Joined:
Jun 28, 2011
Posts:
399
I've got a problem with the above tangent calculations on a procedural cube sphere too. I am guessing it has something to do with the direction of the UVs on the 6 different spherical cube sides, some seams are very apparent. But then again it could be my shader. Or both =)