Friday, June 11, 2010

Tangent Space At A Quick Pace

It is amazing when you finally have all the pieces of a puzzle click right into place. This is a post for the techies; all the other kids can follow along if you want. Kiyome and I have been working on some graphics research lately. The latest concept we’ve been tackling is normal maps. Just like there is more than one way to slap a misbehaving husband, there is more than one way to implement a normal map. You can either slap him in the arm, which is somewhat effective and may more may not get him to stop whatever he’s doing, or you can slap him right across the face and leave a big red imprint, effectively shutting him up for a while. For normal maps, you can either have your map be in object space, that is the reference frame based on the object’s orientation, or you can have the normal map be in tangent space, which is the frame based on the surface of the object.

We chose to go with slapping the husband straight across the face. This method is somewhat involved in the fact that you have to understand what the tangent space really means and what it means to transform vectors into tangent space. When I set off to do this, I went in armed with a very brief, maybe twenty minute introduction into tangent spaces from two years ago. For those of you who are still wondering, yes this does mean I went in without really knowing what I was wondering.

Simply applying the sample code we found produced results that had the sort of normal map effect on it, however there were major errors. First of all the light looked like it was in the totally wrong direction, but even weirder is the fact that the light was bounces off of spots on the tortoise that didn’t make any sense.

Our first thought was that the normal texture must’ve been wrong. We went into Photoshop and together inspected the properties of the normal map. We attempted altering the color components of the normal map in vein. This is where that note about not understanding what I’m doing comes into play. I will explain later why altering the texture makes no sense and has barely any effect.

So after some tweaking and coming up with ideas to debug the shader, the program that does the drawing, I realized that our light vector, the vector from the vertex, a point on the surface, to the light source, was being transformed in the reverse order from the other transforms in the shader. When I reversed it, it seemed to produce far better results. At first I thought it might have actually been correct.

I moved on and added in a feature to move the light around the model to see how the light acts as is goes across. Unfortunately, the light was moving in a very weird direction with respect to how the light moved without the normal map. This of course set off red flags leading me to believe that we were still wrong. It got worse when I realized that the light was actually still highlighting areas that it shouldn’t.

I decided to try toying with values in the pixel shader, this is the shader that does the per pixel calculations onto the screen. I’d like to make a brief note. In the pixel shader, the normal and light vectors are in tangent space. So then I started thinking about what it means to alter a value in tangent space and I realized that it’s impossible to alter a single component to produce a meaningful effect for the entire model because this is tangent space. This is the reason why it didn’t make any sense to alter the normal map, since the normal map is tangent space vectors. You see, in world space, the space whose reference frame is the same for all objects in the world, a normal vector can go in any direction, and changing something like the X component will tweak the component across the entire model in the same way. However, in tangent space, most normals are actually in the Z direction. Negating the X component usually has no effect, but also it is completely dependent on where on the model that particular tangent space is.

So then things started really clicking. What it meant to transform from world space into tangent space in particular was a really important piece of math that I needed to understand. The reason why the light vector operation was reversed was because the tangent space matrix was a matrix that goes from object space to tangent space. Since the light vector was in world space, I needed to transform the light vector by the inverse of the tangent space matrix.

So the light vector was now correct as far as the math is concerned, but I was still not getting proper results. So I decided to take a look at the tangent space matrix. The matrix is composed of the Tangent, BiNormal, and Normal. The Normal is a vector sticking straight out from the point on the surface. The Tangent is a vector perpendicular to the Normal, and is tangents the surface. And the BiNormal is the vector perpendicular to those two. These three vectors create a space.

There is a necessary operation here though. These three vectors come into the vertex shader, the drawing program that handles the points on the model that create the overall shape of the model, in object space. It is the job of the vertex shader to transform these three vectors into world space. You might think to just apply the world matrix transform to these vectors but there’s a final piece of understanding that I knew but didn’t apply until now.

You see, when you transform the surface of a model from object space to world space you use a world matrix to orient, scale, and move the surface. However, the tangent space of the surface cannot be transformed the same way. There is a theorem and proof that shows this better than I could ever explain so either trust my words or go look it up.

Even though I had known this, my false notion that you didn’t necessarily need to apply this came from the fact that I had been learning a lot of HLSL, high level shader language, programming from DirectX samples, and DirectX samples don’t seem to ever follow this rule. What the reason is I will never know, but that doesn’t really matter all that much. Once I applied this bit of knowledge to my own shader and transformed the basis of the tangent space by the the transpose of the world matrix, everything finally came into place. The light was finally moving across the model properly. The higher detail was apparent in the model, and there were no strange artifacts due to things being lit improperly.

0 comments:

Post a Comment