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?
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:
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With