3
$\begingroup$

I've been trying to figure this out for a few days now, but I just can't get it right. I've read multiple tutorials and possible explanations, I even watched a java tutorial without finding the solution. This is what I do:

First I build the tangent values to pass as attributes:

//this parsing has not been updated, unlike the shaders, correct parsing in the answer below
float f;
vec3 edge1, edge2, tangentbuf;
vec2 deltaUV1, deltaUV2;
//for each face, x=vert,y=uv, z=normal
for (size_t i = 0; i < totalrefs.size(); i += 9)
{
    edge1 = verticeref[totalrefs[i + 3] - 1] - verticeref[totalrefs[i] - 1];
    edge2 = verticeref[totalrefs[i + 6] - 1] - verticeref[totalrefs[i] - 1];
    deltaUV1 = texkoordref[totalrefs[i + 4] - 1] - texkoordref[totalrefs[i + 1] - 1];
    deltaUV2 = texkoordref[totalrefs[i + 7] - 1] - texkoordref[totalrefs[i + 1] - 1];
    f = 1.0f / (deltaUV1.x * deltaUV2.y - deltaUV2.x * deltaUV1.y);

    tangentbuf = f*vec3(
        (deltaUV2.y * edge1.x - deltaUV1.y * edge2.x),
        (deltaUV2.y * edge1.y - deltaUV1.y * edge2.y),
        (deltaUV2.y * edge1.z - deltaUV1.y * edge2.z));

    normalize(tangentref[tangentcount++]);
    tangentref[totalrefs[i] - 1] += vec4(normalize(tangentbuf), 1);
    tangentref[totalrefs[i + 3] - 1] += vec4(normalize(tangentbuf), 1);
    tangentref[totalrefs[i + 6] - 1] += vec4(normalize(tangentbuf), 1);
}

//for (auto &x : tangentref)x = normalize(x / x.w); //find average
for (auto &x : tangentref)x = normalize(x); //just normalize is enough?

Normalizing or dividing seems to give the same result, and I think this is correct this far. With these shaders:

vert

#version 400
layout ( location = 0 ) in vec3 vertex_position;
layout ( location = 1 ) in vec2 tex_cord;
layout ( location = 2 ) in vec3 vertex_normal;
layout ( location = 3 ) in vec3 vertex_tangent;

uniform mat4 model; //model matrix, aka model 2 world

uniform mat4 modelviewmatrix; //view*model matrix
uniform mat3 normalmatrix; // transpose/invert(view*model)
uniform mat4 priv_mat; //projection-view-model pre multiplied

uniform vec4 LightPosition; //after view*lightpos, ie viewspace

out vec3 lightdir;
out vec3 viewdir;
out vec2 fUV;
out vec3 position;

void main()
{
    gl_Position=priv_mat*vec4(vertex_position,1.0);
    fUV=tex_cord;
    position=(model*vec4(vertex_position,1.0)).xyz;
    len=length(vertex_position);

    // Transform normal and tangent to eye space
    vec3 n = normalize(normalmatrix * vertex_normal);
    vec3 t = normalize(normalmatrix * vec3(vertex_tangent));
         t = normalize(t-dot(t,n)*n);  //Gramm-Schmidt process
    vec3 bitangent = normalize( cross( n, t ) );
    mat3 tbn = transpose(mat3(t,bitangent,n)); //why transpose??

    vec3 pos=vec3(modelviewmatrix*vec4(vertex_position,1.0));

    // Transform light dir. and view dir. to tangent space
    lightdir = normalize( tbn *  (LightPosition.xyz - pos) );
    viewdir = tbn * normalize(-pos);
}

And frag

#version 400

in float len;
in vec3 lightdir; //tang light
in vec3 viewdir; //tang view
in vec2 fUV;
in vec3 position;

uniform sampler2D png_tex;
uniform sampler2D bump_map;

uniform vec4 LightPosition;
uniform vec3 LightIntensity;

uniform vec3 Kd;            // Diffuse reflectivity
uniform vec3 Ka;            // Ambient reflectivity
uniform vec3 Ks;            // Specular reflectivity
uniform float Shininess;    // Specular shininess factor

out vec4 Color;
vec3 phongModel( vec3 norm, vec3 diffr );

