Jump to content
  • Advertisement
Sign in to follow this  
HackSlash

OpenGL [SOLVED] Tangent-space bump mapping. Am I doing this right?

This topic is 3176 days old which is more than the 365 day threshold we allow for new replies. Please post a new topic.

If you intended to correct an error in the post then please contact us.

Recommended Posts

Ok, so I've got a cube with the normals inverted so I can have lighting inside the box. This is fine, my phong lighting shaders do this very well. I'm trying to put a bump map onto each side of the box but obviously it isn't working, all I get is all black and the occasional flicker from the sides. Which means either I haven't setup the shaders properly (the variables aren't being passed to the shader), my shader is wrong or I'm not calculating the tangent properly. I'm not experienced enough to know which one so I'm hoping if any of you can see where I'm going wrong. My setup code for the cube is:
...
m_aTangents = NULL;
m_aTangents = new GLfloat[72];
for(int i=0; i<72; i++)
	m_aTangents = 0.0f;

calcTangents();
... // load images
s = ShaderManager::GetInstance()->createShaderFromFile("bumpmap2", "bumpmap2.vert", "bumpmap2.frag");
s->link();
// Get the location of 
m_BMapTangentLoc = -1;
m_BMapTangentLoc = s->getAttribLocation("rm_Tangent");


How I calculate my tangents:
// Calculate the tangents for the faces.
// Using the equations described in:
// http://www.blacksmith-studios.dk/projects/downloads/bumpmapping_using_cg.php
int numFaces = 6;
int faceCount = 0;

for(int i=0; i <= 71; i+=12)
{
	// for each face, get 3 vertices
	Vector3f v1(m_aVertices, m_aVertices[i+1], m_aVertices[i+2]);
	Vector3f v2(m_aVertices[i+3], m_aVertices[i+4], m_aVertices[i+5]);
	Vector3f v3(m_aVertices[i+6], m_aVertices[i+7], m_aVertices[i+8]);

	Vector3f v2v1 = v2 - v1;
	Vector3f v3v1 = v3 - v1;

	// Get the tex coords for these vertices
	Vector3f v1T(m_aTex[faceCount], m_aTex[faceCount+1], 0.0f);
	Vector3f v2T(m_aTex[faceCount+2], m_aTex[faceCount+3], 0.0f);
	Vector3f v3T(m_aTex[faceCount+4], m_aTex[faceCount+5], 0.0f);

	float c2c1_T = v2T.x - v1T.x;
	float c2c1_B = v2T.y - v1T.y;

	float c3c1_T = v3T.x - v1T.x;
	float c3c1_B = v3T.y - v1T.y;

	float denominator = c2c1_T * c3c1_B - c3c1_T * c2c1_B;
	float scale = 1.0f / denominator;

	float Tx = (c3c1_B * v2v1.x - c2c1_B * v3v1.x) * scale;
	float Ty = (c3c1_B * v2v1.y - c2c1_B * v3v1.y) * scale;
	float Tz = (c3c1_B * v2v1.z - c2c1_B * v3v1.z) * scale;

	// Now we fill in the tangent array for this face
	m_aTangents = Tx;	m_aTangents[i+3] = Tx;	m_aTangents[i+6] = Tx;	m_aTangents[i+9] = Tx;
	m_aTangents[i+1] = Ty;	m_aTangents[i+4] = Ty;	m_aTangents[i+7] = Ty;	m_aTangents[i+10] = Ty;
	m_aTangents[i+2] = Tz;	m_aTangents[i+5] = Tz;	m_aTangents[i+8] = Tz;	m_aTangents[i+11] = Tz;

	faceCount += 8;

}


My cube draw code:
glActiveTextureARB(GL_TEXTURE0_ARB);
	glBindTexture(GL_TEXTURE_2D, m_TexHandle);

	glActiveTextureARB(GL_TEXTURE1_ARB);
	glBindTexture(GL_TEXTURE_2D, m_NormalMap);
	
	glPushMatrix();

	s->enable();
	s->setUniform1i("ColourMap",0);
	s->setUniform1i("NormalMap",1);		
	
	glEnableClientState(GL_NORMAL_ARRAY);
	glEnableClientState(GL_TEXTURE_COORD_ARRAY);
    glEnableClientState(GL_VERTEX_ARRAY);

	glEnableVertexAttribArrayARB(m_BMapTangentLoc);
    glVertexAttribPointerARB(m_BMapTangentLoc,3,GL_FLOAT, GL_FALSE ,0,m_aTangents);
    
	glNormalPointer(GL_FLOAT, 0, m_aNormals);
	glTexCoordPointer(2, GL_FLOAT, 0, m_aTex);
    glVertexPointer(3, GL_FLOAT, 0, m_aVertices);

	glDrawArrays(GL_QUADS, 0, 24);

	glDisableClientState(GL_VERTEX_ARRAY);
    glDisableClientState(GL_TEXTURE_COORD_ARRAY);
    glDisableClientState(GL_NORMAL_ARRAY);	
	
	glDisableVertexAttribArrayARB(m_BMapTangentLoc);
	s->disable();

	glPopMatrix();


My shader code:
[Vertex Shader]
#define MAXLIGHTS 8
uniform vec3 EyePosition;
varying vec3 EyeDir;
varying vec3 LightDir[MAXLIGHTS];

attribute vec3 rm_Tangent;
   
void main( void )
{
	gl_Position = ftransform();
	gl_TexCoord[0]  = gl_MultiTexCoord0;

	vec3 N = normalize(gl_NormalMatrix * gl_Normal);
	vec3 T = normalize(gl_NormalMatrix * rm_Tangent);
	vec3 B = cross(N, T);
	
	// Calculate the lighting stuff
	vec3 v;
	vec4 objectPosition = gl_ModelViewMatrix * gl_Vertex;
	for(int i=0; i < MAXLIGHTS; i++)
	{
		vec3 tmpLightDir = gl_LightSource.position.xyz - objectPosition.xyz;
		v.x = dot(tmpLightDir, T);
		v.y = dot(tmpLightDir, B);
		v.z = dot(tmpLightDir, N);
		
		LightDir = v;
	}
	
	// Calculate the view stuff
	vec3 viewDirection = EyePosition.xyz - objectPosition.xyz;
	v.x = dot(viewDirection, T);
	v.y = dot(viewDirection, B);
	v.z = dot(viewDirection, N);
	EyeDir = v;
   
}

[Fragment Shader]
#define MAXLIGHTS 8
varying vec3 EyeDir;
varying vec3 LightDir[MAXLIGHTS];

uniform sampler2D ColourMap;
uniform sampler2D NormalMap;

void main()
{
	vec4 baseColour = texture2D(ColourMap, gl_TexCoord[0].st);
	
	vec4 finalColour = gl_FrontLightModelProduct.sceneColor * baseColour;
	
	vec3 V = normalize(EyeDir);
	vec3 N = normalize( texture2D(NormalMap, gl_TexCoord[0].st).xyz * 2.0 - 1.0 );
	  
	float spotEffect;
	float attenuation;

	for(int i=0; i < MAXLIGHTS; i++)
	{
		vec3 L = normalize(LightDir);
		float NdotL = dot(N,L); // Lambert's term
		
		if(NdotL > 0.0)
		{
			float distance = length(LightDir);
			spotEffect = dot(normalize(gl_LightSource.spotDirection), normalize(-LightDir));
			
			if(spotEffect > gl_LightSource.spotCosCutoff)
			{
				spotEffect = pow(spotEffect, gl_LightSource.spotExponent);
				
				/********
				 Attenuation is calculated from the following equation (from the OpenGL redbook)
				 1 / (Kc + Kl*d + Kq*d^2)
				*********/
				attenuation = spotEffect / (gl_LightSource.constantAttenuation +
					gl_LightSource.linearAttenuation * distance +
					gl_LightSource.quadraticAttenuation * distance * distance);
				
				finalColour += attenuation * gl_LightSource.diffuse * gl_FrontMaterial.diffuse * NdotL;
				
				vec3 R = reflect(-L, N);
				float specular = pow(max(dot(R,V), 0.0), gl_FrontMaterial.shininess);
				
				finalColour += gl_LightSource.specular * gl_FrontMaterial.specular * specular;
			}
		}
	}
	
	gl_FragColor = finalColour;
}


My shader code compiles and I think it is correct. All my lights will be spotlights, hence the spot stuff in the fragment shader. I also think the tangent calculation could be correct, it is based on the blacksmith-studios one. I know I will eventually have to get a per-triangle calculation eventually for meshes but I thought because I'm learning, I can make the assumption that the tangents & normals will all be the same for the points on the face. Which leaves the binding stuff. I'm definitely not sure if I am doing this correctly. Should I move all my arrays into VBOs? I did try this at one point and got a memory error at glDrawArrays. Maybe I wasn't doing it correctly but I wasn't too sure either! It was fun when I started this yesterday but it has come to the point where I have no idea where I'm going wrong. So, any help will be much appreciated! [Edited by - HackSlash on December 9, 2009 5:04:14 PM]

Share this post


Link to post
Share on other sites
Advertisement
At first glance the only thing that stands out is this:

Quote:

for(int i=0; i < MAXLIGHTS; i++)
{
vec3 tmpLightDir = gl_LightSource.position.xyz - objectPosition.xyz;
v.x = dot(tmpLightDir, T);
v.y = dot(tmpLightDir, B);
v.z = dot(tmpLightDir, N);

LightDir = v;
}

// Calculate the view stuff
vec3 viewDirection = EyePosition.xyz - objectPosition.xyz;
v.x = dot(viewDirection, T);
v.y = dot(viewDirection, B);
v.z = dot(viewDirection, N);



The dot thing seems quite strange, perhaps I'm missing somehting but that doesn't seem correct.

This is what I have in my shader (not saying its correct :P):


gl_Position = ftransform();
gl_TexCoord[0] = gl_MultiTexCoord0;

/* Create toTangentSpace matrix, converts from modelView to tangent */
vec3 n = gl_NormalMatrix*normalize(gl_Normal);
vec3 t = gl_NormalMatrix*normalize(tangent);
vec3 b = gl_NormalMatrix*normalize(biNormal);

/* This can be a 3x3 */
mat4 toTangentSpace = mat4(
t.x, b.x, n.x, 0.0,
t.y, b.y, n.y, 0.0,
t.z, b.z, n.z, 0.0,
0.0, 0.0, 0.0, 1.0);

/* First work out light direction and eye direction in model view space*/
vec4 vVertex = gl_ModelViewMatrix * gl_Vertex;
vec4 eye = -vVertex;
eye.w = 0.0;
vec4 lVec;
lVec.xyz = gl_LightSource[0].position.xyz - vVertex.xyz*gl_LightSource[0].position.w;
lVec.w = 0.0;

lightVec = (toTangentSpace*lVec).xyz;
toEye = (toTangentSpace*eye).xyz;


Share this post


Link to post
Share on other sites
Hi,
I wasn't too sure about the dot stuff either but I've changed my vertex shader to:

gl_Position = ftransform();
gl_TexCoord[0] = gl_MultiTexCoord0;

vec3 N = normalize(gl_NormalMatrix * gl_Normal);
vec3 T = normalize(gl_NormalMatrix * rm_Tangent);
vec3 B = cross(N, T);

// Construct the TBN matrix
mat3 toTangentSpace = mat3(
T.x, B.x, N.x,
T.y, B.y, N.y,
T.z, B.z, N.z);


// Calculate the lighting stuff
vec3 v;
vec4 objectPosition = gl_ModelViewMatrix * gl_Vertex;
for(int i=0; i < MAXLIGHTS; i++)
{
vec3 tmpLightDir = gl_LightSource.position.xyz - objectPosition.xyz;

v = toTangentSpace * tmpLightDir;

LightDir = v;
}

// Calculate the view stuff
vec3 viewDirection = -objectPosition.xyz;
v = toTangentSpace * viewDirection;
EyeDir = v;



I still get a black box which flickers every time I move the camera. Would my problems have something to do with how I'm passing the tangents in? I also set the tangent variable to (1.0,0.0,0.0) but I still get the same results. Because the textures don't show up at all, I'm thinking my problem could be how I've generated the textures and/or sampling them in the pixel shader.

Share this post


Link to post
Share on other sites
try rendering everyhting to see if tis all correct.

ie if you do glFragColor = normal, does the normal map get displayed correctly? Then try passing through the world space normals/tangent/binormals and see if they are the right "color" when you render things.

Share this post


Link to post
Share on other sites
Nanoha,

After much fiddling here's what I've found out:
My tangent calculations were wrong or they weren't sent to the shader properly. I used a quick hack from ozone3d to get the tangent in the shader:

vec3 tangent;
vec3 c1 = cross(gl_Normal, vec3(0.0, 0.0, 1.0));
vec3 c2 = cross(gl_Normal, vec3(0.0, 1.0, 0.0));

if(length(c1)>length(c2))
{
tangent = c1;
}
else
{
tangent = c2;
}

tangent = normalize(tangent);



Another problem is that the NormalMap is actually in texture unit 0. When I changed gl_FragColor = baseColour, the normal map appeared. So I switched the texture2D on that to sample from the uniform "ColourMap". Hey presto! Bump mapped walls...but no colour info (apart from the lights). I'm not sure what is happening? I thought that using glActiveTextureARB(GL_TEXTURE1_ARB), binding the texture handle to it and then using glUniform1iARB(normalMaploc, 1) would do it.
Do you have any ideas on this? Can you clarify they method I'm using to upload the tangents to the shader?

Share this post


Link to post
Share on other sites
I've sorted out the texture unit problems! I was enabling/disabling the shader inside of another shader, which had it's own glEnable(GL_TEXTURE_2D) & glDisable for it. I might have to re-think how my materials system applies the shaders! All that's left to solve are the tangents and passing them to the shader. Not too sure if I am doing it correctly with the vertex arrays but I'm going to see if VBOs work.

Share this post


Link to post
Share on other sites
Sign in to follow this  

  • Advertisement
×

Important Information

By using GameDev.net, you agree to our community Guidelines, Terms of Use, and Privacy Policy.

We are the game development community.

Whether you are an indie, hobbyist, AAA developer, or just trying to learn, GameDev.net is the place for you to learn, share, and connect with the games industry. Learn more About Us or sign up!

Sign me up!