I've started learning about perlin noise generation, and I wanted to try to make my own generator in JavaScript. To get me started, I've been following along with this youtube tutorial. to try and copy their basic implementation. I've also been reading this article.
I've provided a jsFiddle of my implementation, which shows what I'm doing and what the output is. Instead of the smoothly-flowing, bubbling noise I see in the youtube tutorial, I get tightly-constricted black squiggles. Here's a picture:
Here's my generator code:
var p = [151,160,137,91,90,15,
131,13,201,95,96,53,194,233,7,225,140,36,103,30,69,142,8,99,37,240,21,10,23,
190, 6,148,247,120,234,75,0,26,197,62,94,252,219,203,117,35,11,32,57,177,33,
88,237,149,56,87,174,20,125,136,171,168, 68,175,74,165,71,134,139,48,27,166,
77,146,158,231,83,111,229,122,60,211,133,230,220,105,92,41,55,46,245,40,244,
102,143,54, 65,25,63,161, 1,216,80,73,209,76,132,187,208, 89,18,169,200,196,
135,130,116,188,159,86,164,100,109,198,173,186, 3,64,52,217,226,250,124,123,
5,202,38,147,118,126,255,82,85,212,207,206,59,227,47,16,58,17,182,189,28,42,
223,183,170,213,119,248,152, 2,44,154,163, 70,221,153,101,155,167, 43,172,9,
129,22,39,253, 19,98,108,110,79,113,224,232,178,185, 112,104,218,246,97,228,
251,34,242,193,238,210,144,12,191,179,162,241, 81,51,145,235,249,14,239,107,
49,192,214, 31,181,199,106,157,184, 84,204,176,115,121,50,45,127, 4,150,254,
138,236,205,93,222,114,67,29,24,72,243,141,128,195,78,66,215,61,156,180,
151,160,137,91,90,15,
131,13,201,95,96,53,194,233,7,225,140,36,103,30,69,142,8,99,37,240,21,10,23,
190, 6,148,247,120,234,75,0,26,197,62,94,252,219,203,117,35,11,32,57,177,33,
88,237,149,56,87,174,20,125,136,171,168, 68,175,74,165,71,134,139,48,27,166,
77,146,158,231,83,111,229,122,60,211,133,230,220,105,92,41,55,46,245,40,244,
102,143,54, 65,25,63,161, 1,216,80,73,209,76,132,187,208, 89,18,169,200,196,
135,130,116,188,159,86,164,100,109,198,173,186, 3,64,52,217,226,250,124,123,
5,202,38,147,118,126,255,82,85,212,207,206,59,227,47,16,58,17,182,189,28,42,
223,183,170,213,119,248,152, 2,44,154,163, 70,221,153,101,155,167, 43,172,9,
129,22,39,253, 19,98,108,110,79,113,224,232,178,185, 112,104,218,246,97,228,
251,34,242,193,238,210,144,12,191,179,162,241, 81,51,145,235,249,14,239,107,
49,192,214, 31,181,199,106,157,184, 84,204,176,115,121,50,45,127, 4,150,254,
138,236,205,93,222,114,67,29,24,72,243,141,128,195,78,66,215,61,156,180]
function generatePerlinNoise(x, y){
// Determine gradient cell corners
var xi = Math.floor(x) & 255;
var yi = Math.floor(y) & 255;
var g1 = p[p[xi] + yi];
var g2 = p[p[xi + 1] + yi];
var g3 = p[p[xi] + yi + 1];
var g4 = p[p[xi + 1] + yi + 1];
// Get point within gradient cell
var xf = x - Math.floor(x);
var yf = y - Math.floor(y);
// Get dot products at each corner of the gradient cell
var d1 = generateGradient(g1, xf, yf);
var d2 = generateGradient(g2, xf - 1, yf);
var d3 = generateGradient(g3, xf, yf - 1);
var d4 = generateGradient(g4, xf - 1, yf - 1);
var xFade = fade(xf);
var yFade = fade(yf);
var x1Interpolated = linearInterpolate(xFade, d1, d2);
var x2Interpolated = linearInterpolate(xFade, d3, d4);
var noiseValue = linearInterpolate(yFade, x1Interpolated, x2Interpolated);
return noiseValue;
}
function generateGradient(hash, x, y){
switch(hash & 3){
case 0: return x + y;
case 1: return -x + y;
case 2: return x - y;
case 3: return -x - y;
default: return 0;
}
}
// This is the fade function described by Ken Perlin
function fade(t){
return t * t * t * (t * (t * 6 - 15) + 10);
}
function linearInterpolate(amount, left, right){
return ((1-amount) * left + (amount * right))
}
I'm utilizing the generator function by dividing the pixel x and y values by my canvas height, and scaling by a frequency variable:
var freq = 12;
var noise = generatePerlinNoise((x/canvas.offsetHeight)*freq, (y/canvas.offsetHeight)*freq);
noise = Math.abs(noise);
I'm currently just using the noise value to generate a greyscale color value:
var blue = Math.floor(noise * 0xFF); // Scale 255 by our noise value,
// and take it's integer portion
var green = Math.floor(noise * 0xFF);
var red = Math.floor(noise * 0xFF);
data[i++] = red;
data[i++] = green;
data[i++] = blue;
data[i++] = 255;
The point of this for me is to learn more about noise generation and javascript. I've tried to think through the problem and made some observations:
- There are no visible artifacts, so it seems like my fade function is working fine.
- There don't seem to be any repeating patterns, so that's a good sign.
- I go generate a complete range of values in the greyscale - not just black and white.
- The general issue seems to be how the gradient at each pixel affects its neighbors: Mine seem to clump together in snake-like ropes of fixed widths. It seems like the gradient vector options supplied and the permutation table used to randomly-ish select them would govern this, but mine are an exact copy from the tutorial: The same 4 vectors each pointing into a quadrant at 45 degrees, and the standard permutation table.
This leaves me stumped as to figuring out what the cause is. My general suspicions boil down to:
- I've messed up the algorithm somewhere in a subtle way that I keep overlooking. (Most likely)
- There's a subtle difference in the way JavaScript does something that i'm overlooking. (Maybe)
- I'm generating noise correctly, but incorrectly applying the result to the RGB values used in my canvas image data. (Least likely)
I'd really like to get to the bottom of this. Thanks in advance for your help! :)
Also: I DO think this pattern is cool, and this is a learning exercise, so if anyone can share insight into why I'm getting this pattern specifically, that'd be great!