void main()
{
    vec4 b_normal = (255.0/128.0) * texture( bump_map, fUV )-1.0; // Lookup the normal from the normal map
    vec4 texColor = texture( png_tex, fUV ); // The color texture is used as the diffuse reflectivity
    Color = vec4( phongModel(b_normal.xyz, texColor.rgb), 1.0 );
}

vec3 phongModel( vec3 norm, vec3 diffr )
{
    vec3 r = reflect( -lightdir, norm );
    vec3 ambient = LightIntensity * Ka;
    float sDotN = max( dot(lightdir, norm), 0.0 );
    vec3 diffuse = Kd*LightIntensity * diffr * sDotN;
    vec3 spec = vec3(0.0);
    if( sDotN > 0.0 )
        spec = LightIntensity * Ks * pow( max( dot(r,viewdir), 0.0 ), Shininess );
    return ambient + diffuse + spec;
}

I only got this result: the shaders are updated as I went along, so the ones posted above work (for me anyway)

when rotated it turns dark

Which has 2 obvious errors: When I rotate the object, the shading doesn't follow the lightsource and goes from dark to full bright, and, it seems to have dents at seams where the cube-sphere wraps. The normalmapping seems to work though, but again I struggle with the shading. Is there an obvious error here?

$\endgroup$

1 Answer 1

2
$\begingroup$

Silly me, posting all of this hoping to get someone to solve it for me, I managed to find the solution tho, but perhaps more as a result of trial and error, than of actual understanding. Either way, this is the object I'm looking at now:

UV ordering, transpose and crossing in the wrong order

Looking at my current tangent calculation:

vector<vec4>tangentref(texkoordref.size()); //math UV size, w=amount for divide
vec3 tangentbuf;

float f;
vec3 edge1, edge2;
vec2 deltaUV1, deltaUV2;
for (size_t i = 0; i < totalrefs.size(); i += 9)
{
    //i=vert, i+1=uv, i+2 vn//i+3=vert2,i+4=uv2,i+5=vn2;//i+6=vert3, i+7=uv3, i+8=vn3;
    //get the tangent for each face
    edge1 = verticeref[totalrefs[i + 3] - 1] - verticeref[totalrefs[i] - 1];
    edge2 = verticeref[totalrefs[i + 6] - 1] - verticeref[totalrefs[i] - 1];
    deltaUV1 = texkoordref[totalrefs[i + 4] - 1] - texkoordref[totalrefs[i + 1] - 1];
    deltaUV2 = texkoordref[totalrefs[i + 7] - 1] - texkoordref[totalrefs[i + 1] - 1];
    f = 1.0f / (deltaUV1.x * deltaUV2.y - deltaUV2.x * deltaUV1.y);

    tangentbuf = f*vec3(
        (deltaUV2.y * edge1.x - deltaUV1.y * edge2.x),
        (deltaUV2.y * edge1.y - deltaUV1.y * edge2.y),
        (deltaUV2.y * edge1.z - deltaUV1.y * edge2.z));

    //one tangent per uv coordinate
    tangentref[totalrefs[i+1] - 1] += vec4(normalize(tangentbuf), 1);
    tangentref[totalrefs[i + 4] - 1] += vec4(normalize(tangentbuf), 1);
    tangentref[totalrefs[i + 7] - 1] += vec4(normalize(tangentbuf), 1);
}

for (auto &x : tangentref)x = normalize(x / x.w); //find average
//some tutorials say to just normalize these

I cycle through every face, and totalref[i], is here 9 integers per cycle, that each refer to +0=vert, +1=uv, and +2=vert normal. By populating the tangentref according to each of the UVs, I get a list of tangents that go along with the UV values.

I think its worth mentioning specifically, the tangents describe not the normal, or the vertex, but the direction of the uv, together with the bitangent, they make out an xy coordinate that describes the UV direction.

It only makes sense to make tangents according to UVs, and the results are not disagreeing.

Secondly, I was building the tbn matrix incorrectly.

//in the vert shader
vec3 bitangent = normalize( cross( n, t ) );

The bitangent was in the wrong order, and

mat3 tbn = transpose(mat3(t,bitangent,n)); //why transpose??

The tbn was supposed to be transpose. Transpose is cheap (apparently) so thats not a problem, but why exactly this is correct? I'm not sure.

It works (better) however, so maybe It will make sense later.

enter image description here

$\endgroup$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.