Tangent of a normal in an ofPrimitive

I’m trying to add normal mapping from a texture to a sphere. I’m following these resources:
http://www.opengl-tutorial.org/intermediate-tutorials/tutorial-13-normal-mapping/

http://fabiensanglard.net/bumpMapping/index.php

http://learnopengl.com/#!Advanced-Lighting/Normal-Mapping

http://www.gamasutra.com/blogs/RobertBasler/20131122/205462/Three_Normal_Mapping_Techniques_Explained_For_the_Mathematically_Uninclined.php?print=1

I’m wondering what is the easiest way to get the tangent of a normal in OF, is there already something prebuilt? should I set an attribute for each vertex containing the tangent?

Interesting, it is possible to calculate the tangent in the shader
http://www.geeks3d.com/20130122/normal-mapping-without-precomputed-tangent-space-vectors/

Hi,
there is
ofVec3f ofVec3f::getPerpendicular (const ofVec3f& vec );
the argument you pass is a ofVec3f that’s also perpendicular, otherwise you wont have a single perpendicular vector.
you can pass a vector pointing to the camera or something like ofVec3f(1,0,0), but you need to make sure that the vector over which you are calculating the perpendicular vector is not equal to the one used in the argument.

ofVec3f normal;
ofVec3f ref (1,0,0);
ofVec3f tangent;
if(normal == ref){
tangent = normal.getPerpendicular(ofVec3f(0,1,0));// argument different to ref
}else{
tangent = normal.getPerpendicular(ref);
}

}

This is doing mostly the same as the last link you provide, only that’s been done in the CPU. Hence, doing such in the GPU would be much more faster.

Cheers

Hello @roymacdonald, thank you for your answer. I’m doing it in the shader, because it is faster and I already have a lot of things going on that takes resources. The only thing that is not working now, or at least I think is not working, is the light position.
I’m passing the light in the app as a glm::vec3.

// in App.h
// ofxVec3Slider lightPos;
updateRender.setUniform3f("lightPos", lightPos);

And this is my vertex shader, done following this tutorial http://learnopengl.com/#!Advanced-Lighting/Normal-Mapping and this file where it looks like you have collaborated too https://github.com/patriciogonzalezvivo/ofxFX/blob/master/src/operations/ofxNormals.h.

#version 150

uniform mat4 modelViewProjectionMatrix;
uniform mat4 modelMatrix;

//used in the normal map
float xOffset = 1.0;
float yOffset = 1.0;

uniform sampler2DRect tex0;
uniform float displaceAmount;

in vec4 position;
in vec2 texcoord;
in vec3 normal;

out vec2 vTexCoord;
out vec4 vPosition;
out vec3 vNormal;

// normally, when doing normal mapping, the normal map is given throug a texture.
// Here it is calculated on the fly. See normals.frag, normals.vert, and try to
// apply the shaderNormalMap to debug it.
// this calculations comes from https://github.com/patriciogonzalezvivo/ofxFX/blob/master/src/operations/ofxNormals.h
vec3 vFromNormalMap(){
    float sCoord		= texcoord.s;
    float tCoord		= texcoord.t;

    float center		= texture(tex0, vec2( sCoord, tCoord ) ).r;
    float topLeft	= texture(tex0, vec2(sCoord - xOffset	, tCoord - yOffset	) ).r;
    float left		= texture(tex0, vec2(sCoord - xOffset	, tCoord			) ).r;
    float bottomLeft	= texture(tex0, vec2(sCoord - xOffset	, tCoord + yOffset	) ).r;
    float top		= texture(tex0, vec2(sCoord			, tCoord - yOffset	) ).r;
    float bottom		= texture(tex0, vec2(sCoord			, tCoord + yOffset	) ).r;
    float topRight	= texture(tex0, vec2(sCoord + xOffset	, tCoord - yOffset	) ).r;
    float right		= texture(tex0, vec2(sCoord + xOffset	, tCoord			) ).r;
    float bottomRight= texture(tex0, vec2(sCoord + xOffset	, tCoord + yOffset	) ).r;

    float dX = topRight + 2.0 * right + bottomRight - topLeft - 2.0 * left - bottomLeft;
    float dY = bottomLeft + 2.0 * bottom + bottomRight - topLeft - 2.0 * top - topRight;

    vec3 N = normalize( vec3( dX, dY, 0.01) );

    // this is needed to transform a vector that goes from 0 to 1 to a vector that
    // goes from -1 to 1. Normals vector, goes from -1 to 1, not from 0 to 1
    // It is like to say
    // vec3 N = N * 0.5 + 0.5; // transforms from [-1,1] to [0,1]
    N *= 0.5;
    N += 0.5;
    return N;
}

