# Transforming Normals in Bump Maps

## Question

I have a noise texture (perlin noise, to be precise) with its value in the alpha channel and the normal components in rgb. I now wish to, on the fly, apply some function to the alpha part of the texture. Is there an easy way to calculate the new normal?

My thoughts are that if we know the form of f, then

∂f/∂x = df/da * ∂a/∂x


Now, we want to know ∂f/∂x as from this we can calculate the x component of the normal, we know df/da as we know the form of f(a) and ∂a/∂x is encoded in the normal that we are given in the first place.

## Context:

I am creating a procedural rock shader and want some "cracks". To do this I need to take my lumpy perlin noise and apply some function to it. At the moment the function is:

f(x) = 1 - 5.5 * abs(noise);


This makes it more sharp (noise runs from -1 to 1).

Here is the basic noise:

And here it is after my transformation:

Here are the normals for the first noise:

What are the normals for the second? You may notice that the given normals are very "steep" This is because the bump map they are computed from goes to +-1 several times per unit area. I thus also need to know the answer to this question so that I can scale down the noise and keep the normals correct.

• How did you compute your first normal map and why can't you do the same for the 2nd one? And for smoothing your normals it should be enough just to scale down your noise values. May 21, 2014 at 12:10
• I want to calculate it GPU side, so that I only pass the one texture to the GPU (I may also use it for other things). Also, it seems as if it should be possible, so I want to know if it is. As for the scaling, I know that that works to some approximation. Is it exact though? May 21, 2014 at 16:59
• I think that trying to compute it in one pass, with gradient equation will be much more work to your GPU and it can result in random reads, cache misses etc. But it should be quite fast and simple if you compute your height in one pass and then compute gradients from height. May 21, 2014 at 17:08
• Note that the concepts of bump maps and normal maps tend to work best with small bumps, I don't think the geometry suggested by the second image will be conveyed well through a simple normal map. May 22, 2014 at 16:26
• Good point, but for small deformations they are useful. Besides, the method is fairly general. May 25, 2014 at 8:01

So, you have a noise function from RxR to R, called f(x, y). You are applying on that function a new transformation:

t(x, y) = 1 - 5.5 * f(x, y)


You seem to know the normals of z = f(x, y), which are proportional to the gradient of the equivalent implicit function F(x, y, z) = f(x, y) - z = 0. The normal vector is then:

   (∂F(x, y, z)/∂x , ∂F(x, y, z)/∂y , ∂F(x, y, z)/∂z)
= (∂f(x, y)/∂x    , ∂f(x, y)/∂y    , 1)
= 1/b * (r, g, b)    // proportional to your rgb values that represent the normal
= (r/b, g/b, 1)


Where r^2 + g^2 + b^2 = 1.

So, now you want to calculate the new normals of t(x, y), which are again proportional to the gradient of the implicit function: T(x, y, z) = t(x, y) - z = 0. So you need:

   ( ∂T(x, y, z)/∂x          , ∂T(x, y, z)/∂y          , ∂T(x, y, z)/∂z )
= ( ∂t(x, y)/∂x             , ∂t(x, y)/∂y             , 1  )
= ( -5.5 * ∂abs(f(x, y))/∂x , -5.5 * ∂abs(f(x, y))/∂y , 1  )
= ( -5.5 * r/b              , -5.5 * g/b              , 1  ) // TODO sign switch of abs


This is a problem, since the derivate of abs(x) is undefined for x = 0. Maybe, you can use x^2 instead. If you really want to, you could also use 0 as derivate of abs(x) for x = 0.

If you use abs(x), you will have to program an if-statement that checks wether f(x, y) is less then, equal or greater than zero. Accordingly change the sign, or make zero.

So, conclusion, your gradient scales by factor 5.5 for the x and y components, which makes sense, because your function is a lot steeper. And it is also possible to get a sign switch. However, we are not done yet! To get the normals, you need to normalize them, which means you have to divide them by their lengths. The length of the not normalized normal vector is:

 N = sqrt((-5.5 * r/b)^2 + (-5.5 * g/b)^2 + 1)


Now, normalizing the vector gives us:

  (-5.5 * r/b, -5.5 * g/b, 1) / sqrt((-5.5 * r/b)^2 + (-5.5 * g/b)^2 + 1)
= (-5.5 * r/b, -5.5 * g/b, 1) * b / sqrt((-5.5 * r)^2 + (-5.5 * g)^2 + b^2)
= (-5.5 * r, -5.5 * g, b) / sqrt((-5.5 * r)^2 + (-5.5 * g)^2 + b^2)


In code this means you would have:

for-each (pixel p(v, n_x, n_y, n_z) of my texture)
{
pixel newPixel;
newPixel.v = 1 - 5.5 * p.v;
float invNorm = 1 / sqrt( (-5.5 * p.n_x)^2 + (-5.5 * p.n_y)^2 + p.n_b^2 );
newPixel.n_x = -5.5 * sgn(p.n_x) * p.n_x * invNorm;
newPixel.n_y = -5.5 * sgn(p.n_y) * p.n_y * invNorm;
newPixel.n_z = p.n_z * invNorm;
}


Where sgn(float) is:

float sgn(float f)
{
if (f < 0) return -1;
if (f > 0) return 1;
return 0;
}


However, it seems like you are clamping the values. If your value is clamped, use normal (0, 0, 1).