Search Unity

  1. Megacity Metro Demo now available. Download now.
    Dismiss Notice
  2. Unity support for visionOS is now available. Learn more in our blog post.
    Dismiss Notice

How to Calculate Mesh Tangents

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

  1. amasinton

    amasinton

    Joined:
    Aug 12, 2006
    Posts:
    137
    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?

    Thanks for your help.
    -- Anthony
     
  2. noontz

    noontz

    Joined:
    Nov 7, 2009
    Posts:
    16
    http://answers.unity3d.com/questions/271/calculating-tangents-vector4

    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:
     
  3. Eric5h5

    Eric5h5

    Volunteer Moderator Moderator

    Joined:
    Jul 19, 2006
    Posts:
    32,401
    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

    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.
     
  5. Eric5h5

    Eric5h5

    Volunteer Moderator Moderator

    Joined:
    Jul 19, 2006
    Posts:
    32,401
    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

    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..

    Thanks again for your respons!
     
  7. Eric5h5

    Eric5h5

    Volunteer Moderator Moderator

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

    --Eric
     
  8. noontz

    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...
     
  9. Eric5h5

    Eric5h5

    Volunteer Moderator Moderator

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

    --Eric
     
  10. noontz

    noontz

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

    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. }
     
  11. Eric5h5

    Eric5h5

    Volunteer Moderator Moderator

    Joined:
    Jul 19, 2006
    Posts:
    32,401
    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
     
  12. Daniel_Brauer

    Daniel_Brauer

    Unity Technologies

    Joined:
    Aug 11, 2006
    Posts:
    3,355
    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.
     
    Arkade likes this.
  13. noontz

    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..
     
  14. Eric5h5

    Eric5h5

    Volunteer Moderator Moderator

    Joined:
    Jul 19, 2006
    Posts:
    32,401
    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
     
  15. Daniel_Brauer

    Daniel_Brauer

    Unity Technologies

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

    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. }
     
  17. Eric5h5

    Eric5h5

    Volunteer Moderator Moderator

    Joined:
    Jul 19, 2006
    Posts:
    32,401
    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

    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?

    EDIT: ( answers the above ) : http://answers.unity3d.com/question...erived-from-the-object-class-so-what-are-they

    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

    amasinton

    Joined:
    Aug 12, 2006
    Posts:
    137
    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!
     
  20. noontz

    noontz

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

    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. }
     
  22. Eric5h5

    Eric5h5

    Volunteer Moderator Moderator

    Joined:
    Jul 19, 2006
    Posts:
    32,401
    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

    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

    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
     
  25. Eric5h5

    Eric5h5

    Volunteer Moderator Moderator

    Joined:
    Jul 19, 2006
    Posts:
    32,401
    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

    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:-

    On answers page please either replace:-

    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

    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

    DaveA

    Joined:
    Apr 15, 2009
    Posts:
    310
    You guys should post this on the Script Wiki no?
     
  29. col000r

    col000r

    Joined:
    Mar 27, 2008
    Posts:
    698
    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

    HonoraryBob

    Joined:
    May 26, 2011
    Posts:
    1,214
    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....
     
    GodlikeAurora likes this.
  31. DaveA

    DaveA

    Joined:
    Apr 15, 2009
    Posts:
    310
    If you have a fix, can you post it here?
     
  32. HonoraryBob

    HonoraryBob

    Joined:
    May 26, 2011
    Posts:
    1,214
    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
  33. ArnoC

    ArnoC

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

    blaher

    Joined:
    Oct 21, 2013
    Posts:
    80
    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

    imphenzia

    Joined:
    Jun 28, 2011
    Posts:
    413
    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 =)
     
  36. DekkerC

    DekkerC

    Joined:
    Oct 7, 2017
    Posts:
    17
    Had to change the code to make it work correctly.
    Tan2 worked a lot better for me. So my code is based on tan2. Only the x axis was reversed oposed to what it should have been. So I changed it's sign.

    Though I guess the code depends on the order in which the triangles value goes through the vertices (in other words it depends on the direction of points in of the vertexes on your model).

    Disclamer: I don't know what I'm doing with tangents. Just that it is working for me with the following code correction;).

    Code (CSharp):
    1. for (int i = 0; i < (vertexCount); i++)
    2.         {
    3.             Vector3 n = normals[i];
    4.             Vector3 t = tan2[i];
    5.  
    6.             // Gram-Schmidt orthogonalize
    7.             Vector3.OrthoNormalize(ref n, ref t);
    8.  
    9.             tangents[i].x = -t.x;
    10.             tangents[i].y = t.y;
    11.             tangents[i].z = t.z;
    12.  
    13.             // Calculate handedness
    14.             tangents[i].w = (Vector3.Dot(Vector3.Cross(n, t), tan2[i]) < 0.0f) ? -1.0f : 1.0f;
    15.         }