void main() {
    //all this part comes from http://learnopengl.com/#!Advanced-Lighting/Normal-Mapping
    vTexCoord = texcoord;
    // Now we go in Tangent Space
    // The Tangent Space is the coordinate space that the normals in a normal map are in.
    // this is an approximative method to obtain the tangent from the normal.
    // it comes from http://www.geeks3d.com/20130122/normal-mapping-without-precomputed-tangent-space-vectors/
    vec3 tangent;
    vec3 bitangent;
    vec3 c1 = cross(normal, vec3(0.0, 0.0, 1.0));
    vec3 c2 = cross(normal, vec3(0.0, 1.0, 0.0));
    if (length(c1) > length(c2))
        tangent = c1;
    else
        tangent = c2;

    tangent     = normalize(tangent);
    bitangent   = normalize(cross(normal, tangent));

    // now we need what it is called a TBN matrix.
    // A TBN matrix converts normals from the normal map (in Tangent Space) to Model Space.
    // A TBN matrix looks like this:
    //    tangent = normalize(tangent);
    //    bitangent = normalize(cross(normal, tangent));
    //    mat3 tbn = mat3( tangent, bitangent, normal );
    // but what we need is to convert the tangen space to world space, in a way that we
    // we can calculate the light direction
    vec3 T = normalize(vec3(modelMatrix * vec4(tangent,   0.0)));
    vec3 B = normalize(vec3(modelMatrix * vec4(bitangent, 0.0)));
    vec3 N = normalize(vec3(modelMatrix * vec4(normal,    0.0)));
    mat3 TBN = mat3(T, B, N);

    vec3 normalFromNormalMap = vFromNormalMap();
    vNormal = normalize(TBN * normalFromNormalMap);

    vPosition = modelViewProjectionMatrix * vec4( position.xyz, 1.0 ) ;
    gl_Position = vPosition;
}

The modelMatrix comes from a uniform:

//in the draw method
ofMatrix4x4 modelMatrix = ofMatrix4x4::newIdentityMatrix();
updateRender.setUniformMatrix4f("modelMatrix", modelMatrix);

This is the fragment shader:

#version 150

uniform sampler2DRect tex0;
uniform float discardRed;
uniform vec3 lightPos;

in vec2 vTexCoord;
in vec4 vPosition;
in vec3 vNormal;

out vec4 vFragColor;

void main() {
    vec4 texColor = texture(tex0, vTexCoord);
    vec3 lightDirection = normalize(vPosition.xyz - lightPos);
    float dProd = max(0.3, dot(vNormal, lightDirection));
    vec4 colorWithLight = vec4( vec3( dProd ) * vec3( texColor ), 1.0 );
    vFragColor = colorWithLight;
}

But the resulting image looks like this:

I do not understant why the face “A” looks like if it is not receiving the light as the face “B” is doing. In the update method, I’m updating the light position used to debug it.

void ofApp::update(){
    light.setPosition(lightPos);
}

The whole code is available here https://github.com/edap/reactionDiffusion/tree/master/reactionDiffusionTo3D

And this error is even more evident using a sphere

If I remove the TBN matrix from the vertex shader, and I calculate the normal position simply like:

vNormal = normalize(vec3(modelMatrix * vec4(normalFromNormalMap, 1.0)).xyz);

