Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Normal Mapping and translation disrupts my lighting

I got a normal mapping issue. I have a texture and a normal texture on each model loaded via the ASSIMP library. I am calculating the tangent vectors on each object with the help of the ASSIMP library so these should be fine. The objects work perfectly with normal mapping but as soon as I start translating one of the objects (thus influence the Model matrix with translations) the lighting fails. As you can see on the image, the floor (which is translated down the y axis) seems to lose most of its diffuse lighting and its specular lighting is in the wrong direction (it should be between the lightbulb and the player position)

Normal mapping gone wrong

It might have something to do with the normal matrix (although translations should be lost), maybe something with a wrong matrix used in the shaders. I am out of ideas and was hoping you could shed some insight into the issue.

Vertex shader:

#version 330

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

// fragment pass through
out vec3 Position;
out vec3 Normal;
out vec3 Tangent;
out vec3 Color;
out vec2 TexCoord;

out vec3 TangentSurface2Light;
out vec3 TangentSurface2View;

uniform vec3 lightPos;
uniform vec3 playerPos;

// vertex transformation
uniform mat4 model;
uniform mat4 view;
uniform mat4 projection;

void main()
{
    mat3 normalMatrix = mat3(transpose(inverse(model))); 
    Position = vec3(model * vec4(position, 1.0)); 
    Normal = normalMatrix * normal;
    Tangent = tangent;
    Color = color;
    TexCoord = texCoord;

    gl_Position = projection * view * model * vec4(position, 1.0);

    // Calculate tangent matrix and calculate fragment bump mapping coord space.
    vec3 light = lightPos;
    vec3 n = normalize(normalMatrix * normal);
    vec3 t = normalize(normalMatrix * tangent);
    vec3 b = cross(n, t);
    // create matrix for tangent (from vertex to tangent-space)
    mat3 mat = mat3(t.x, b.x ,n.x, t.y, b.y ,n.y, t.z, b.z ,n.z);
    vec3 vector = normalize(light - Position);
    TangentSurface2Light = mat * vector;
    vector = normalize(playerPos - Position);
    TangentSurface2View = mat * vector;
}

Fragment Shader

    #version 330

in vec3 Position;
in vec3 Normal;
in vec3 Tangent;
in vec3 Color;
in vec2 TexCoord;

in vec3 TangentSurface2Light;
in vec3 TangentSurface2View;

out vec4 outColor;

uniform vec3 lightPos;
uniform vec3 playerPos;
uniform mat4 view;
uniform sampler2D texture0;
uniform sampler2D texture_normal; // normal

uniform float repeatFactor = 1;

void main()
{   
    vec4 texColor = texture(texture0, TexCoord * repeatFactor);
    vec4 matColor = vec4(Color, 1.0);
    vec3 light = vec3(vec4(lightPos, 1.0));
    float dist = length(light - Position);
    // float att = 1.0 / (1.0 + 0.01 * dist + 0.001 * dist * dist);
    float att = 1.0;
    // Ambient
    vec4 ambient = vec4(0.2);
    // Diffuse
    // vec3 surface2light = normalize(light - Position);
    vec3 surface2light = normalize(TangentSurface2Light);
    // vec3 norm = normalize(Normal); 
    vec3 norm = normalize(texture(texture_normal, TexCoord * repeatFactor).xyz * 2.0 - 1.0); 
    float contribution = max(dot(norm, surface2light), 0.0);
    vec4 diffuse = contribution * vec4(0.6);
    // Specular
    // vec3 surf2view = normalize(-Position); // Player is always at position 0
    vec3 surf2view = normalize(TangentSurface2View);
    vec3 reflection = reflect(-surface2light, norm); // reflection vector
    float specContribution = pow(max(dot(surf2view, reflection), 0.0), 32);
    vec4 specular = vec4(1.0) * specContribution;

    outColor = (ambient + (diffuse * att)+ (specular * pow(att, 3))) * texColor;
    // outColor = vec4(Color, 1.0) * texture(texture0, TexCoord);
}

EDIT

Edited the shader code to calculate everything in world space instead of pingponging between world and camera space (easier to understand and less error-prone).

like image 400
Joey Dewd Avatar asked Aug 15 '13 08:08

