6
\$\begingroup\$

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:

Perlin Noise

And here it is after my transformation:

Transformed Noise

Here are the normals for the first noise:

enter image description here

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.

\$\endgroup\$
5
  • \$\begingroup\$ 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. \$\endgroup\$
    – kolenda
    May 21, 2014 at 12:10
  • \$\begingroup\$ 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? \$\endgroup\$
    – rspencer
    May 21, 2014 at 16:59
  • \$\begingroup\$ 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. \$\endgroup\$
    – kolenda
    May 21, 2014 at 17:08
  • \$\begingroup\$ 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. \$\endgroup\$ May 22, 2014 at 16:26
  • \$\begingroup\$ Good point, but for small deformations they are useful. Besides, the method is fairly general. \$\endgroup\$
    – rspencer
    May 25, 2014 at 8:01

1 Answer 1

1
+100
\$\begingroup\$

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).

\$\endgroup\$

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .