# Parsing tangent values correctly | c++/opengl

## Recommended Posts

Ive been trying to make a 3Dmodel parser, on one hand to understand the process better, and on the other hand to be able to make formats that I can call my own, but the result creates seams once I introduce normal mapping. Maybe somebody can explain what is happening, and/or what I should be doing different.

What I do is make an object in maya (in this a case a cube-sphere), export as obj and then try to parse it. My problem is: how do I handle shared vertices and edges for tangents? When it comes to vertex_normals this is fairly straight forward:

    vector <vec3>normalref(normalsize, vec3(0)); //normalsize, amount of vn, fill with 0

//fill normalref with calculated normals
vec3 normalbuffer;

//i=vert1, i+1=uv1, i+2 vn1
//i+3=vert2,i+4=uv2,i+5=vn2
//i+6=vert3, i+7=uv3, i+8=vn3
for (size_t i = 0; i < totalrefs.size(); i += 9) {
normalbuffer = cross(
vref[totalrefs[i + 3] - 1] - vref[totalrefs[i] - 1],
vref[totalrefs[i + 6] - 1] - vref[totalrefs[i] - 1]);

normalref[totalrefs[i + 2] - 1] += normalbuffer;
normalref[totalrefs[i + 5] - 1] += normalbuffer;
normalref[totalrefs[i + 8] - 1] += normalbuffer;
}
for (auto&x : normalref)x = normalize(x); //and normalize

For each face, add the cross (plane angle) to each vn connected, then after, normalize each of the vns, And you will have a vertex_normal that will work with a seamless phong shader.

But when It comes to tangent values, this is not as straight forward. The idea is that you convert the tangent on gpu, alongside with the normal for that vertice/corner, and make a bitangent, and tbn matrix, that you use for further calculations. But what I cant figure out is: How do you balance the tangent values for each side?

I keep getting edges that overlap and shadows that do not make any sense. Calculating the tangents themselves for each face is not very hard:

for (size_t i = 0; i < totalrefs.size(); i += 9)
{
edge1 = vref[totalrefs[i + 3] - 1] - vref[totalrefs[i] - 1];
edge2 = vref[totalrefs[i + 6] - 1] - vref[totalrefs[i] - 1];
deltaUV1 = uvref[totalrefs[i + 4] - 1] - uvref[totalrefs[i + 1] - 1];
deltaUV2 = uvref[totalrefs[i + 7] - 1] - uvref[totalrefs[i + 1] - 1];
f = 1.0f / (deltaUV1.x * deltaUV2.y - deltaUV2.x * deltaUV1.y);

tangentbuf = normalize(f*vec3(
(deltaUV2.y * edge1.x - deltaUV1.y * edge2.x),
(deltaUV2.y * edge1.y - deltaUV1.y * edge2.y),
(deltaUV2.y * edge1.z - deltaUV1.y * edge2.z)));

tangents[tangcount++] = tangentbuf;
tangents[tangcount++] = tangentbuf;
tangents[tangcount++] = tangentbuf;
}

But this approach that works with vertex_normals, does not work for tangents. Even if i map them per vertex, per uv, or per vn. I get the same poor result showing seams. As far as I can gather, the problem is shared vertex/corner balancing, and I do not know how to do that correctly.

Somebody help!

this is what it currently looks like:

Where the specular value is turned up, to make out the seam more clearly. This seam is not there when I use normals to do the shading, and, each face isnt square-ish either, but interpolated properly. Im pretty certain this is caused by the way I either calculate the tangents, or how I index them, can somebody explain to me how this should be done?

Edited by Melley

##### Share on other sites

Or maybe someone can just suggest a way to parse the tangents? Assuming you have a working way to get vertices, uvs and normals into the environment?

##### Share on other sites

Just to be sure that your problem is really related to the TBN coordinates. You don't have seams with applying texturing ?

Also, seams with TBN is something that can happen.

##### Share on other sites

Just to be sure that your problem is really related to the TBN coordinates. You don't have seams with applying texturing ?

Also, seams with TBN is something that can happen.

The texturing does not get seams, and when I make use of only the normal for non-normal-mapping (plain phong/ads shading) I do not get seams either.

Tangent calculation seems to get messed up when sharing edges/vertices and when wrapping around

Edited by Melley

##### Share on other sites

OK. Try to render your TBN unit vectors now and see if things look OK at the area where you have seams.

##### Share on other sites

