Whats Wrong With My Perlin Noise Function?

Started by
9 comments, last by ProgrammerGuy123 11 years, 10 months ago
I understand the concept behind perlin noise but I can't understand why the method doesn't return perlin values:



public int PerlinNoise2D(int x) {
int freq = 4;//initFrequency;
int amp = 1;//initAmplitude;
int total = 0;

for(int i = 0; i < octaves; i++) {
total += InterpolateNoise(x * freq) * amp;
}

return total;
}

int InterpolateNoise(int x) {

int v1 = new Random(x).Next(64);//this returns a pseudo-random number less than 64 with "x" as the seed
int v2 = new Random(x + 1).Next(64);

return CosineInterpolate(v1, v2, x, 4);
}

int CosineInterpolate(int a, int b, int x, int length) {
return (int)((1 + Math.Cos(3.1415f * x / length)) / 2 * (a - b) + b);
}


It generates repeating values that are not perlin and as explained before I need help on fixing it.

Thanks.
Advertisement
If I recall correctly, each time around the main loop you're meant to double the frequency and decrease the amplitude. You seem to be keeping them constant.
You are also using Random() wrong.
You should initialize it once, with a single seed of your choosing, and then just continuously call "next" to read out the random value.
You also aren't actually generating Perlin noise. The Hugo Elias article gets that wrong.

/pedanticdickweed

Random isn't designed to be used this way. If I remember correctly, it uses Knuth's subtractive generator under the hood, and I believe the subtractive generator uses a state table that is 56 entries long, which is iterated and the values set every time a seed is set. A seed is set twice every time InterpolateNoise is called, so that is a lot of redundant state setting going on in your inner loop. Random is fine to use in an application where you seed it once then just generate a sequence of random numbers; Perlin noise doesn't work that way, however.

For performance reasons, you should implement some sort of hash or folding algorithm instead. The reference implementation of Perlin noise performs hashing by using a table to "fold" bytes. The table is static and the random generation is stateless, meaning it is designed to not have an explicit seed method. Other possibilities include algorithms such as the FNV hash. These hash algorithms generate a random-seeming output based on an input, without storing any internal state or iterating any seed table.

Note that the noise that is implemented in the Hugo Elias article is commonly called "value noise", given that explicit values are generated at lattice points. It has the failing that it generates a lot of lattice based artifacts. Real Perlin noise is implemented as a set of wavelet functions centered upon lattice points; the weighted sum of wavelets has the effect of moving the highs and lows off of the lattice grid, serving to significantly reduce the occurrence of grid-based artifacts.

I highly recommend the paper "Simplex noise demystified"; it is mainly about Perlin's later algorithm, simplex noise, but it also includes a fine explanation of standard Perlin noise.

If I recall correctly, each time around the main loop you're meant to double the frequency and decrease the amplitude. You seem to be keeping them constant.


The values were not changing because I only had one octave for simplicity therefor there was no need for them to change.



You are also using Random() wrong.
You should initialize it once, with a single seed of your choosing, and then just continuously call "next" to read out the random value.


This was a mistake on my part I was thinking I needed a random value based on "x" which mean if "x" is ever the same it should generate the same number.

I have revised the code I think to fix this problem but no luck. First I need just one octaves to work then I can generate more and add them up but for now I'm focusing on one octave which you will see in my code:


public int PerlinNoise2D(int x) {
int total = 0;
int octaves = 1; //only one octave for simplicity
for(int i = 0; i < octaves; i++) {
int freq = 16;//16 for simplicity I only have one octave so it doesn't need to change
int amp = 1; //1 for simplicity again ^^^
total += InterpolateNoise(x * freq) * amp;
}
return total / octaves;
}
int InterpolateNoise(int x) {
int v1 = random.Next(64);
int v2 = random.Next(64);
return CosineInterpolate(v1, v2, x, 16); // the 16 is from the frequency
}
int CosineInterpolate(int a, int b, int x, int length) {
return (int)((1 + Math.Cos(3.1415f * x / length)) / 2 * (a - b) + b);
}
You have revised it to be even more wrong than before. By just calling Next without the seeding operation, you guarantee you get a different value for any given input point each time it is called. Random isn't really what you want to use here. You want a hash function, that correlates an input, x, to an output, y. The hash needs to ensure that the hash for x+1 is in no way correlated to the hash for x, so that patterns do not appear in the output.

jtippets linked to the reference implementation for Perlin noise. It might be a good idea to look and see how it does hashing, interpolation, etc...

Also I note that you seem to be using a lot of integer coordinates and values. There is a problem with this. The algorithm you are using will generate pseudo-random values at integer coordinates, but since you never sample the "between" points (non-integer), the result is probably going to look a great deal like white noise.

You have revised it to be even more wrong than before. By just calling Next without the seeding operation, you guarantee you get a different value for any given input point each time it is called. Random isn't really what you want to use here. You want a hash function, that correlates an input, x, to an output, y. The hash needs to ensure that the hash for x+1 is in no way correlated to the hash for x, so that patterns do not appear in the output.

