5

I've been wrestling with this for days. I think I've finally narrowed it down to a problem with the per vertex tangents, but I'm not sure the best way to fix it.

Context is iPhone app, opengl es2 using my own engine. My shader is a bump map (normal map) variety using the supplied per vertex tangent to create a TBN matrix. The vertex shader transforms the light vectors and eye vectors to tangent space, passes them to the fragment shader and calculates the lights. But some geometry in my first two test models is showing weird artifacts in the lighting. It is easiest to see in the specular component.

In trying to debug this I've replaced the normal map with a flat normal png.. all pixels are 128,128,255. I also hard coded the color.

My first model is a button shape. It shows the artifact as a scalloped specular hit on the outside ring. Important to note is that the UV mapping method here was 'flat' to make the sides which are perpendicular to the mapping basically streak the texture across there. I would think this would make the tangents hard to calculate for that geometry since two of the points would have the exact same texture coordinate.

I tried debug renders setting gl_FragColor to different variables. When setting it to the per vertex normals we see the scalloping is gone, indicating that the normals are correct. But when setting it to the per vertex tangents you can see the scallop.

enter image description here

The next shape is a simple sphere. With it the very top and bottom of the model is where the artifact shows. In the wireframe you'll see that this is where several triangles are meeting at one vertex. I can't wrap my head around what that means for the tangents as each triangle has completely different UV mapping there.

I suppose I'll get a lot of flack if I don't show the shader code...

Vertex Shader:

v_fragmentTexCoord0 = a_vertexTexCoord0;

gl_Position = u_modelViewProjectionMatrix * vec4(a_vertexPosition,1.0);

vec3 normal = normalize(u_normalMatrix * a_vertexNormal);
vec3 tangent = normalize(u_normalMatrix * a_vertexTangent);
vec3 bitangent = cross(normal, tangent);
mat3 TBNMatrix = mat3(tangent, bitangent, normal);

v_eyeVec =  -a_vertexPosition.xyz;
v_eyeVec *= TBNMatrix;

v_lightVec = u_lightPosition - a_vertexPosition.xyz;
v_lightVec *= TBNMatrix;

v_normal = a_vertexTangent;

Fragment Shader:

vec3 norm = texture2D(u_normalSampler, v_fragmentTexCoord0).rgb * 2.0 - 1.0;
vec4 baseColor = vec4(0.6015625,0.0,0.0,1.0); // is normally texture2D(u_textureSampler,v_fragmentTexCoord0);

float dist = length(v_lightVec);
vec3 lightVector = normalize(v_lightVec);
float nxDir = max(0.0, dot(norm, lightVector));
vec4 diffuse = u_lightColorDiffuse * nxDir;
float specularPower = 0.0;
if(nxDir != 0.0)
{
    vec3 cameraVector = v_eyeVec;
    vec3 halfVector = normalize(v_lightVec + cameraVector);
    float nxHalf = max(0.0,dot(norm, halfVector));
    specularPower = pow(nxHalf, u_shininess);
}
vec4 specular = u_lightColorSpecular * specularPower;

gl_FragColor = (diffuse * vec4(baseColor.rgb,1.0)) + specular;

In trying to figure out the shader and looking at all the samples and tutorials online I could find... I was confused by why the bump map shaders wouldn't just perturb the normal map supplied by the 3d model. I guess without some sort of reference point how you know HOW to modify model's normal? By what orientation? I guess that's what the TBN matrix is for. But in my test above the normal vector in the normal map is 0,0,1 - straight up. So it seems like it shouldn't be modifying it at all. Anything times 1 is still 1. But there must be something screwed up enough in the math or in the TBN matrix that it's not coming up with the same normal as the one in the 3d model. Then that idea struck something with me.. that in my vertex shader I'm also first multiplying the vertex normal by the normalMatrix to get it in to model space. But then again, those debug renders are of the vertex normal before it was transformed.

Is there another method to perturb the model's normal without using a TBN matrix? Rendering with a phong shader without the normal map doesn't show the artifact.

UPDATE: I'm almost positive that the problem is the precalulated tangents created by the importing app. After trying different models with different UV Maps I'm finding similar looking problems, sometimes it's a darkness instead of a highlight. So unless I can apply a normal map without a TBM matrix or converting to tangent space, I will need to find another importer.

UPDATE #2: I just found this other question that sounds like it might be a clue to the real problem. 3d graphics, unit vectors and orthogonal matrices

It's important to note that these weird lights don't happen on vertices but only in between them. Even on the sphere we're seeing a ring which is between the top vertex and the ones right below it. I'm starting to believe that this is an interpolation problem. Take just one of those triangles up there:

enter image description here

Ignore the bad bitangent for a second cause I'm recalculating that. But the tangents have opposite polarity. So while being interpolated between those two states your going to get vectors that point all over the place.

The new question is how do I solve it? Fixing the tangents? Fixing the shader to deal with it?

Community
  • 1
  • 1
