1
votes

I'm creating a model loading program on OpenGL. I took care of light and specular reflection, but I stuck on the normal map. I think I'm making a mistake in the normal map calculation.

Normal image is:

enter image description here

When I apply the Normal Mapping effect this is how it looks, here is the screenshot:

out

My vertex shader:

#version 430 core

layout(location = 0) in vec3 position;
layout(location = 1) in vec3 normal;
layout(location = 2) in vec2 uvw;
layout(location = 3) in vec3 tangent;
layout(location = 4) in vec3 biTangent;

uniform mat4 M;
uniform mat4 MVP;
uniform mat3 N;

out block
{
    vec4 position;
    vec3 normal;
    vec2 uvw;
    vec3 tangent;
    vec3 biTangent;
    mat3 TBN;
} Out;

void main()
{
    Out.position = M * vec4(position, 1.0);
    Out.normal = normalize(N * normal);
    Out.uvw = uvw;
    Out.tangent = (M * vec4(tangent, 0.0)).xyz;
    Out.TBN = mat3(tangent, biTangent, normal); 

    gl_Position = MVP * vec4(position, 1.0);
}

Fragment shader is:

#version 430 core
#define M_PI        3.14159265358979323846

layout(binding = 0) uniform sampler2D dts;
layout(binding = 1) uniform sampler2D sts;
layout(binding = 2) uniform sampler2D nts;

struct Light {
    vec3 position;
    vec3 filterColor;
    float multiplier;
};

struct Material {
    vec3 baseColor;
    float baseColorMultiplier;
    float roughness;
    float ior;
};

uniform Light light;
uniform Material material;

in block
{
    vec4 position;
    vec3 normal;
    vec2 uvw;
    vec3 tangent;
    vec3 biTangent;
    mat3 TBN;
} In;

out vec4 color;


vec3 Le(Light light, vec4 position, vec3 wi) {
    vec3 Le;
    float dist = length(wi);
    Le = light.filterColor / (dist * dist);

    return Le;
}

vec3 Fresnel(vec3 spec, vec3 normal, vec3 wi)
{
    return spec + (1 - spec) * pow((1 - max(0.0, dot(wi, normal))), 5);
}

vec3 Normal()
{
    vec4 norm = texture2D(nts, In.uvw);

    return vec3(norm);
}

vec3 Diffuse(Material material, vec2 uvw) {
    vec4 diff = texture2D(dts, uvw);
    return vec3(diff) / M_PI;
}

vec3 Reflection(Material material, vec3 wi, vec3 normal, vec2 uvw)
{
    vec3 f;
    float cosTheta = dot(normal, wi);

    vec4 spec = texture2D(sts, uvw);
    f += vec3(1.0) * vec3(spec) * pow(max(0.0, abs(cosTheta)), material.roughness);

    f += Fresnel(vec3(spec), normal, wi);
    return f;
}

vec3 BRDF(Light light, Material material, vec3 wo, vec3 wi, vec4 position, vec3 normal, vec2 uvw) {
    vec3 L;

    // Evaluate emitted light
    vec3 Li = Le(light, position, wi);

    // Diffuse
    vec3 f = Diffuse(material, uvw);    

    // Reflection
    float cosThetaI = max(0.0, dot(wi, normal));
    if(cosThetaI > 0.0) {
        f += Reflection(material, wi, normal, uvw);
    }

    // BRDF function
    L += f * Li * max(0.0, dot(wi, normal));

    return L;
}

void main(void)
{   
    vec3 L;
    vec3 wi, wo;

    // Evaluate incoming and outgoing light direction
    wi = normalize(light.position - vec3(In.position));
    wo = reflect(-wi, In.normal);

    // Evaluate normal map
    vec4 normal = texture2D(nts, In.uvw);
    normal = normalize(normal * 2.0 -1.0);
    normal = vec4(normalize(In.TBN * normal.xyz), 0.0);

    L += BRDF(light, material, wo, wi, In.position, normal.xyz, In.uvw);
    color = vec4(L, 1.0);
}

