4

I have a flat water surface with a dudv and a normal map attached to it. The dudv map works correct and the normal map is attached correct as well (visualizing the normal map looks like it should). The specular highlights are always showing at the wrong place though as if the light direction is incorrect. The lighting works correct without normal mapping, so I don't believe it is the light direction, but probably something with the tangent space. Since I calculate the tangent space from a static set of vectors I'm confused where it could go wrong.

Here in the vertex shader I create the TBN matrix that I use to create the tangent space vectors that I send to the fragment shader:

const vec3 TANGENT = vec3(1.0, 0.0, 0.0);
const vec3 NORMAL = vec3(0.0, 1.0, 0.0);
const vec3 BITANGENT = vec3(0.0, 0.0, -1.0);

out vec3 FragPos;
out vec3 TangentFragPos;
out vec3 TangentLightDir;
out vec3 TangentPlayerPos;

void main()
{
    FragPos = vec3(model * vec4(vertex, 1.0));
    mat3 mod = transpose(inverse(mat3(model)));
    [...]
    vec3 n = normalize(mod * NORMAL);
    vec3 t = normalize(mod * TANGENT);
    vec3 b = normalize(mod * BITANGENT);
    mat3 TBN = transpose(mat3(t, b, n));
    TangentFragPos =  TBN * FragPos;
    TangentLightDir =  TBN * sun.Position.xyz;
    TangentPlayerPos =  TBN * playerPos;
}

In the fragment shader I then sample a normal vector from the normal map and use the transformed tangent space vectors to calculate specular highlights:

in vec3 FragPos;
in vec3 TangentFragPos;
in vec3 TangentLightDir;
in vec3 TangentPlayerPos;

uniform sampler2D normalMap;

void main()
{    
    [...]
    vec3 normal = texture(normalMap, vec2(TexCoords * TextureScale) + vec2(Time)).xyz;
    normal = normalize(normal * 2.0 - 1.0);
    // normal = vec3(0.0, 0.0, 1.0); // this gives proper specular highlights, but not mapped

    // Specular lighting
    vec3 lightDir = normalize(-TangentLightDir);
    viewDir = normalize(TangentPlayerPos - TangentFragPos);
    vec3 reflectDir = normalize(reflect(-lightDir, normal));
    float spec = pow(max(dot(viewDir, reflectDir), 0.0), 64);
    vec3 lighting = sun.Specular * spec * fresnel;

    color.xyz *= lighting;       
}

Note that I do the lighting in world-space.

The lighting works fine without normal mapping, but as soon as I introduce the TBN matrix into the equations the specular highlights are not in the right direction on the water.

EDIT

I've added an image to see how it currently looks and where the specular light should be. That might add some extra insight into the problem:

 Screenshot

EDIT2

Here's the weird thing. If I manually define the normal vector as vec3(0.0, 0.0, 1.0) (pointing upwards in tangent space) I get perfect specular highlights (but with no mapped variation) and once I take the normal from the normal map, I get incorrect highlights again so I'd say the cause of the issue would be at the normals. The normal map I'm using however is a default normal map you'd usually see as you can see below:

Normal map of water

Why is it that once I take a normal from this normal map (which already should be in tangent space), the specular highlights break down?

ABHAY
  • 221
  • 2
  • 12
  • Is this for deferred shading? If not, you should be computing those lighting vectors in the vertex shader and interpolating them. Otherwise you are doing a lot of unnecessary work in the fragment shader (which runs orders of magnitude more often than the vertex shader). – Andon M. Coleman Mar 09 '15 at 16:51
  • More importantly, however, your TBN matrix appears to be transposed. I would try `mat3 (TANGENT, BITANGENT, NORMAL)` instead. – Andon M. Coleman Mar 09 '15 at 16:55
  • It's for forward rendering. I'll try using the tranpose version of TBN and thanks for suggestions. As soon as it works I'll do the tangent calculations in the vertex shader – ABHAY Mar 09 '15 at 17:02
  • I can see (from GLSL wiki) that indeed using your constructor overload version of `mat3` should give the transposed `TBN` matrix of what I first had. However, the results are similar (or the same). – ABHAY Mar 09 '15 at 17:06
  • @AndonM.Coleman I've added an image and still couldn't find the issue. Do you see anything else that might be wrong ? – ABHAY Mar 10 '15 at 08:43
  • The only weird thing I see is the double negation of `lightDir`. I would consider declaring `lightDir` as `normalize(TBN * lights[0].Position.xyz)`, – Andon M. Coleman Mar 10 '15 at 20:28
  • @AndonM.Coleman: yeah I already tried negating vectors in any combination in case I accidentally had one in the wrong direction, but to no effect. Currently the specular highlights are sort of close by, but negating a vector completely removes/destroys the specular lighting. I added how I calculate the `lightDir`, but that's probably not the cause. I don't know what else to add that might be of relevance. – ABHAY Mar 10 '15 at 21:13