jtippets linked to the reference implementation for Perlin noise. It might be a good idea to look and see how it does hashing, interpolation, etc...

Also I note that you seem to be using a lot of integer coordinates and values. There is a problem with this. The algorithm you are using will generate pseudo-random values at integer coordinates, but since you never sample the "between" points (non-integer), the result is probably going to look a great deal like white noise.


Ok so I tried to fix a lot all of these problems but the function is still acting up and I'm really confused on why because I scrapped the whole thing to use double values instead of integers and I even took the complete RNG from this article about perlin noise. This solves the problem of having to use hash tables because now I'll get a random number where x and x+1 is a completely different number. However now it seems to return negative values which makes absolutely no since considering I use his RNG not .NET's. Also the InterpolatedNoise method is almost the exact same I'm just not smoothing the values because I want to simplify it. Then there is the CosineInterpolate method. To me logically this must be the reason for my negative values because as stated before I took the entire RNG from the article so it can't be that. The CosineInterpolate method was taken from this article.

I've made no methods all the methods responsible for generating perlin values are from reliable sources. What is going on?


public double PerlinNoise2D(double x) {
int octaves = 1;
double freq = 16;
double amp = 1;
double total = 0;
for(int i = 0; i < octaves; i++) {
total += InterpolateNoise(x * freq) * amp;
}
return total;
}
double InterpolateNoise(double x) {
int xInt = (int)x;
double xFrac = x - xInt;
double v1 = Noise(xInt);
double v2 = Noise(xInt + 1);
return CosineInterpolate(v1, v2, xFrac);
}
double Noise(int x) {
x = (x<<13) ^ x;
return ( 1.0 - ((x * (x * x * 15731 + 789221) + 1376312589) & 0x7fffffff) / 1073741824.0);
}
double CosineInterpolate(double a, double b, double x) {
double f = (1 - Math.Cos(x * Math.PI)) / 2;
return (a * (1 - f) + b * f);
}
What result are you getting, and what do you expect?

A common mistake I see people make when they are first starting with noise is something like this:


for(x=0; x<100; ++x)
{
for(y=0; y<100; ++y)
{
values[x][y]=Noise(x,y);
}
}


There is a problem here that you can see if you understand that value noise generates pseudo-random values on the integer boundaries. If you are sampling like the above, you are only ever going to get values obtained on integer boundaries, and not any of the smoothly interpolated values that lie in between.

Of course, can't really say whether or not this is your problem without seeing the code where you actually call PerlinNoise2D from.

Edit: As regards to negative values, perhaps you didn't notice this part:


return ( 1.0 - ((x * (x * x * 15731 + 789221) + 1376312589) & 0x7fffffff) / 1073741824.0);


I don't want to personally figure out ranges for this monster, but I bet if you did you'd see that the subtraction sometimes causes it to go negative. Additionally, having a noise function that outputs in the range [-1,1] is common.
That is indeed how I'm called the method:


byte[,] temp = new byte[height, width];


for(int x = 0; x < width; x++) {
byte value = (byte)((PerlinNoise2D((double)x) * 64) + 64);
temp[value, x] = 1;
}



This is exactly why I was using integers in my pervious attempts for everything and not decimals. Is there any way I could efficiently convert the integers into decimals and back into inegers again? Or should I go back to the way I was doing it and have everything calculated with integers. Right now I'm multiplying the perlin value by 64 just for testing as you can see then adding the same value to get rid of any negatives. But like you said I'm only going to get values based on integer boundries so what should I do?
I like to look at this from the perspective of "how many features do I want to encompass?" Consider that each feature is a low or a high spot. In a single layer of lattice noise, there is roughly 1 feature per lattice point. So if your output buffer is, say, 100x100 and you do the naive mapping as above you are going to end up with 100x100 features, which is tantamount to white noise. The solution, then, is to remap the coordinate ranges to encompass as many features as you desire.

Consider this code:


for(x=0; x<100; ++x)
{
for(y=0; y<100; ++y)
{
values[x][y]=Noise(x/100,y/100);
}
}


The line Noise(x/100, y/100) effectively remaps the [0,100) coordinate range to the [0,1) coordinate range. Now, what once encompassed 100x100 features will instead encompass 1x1 features. So instead of seeing a "view" of a grid of 100x100 mountains, you are seeing a single mountain up close. This is where the smoothing of interpolation will generate useful output; the 4 bounding features of the lattice points defined by (0,0)->(1,1) are interpolated, generating all the gradual in-between values.

Conversely, the code:

for(x=0; x<100; ++x)
{
for(y=0; y<100; ++y)
{
values[x][y]=Noise(x/100 * 4, y/100 *4);
}
}


would encompass roughly 4x4 features, representing a slightly zoomed out view of roughly 4x4 mountains/valleys. This is why you want to operate upon floating-point coordinates, so that you can represent the locations in between integer coordinates.

This topic is closed to new replies.

Advertisement