7

I am trying to use a normal map instead of the vertex normals and I can't figure out which step I'm doing wrong. I want to transform the light direction into tangent space and do the calculation from there. First, I transform the vector normals and tangents into viewspace.

vec3 norm_viewsp = mat3(view) * normalize(norm);
vec3 tangent_viewsp = mat3(view) * normalize(tangent);
vec3 bitangent_viewsp = cross(tangent_viewsp, norm_viewsp);

Tangents in viewspace

Next, I build the matrix which should perform the transformation from viewspace to tangent space and use it to calculate the distance to the light in tangent space.

mat3 tbn = transpose(mat3(tangent_viewsp, bitangent_viewsp, norm_viewsp));
vec3 light_dir = light_pos - pos.xyz;
vec3 light_dir_viewsp = mat3(view) * light_dir;

v_light_dir_tansp = tbn * light_dir_viewsp;

Light directions in tangent space

In the fragment shader I then sample the normal from the normal map and multiply it with the light direction.

vec3 norm = texture(normal_map, v_tex_pos).rgb * 2.0 - 1.0;
float diffuse = dot(normalize(norm), normalize(v_light_dir_tansp));

The normal map definitely loads correctly.

Normal map

In the result the top left corner is too dark and the bottom left too bright.
I think the problem lies somewhere with the light direction but I can't spot the mistake.

Result

Nore that in the above pictures the values are between -1 and 1, that's why e.g. the normal map looks too dark.

The full shader code

Edit: The light is in the center of the model. Lighting works fine if using the vertex normals. Doing the calculations in view space instead of tangent space makes no difference.

Rabbid76
  • 202,892
  • 27
  • 131
  • 174
Sogomn
  • 304
  • 3
  • 16

2 Answers2

6

First you have to calculated the tangent space matrix, which is done like this in a right hand system:

vec3 norm_viewsp      = mat3(view) * normalize(norm);
vec3 tangent_viewsp   = mat3(view) * normalize(tangent);
vec3 bitangent_viewsp = cross(norm_viewsp, tangent_viewsp);
mat3 tbn              = mat3(tangent_viewsp, bitangent_viewsp, norm_viewsp));

And of course you need the normal vector from the normal map in tangent space and the vector to the light source in view space:

vec3 norm_map_tbn     = normalize(texture(normal_map, v_tex_pos).xyz * 2.0 - 1.0);
vec3 light_dir_viewsp = normalize(mat3(view) * (light_pos - pos.xyz));

After that you have 2 possibilities. Since the normal vector from the normal map is in tangent space, the light vector can be transform the to tangent space, too and the light calculation can be don in tangent space. For this the light vector has to be transformed from by the inverse tangent space matrix. The inverse matrix can be calculated by the GLSL function inverse and not by transpose (see What is the difference between "matrix inverse" and "matrix transpose"?).