My tangent and bitangent calculation code is:

void CGLPrimitive::CalculateTangentBiTangent()
{
    for (unsigned int i = 0; i < positions.size(); i+=3) {
        CVector3<float> p1 = positions[i];
        CVector3<float> p2 = positions[i + 1];
        CVector3<float> p3 = positions[i + 2];

        CVector2<float> uv1 = uv[i];
        CVector2<float> uv2 = uv[i+1];
        CVector2<float> uv3 = uv[i+2];


        CVector3<float> e1 = p2 - p1;
        CVector3<float> e2 = p3 - p1;

        CVector2<float> deltaUV1 = uv2 - uv1;
        CVector2<float> deltaUV2 = uv3 - uv1;

        float f = 1.0f / (deltaUV1.x * deltaUV2.y - deltaUV2.x * deltaUV1.y);


        CVector3<float> t;
        t.x = f * (deltaUV2.y * e1.x - deltaUV1.y * e2.x);
        t.y = f * (deltaUV2.y * e1.y - deltaUV1.y * e2.y);
        t.z = f * (deltaUV2.y * e1.z - deltaUV1.y * e2.z);
        t = Normalize(t);
        tangents.push_back(t);
        tangents.push_back(t);
        tangents.push_back(t);

        CVector3<float> b;
        b.x = f * (-deltaUV2.x * e1.x + deltaUV1.x * e2.x);
        b.y = f * (-deltaUV2.x * e1.y + deltaUV1.x * e2.y);
        b.z = f * (-deltaUV2.x * e1.z + deltaUV1.x * e2.z);
        b = Normalize(b);
        biTangents.push_back(b);
        biTangents.push_back(b);
        biTangents.push_back(b);
    }


}

I wonder where I'm making a mistake. Thank you.

2
how is the image different from what you want?463035818_is_not_a_number
@user463035818: Sorry. I added a normal output image and normal map.user5044221
The tangent and the biTangent have to be transformed by the normal matrix Out.tangent = normalize(N * tangent); Out.biTangent = normalize(N * biTangent);Rabbid76

2 Answers

0
votes

The tangent and the biTangent have to be transformed by the normal matrix as you do it with the normal vector:

Out.tangent   = normalize(N * tangent); 
Out.biTangent = normalize(N * biTangent);
Out.normal    = normalize(N * normal);

In the fragment shader the calculations are done in world space. For this you have to transform the normal vector of the normal map to world space.

mat3(tangent, biTangent, normal) is the matrix which transforms from texture space to model space.

The matrix which transforms from texture space to world space is:

Out.TBN = N * mat3(tangent, biTangent, normal);  

or

Out.TBN = mat3(Out.tangent, Out.biTangent, Out.normal);       

Use on of the above calculations of the tangent space matrix to solve your issue.

0
votes

Few things:

1) As Rabbid76 said, you have to multiply tangents and bitangets. You can use the modelview matrix, since these vectors does not suffer from any distortion caused by non uniform scale.

2) We dont know the format of your texture in GPU. If it has an alpha channel set to 1.0 and then you normalize the whole vector:

vec4 normal = texture2D(nts, In.uvw);
normal = normalize(normal * 2.0 -1.0); 

The normalized vector might be normalized in 4D, but might not in 3D. Just take the rgb from the texture and use it.

vec3 normal = texture2D(nts, In.uvw).rgb;

3) What is this for? normal = normal * 0.5 + 0.5; I believe this is the root of your problem, as you are smoothing the normals in the wrong way. Example:

normal (1,0,0) would become (1,0.5,0.5)

but

normal (-1,0,0) would become (0,0.5,0.5)

I would also recommend you to debug your normals, tangents, and bitangents by drawing them (using a geometry shader for example) to make sure they are correct.