Tangent space – bump mapping HELP!

Mr.Pink

Newcomer
I’ m implementing tangent space calculation, embedded in an .Obj format mesh reader with that header

Code:
class ObjModel
  {
    public:
                    ObjModel();
                    ~ObjModel();            
        bool  loadModelData(char *fileName);
        GLint createModelList();
        void  boundingBox();
        void  scaleDown();
        void  printData();
        GLint showTangentBasis();
        bool  computeTBN();
   
    private:
        void  decodeLine(char *fileLine);
        void  readVertexInfo(char *fileLine);
        void  readFaceInfo(char *fileLine);
        void  triangleTangentSpace(Vec3& tangent, Vec3& binormal, Vec3& normal,
                                              Vec3 v[3], Vec2 t[3]);
   
        ifstream                m_file;
        unsigned int            m_numVertices;
        unsigned int            m_numFaces;
        unsigned int            m_numTextures;
        unsigned int            m_numNormals;
        vector<Vec3>            m_vertexCoords;
        vector<Vec2>            m_textureCoords;
        vector<Vec3>            m_normalCoords;
        Vec3                    m_minVertexCoord;
        Vec3                    m_maxVertexCoord;
        bool                    m_hasNormals;
        bool                    m_hasTextures;
        vector<Vec3>            m_tangentCoords;
        vector<Vec3>            m_binormalCoords;
        bool                    m_hasTangentSpace;
   
        struct Face
        {
              vector<unsigned int> vertices;
              vector<unsigned int> texcoords;
              vector<unsigned int> normals;
        };
        vector<Face>            m_faces;
  };
I essentially followed the math exposed in book “maths for 3d game programming and computer graphics†– eric lengyel

Vec3, Vec2 are self explanatory classes

//here are the core functions
//compute tangent basis per face

Code:
void
  ObjModel::triangleTangentSpace(Vec3 &tangent, Vec3 &binormal, Vec3 &normal, Vec3 v[3], Vec2 t[3])
  {
        Vec3 Q1 = v[1] - v[0];
        Vec3 Q2 = v[2] - v[0];
        Vec2 s1t1 = t[1] - t[0];
        Vec2 s2t2 = t[2] - t[0];
   
        //matrices in column major order
        float mat2x2[4] = {s1t1.getX(), s2t2.getX(), s1t1.getY(), s2t2.getY()};
        float *imat2x2 = new float[4];
   
        imat2x2 = invert2x2(imat2x2, mat2x2);
        tangent.setX(imat2x2[0] * Q1.getX() + imat2x2[2] * Q2.getX());
        tangent.setY(imat2x2[0] * Q1.getY() + imat2x2[2] * Q2.getY());
        tangent.setZ(imat2x2[0] * Q1.getZ() + imat2x2[2] * Q2.getZ());
   
        binormal.setX(imat2x2[1] * Q1.getX() + imat2x2[3] * Q2.getX());
        binormal.setY(imat2x2[1] * Q1.getY() + imat2x2[3] * Q2.getY());
        binormal.setZ(imat2x2[1] * Q1.getZ() + imat2x2[3] * Q2.getZ());
        
        normal = Q1.cross(Q2);   
  }
   
  //tangent basis per vertex smoothing
  bool
  ObjModel::computeTBN()
  {
        if (!m_hasTextures )return false;
   
        unsigned int nn = m_vertexCoords.size(); 
        
        m_tangentCoords.resize(nn);
        m_binormalCoords.resize(nn);
        m_normalCoords.assign(m_vertexCoords.size(), Vec3());
   
        int nf = m_faces.size();
        vector<Vec3> faceTangents = vector<Vec3>(nf);
        vector<Vec3> faceBinormals = vector<Vec3>(nf);
        vector<Vec3> faceNormals = vector<Vec3>(nf);
   
        for (int i = 0; i < m_faces.size(); i++) {
              //gathering data for per face tangent
              Vec3 v[3];
              Vec2 t[3];
              v[0] = m_vertexCoords[m_faces[i].vertices[0]];                   
              v[1] = m_vertexCoords[m_faces[i].vertices[1]];                   
              v[2] = m_vertexCoords[m_faces[i].vertices[2]];
                                     
              t[0] = m_textureCoords[m_faces[i].texcoords[0]];                 
              t[1] = m_textureCoords[m_faces[i].texcoords[1]];                 
              t[2] = m_textureCoords[m_faces[i].texcoords[2]];
                               
              triangleTangentSpace(faceTangents[i], faceBinormals[i], faceNormals[i], v, t);
   
        }
        
        for (unsigned int i = 0; i < nn; i++) {
              for (int j = 0; j < m_faces.size(); j++) {
                    if (m_faces[j].vertices[0] == i || m_faces[j].vertices[1] == i || m_faces[j].vertices[2] == i) {
                          m_tangentCoords[i] += faceTangents[j];
                          m_binormalCoords[i] += faceBinormals[j];
                          m_normalCoords[i] += faceNormals[j];
                    }
              }
                          m_tangentCoords[i].normalize();
                          m_binormalCoords[i].normalize();
                          m_normalCoords[i].normalize();
        }
              
        //mio
        for (unsigned int i = 0; i < nn; i++) {
              Vec3 n = m_normalCoords[i];
              Vec3 t = m_tangentCoords[i];
              Vec3 b = m_binormalCoords[i];
   
              m_tangentCoords[i] = (t - n * n.dot(t));
              m_binormalCoords[i] = (b - n * n.dot(b) - m_tangentCoords[i] * m_tangentCoords[i].dot(b));
   
              m_tangentCoords[i].normalize();
              m_binormalCoords[i].normalize();
              m_normalCoords[i].normalize();
        }
   
        m_hasTangentSpace = true;
        return true;
  }