vec3 light_dir_tbn    = inverse(tbn) * light_dir_viewsp;
float diffuse         = max(0.0, dot(norm_map_tbn, light_dir_tbn);

But you can also do it the other way around. The normal vector from the normal map can be transformed to view space and the light calculation can be done in view space. For this the normal vector has to be transformed from by the tangent space matrix. This would avoid the expensive inverse operation. Replace the normal vector by tbn[2](Z-axis of the tangent space matrix), to visualize the effect without influences of the normal-map.

vec3 norm_map_viewsp  = tbn * norm_map_tbn;
float diffuse         = max(0.0, dot(norm_map_viewsp, light_dir_viewsp);


Note, a Lambertian diffuse light model is commonly calculated like this:

f_lambertian = max( 0.0, dot(N, L ))


If the light source is very close tho the object, then the angle between the light vector and the normal vector of the face may differ bewteen the uper left edge and the lower right edge in a large scale. This will cause in a darker and brighter areas, because the lambertian diffuse light model depends linearly on the cosine of theis angle.
Replace the normal vector by tbn[2](Z-axis of the tangent space matrix), to visualize the effect without influences of the normal-map.


Note, you have to ensure, that all the normal vectors are directed in the same direction and tangents are directed in the same direction. See the example, that demonstrates, what happens if one of the normal vectors is inverted:

glArrayType = typeof Float32Array !="undefined" ? Float32Array : ( typeof WebGLFloatArray != "undefined" ? WebGLFloatArray : Array );

function IdentityMat44() {
  var m = new glArrayType(16);
  m[0]  = 1; m[1]  = 0; m[2]  = 0; m[3]  = 0;
  m[4]  = 0; m[5]  = 1; m[6]  = 0; m[7]  = 0;
  m[8]  = 0; m[9]  = 0; m[10] = 1; m[11] = 0;
  m[12] = 0; m[13] = 0; m[14] = 0; m[15] = 1;
  return m;
};

function RotateAxis(matA, angRad, axis) {
    var aMap = [ [1, 2], [2, 0], [0, 1] ];
    var a0 = aMap[axis][0], a1 = aMap[axis][1]; 
    var sinAng = Math.sin(angRad), cosAng = Math.cos(angRad);
    var matB = new glArrayType(16);
    for ( var i = 0; i < 16; ++ i ) matB[i] = matA[i];
    for ( var i = 0; i < 3; ++ i ) {
        matB[a0*4+i] = matA[a0*4+i] * cosAng + matA[a1*4+i] * sinAng;
        matB[a1*4+i] = matA[a0*4+i] * -sinAng + matA[a1*4+i] * cosAng;
    }
    return matB;
}

function Cross( a, b ) { return [ a[1] * b[2] - a[2] * b[1], a[2] * b[0] - a[0] * b[2], a[0] * b[1] - a[1] * b[0], 0.0 ]; }
function Dot( a, b ) { return a[0]*b[0] + a[1]*b[1] + a[2]*b[2]; }
function Normalize( v ) {
    var len = Math.sqrt( v[0] * v[0] + v[1] * v[1] + v[2] * v[2] );
    return [ v[0] / len, v[1] / len, v[2] / len ];
}

var Camera = {};
Camera.create = function() {
    this.pos    = [0, 1.5, 0.0];
    this.target = [0, 0, 0];
    this.up     = [0, 0, 1];
    this.fov_y  = 90;
    this.vp     = [800, 600];
    this.near   = 0.5;
    this.far    = 100.0;
}
Camera.Perspective = function() {
    var fn = this.far + this.near;
    var f_n = this.far - this.near;
    var r = this.vp[0] / this.vp[1];
    var t = 1 / Math.tan( Math.PI * this.fov_y / 360 );
    var m = IdentityMat44();
    m[0]  = t/r; m[1]  = 0; m[2]  =  0;                              m[3]  = 0;
    m[4]  = 0;   m[5]  = t; m[6]  =  0;                              m[7]  = 0;
    m[8]  = 0;   m[9]  = 0; m[10] = -fn / f_n;                       m[11] = -1;
    m[12] = 0;   m[13] = 0; m[14] = -2 * this.far * this.near / f_n; m[15] =  0;
    return m;
}
Camera.LookAt = function() {
    var mz = Normalize( [ this.pos[0]-this.target[0], this.pos[1]-this.target[1], this.pos[2]-this.target[2] ] );
    var mx = Normalize( Cross( this.up, mz ) );
    var my = Normalize( Cross( mz, mx ) );
    var tx = Dot( mx, this.pos );
    var ty = Dot( my, this.pos );
    var tz = Dot( [-mz[0], -mz[1], -mz[2]], this.pos ); 
    var m = IdentityMat44();
    m[0]  = mx[0]; m[1]  = my[0]; m[2]  = mz[0]; m[3]  = 0;
    m[4]  = mx[1]; m[5]  = my[1]; m[6]  = mz[1]; m[7]  = 0;
    m[8]  = mx[2]; m[9]  = my[2]; m[10] = mz[2]; m[11] = 0;
    m[12] = tx;    m[13] = ty;    m[14] = tz;    m[15] = 1; 
    return m;
} 

var ShaderProgram = {};
ShaderProgram.Create = function( shaderList ) {
    var shaderObjs = [];
    for ( var i_sh = 0; i_sh < shaderList.length; ++ i_sh ) {
        var shderObj = this.CompileShader( shaderList[i_sh].source, shaderList[i_sh].stage );
        if ( shderObj == 0 )
            return 0;
        shaderObjs.push( shderObj );
    }
    var progObj = this.LinkProgram( shaderObjs )
    if ( progObj != 0 ) {
        progObj.attribIndex = {};
        var noOfAttributes = gl.getProgramParameter( progObj, gl.ACTIVE_ATTRIBUTES );
        for ( var i_n = 0; i_n < noOfAttributes; ++ i_n ) {
            var name = gl.getActiveAttrib( progObj, i_n ).name;
            progObj.attribIndex[name] = gl.getAttribLocation( progObj, name );
        }
        progObj.unifomLocation = {};
        var noOfUniforms = gl.getProgramParameter( progObj, gl.ACTIVE_UNIFORMS );
        for ( var i_n = 0; i_n < noOfUniforms; ++ i_n ) {
            var name = gl.getActiveUniform( progObj, i_n ).name;
            progObj.unifomLocation[name] = gl.getUniformLocation( progObj, name );
        }
    }
    return progObj;
}
ShaderProgram.AttributeIndex = function( progObj, name ) { return progObj.attribIndex[name]; } 
ShaderProgram.UniformLocation = function( progObj, name ) { return progObj.unifomLocation[name]; } 
ShaderProgram.Use = function( progObj ) { gl.useProgram( progObj ); } 
ShaderProgram.SetUniformI1  = function( progObj, name, val ) { if(progObj.unifomLocation[name]) gl.uniform1i( progObj.unifomLocation[name], val ); }
ShaderProgram.SetUniformF1  = function( progObj, name, val ) { if(progObj.unifomLocation[name]) gl.uniform1f( progObj.unifomLocation[name], val ); }
ShaderProgram.SetUniformF2  = function( progObj, name, arr ) { if(progObj.unifomLocation[name]) gl.uniform2fv( progObj.unifomLocation[name], arr ); }
ShaderProgram.SetUniformF3  = function( progObj, name, arr ) { if(progObj.unifomLocation[name]) gl.uniform3fv( progObj.unifomLocation[name], arr ); }
ShaderProgram.SetUniformF4  = function( progObj, name, arr ) { if(progObj.unifomLocation[name]) gl.uniform4fv( progObj.unifomLocation[name], arr ); }
ShaderProgram.SetUniformM33 = function( progObj, name, mat ) { if(progObj.unifomLocation[name]) gl.uniformMatrix3fv( progObj.unifomLocation[name], false, mat ); }
ShaderProgram.SetUniformM44 = function( progObj, name, mat ) { if(progObj.unifomLocation[name]) gl.uniformMatrix4fv( progObj.unifomLocation[name], false, mat ); }
ShaderProgram.CompileShader = function( source, shaderStage ) {
    var shaderScript = document.getElementById(source);
    if (shaderScript) {
      source = "";
      var node = shaderScript.firstChild;
      while (node) {
        if (node.nodeType == 3) source += node.textContent;
        node = node.nextSibling;
      }
    }
    var shaderObj = gl.createShader( shaderStage );
    gl.shaderSource( shaderObj, source );
    gl.compileShader( shaderObj );
    var status = gl.getShaderParameter( shaderObj, gl.COMPILE_STATUS );
    if ( !status ) alert(gl.getShaderInfoLog(shaderObj));
    return status ? shaderObj : 0;
} 
ShaderProgram.LinkProgram = function( shaderObjs ) {
    var prog = gl.createProgram();
    for ( var i_sh = 0; i_sh < shaderObjs.length; ++ i_sh )
        gl.attachShader( prog, shaderObjs[i_sh] );
    gl.linkProgram( prog );
    status = gl.getProgramParameter( prog, gl.LINK_STATUS );
    if ( !status ) alert("Could not initialise shaders");
    gl.useProgram( null );
    return status ? prog : 0;
}

var VertexBuffer = {};
VertexBuffer.Create = function( attributes, indices ) {
    var buffer = {};
    buffer.buf = [];
    buffer.attr = []
    for ( var i = 0; i < attributes.length; ++ i ) {
        buffer.buf.push( gl.createBuffer() );
        buffer.attr.push( { size : attributes[i].attrSize, loc : attributes[i].attrLoc } );
        gl.bindBuffer( gl.ARRAY_BUFFER, buffer.buf[i] );
        gl.bufferData( gl.ARRAY_BUFFER, new Float32Array( attributes[i].data ), gl.STATIC_DRAW );
    }
    buffer.inx = gl.createBuffer();
    gl.bindBuffer( gl.ELEMENT_ARRAY_BUFFER, buffer.inx );
    gl.bufferData( gl.ELEMENT_ARRAY_BUFFER, new Uint16Array( indices ), gl.STATIC_DRAW );
    buffer.inxLen = indices.length;
    gl.bindBuffer( gl.ARRAY_BUFFER, null );
    gl.bindBuffer( gl.ELEMENT_ARRAY_BUFFER, null );
    return buffer;
}
VertexBuffer.Draw = function( bufObj ) {
  for ( var i = 0; i < bufObj.buf.length; ++ i ) {
        gl.bindBuffer( gl.ARRAY_BUFFER, bufObj.buf[i] );
        gl.vertexAttribPointer( bufObj.attr[i].loc, bufObj.attr[i].size, gl.FLOAT, false, 0, 0 );
        gl.enableVertexAttribArray( bufObj.attr[i].loc );
    }
    gl.bindBuffer( gl.ELEMENT_ARRAY_BUFFER, bufObj.inx );
    gl.drawElements( gl.TRIANGLES, bufObj.inxLen, gl.UNSIGNED_SHORT, 0 );
    for ( var i = 0; i < bufObj.buf.length; ++ i )
       gl.disableVertexAttribArray( bufObj.attr[i].loc );
    gl.bindBuffer( gl.ARRAY_BUFFER, null );
    gl.bindBuffer( gl.ELEMENT_ARRAY_BUFFER, null );
}
        
function drawScene(){

    var canvas = document.getElementById( "camera-canvas" );
    Camera.create();
    Camera.vp = [canvas.width, canvas.height];
    var currentTime = Date.now();   
    var deltaMS = currentTime - startTime;

    var texUnit = 0;
    gl.activeTexture( gl.TEXTURE0 + texUnit );
    gl.bindTexture( gl.TEXTURE_2D, textureObj );

    var mapUnit = 1;
    gl.activeTexture( gl.TEXTURE0 + mapUnit );
    gl.bindTexture( gl.TEXTURE_2D, normalMapObj );

    gl.viewport( 0, 0, canvas.width, canvas.height );
    gl.enable( gl.DEPTH_TEST );
    gl.clearColor( 0.0, 0.0, 0.0, 1.0 );
    gl.clear( gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT );
    
    // set up draw shader
    ShaderProgram.Use( progDraw );
    ShaderProgram.SetUniformM44( progDraw, "u_projectionMat44", Camera.Perspective() );
    ShaderProgram.SetUniformM44( progDraw, "u_viewMat44", Camera.LookAt() );
    var modelMat = IdentityMat44()
    modelMat = RotateAxis( modelMat, 105.0 * Math.PI / 180.0, 0 );    
    ShaderProgram.SetUniformM44( progDraw, "u_modelMat44", modelMat );
    ShaderProgram.SetUniformI1( progDraw, "tex", texUnit );
    ShaderProgram.SetUniformI1( progDraw, "normal_map", mapUnit );
    
    // draw scene
    var chg_tang = document.getElementById( "change_tangent" ).checked;
    if ( chg_tang )
      VertexBuffer.Draw( bufPlane2 );
    else
      VertexBuffer.Draw( bufPlane );
}

var Texture = {};
Texture.HandleLoadedTexture2D = function( image, texture, flipY ) {
    gl.activeTexture( gl.TEXTURE0 );
    gl.bindTexture( gl.TEXTURE_2D, texture );
    gl.texImage2D( gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, image );
    if ( flipY != undefined && flipY == true )
      gl.pixelStorei( gl.UNPACK_FLIP_Y_WEBGL, true );
    gl.texParameteri( gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR );
    gl.texParameteri( gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR );
    gl.texParameteri( gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.REPEAT );
   gl.texParameteri( gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.REPEAT );
    gl.bindTexture( gl.TEXTURE_2D, null );
    return texture;
}
Texture.LoadTexture2D = function( name ) {
    var texture = gl.createTexture();
    texture.image = new Image();
    texture.image.setAttribute('crossorigin', 'anonymous');
    texture.image.onload = function () {
        Texture.HandleLoadedTexture2D( texture.image, texture, true )
    }
    texture.image.src = name;
    return texture;
}

var gl;
var progDraw;
var bufCube = {};
var bufTorus = {};
function sceneStart() {

    var canvas = document.getElementById( "camera-canvas");
    var vp = [canvas.width, canvas.height];
    gl = canvas.getContext( "experimental-webgl" );
    if ( !gl )
      return;

    progDraw = ShaderProgram.Create( 
      [ { source : "draw-shader-vs", stage : gl.VERTEX_SHADER },
        { source : "draw-shader-fs", stage : gl.FRAGMENT_SHADER }
      ] );
    progDraw.inPos = gl.getAttribLocation( progDraw, "pos" );
    progDraw.inNV  = gl.getAttribLocation( progDraw, "norm" );
    progDraw.inTV  = gl.getAttribLocation( progDraw, "tangent" );
    progDraw.inTex = gl.getAttribLocation( progDraw, "tex_pos" );
    if ( progDraw == 0 )
        return;

    var planPosData = [-1.0, -1.0, 0.0,     1.0, -1.0, 0.0,     1.0,  1.0,  0.0,    -1.0, 1.0, 0.0];
    var planNVData  = [ 0.0,  0.0, 1.0,     0.0,  0.0, 1.0,     0.0,  0.0,  1.0,     0.0, 0.0, 1.0];
    var planTVData  = [ 1.0,  0.0, 0.0,     1.0,  0.0, 0.0,     1.0,  0.0,  0.0,     1.0, 0.0, 0.0];
    var planTexData = [ 0.0,  0.0,          0.0,  1.0,          1.0,  1.0,           1.0, 0.0     ];
    var planInxData = [0,1,2,0,2,3];
    bufPlane = VertexBuffer.Create(
    [ { data : planPosData, attrSize : 3, attrLoc : progDraw.inPos },
      { data : planNVData,  attrSize : 3, attrLoc : progDraw.inNV },
      { data : planTVData,  attrSize : 3, attrLoc : progDraw.inTV },
      { data : planTexData, attrSize : 2, attrLoc : progDraw.inTex } ],
      planInxData );

    var planPosData2 = [-1.0, -1.0, 0.0,     1.0, -1.0, 0.0,     1.0,  1.0,  0.0,    -1.0, 1.0, 0.0];
    var planNVData2  = [ 0.0,  0.0, 1.0,     0.0,  0.0, 1.0,     0.0,  0.0,  -1.0,     0.0, 0.0, 1.0];
    //var planTVData2  = [ 1.0,  0.0, 0.0,     1.0,  0.0, 0.0,     1.0,  0.0,  0.0,     1.0, 0.0, 0.0];
    var planTVData2  = [ 1.0,  0.0, 0.0,     1.0,  0.0, 0.0,     1.0,  0.0,  0.0,     1.0, 0.0, 0.0];
    var planTexData2 = [ 0.0,  0.0,          0.0,  1.0,          1.0,  1.0,           1.0, 0.0     ];
    var planInxData2 = [0,1,2,0,2,3];
    bufPlane2 = VertexBuffer.Create(
    [ { data : planPosData2, attrSize : 3, attrLoc : progDraw.inPos },
      { data : planNVData2,  attrSize : 3, attrLoc : progDraw.inNV },
      { data : planTVData2,  attrSize : 3, attrLoc : progDraw.inTV },
      { data : planTexData2, attrSize : 2, attrLoc : progDraw.inTex } ],
      planInxData2 );  

    textureObj = Texture.LoadTexture2D( "https://raw.githubusercontent.com/Rabbid76/graphics-snippets/master/resource/texture/Gominolas.png" );
    normalMapObj = Texture.LoadTexture2D( "https://raw.githubusercontent.com/Rabbid76/graphics-snippets/master/resource/texture/GominolasBump.png" );  

    startTime = Date.now();
    setInterval(drawScene, 50);
}
<script id="draw-shader-vs" type="x-shader/x-vertex">
precision mediump float;

attribute vec4 pos;
attribute vec3 norm;
attribute vec3 tangent;
attribute vec2 tex_pos;

varying vec2 v_tex_pos;
varying vec3 v_light_dir_tansp;

// ADDED -----
varying vec3 v_tangent_vsp;
varying vec3 v_binoraml_vsp;
varying vec3 v_norm_vsp;
// ADDED -----

//uniform mat4 view;
//uniform mat4 view_projection;
// ADDED -----
uniform mat4 u_projectionMat44;
uniform mat4 u_viewMat44;
uniform mat4 u_modelMat44;
// ADDED -----

//const vec3 light_pos = vec3(50.0, 25.0, 50.0);

// ADDED -----
const vec3 light_pos = vec3(0.0, 0.0, 0.25);
// ADDED -----

// ADDED -----
mat3 transpose(mat3 m)
{
    mat3 tm = m;
    for(int i = 0; i < 3; ++i)
    {
       for(int j = 0; j < 3; ++j)
           tm[j][i]=m[i][j];
    }
    return tm;
}
// ADDED -----

void main() {
    // ADDED -----
    mat4 view = u_viewMat44 * u_modelMat44; 
    mat4 view_projection = u_projectionMat44 * view;
    // ADDED -----

 vec3 norm_viewsp = mat3(view) * normalize(norm);
 vec3 tangent_viewsp = mat3(view) * normalize(tangent);
 vec3 bitangent_viewsp = cross(tangent_viewsp, norm_viewsp);
 mat3 tbn = transpose(mat3(tangent_viewsp, bitangent_viewsp, norm_viewsp));
 vec3 light_dir = light_pos - pos.xyz;
 vec3 light_dir_viewsp = mat3(view) * light_dir;
 
 v_tex_pos = tex_pos;
 v_light_dir_tansp = tbn * light_dir_viewsp;
 
 gl_Position = view_projection * pos;

    // ADDED -----
    v_tangent_vsp = tangent_viewsp;
    v_binoraml_vsp = bitangent_viewsp;
    v_norm_vsp = norm_viewsp;
    // ADDED -----
}
</script>

<script id="draw-shader-fs" type="x-shader/x-fragment">
precision mediump float;

varying vec2 v_tex_pos;
varying vec3 v_light_dir_tansp;

// ADDED -----
varying vec3 v_tangent_vsp;
varying vec3 v_binoraml_vsp;
varying vec3 v_norm_vsp;
// ADDED -----

uniform sampler2D tex;
uniform sampler2D normal_map;

void main() {
 vec3 norm = texture2D(normal_map, v_tex_pos).rgb * 2.0 - 1.0;
 float diffuse = dot(normalize(norm), normalize(v_light_dir_tansp));
 diffuse = clamp(diffuse, 0.0, 1.0);

    gl_FragColor = texture2D(tex, v_tex_pos) * diffuse;
 
    // ADDED -----
 vec4 texColor = texture2D(tex, v_tex_pos);
    gl_FragColor = vec4( texColor.rgb * diffuse * 2.0, 1.0 );
    //gl_FragColor = vec4(abs(v_tangent_vsp), 1.0);
    //gl_FragColor = vec4(abs(v_binoraml_vsp), 1.0);
    //gl_FragColor = vec4(abs(v_norm_vsp), 1.0);
    //gl_FragColor = vec4(texture2D(tex, v_tex_pos).rgb, 1.0);
    //gl_FragColor = vec4(texture2D(normal_map, v_tex_pos).rgb, 1.0);
    // ADDED -----
}
</script>

<body onload="sceneStart();">
    <div style="margin-left: 520px;">
        <div style="float: right; width: 100%; background-color: #CCF;">
            <form name="inputs">
                <table>
                    <tr> <td> change tangent </td>
                        <td> <input type="checkbox" id="change_tangent"/>  
                    </td> </tr>
                </table>
            </form>
        </div>
        <div style="float: right; width: 520px; margin-left: -520px;">
            <canvas id="camera-canvas" style="border: none;" width="512" height="512"></canvas>
        </div>
        <div style="clear: both;"></div>
    </div>
</body>
Rabbid76
  • 202,892
  • 27
  • 131
  • 174
  • Since the normal and the tangents are orthogonal the transposed matrix should be the same as the inverse. I'm still getting the darker and brighter corner. – Sogomn Oct 18 '17 at 21:24
  • 1
    That's why I'm so confused. I just can't find the problem, everything seems right. These are normal, tangent and bitangent in viewspace: https://imgur.com/a/YFt1k . It looks like they are perpendicular so the matrix should be right aswell. Also the result is the same if I do the calculations in view space instead of tangent space. Thank you for helping me with this issue! – Sogomn Oct 19 '17 at 18:38
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/157096/discussion-between-sogomn-and-rabbid76). – Sogomn Oct 19 '17 at 18:51
  • But that's not how it looks like in any of the guides I followed. Why is one side so dark and one so bright? Even if I change the height of the light it makes no difference. – Sogomn Oct 22 '17 at 12:38
  • Yes, it is. The object is 100 units in the x and z direction and the light is at 50 x and 50 z. – Sogomn Oct 22 '17 at 12:42
  • Yes, inverting them only flips the light (i.e. the dark spot is the bright one and other way around). – Sogomn Oct 22 '17 at 20:34
  • The lighting works when using the vertex normals but in tangent space. So the TBN matrix is definitely correct. That only leaves the sampled normal from the normal map as a possible error factor but that's not the case either. I'm clueless. – Sogomn Oct 22 '17 at 22:09
1

IMO there is no need for matrix voodoo:

v_light_dir_tansp = vec3(
  dot(tangent_viewsp, light_dir_viewsp),
  dot(bitangent_viewsp, light_dir_viewsp),
  dot(norm_viewsp, light_dir_viewsp)
);

And by the way, your v_light_dir_tansp won't be accurate due to being interpolated.

  • That's exactly how matrix multiplication works. Calculating the light direction in the fragment shader doesn't make a difference either. – Sogomn Oct 27 '17 at 09:41
  • A multiplication of matrix and a vector is nothing else than to calculate dot-products. But it is less code. – Rabbid76 Nov 22 '19 at 05:08