I’ve seen code floating around that computes a normal map from a height map by estimating tangent vectors and doing a cross product. That includes a bit of unnecessary extra math, so here’s a better way to compute the same thing.
First, let’s write the height field as a function of x & y (ok, it’s a texture, so call them u and v if it makes you feel better). So a height field is basically a texture that defines
z = h(x,y)
The tangent-based code already estimated the partial derivatives of h(x,y) with respect to x and y. Let’s call those hx and hy. There are several ways to do this: forward differences
hx = h(x+1) – h(x)
central differences:
hx = (h(x+1) – h(x-1)) / 2
Sobel filter (no, I’m not going to write that one out), or other derivative of a filter.
The trick comes in rewriting this thing as an implicit function of x,y,z that’s 0 on the surface, negative under the surface and positive outside the surface. That’s actually pretty easy:
f(x,y,z) = z – h(x,y)
So the neat thing is that the normal to an implicit surface like this is just the gradient of the implicit function:
N = (fx, fy, fz) = (-hx, -hy, 1)
That’s it. Much friendlier than cross( (1,0,hx), (0,1,hy) ). You could actually get exactly the same result by expanding out the cross product, but I think it’s much cooler (and more applicable in other situations) to know about the normal to an implicit function trick.
Marc
Should that not be something like N =(-hx, -hy, 1-sqrt(hx*hx+hy*hy)) to account for the normal’s unit length?
Actually, it should be
nlen=sqrt(1+hx*hx+hy*hy) (-hx,-hy,1)/nlenI didn’t mention it in the post, but I was doing this in the context of LEAN mapping for Civilization V, which needs un-normalized normals projected onto the z=1 plane. None the less, you want to scale all of the components equally to keep the normal pointing in the same direction.
By the way, this copy of the blog is still lingering around, but the main copy is now on gaim.umbc.edu.
Marc