Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Normal map lighting is different depending on the direction

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.

like image 486
Sogomn Avatar asked Oct 18 '17 06:10

Sogomn


People also ask

Are normal maps RGB?

A normal map is an RGB texture, where each pixel represents the difference in direction the surface should appear to be facing, relative to its un-modified surface normal. These textures tend to have a bluey-purple tinge, because of the way the vector is stored in the RGB values.

What do the colors on a normal map do?

The RGB color channels (red, green, and blue) in a normal map correspond to the respective X, Y, and Z coordinates of surface normals. While normal maps cannot represent deep extrusions and wildly irregular geometry, they are capable of faking small indents and bumps along a flat plane.

How does a normal map work?

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.

Do normal maps affect performance?

Normal mapping is best used to add smaller details like wrinkles, bolts, and other details that need lots of triangles to model. The usage of normal mapping can depend on the type and art direction of a game. In most of our internal projects, we use normal mapping with no noticeable degradation in performance.


2 Answers

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>
like image 181
Rabbid76 Avatar answered Nov 15 '22 10:11

Rabbid76


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.

like image 25
game development germ Avatar answered Nov 15 '22 09:11

game development germ