Search code examples
mathvectornoise

Tricky Perlin Noise Question. How does the Grad Function Use Normalized Vectors?


I'm currently in the process of understanding improved Perlin noise. I understand the theory entirely however, I am confused by an aspect of its common implementation, such as this.

My question is, how does the grad() function return the dot product of normalized vectors (gradient and direction)? What I mean is that the dot product of normalized vectors has a range of -1 to 1 which is the normal output of Perlin noise after blending (fading) all dot products together. But the vectors which undergo the dot product are not normalized (none of the gradient functions are nor are the direction vectors). So, how does the output fall within the -1 to 1 range?

My only guess is that the gradient vectors all have a magnitude of root 2 and that all axes of the direction vectors are in the range of -1 to 1. So, I assume this is why the Perlin noise output ultimately falls within the -1 to 1 range. Is this why? Can anyone prove or find a proof?

Thanks guys


Solution

  • The vectors aren't normalized (their length is sqrt(2) as the other contributor described), and the total output of the noise isn't precisely -1 to 1. If vectors of <1, 1, 1> and all sign permutations were possible, then I believe the maximum would be right in the center of each cube. It would be dot(grad, offsetFromVertex) = dot(<1, 1, 1>, <0.5, 0.5, 0.5>) = 1.5, /8 for the 0.5^3 from having half the interpolation weight in each of the 3 directions, *8 again for having 8 vertices so it cancels. If the vectors were normalized, and they could point to the center of each cube, then you would have dot(<1, 1, 1>/sqrt(3), <0.5, 0.5, 0.5>) = 1.5/sqrt(3) ≈ 0.8660254037844387.

    But, neither of these scenarios are the case, so the actual min/max value of the noise is more complicated. I've run gradient ascent on the noise in the past, using the gradient set you're looking at, to find its true maximum. The maximum was slightly greater than 1, and was not in the center. The value to multiply the whole noise by (or equivalently pre-multiply each gradient in the table by), to correct the output range, is 0.964921414852142333984375 .

    By the way, if you haven't found this out through other sources: Perlin is great to learn, but it produces a lot of bias to the cardinal axes and yields low variety in the angle distributions of its features. Good Simplex type noise implementations produce generally nicer results. If you're going to use Perlin, I recommend picking whatever your vertical (or time, or unused) direction is, and using one of the following formulas on the input coordinate. You can either do it before you fractal sum (more efficient) or throw it into the beginning of your function definition (more convenient). Once you do this, it's great, and can look nicer than some Simplex type noise implementations when used in the right cases. But note that you need to always use 3D noise for this technique, even if your use case is only 2D.

    If Z is vertical, time, or unused:

    double xy = x + y;
    double s2 = xy * -0.211324865405187;
    z *= 0.577350269189626;
    x += s2 - z;
    y = y + s2 - z;
    z += xy * 0.577350269189626;
    

    If Y is vertical, time, or unused:

    double xz = x + z;
    double s2 = xz * -0.211324865405187;
    y *= 0.577350269189626;
    x += s2 - y;
    z = z + s2 - y;
    y += xz * 0.577350269189626;
    

    Before (lots of 45 and 90 degree parts)

    Un-domain-rotated 3D Perlin

    After (directional bias largely invisible)

    Domain-rotated 3D Perlin