[font=&quot]I tryed different type of smoothing and orthonormalization like:[/font]


Code:
//alternative smoothing
for (int i = 0; i < m_faces.size(); i++) {
  for (int j = 0; j < m_faces[i].vertices.size(); j++) {
                    m_tangentCoords[m_faces[i].normals[j]] += faceTangents[i];
                    m_binormalCoords[m_faces[i].normals[j]] += faceBinormals[i];
                    m_normalCoords[m_faces[i].normals[j]] += faceNormals[i];
              }
  }
   
   //alternative orthonormalization
        for (int i = 0; i < nn; i++) {
              m_normalCoords[i].normalize();
              m_binormalCoords[i] -= m_normalCoords[i] * (m_normalCoords[i].dot(m_binormalCoords[i]));
              m_binormalCoords[i].normalize();
   
              m_tangentCoords[i] -= m_normalCoords[i] * (m_normalCoords[i].dot(m_tangentCoords[i]));
              m_tangentCoords[i].normalize();
        }

[font=&quot]All that code generate tangent space like in figure[/font]
tangentBURKE1.JPG




[font=&quot]Here are shaders for diffuse bump map lighting [/font]

[font=&quot]I tryied transforming lighting vectors in object space , eye space, but I get the same results.[/font]

Code:
vec4 lightPos = vec4(3.0, 0.0, 5.0, 1.0);
varying     vec3 g_lightVec;
  
void main()
  {
        gl_Position = ftransform();
        gl_TexCoord[0] = gl_MultiTexCoord0;
        
        mat3 TBN_Matrix = gl_NormalMatrix * mat3(gl_MultiTexCoord3.xyz, gl_MultiTexCoord4.xyz, gl_Normal);
        vec4 mv_Vertex = gl_ModelViewMatrix * gl_Vertex;
   
        vec4 light = gl_ModelViewMatrix * lightPos;
        vec3 lightVec = light.xyz - mv_Vertex.xyz;
        g_lightVec = lightVec * TBN_Matrix; 
  }
   
   /*fragment shader*/
   
  uniform sampler2D Normal;
  uniform sampler2D base_tex;
   
  varying     vec3 g_lightVec;
   
  void main()
  {   
        vec3 lightVec = normalize(g_lightVec);
   
        vec4 color_base = texture2D(base_tex ,gl_TexCoord[0].st);
        vec3 bump = texture2D(Normal, gl_TexCoord[0].st).rgb * 2.0 - 1.0;
        bump = normalize(bump);
   
        float diffuse = clamp(dot(lightVec, bump), 0.0, 1.0);
   
        gl_FragColor = /*color_base */ diffuse;                    
        gl_FragColor.a = 1.0;                            
  }


here is executable
http://demasprojects.altervista.org/files/Lighting.rar
it is driving me insane!!! please help me to solve this problem!!!
 
Hmmm..
Looking at http://allyoucanupload.webshots.com/v/2000889194408629433 there does seem to be definite problem where several triangle meet at the same vertex. If the bumps are "small" then the shading should all match up.

I presume you are interpolating the Tangent/Bi-tangent across the polygons?

EDIT: Actually, looking at the cheek where 8 tris meet, it looks like 1/2 the triangles obtain one calculation result and the other 3 have another result.
 
Hmmm..
Looking at http://allyoucanupload.webshots.com/v/2000889194408629433 there does seem to be definite problem where several triangle meet at the same vertex. If the bumps are "small" then the shading should all match up.

I presume you are interpolating the Tangent/Bi-tangent across the polygons?
yes i interpolate tangent vectors per vertex using

Code:
for (unsigned int i = 0; i < nn; i++) {
              for (int j = 0; j < m_faces.size(); j++) {
                    if (m_faces[j].vertices[0] == i || m_faces[j].vertices[1] == i || m_faces[j].vertices[2] == i) {
                          m_tangentCoords[i] += faceTangents[j];
                          m_binormalCoords[i] += faceBinormals[j];
                          m_normalCoords[i] += faceNormals[j];
                    }
              }
                          m_tangentCoords[i].normalize();
                          m_binormalCoords[i].normalize();
                          m_normalCoords[i].normalize();
        }


EDIT: Actually, looking at the cheek where 8 tris meet, it looks like 1/2 the triangles obtain one calculation result and the other 3 have another result.
yes, here they are emphasizes
 
Curious about this...
Code:
       mat3 TBN_Matrix = gl_NormalMatrix * mat3(gl_MultiTexCoord3.xyz, gl_MultiTexCoord4.xyz, gl_Normal);
        vec4 mv_Vertex = gl_ModelViewMatrix * gl_Vertex;
   
        vec4 light = gl_ModelViewMatrix * lightPos;
        vec3 lightVec = light.xyz - mv_Vertex.xyz;
It's been a while since I've looked at GL, but it looks like you're transforming the basis vectors into view space, but transforming the light into world space. Granted, that alone shouldn't make for triangle-local basis spaces, but I'm not sure what the point of that is.

Personally, I prefer keeping everything in world space, but that's what makes most sense to me, especially when you sometimes have to work with stuff that is built offline and need vectors in more than one space at a time... and also, I find that I hit vertex shading limits long before I hit fragment shading limits, so I'd rather move instructions to the fragments.

For example --
Code:
     vec3 t = gl_ModelViewMatrix * gl_MultiTexCoord3.xyz;
     vec3 b = gl_ModelViewMatrix * gl_MultiTexCoord4.xyz;
     vec3 n = gl_ModelViewMatrix * gl_Normal;

     vec4 mv_Vertex = gl_ModelViewMatrix * gl_Vertex;
   
     vec4 light = gl_ModelViewMatrix * lightPos;
     vec3 lightVec = light.xyz - mv_Vertex.xyz;
     g_LightVec = normalize(lightVec);
     // output t, b, and n in some interpolators (e.g. texcoords)
and in the fragment shader, I'd do something like...
Code:
     vec3 lightVec = normalize(g_lightVec);

     vec3 bump = texture2D(Normal, gl_TexCoord[0].st).rgb * 2.0 - 1.0;
     bump = normalize(bump);
     vec3 wbump = bump.x * normalize(t) + bump.y * normalize(b) + bump.z * normalize(n);
     wbump = normalize(wbump);
     float diffuseIntensity = dot(lightVec, wbump);
 
Mr.Pink, i did not check your tangents/normals/binormals computations, but strictly from your shaders:

/* disclaimer: i'm talking from Cg experience here, so if i'm missing something GLSL-ish i hope you'll correct me. i'll also make a couple of assumptions next - so correct me there too */

assumption1: what is that normal matrix containing? i'm assuming the inverse-transpose of your model-view matrix, right?

assumption2: mat3() composes a column-major matrix, i.e. the arguments go into the matix columns, right?

assumption3: tangent comes as texCoord3 and binormal - as texCoord4

assumption4: v * m does signify mT * v in GLSL

if that's the case, then:

since light direction vector is computed from within view space, you need a transform that throws that back into model space, and from there into tangent space.

a forward transform from tangent space to view space would be model_viewIT * [ T | B | N ] for column-major matrices (assuming your T, B & N are all defined in model-space), which your vertex shader does seem to compute correctly into TBN. from there on light_dirT * TBN should get you a light_dir vector in tangent space, as long as both your model_viewIT _and_ [ T | B | N ] were orthonormal.

so as far as i can see from your shaders you got the transform computations covered. so unless some of my original assumptions above are wrong, the issue remains to be somewhere in your T, B and N computing originally.
 
Last edited by a moderator:
I'll rather copy paste some code snippets from my engine.

<VERTEX_SHADER>
Code:
/* Attributes */
attribute vec3 a_vVertex;
attribute vec3 a_vNormal;
attribute vec2 a_vTexCoord0;
attribute vec4 a_vTangent;

/* Fragment Shader Linkage */
varying vec3 v_vWSLightVector;	//VL = L - V (World Space)
varying float v_fLightExtent;
varying vec3 v_vTSLightDir;		//VL = L - V (Tangent Space)
varying vec3 v_vTSViewDir;			//VE = E - V (Tangent Space)

/* Local Variables */
uniform vec4 vLightPosition;	//Light Position (World Space)
uniform float fLightExtent;	//Light Extent (World Space)
uniform mat4 mModelMatrix;		//Object Space => World Space
uniform mat4 mViewMatrix;		//World Space => Eye Space

Main:
Code:
	/* Lighting */
	//Tangent Binormal Normal Matrix (Tangent Space => Eye Space, thx to gl_NormalMatrix)
	mat3 mTBNMatrix;
	mTBNMatrix[0] = gl_NormalMatrix * a_vTangent.xyz;
	mTBNMatrix[1] = gl_NormalMatrix * (cross( a_vNormal, a_vTangent.xyz ) * a_vTangent.w);	//Binormal
	mTBNMatrix[2] = gl_NormalMatrix * a_vNormal;
	
	//Compute ViewDir (not normalized)
	vec4 ESVertex = gl_ModelViewMatrix * gl_Vertex; 
	v_vTSViewDir = vec3( -ESVertex ) * mTBNMatrix;	//Eye Space => Tangent Space

	//Compute LightDir (not normalized)
	vec4 ESLightVect = mViewMatrix * WSLightVect;	//World Space => Eye Space
	v_vTSLightDir = ESLightVect.xyz * mTBNMatrix;	//Eye Space => Tangent Space

<FRAGMENT_SHADER>
Code:
	//Bump Mapping
	vec3 vNormal = texture2D( tNormalMap, gl_TexCoord[0].st ).rgb * 2.0 - 1.0; //Scale & Bias

Should do the trick. At least if you don't get correct results you'll know it's from somewhere else ^^
 
Thanks to all of you guys!! i solved the problem :): it was not a math or shader issue but a programming mistake while sending vertex attribute to opengl via Display List

just for clear things:

You might want to have a look there : http://www.terathon.com/code/tangent.php
and change your code accordingly.

i saw that piece of code, and it is wrong while calculating inverse texture gradient!

Darkblu-> you were correct: the shader code is ok!!

i will post my demo in this thread soon when completed
 
Back
Top