The bump looks correct, but the lighting is wrong, this side should be dark

Hi,
assuming that the vertext shader is ok, there is a subtle error in the frag shader.
I think the problem is in the dot product.
the dot product will give you the cosine of the angle between the two vectors, this is if the two vectors are equal, thus the angle between these is 0, the result will be 1, when the angle is 90 it becomes 0 and when it is 180 it is -1.
so, if the face of your cube is facing directly towards the light, its normal should be pointing to the light, hence that normal is pointing in the opposite direction as the light direction vector. so, the dot product will be -1. I guess that you want it to be 1. :slight_smile:
hope it help.
best

Hello @roymacdonald, I think I did not get where did you spot the error. This is the formula of the lambert light, I’ve chose this because it is the easiest to implement

vec3 lightDirection = normalize(vPosition.xyz - lightPos);
float dProd = max(0.3, dot(vNormal, lightDirection));

I’ve for a moment disabled the vNormal calculation in the vertex shader, that one that uses the TBN matrix, and I’ve simply passed as normal value the normal that comes from openFrameoworks. But also in this case, the light is wrong!

I’m passing the lightPos uniform as it is, without any matrix transformation. In the ofApp.cpp is initialized as follow:

gui.add(lightPos.setup("lightPosition",
                       ofVec3f(ofGetWidth()*.5, ofGetHeight()*.5, 100),
                       ofVec3f(0, 0, -100),
                       ofVec3f(ofGetWidth(), ofGetHeight(),200)));

Maybe this is the problem?

Hi,
where did you took the lambert shading algorithm from?
so think it this way:
say we have a vertex on the sphere surface, that faces the light directly.
so, lets give this some values:
light pos: 0,0,0
vertex pos: 1,0,0
vertex normal: -1,0,0 (this points towards the light)

so, if you put that into your code it would look something like
lightDirection = (1,0,0) - (0,0,0) = (1,0,0)
dot((-1,0,0), (1,0,0)) = -1
dProd = -1

so anything you end up multiplying by dProd will become negative, hence it will be black. in this case dProd should be 1, because we want to have the color with no attenuation as it is being fully lit.

This same thing explains why you get the back of the object lit up.

so, solution:

vec3 lightDirection = normalize(lightPos -vPosition.xyz );
float dProd = max(0.3, dot(vNormal, lightDirection));

As for your last doubt, the light position should not be multiplied by any matrix as I assume that the vertices we are working with are in the same coordinate space (world space)

Many thanks @roymacdonald, I’ve should have noticed that they were flipped. Now the sphere is correctly illuminated, I’ve tried without texture. When adding the texture with the normal and the Tangent Bitangen Normal matrix, it is still wrong.

This looks a bit like the error here http://www.geeks3d.com/20130122/normal-mapping-without-precomputed-tangent-space-vectors/ where the article says “a kind of pattern due to the imprecision of that method”. Although this is more than a pattern.

Could be that calculating the tangent for each vertex in the cpu can give me better results?

Hey,
Good to know that it worked.
But I would need to get a bit deeper into that tangent matrix stuff in order to understand it. unfortunately I’m super short of time right now and I can not do so any time soon.
cheers

Thank you for your help, I think I’ve find the error:

The normal vector that i was obtaining from the normal map goes from 0 to 1.
When I’ve to do the light calculation, the normal vector should go from -1 to 1.
I’ve change this:

vec3 normalFromNormalMap = normalize(vFromNormalMap());
vNormal = normalize(TBN * normalFromNormalMap);

to this:

vec3 normalFromNormalMap = normalize(vFromNormalMap() * 2.0 -1.0);
vNormal = normalize(TBN * normalFromNormalMap);

And now it looks better. There is still something fishy going on, as the brighter side of the sphere it is not perfectly aligned with the light, and the bump looks a bit more scattering, but it is better than before.

Just in case someone will find this thread. Another problem was in the normal declaration in the vertex shader. This was wrong

in vec3 normal;

Because normals are passed as vec4, this is correct:

in vec4 normal;

Now everything works