2

First of all, I must apologize for posting yet another question on this subject (there are a lot already!). I did search for other related questions and answers, but unfortunately none of them showed me the solution. Now I'm desperate! :D

It is worth mentioning that the code posted below gives a satisfying 'bumpy' effect. It is the scene enlightenment that seems to be wrong.

The scene: is dead simple! A cube in the center, a light source rotating around it (parallel to the ground) and above.

My approach is to start from my basic light shader, which gives me adequate outputs (or so I think!). The first step is to modify it to do the calculations in tangent space, then use the normal extracted from a texture.

I tried to comment the code nicely, but in short I have two questions:

1) Doing only basic lighting (no normal mapping), I expect the scene to look exactly the same, with or without transforming my vectors into tangent space with the TBN matrix. Am I wrong?

2) Why do I get incorrect enlightenment?

A couple of screenshots to give you an idea (EDITED) - following LJ's comment, I am no longer summing normals and tangent per vertex/face. Interestingly, it highlights the issue (see on the capture, I have marked how the light moves).

Basically it is as if the cube was rotated 90 degrees to the left, or, as if the light was turing vertically instead of horizontally

Result with normal mapping:

Result with normal mapping

Version with simple light:

Version with simple light

Vertex shader:

// Information about the light. 
// Here we care essentially about light.Position, which
// is set to be something like vec3(cos(x)*9, 5, sin(x)*9)
uniform Light_t Light;

uniform mat4 W; // The model transformation matrix
uniform mat4 V; // The camera transformation matrix
uniform mat4 P; // The projection matrix

in vec3 VS_Position; 
in vec4 VS_Color;
in vec2 VS_TexCoord;
in vec3 VS_Normal;
in vec3 VS_Tangent;

out vec3 FS_Vertex;
out vec4 FS_Color;
out vec2 FS_TexCoord;
out vec3 FS_LightPos;
out vec3 FS_ViewPos;
out vec3 FS_Normal;

// This method calculates the TBN matrix:
// I'm sure it is not optimized vertex shader code,
// to have this seperate method, but nevermind for now :)
mat3 getTangentMatrix()
{
    // Note: here I must say am a bit confused, do I need to transform
    // with 'normalMatrix'? In practice, it seems to make no difference...
    mat3 normalMatrix = transpose(inverse(mat3(W)));
    vec3 norm = normalize(normalMatrix * VS_Normal);
    vec3 tang = normalize(normalMatrix * VS_Tangent);
    vec3 btan = normalize(normalMatrix * cross(VS_Normal, VS_Tangent));
    tang = normalize(tang - dot(tang, norm) * norm);
    return transpose(mat3(tang, btan, norm));
}

void main()
{
    // Set the gl_Position and pass color + texcoords to the fragment shader
    gl_Position = (P * V * W) * vec4(VS_Position, 1.0); 
    FS_Color = VS_Color;
    FS_TexCoord = VS_TexCoord;

    // Now here we start:
    // This is where supposedly, multiplying with the TBN should not
    // change anything to the output, as long as I apply the transformation
    // to all of them, or none.
    // Typically, removing the 'TBN *' everywhere (and not using the normal 
    // texture later in the fragment shader) is exactly the code I use for 
    // my basic light shader.
    mat3 TBN = getTangentMatrix();
    FS_Vertex = TBN * (W * vec4(VS_Position, 1)).xyz;
    FS_LightPos = TBN * Light.Position;
    FS_ViewPos = TBN * inverse(V)[3].xyz;

    // This line is actually not needed when using the normal map: 
    // I keep the FS_Normal variable for comparison purposes, 
    // when I want to switch to my basic light shader effect.
    // (see later in fragment shader)
    FS_Normal = TBN * normalize(transpose(inverse(mat3(W))) * VS_Normal);
}

And the fragment shader:

struct Textures_t 
{
    int SamplersCount;
    sampler2D Samplers[4];
};

struct Light_t 
{
    int   Active;
    float Ambient;
    float Power;
    vec3  Position;
    vec4  Color;
};

uniform mat4 W;
uniform mat4 V;
uniform Textures_t Textures;
uniform Light_t    Light;

in vec3 FS_Vertex; 
in vec4 FS_Color;
in vec2 FS_TexCoord;
in vec3 FS_LightPos;
in vec3 FS_ViewPos; 
in vec3 FS_Normal;

out vec4 frag_Output;

