5

I've been trying to get Perlin Noise generation working all day, and I'm having trouble implementing the pseudocode in this tutorial.

Similar code is shown in the answer to this question.

The trouble is that I have no idea what the input values for x and y are supposed to be in the PerlinNoise_2D function near the bottom of the Hugo Elias article (or the i and j values in the Total function if you're looking at the earlier Stack Overflow question).

I have a 500 by 500 array that I'm storing my pixel values into, so at first I thought I was just supposed to loop through the PerlinNoise_2D (or Total) function for each and every pixel, but that causes me to go out of bounds of my array IMMEDIATELY, because one of the first function calls has code that uses the index x - 1, which of course means when I give it my first x index (0, obviously), it breaks.

The PerlinNoise_2D (or Total) function definitely looks like the intended entry point for the Perlin class, and it also looks like the function that will give me back the values I want, but I can't for the life of me figure out what I'm supposed to be passing in, because it definitely isn't my x and y pixel array indices.

Does anybody know what I'm supposed to be passing in here?

genpfault
  • 51,148
  • 11
  • 85
  • 139
nullptr
  • 97
  • 1
  • 1
  • 7
  • What is this function call that breaks — the "one of the first function calls" — and how does it cause you to go out of bounds on your array? The noise function doesn't do any indexing into anything. – molbdnilo Apr 18 '15 at 01:20
  • In the interest of not perpetuating a common misconception: The noise described by Elias' article is not Perlin noise at all, but value noise, layered into fractal noise (see relevant wikipedia articles). – LarsH Jan 10 '17 at 11:22

1 Answers1

9

You were correct in your original supposition that the function expects you to pass in a coordinate pair. Where you see x-1 in the code, it's just an intermediate value for the noise calculation. That value isn't used as an index into your array.

I have implemented the pseudocode in the following C++ program.

// Two-dimensional value noise based on Hugo Elias's description:
//   http://freespace.virgin.net/hugo.elias/models/m_perlin.htm

#include <cstdio>
#include <cmath>
#include <cstdlib>
using namespace std;

int numX = 512,
    numY = 512,
    numOctaves = 7;
double persistence = 0.5;

#define maxPrimeIndex 10
int primeIndex = 0;

int primes[maxPrimeIndex][3] = {
  { 995615039, 600173719, 701464987 },
  { 831731269, 162318869, 136250887 },
  { 174329291, 946737083, 245679977 },
  { 362489573, 795918041, 350777237 },
  { 457025711, 880830799, 909678923 },
  { 787070341, 177340217, 593320781 },
  { 405493717, 291031019, 391950901 },
  { 458904767, 676625681, 424452397 },
  { 531736441, 939683957, 810651871 },
  { 997169939, 842027887, 423882827 }
};

double Noise(int i, int x, int y) {
  int n = x + y * 57;
  n = (n << 13) ^ n;
  int a = primes[i][0], b = primes[i][1], c = primes[i][2];
  int t = (n * (n * n * a + b) + c) & 0x7fffffff;
  return 1.0 - (double)(t)/1073741824.0;
}

double SmoothedNoise(int i, int x, int y) {
  double corners = (Noise(i, x-1, y-1) + Noise(i, x+1, y-1) +
                    Noise(i, x-1, y+1) + Noise(i, x+1, y+1)) / 16,
         sides = (Noise(i, x-1, y) + Noise(i, x+1, y) + Noise(i, x, y-1) +
                  Noise(i, x, y+1)) / 8,
         center = Noise(i, x, y) / 4;
  return corners + sides + center;
}

double Interpolate(double a, double b, double x) {  // cosine interpolation
  double ft = x * 3.1415927,
         f = (1 - cos(ft)) * 0.5;
  return  a*(1-f) + b*f;
}

double InterpolatedNoise(int i, double x, double y) {
  int integer_X = x;
  double fractional_X = x - integer_X;
  int integer_Y = y;
  double fractional_Y = y - integer_Y;

  double v1 = SmoothedNoise(i, integer_X, integer_Y),
         v2 = SmoothedNoise(i, integer_X + 1, integer_Y),
         v3 = SmoothedNoise(i, integer_X, integer_Y + 1),
         v4 = SmoothedNoise(i, integer_X + 1, integer_Y + 1),
         i1 = Interpolate(v1, v2, fractional_X),
         i2 = Interpolate(v3, v4, fractional_X);
  return Interpolate(i1, i2, fractional_Y);
}

double ValueNoise_2D(double x, double y) {
  double total = 0,
         frequency = pow(2, numOctaves),
         amplitude = 1;
  for (int i = 0; i < numOctaves; ++i) {
    frequency /= 2;
    amplitude *= persistence;
    total += InterpolatedNoise((primeIndex + i) % maxPrimeIndex,
        x / frequency, y / frequency) * amplitude;
  }
  return total / frequency;
}

int main(int argc, char** args) {
  if (argc >= 3) {
    numX = atoi(args[1]);
    numY = atoi(args[2]);
  }
  if (argc >= 4) {
    numOctaves = atoi(args[3]);
  }
  if (argc >= 5) {
    persistence = atof(args[4]);
  }
  if (argc >= 6) {
    primeIndex = atoi(args[5]) % maxPrimeIndex;
  }
  fprintf(stderr, "numX: %d, numY: %d, numOctaves: %d, persistence: %.5f, ",
      numX, numY, numOctaves, persistence);
  fprintf(stderr, "primeIndex: %d\n", primeIndex);
  printf("var rawNoise = [\n");
  for (int y = 0; y < numY; ++y) {
    for (int x = 0; x < numX; ++x) {
      double noise = ValueNoise_2D(x, y);
      if (x == 0) {
        printf("  [");
      }
      printf("%.5f", noise);
      if (x == numX-1) {
        printf("]");
        if (y == numY-1) {
          printf("\n];\n");
        } else {
          printf(",\n");
        }
      } else {
        printf(", ");
      }
    }
  }
  return 0;
}

This program accepts up to five arguments on the command line. The first four arguments correspond to the parameters numX, numY, numOctaves, and persistence, respectively.

The fifth argument is primeIndex, an integer from 0 to 9, which determines which of the ten random-number generators is called first. Thus, you can get ten different results after fixing the values of the other four parameters.

The output of the program is a JavaScript array. If you store this output in a file called rawNoise.js, you can load the following web page to view an image of the noise.

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
 "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head>
  <title> Demonstration of two-dimensional value noise </title>
<script src="rawNoise.js"></script>
<script>
  var ValueNoise = {
    noise: { raw: rawNoise }
  };
  ValueNoise.load = function () {
    var g = ValueNoise,
        raw = g.noise.raw,
        numR = g.numR = raw.length,
        numC = g.numC = raw[0].length,
        minValue = raw[0][0],
        maxValue = minValue;
    for (var r = 0; r < numR; ++r) {
      for (var c = 0; c < numC; ++c) {
        maxValue = Math.max(maxValue, raw[r][c]);
        minValue = Math.min(minValue, raw[r][c]);
      }
    }
    var valueSpread = maxValue - minValue;
    console.log(minValue, maxValue, valueSpread);
    var container = document.getElementById('display'),
        canvas = document.createElement('canvas'),
        context = canvas.getContext('2d'),
        imageData = context.createImageData(numC, numR),
        data = imageData.data;
    for (var r = 0; r < numR; ++r) {
      for (var c = 0; c < numC; ++c) {
        var value = raw[r][c],
            scaled = Math.round(255 * (value - minValue) / valueSpread),
            pos = r*4*numC + 4*c;
        data[pos] = data[pos+1] = data[pos+2] = scaled;
        data[pos+3] = 255;
      }
    }
    console.log(imageData);
    canvas.width = numC;
    canvas.height = numR;
    container.appendChild(canvas);
    context.putImageData(imageData, 0, 0);
  };
  window.onload = ValueNoise.load;
</script>
</head>
<body>
<div id="wrapper">

<div id="display"></div>

</div><!--end wrapper -->
</body>
</html>

On a Unix-style command line, you can compile and run the C++ program like this:

g++ -O2 noise.cpp -o noise
./noise 800 800 9 0.65 3 > rawNoise.js

Then if you open the above web page, you'll see this image:

enter image description here

Michael Laszlo
  • 12,009
  • 2
  • 29
  • 47
  • Thanks a ton. I thought I was supposed to be storing an array of just the raw noise data, and then looping through that array and using it to create a smoothed version, which then led me down the path of thinking that the x - 1 was supposed to be the index into it. Thanks for clearing that up. – nullptr Apr 18 '15 at 02:12
  • I still was having issues, so I copy and pasted your code in line for line, and I'm still getting this: http://i.imgur.com/IxoBSE6.png Is there something I'm missing here? I changed the numX and numY to make a 500x500 map. The highest value out of all 250000 values i got back was 3.12 something, and the lowest was 0.0015 something. So I'm taking the range 0 to 4 and mapping it to 0 to 255, and displaying the color using that (0 being black, 255 being white), and that's what I get. Is there something I've yet to do? – nullptr Apr 18 '15 at 03:40
  • The repetition seems to be caused by the algorithm parameters. Have you tried playing around with those? – Michael Laszlo Apr 18 '15 at 03:49
  • By still, I mean even though I was using your code as reference when making mine, and I'm having issues. I decided to just copy and past your entire code to see if yours was returning something mine wasn't. It returned more or less the same thing. And yes, I checked my array. I taking the double noise, and immediately storing it into my array. After that, I map those values to a range that I can use for color. – nullptr Apr 18 '15 at 03:50
  • Yeah, I messed with the parameters a little, but It doesn't seem to be doing much good. I'm not super concerned with the repetition, though. I thought by this point, it was supposed to have a cloudy look, like this: http://cochoy-jeremy.developpez.com/tutoriels/2d/introduction-bruit-perlin/images/perlin_noise.png – nullptr Apr 18 '15 at 03:52
  • I'm surprised by the repetition, too. Let me plug in some values and see what I come up with. Hang on. – Michael Laszlo Apr 18 '15 at 03:57
  • I took a look at the [article with that cloudy picture](http://cochoy-jeremy.developpez.com/tutoriels/2d/introduction-bruit-perlin/) and found that it's the result of a three-step process: noise generation; smoothing; juxtaposition at different scales. [This picture in the article](http://cochoy-jeremy.developpez.com/tutoriels/2d/introduction-bruit-perlin/images/3step.png) illustrates the three steps. That doesn't explain the repetitive noise, though. I'm still looking into it. – Michael Laszlo Apr 18 '15 at 04:49
  • I found a bug in my code. The frequency is supposed to be 2 to the power of i, not 2 times i. Likewise for persistence. I changed the relevant lines above. You can look for the two lines that contain `pow` or just copy the whole thing again. Now the output looks cloudy as it's supposed to. I'm not sure about the repetition yet. My investigation continues. – Michael Laszlo Apr 18 '15 at 05:03
  • It looks like [Hugo Elias's article](http://freespace.virgin.net/hugo.elias/models/m_perlin.htm) has a similar image about a quarter of the way down the page, as if that's supposed to be the end result. I think that's what the `PerlinNoise_2` function is supposed to be doing. It adds up those 8 different octaves, and the result is the cloudy image – nullptr Apr 18 '15 at 05:05
  • I've found another problem with my implementation. There's supposed to be a whole suite of noise functions that are parameterized by random primes. I'm working on a fix. – Michael Laszlo Apr 18 '15 at 05:08
  • You said the image you're getting now looks cloudy like it's supposed to. Are you using the same parameters as before? I went in and corrected the multiplying issues, and while the image did get a little blurrier, it's still pretty far from looking like that [cloudy image](http://cochoy-jeremy.developpez.com/tutoriels/2d/introduction-bruit-perlin/images/perlin_noise.png). Right now, it looks like [this](http://i.imgur.com/MiXu1KT.png) for me. Oh, and thanks for all your help so far btw – nullptr Apr 18 '15 at 06:12
  • Yeah, that's what I'm getting too. Not really cloudy, just blurred. I have a suite of different random-number generators now, and the image still isn't coming out right. At this point it looks to me like my implementation does exactly what the pseudocode says, so I'm wondering what else might be wrong. I'm going to look into this a little bit more before I go to bed. I'll resume tomorrow because I'm interested in this subject. – Michael Laszlo Apr 18 '15 at 06:23
  • Hurray! It's working at last. I figured out that you have to divide the noise values by the frequency, not multiply them. I don't know why the pseudocode shows multiplication. After fixing that problem, I added basic support for command-line arguments so that you can test different settings without having to recompile. Also, the output is now a JavaScript array that can be used in a web page, which I have also provided. – Michael Laszlo Apr 18 '15 at 10:16