2 Answers2

3

It's been more than a week of debugging now and I finally found the issue. I've been using gamma correction and my texture class by defaults loads texture with the GL_SRGB property as the texture's internal format so OpenGL properly converts gamma corrected textures to their linear brothers.

The problem was that I also loaded the dudv and Normal map with this texture class without changing this property so the normal and dudv maps had OpenGL's gamma->linear correction applied and thus gave incorrect results.

Loading these textures with GL_RGB solved the issue.

ABHAY
  • 221
  • 2
  • 12
2

Are you on a left-hand or right-hand coordinate system? Have you considered checking the validity of your static TBN matrix vectors?

EDIT: The bitangent is extracted by crossing normal and tangent. So, normal(0,1,0) x tangent(1,0,0) equals bitangent(0,0,-1)

Dimo Markov
  • 422
  • 2
  • 9
  • This should really be a comment, not an answer. – Fantastic Mr Fox Mar 13 '15 at 15:46
  • I'm on a right-handed coordinate system. If I recall correctly this means that the bitangent should be equal to the cross product of the normal and the tangent which with the right-hand rule gives a vector in positive z direction (in r-handed coordinate system). If I'm not mistaken they should be correct? – ABHAY Mar 13 '15 at 16:03
  • 1
    Note that order matters when you take cross products. `Tangent x Normal`, and `Normal x Tangent` point to opposite directions. – Dimo Markov Mar 14 '15 at 09:18
  • 1
    So, `normal(0,1,0) x tangent(1,0,0)` equals `bitangent(0,0,-1)` – Dimo Markov Mar 14 '15 at 09:44
  • Ahh indeed you're right. I fixed the bitangent and noticed something interesting (problem is still not entirely gone), I edited the question; perhaps you could shed some insight? – ABHAY Mar 14 '15 at 10:10
  • This to me sounds like a texture mismatch. How do you generate your bump map? Is it like XYZ == RGB? Can you share it, so I can see it? Also, XY in TBN space should match UV coordinates, which leaves Z to be the normal, yes. A bump map should work correctly, if it's predominantly blue. – Dimo Markov Mar 14 '15 at 10:39
  • If you're using a dudv map for both reflection/refraction and bump, you can cheat by doing: normal = normalize(vec3(red,green,1)), as long as RED and GREEN channels correspond to X and Y values. Otherwise, you might have to swap them. :) You could also throw a factor: `red * factor, green * factor` to control the "spiky-ness" of the bump, since dudv maps tend to be quite rough. :) – Dimo Markov Mar 14 '15 at 10:45
  • I added the normal map to the question as well, looks valid to me at a first glance. The trick to get normals from the dudv map itself seems to work, although it gives much smoother specular highlights as a result, but it does work. – ABHAY Mar 14 '15 at 11:18
  • The normal map seems to be in order. Have you tried using the same trick with it (overriding the Z component)? – Dimo Markov Mar 14 '15 at 11:28
  • Nevermind, I multiplied the dudv coordinates with a factor of 33 (since I originally downscaled the dudv coordinates by 33) to get the normal vectors, but then it's the same result again, with specular highlights at incorrect positions. – ABHAY Mar 14 '15 at 11:30
  • If I only multiply the duvd color on the x coordinate alone the specular highlight's locations seem mostly correct like `normal = normalize(vec3(dudvColor.x * 33, dudvColor.y, 1.0));` which would lead me to believe the y coordinates in tangent space are messing things up. – ABHAY Mar 14 '15 at 11:32