vec4 getPixelColor()
{
    return Textures.SamplersCount >= 1 
        ? texture2D(Textures.Samplers[0], FS_TexCoord)
        : FS_Color;
}

vec3 getTextureNormal()
{
    // FYI: the normal texture is always at index 1
    vec3 bump = texture(Textures.Samplers[1], FS_TexCoord).xyz;
    bump = 2.0 * bump - vec3(1.0, 1.0, 1.0);
    return normalize(bump);
}

vec4 getLightColor()
{
    // This is the one line that changes between my basic light shader
    // and the normal mapping one:
    // - If I don't do 'TBN *' earlier and use FS_Normal here, 
    // the enlightenment seems fine (see second screenshot)
    // - If I do multiply by TBN (including on FS_Normal), I would expect
    // the same result as without multiplying ==> not the case: it looks
    // very similar to the result with normal mapping 
    // (just has no bumpy effect of course)
    // - If I use the normal texture (along with TBN of course), then I get
    // the result you see in the first screenshot.
    vec3 N = getTextureNormal(); // Instead of 'normalize(FS_Normal);'

    // Everything from here on is the same as my basic light shader
    vec3 L = normalize(FS_LightPos - FS_Vertex);
    vec3 E = normalize(FS_ViewPos  - FS_Vertex);
    vec3 R = normalize(reflect(-L, N)); 

    // Ambient color: light color times ambient factor
    vec4 ambient = Light.Color * Light.Ambient;

    // Diffuse factor: product of Normal to Light vectors
    // Diffuse color: light color times the diffuse factor
    float dfactor = max(dot(N, L), 0);
    vec4  diffuse = clamp(Light.Color * dfactor, 0, 1);

    // Specular factor: product of reflected to camera vectors
    // Note: applies only if the diffuse factor is greater than zero
    float sfactor = 0.0;
    if(dfactor > 0)
    {
        sfactor = pow(max(dot(R, E), 0.0), 8.0);
    }

    // Specular color: light color times specular factor
    vec4 specular = clamp(Light.Color * sfactor, 0, 1);

    // Light attenuation: square of the distance moderated by light's power factor
    float atten = 1 + pow(length(FS_LightPos - FS_Vertex), 2) / Light.Power;

    // The fragment color is a factor of the pixel and light colors:
    // Note: attenuation only applies to diffuse and specular components
    return getPixelColor() * (ambient + (diffuse + specular) / atten);
}

void main()
{
    frag_Output = Light.Active == 1 
        ? getLightColor() 
        : getPixelColor();
}

That's it! I hope you have enough information and of course, your help will be greatly appreciated! :) Take care.

emackey
  • 11,818
  • 2
  • 38
  • 58
Smoove
  • 163
  • 9
  • Without looking at your code, from the images it looks like your cube consists of 8 shared vertices with gouraud normals?! So your normals aswell as your tangents are wrong, gauraud shading is used to approximate smooth surfaces, a cube is certainly not a smooth surface. Before you do anything else fix your surface normals(hint they should be perpendicular to the surfaces, extra hint: yes that means no shared vertices). – LJᛃ Sep 15 '16 at 02:40
  • Many thanks LJ - gouraud normals were not the root of the problem, but your remark made the problem more obvious I find: now the face on the left is always in the light, as if it was on top - and the one the right (not visible on the screenshot) is always in the dark! – Smoove Sep 15 '16 at 05:04

1 Answers1

0

I am experiancing a very similar problem, and i can not explain why the lighting doesn't work right, but i can answer your first question and at the very least explain how i somehow got lighting working acceptably (though your problem may not necesarrily be the same is mine).

Firstly in theory if you tangents and bitangents are calculated correctly, then you should get exactly the same lighting result when doing the calculation in tangentspace with a tangentspace normal [0,0,1].

Secondly while it is common knowledge that you should transform your normals from model to cameraspace by multiplying by inverse transpose model-view matrix as explained by this tutorial, i found that the problem with the lighting being transformed wrong can be solved if you transform the normal tangent by the model-view matrix rather than the inverse transpose model-view. Ie use normalMatrix = mat3(W); instead of normalMatrix = transpose(inverse(mat3(W)));.

In my case this did »fix« the problems with the light, but i don't know why this fixed it, but i make no guarantee that it does not (in fact i assume that it does) introduce other problems with the shading

Nikolaj
  • 1,137
  • 10
  • 22