The first article in the latest GPU gems is brilliant for so many reasons. It's no wonder they chose it for the first article. Not only does Ryan basically blow apart all previous heightmap-based work as far as terrain generation goes, but, on the side, he slips in an elegant (and blindingly-simple) little method for texturing arbitrary surfaces that don't seem easily-texturable. To cap it off, he mentions how to generate tangent bases for performing good-looking bumpmapping on the resultant surface. He could have stopped after GPU terrain generation with geometry shaders and still had a killer article...but I'm oh so glad that he didn't 🙂
Incidentally, I missed the bit about tangent bases on the first few reads, which is why I've been doing them wrong until now. In my defense, there's literally only one paragraph dedicated to the trick. But it's so, so simple: compute an approximate tangent basis separately for each projection, and blend the resulting bump information. How? Easy...you know the axis of the normal for each plane, so the intuitive thing to do is rotate the normal 90 degrees about the other two axes to obtain two approximate tangents. In practice, this means we can just define:
vec3 tanX = vec3( normal.x, -normal.z, normal.y);
vec3 tanY = vec3( normal.z, normal.y, -normal.x);
vec3 tanZ = vec3(-normal.y, normal.x, normal.z);
Then for the x projection, you use tanY and tanZ as your tangent frame, tanX and tanZ for y projection, etc. Now, if you stare at it a bit, you may quickly realize why it's a hack: these tangent vectors are not necessarily orthogonal to the normal!! Not much of a tangent plane if it's not..tangent -_- Only in the case of a perfectly-axis-aligned normal do we get an actually-tangent tangent frame. Otherwise, we get something that is not-tangent, where the not-tangency of each tangent vector is proportional to the square of the normal's component along that axis. Let me try to make an even more precise run-on sentence: we get a frame where the deviation of the cosine of each tangent vector to a true tangent vector is proportional to the square of the normal's component along the corresponding axis of rotation. Pull that one out at a dinner party and see how many twitter followers you acquire. But what I was trying to say, intuitively, is that, if you think about the rotations we performed, they only yield orthogonal vectors when the vector to be rotated is orthogonal to the axis of rotation. Right.
Is this a problem? Not really. In practice it looks good. If you want to get fancy, I guess you could do G-S orthogonalization or something of that nature. Better yet, you can probably come up with a compact little closed-form answer for each tangent frame based on axis of projection (because if we restrict to each hemisphere, we can chart it with a continuous frame...diff geom experts correct me if I am mistaken). But this looks good to me! And it's so simple!!
Thanks, NVIDIA, for an excellent article in an excellent book 🙂