badweasel
  • 2,349
  • 1
  • 19
  • 31
  • Why not pass the normal and tangent vectors (LERP'd) to the fragment shader, and handle the TBN transforms there? If the normals vary at each vertex, you'll get smoother results with per-fragment calculations. – Brett Hale Mar 13 '13 at 12:59
  • I guess I worried it would be too slow. When it has the texture map on it and the bump map it looks really good and you don't notice it to much. If it's would fix the problem I would. – badweasel Mar 13 '13 at 13:41
  • It's nothing much for a shader, especially compared to the profile of something like the `pow` function. As you say though, it's probably not the source of the problem. – Brett Hale Mar 13 '13 at 13:55
  • `vec3 norm = texture2D(u_normalSampler, v_fragmentTexCoord0).rgb * 2.0 - 1.0;` - shouldn't that be `- vec3(1.0, 1.0, 1.0)`? – Brett Hale Mar 13 '13 at 14:47
  • No, @Brett Hale, it would be vec3(0., 0., 1.). –  Mar 13 '13 at 14:53
  • @badweasel I don't think this is worth putting any time into until you have a decent UV map. –  Mar 13 '13 at 14:59
  • @BrettHale I could have hard coded the vec3 norm as a form of debug but the provided normal sampler was already giving 0,0,1 to the shader. – badweasel Mar 13 '13 at 15:16
  • @Jessy The uv mapping looks great on this object, even with the weird additional specular light hits. Good enough that I didn't even notice the problem at first. But it's not right and for most situations it is going to cause a problem. – badweasel Mar 13 '13 at 15:17
  • Oh sorry @BrettHale I misunderstood your vec3 norm comment. No the -1 shouldn't be - 1,1,1. It does the *2 -1 math on all 3 components without doing that. – badweasel Mar 13 '13 at 15:25
  • @badweasel you said that you have triangles with zero area in the UV map. That is not a component of a good UV map. –  Mar 13 '13 at 18:48
  • (At least, that's how I read what you said. Why don't you post pictures of the UV map and texture mapping with a checkerboard?) –  Mar 13 '13 at 18:57
  • @Jesssy.. here's a jpg explaining the UV map the best I knew how... http://nslog.me/attachments/shaderProblemsMapping.jpg – badweasel Mar 14 '13 at 03:07
  • The renders with the grid are from the 3d program but you can see the streaks on the edges of the button/mint and the detail of how the uv mapping is done. I could add more poly around the center of the ring, pull it out some so that it's not just a single line, but I don't think that will fix it. The renders with the blue background are using my shader with a slight normal map (crazybump map of the texture). The closeup of the edge looks really good. But at certain angles that edge gets bumpy. This is why I started debugging the shader to find out why.. – badweasel Mar 14 '13 at 03:16
  • You'll never be able to get that to look good if you use CrazyBump. CrazyBump assumes that your surface normals approximate the face normals, and that won't happen with large flat areas at that vertex resolution. Either add edge loops or model the thing properly in Zbrush and then bake normal maps with an appropriate tangent basis. –  Mar 14 '13 at 18:07
  • @jessy would you be willing to email me directly. I've been learning this for months and have a decent grasp, but there are things about the methods of doing normal/bump mapping that I don't understand. Like why it's done this way instead of other ways. I also don't get why in my current debug project that with my normal map all at 0,0,1 why there would be any disturbance in the lighting no matter what the tangent is. If the normal at the top is 0,1,0.. you know? – badweasel Mar 15 '13 at 04:33
  • I'd prefer if we could talk on a a public forum, so other people might be helped down the road, but if you don't know of a good place for it, oh well. Personally, I think the Unity forum would be good, but if it would take too long for you to learn how to deal with the basics of importing into Unity, we shouldn't use their forum. I'm not sure if I understand your confusion about the lighting disturbance. Light vectors get rotated into tangent space, and if tangent space isn't accurate to the surface, the light vectors will be wrong. I just noticed, too, "v_eyeVec" doesn't use your camera. –  Mar 15 '13 at 13:39
  • We can discuss it here.. that's fine. Although I'm not sure who will be helped down the line with this discussion. Or I could migrate all of this into a blog post on my rarely used blog. That works for me too. Also FYI see this related question: http://stackoverflow.com/questions/15433917/in-a-tbn-matrix-are-the-normal-tangent-and-bitangent-vectors-always-perpendicu – badweasel Mar 16 '13 at 11:04
  • I'm not sure if I should answer my own question here or not. I don't have the lighting thing fixed but I'm also very sure that it's just bad tangents supplied by my obj importer. This weekend I'm going to add the code to recalculate the tangents in the importer. On the eyeVec.. yeah I have a version with that. To me the light responded better to camera movementswithout it. It wasn't much difference and to be honest my camera isn't going to move. So from a cycle savings on this app it's fine not to have it. I get that it might not be right for all applications. – badweasel Mar 16 '13 at 11:10

1 Answers1

1

Sometimes the simplest answer is the right one. The tangents were bad. They were not orthogonal to the normal.

I manually recalculated all the tangents and bitangents using the methods described here:

http://www.terathon.com/code/tangent.html

And that problem went away.

badweasel
  • 2,349
  • 1
  • 19
  • 31