OK. Try to render your TBN unit vectors now and see if things look OK at the area where you have seams.

for (auto&x : tangentref)x = normalize(x); //clean up
Edited by Melley

##### Share on other sites

OK. Try to render your TBN unit vectors now and see if things look OK at the area where you have seams.

for (auto&x : tangentref)x = normalize(x); //clean up

I wanted to check if some of the TBN vectors could be reversed/swapped.

From your first image it looks like you have a little decay. This can come from the original topology or from something else.

Just display in the screen each of the TBN vectors with a distinct color and look at the region where you have these seams. I suspect this is the area where the texture goes from the right to the left again.

Also, check with another model (not spherical), and check if you have such seams at this area (junction of texture coordinates when they move from 1 to 0 again).

##### Share on other sites

Just display in the screen each of the TBN vectors with a distinct color and look at the region where you have these seams. I suspect this is the area where the texture goes from the right to the left again

What are possible fixes for that? Im using the wrong matrix or something? I've been trying to recalculate the tangents, but maybe I shouldnt?

##### Share on other sites

export as obj and then try to parse it

Don't use obj as your interchange format; use a format that supports tangents.
Don't compute your own normals/tangents from the vertex positions either -- use the exact values that were exported into your file.

##### Share on other sites

It's quite hard to model a sphere without any seams in the tangent basis. Quite likely you will find that the seam is present in your original model too.

##### Share on other sites
Well im looking for a way to calculate tangents none the less. These are the best sources I have:

[[removed pirated link to Mathematics for 3D Game Programming and Computer Graphics, Third Edition]

The problem Im facing is doing it on a per vertice basis.

##### Share on other sites
Morton Mikkleson's "mikktspace" is a common algorithm to use:
http://image.diku.dk/projects/media/morten.mikkelsen.08.pdf
https://github.com/teared/mikktspace-for-houdini/blob/master/source/mikktspace.c

The gist of most algorithms is to generate tangents such that they're perpendicular to the normal and point in the direction where the 'U' texcoord is increasing, and bitangents pointing in the direction where 'V' is increasing, whilr still being perpendicular to the other two vectors. There's infinite ways to generate tangents, as there's a full circle of perpendicular vectors to the normal. Technically it doesn't even have to be perpendicular - a skewed tangent basis will still work.

What's important to note is that the tangent basis is a key for compression and decompression of normals. You can think of it like encryption and decryption. A tangent-space-normal map (the mostly blue kind) contains world-space normals that have been encrypted using a particular key. If you want to get those original world-space normals back, you need to decrypt the texture using the same key.

You can't just get any normal map and any tangents and expect to get sensible results. If the tangents used by the modelling tool and the engine match, then the encoding/decoding steps match, and you can get perfect results even if there's seams in your tangent space.

However, one common use of tangent-space normal maps is to reuse them across different objects, which means deliberately making the encoding/decoding steps mismatch... As long as you don't already have problems with UV seams and use a consistent generation algorithm on every object (e.g. tangent follows +U, bitangent follows +V) then you can make this work too, but you're playing with fire.

##### Share on other sites

eh, the tangents and uv have to match, idk what your talking about encryption for

##### Share on other sites

Just background on the general form of the problem. Key-based encryption/decryption was an analogy for TBN-basis encoding/decoding.
If the basis that was used to generate the texture does not match the one that's used to decode the texture, then you're not going to recover the original data.
If you care about recovering the original normals that are stored in the texture, then the tangents that were used to create the normal map in the first place have to match the ones used to decode it.
If you don't care about such things, then you'll have to tolerate the seam artefacts and inside-out bumps that occur due to incorrect decoding. These artefacts can be minimised by at least ensuring that the same tangent-generation algorithm was used by the tool that generated the texture and your engine that's decoding it.

That's the general case. In the general case, it doesn't matter what the tangents are, as long as they match on both ends, which is why model importers should always import that data from the artist's art tools.

For the specific case of tiling normal maps, then you need your tangent-generation algorithm to match the UVs (e.g. tangent and bitangent follow the derivatives of U and V) and not generate any seams in the tangents (unless there's also a seam in the gradient of the UVs).

Try the mikktspace algorithm I linked above - I think it has these properties.

##### Share on other sites

Well im looking for a way to calculate tangents none the less. These are the best sources I have:

http://canvas.projekti.info/ebooks/xxx.pdf

Could you please stop using a pirated copy of my book?

Edited by Eric Lengyel