Joey Dewd


People also ask

How normal mapping works?

A normal map uses RGB information that corresponds directly with the X, Y and Z axis in 3D space. This RGB information tells the 3D application the exact direction of the surface normals are oriented in for each and every polygon.

How do I use opengl with normal maps?

To get normal mapping to work we're going to need a per-fragment normal. Similar to what we did with diffuse and specular maps we can use a 2D texture to store per-fragment normal data. This way we can sample a 2D texture to get a normal vector for that specific fragment.

What is normal map rendering?

What is a normal map? A normal map is a texture that rendering engines use to fake bumps and imperfections on a surface. They're very effective and also very efficient and nearly all materials will make use of them in one way or another.

How do you find tangent and Bitangent from normal?

To calculate the bitangent, we take the cross product of the normal and tangent vectors then multiply it by a constant in tangent. w which is the handedness of the tangent space. The bitangent points along the V texture coordinate axis of the face.


1 Answers

You are making strange manipulations with matrices. In VS you transform normal (that is model-space) by inverse view-world. That doesn't make any sense. It may be easier to do calculations in world-space. I've got some working sample code, but it uses a bit different naming.

Vertex shader:

void main_vs(in A2V input, out V2P output) 
{
    output.position = mul(input.position, _worldViewProjection);
    output.normal = input.normal;
    output.binormal = input.binormal;
    output.tangent = input.tangent;
    output.positionWorld = mul(input.position, _world);
    output.tex = input.tex;
}

Here we transform position to projection(screen)-space, TBN is left in model-space, they will be used later. Also we get world-space position for lighting evaluation.

Pixel shader:

void main_ps(in V2P input, out float4 output : SV_Target)
{
    float3x3 tbn = float3x3(input.tangent, -input.binormal, input.normal);

    //extract & decode normal:
    float3 texNormal = _normalTexture.Sample(_normalSampler, input.tex).xyz * 2 - 1;

    //now transform TBN-space texNormal to world space:
    float3 normal = mul(texNormal, tbn);
    normal = normalize(mul(normal, _world));

    float3 lightDirection = -_lightPosition.xyz;//directional
    float3 viewDirection = normalize(input.positionWorld - _camera);
    float3 reflectedLight = reflect(lightDirection, normal);

    float diffuseIntensity = dot(normal, lightDirection);
    float specularIntensity = max(0, dot(reflectedLight, viewDirection)*1.3);

    output = ((_ambient + diffuseIntensity * _diffuse) * _texture.Sample(_sampler, input.tex) 
        + pow(specularIntensity, 7) * float4(1,1,1,1)) * _lightColor;
}

Here I use directional light, you should do something like

float3 lightDirection = normalize(input.positionWorld - _lightPosition.xyz);//omni

Here we first have normal from texture, that is in TBN-space. Then we apply TBN matrix to transform it to model-space. Then apply world matrix to transform it to world-space, were we already have light position, eye, etc.

Some other shader code, ommitted above (DX11, but it's easy to translate):

cbuffer ViewTranforms
{
    row_major matrix _worldViewProjection;
    row_major matrix _world;
    float3 _camera;
};

cbuffer BumpData
{
    float4 _ambient;
    float4 _diffuse;
};

cbuffer Textures
{
    texture2D _texture;
    SamplerState _sampler;

    texture2D _normalTexture;
    SamplerState _normalSampler;
};

cbuffer Light
{
    float4 _lightPosition;
    float4 _lightColor;
};

//------------------------------------

struct A2V
{
    float4 position : POSITION;
    float3 normal : NORMAL;
    float3 binormal : BINORMAL;
    float3 tangent : TANGENT;
    float2 tex : TEXCOORD;
};

struct V2P
{
    float4 position : SV_POSITION;
    float3 normal : NORMAL;
    float3 binormal : BINORMAL;
    float3 tangent : TANGENT;
    float3 positionWorld : NORMAL1;
    float2 tex : TEXCOORD;
};

Also, here I use precomputed binormal: you shall leave your code, that computes it (via cross(normal, tangent)). Hope this helps.

like image 130
Celestis Avatar answered Sep 24 '22 12:09

Celestis