Ben Deitch

You must install Adobe Flash to view this content.

Home Blog Perlin Noise

Perlin Noise is essentially the average of smoothed noise. I'm just gonna dive right into how to do it becasue I don't really have a "hook"... ***Code is at the very end!***

How to make Perlin Noise:

1. Start with some regular "snow" with a given width/height (pixel)

private function Graph():void { for (var i:uint=0;i<stageW;i+=pixel) { for (var j:uint=0;j<stageH;j+=pixel) { var val = Math.random(); Draw(i,j,val); } } } private function Draw(xx:int,yy:int,alpha:Number):void { canvas.graphics.beginFill0x000000,alpha); canvas.graphics.drawRect(xx,yy,pixel,pixel); canvas.graphics.endFill(); }2. Smooth that snow linearly so that it doesn't look quite so harsh

Basically we loop through each 1x1 pixel in the image and determine what its value should be

So for each 1x1 pixel...

- Determine what quadrant of the father pixel the 1x1 pixel is in
- Depending on it's x and y position within the quadrant take the weighted average of the 4 closest father pixels
- That weighted average is now the value of the 1x1 pixel
- Store all of this data in an array associated with father pixel size

3. Now the goal is to do this for several different sizes of pixel and take the weighted average of all of these.

Let's say we're using pixels of size 64,32,16,8,4,2, and 1. After we've attained arrays of values for all of these pixel sizes we will divide the first array by 2^1, the second by 2^2, the third by 2^3, and so on.

Next, we sum up all of these arrays to get the final Perlin array!

Check it out below! Pixels of size 64,32,16,8,4,2, and 1 are being used. The left image is the unsmoothed noise, and the right image is the smoothed noise. The final image is the weighted average of all of the preceeding right side images, and creates Perlin noise!!! A bigger one.

You must install Adobe Flash to view this content.

package { import flash.display.Sprite; public class PerlinNoise extends Sprite { private var canvas:Sprite; private var stageW:Number = stage.stageWidth; private var stageH:Number = stage.stageHeight; private var pixels:Array = [128,64,32,16,8,4,2,1]; //pixel sizes private var Layers:Array = new Array(); public function PerlinNoise() { canvas = new Sprite(); addChild(canvas); MakeLayers(); CombineLayers(); } private function MakeLayers():void { for (var i:uint=0;i<pixels.length;i++) { Layers[i] = MakeALayer(pixels[i]); } }//Creates smoothed noise for each of the differnt pixel sizes private function CombineLayers():void { var A:Array = []; for (var i:uint=0;i<Layers[0].length;i++) { A[i] = new Array(); for (var j:uint=0;j<Layers[0][0].length;j++) { var val:Number = 0; for (var k:uint=0;k<Layers.length;k++) { var Exp:Number = Math.pow(2,k+1); if (k == (Layers.length-1)) {Exp = Math.pow(2,k);} val += (Layers[k][i][j]/Exp); } A[i][j] = (val); } } Graph(A); }//Combines each of the smoothed noises to create the Perlin noise private function Graph(PERLIN:Array):void { for (var i:uint=0;i<stageW;i++) { for (var j:uint=0;j<stageH;j++) { Draw(i,j,PERLIN[i][j]); } } }//Creates the graph private function MakeALayer(pixel:int):Array { var OneLayer:Array = new Array(); for (var i:uint=0;i<(stageW/pixel);i++) { OneLayer[i] = new Array(); for (var j:uint=0;j<(stageH/pixel);j++) { var val = Math.random(); OneLayer[i][j] = val; } } return(Smoothing(OneLayer,pixel)); }//Creates the unsmoothed noise and then sends it on to be smoothed. private function Smoothing(OneLaye:Array,pixe:int):Array { var val:Number = 0; var q:Number; //quadrant var help:Array = new Array(); for (var i:uint=0;i<stageW;i++) { help[i] = new Array(); for (var j:uint=0;j<stageH;j++) { var n = Math.floor(i/pixe); //What column we are in var m = Math.floor(j/pixe); //What row we are in var xish = (i%pixe)/(pixe) - 0.5;//x distance from left edge var yish = (j%pixe)/(pixe) - 0.5;//y distance from top edge q = Quadrant(xish,yish); xish = Fix(xish); yish = Fix(yish); /*There are four cases... and each case uses different adjacent squares 1. Pixel is in the first quadrant. x=+ y=+ //in flash +y is down ...*/ val = FindVal(q,n,m,xish,yish,OneLaye,pixe); help[i][j] = val; } } return (help); }//Smoothes out the noise private function FindVal(q:int,n:int,m:int,xish:Number,yish:Number,A:Array,pix:int):Number { var val = 0; if (q == 1) { val += (xish*yish)*A[n][m]; if (n != Math.ceil((stageW/pix)-1)){val += ((1-xish)*yish)*A[n+1][m];} if (m != Math.ceil((stageH/pix)-1)){val += (xish*(1-yish))*A[n][m+1];} if ((n != Math.ceil((stageW/pix)-1))&&(m != Math.ceil((stageH/pix)-1))){val += ((1-xish)*(1-yish))*A[n+1][m+1];} } if (q == 2) { val += (xish*yish)*A[n][m]; if (n != 0){val += ((1-xish)*yish)*A[n-1][m];} if (m != Math.ceil((stageH/pix)-1)){val += (xish*(1-yish))*A[n][m+1];} if ((n != 0) && (m != Math.ceil((stageH/pix)-1))){val += ((1-xish)*(1-yish))*A[n-1][m+1];} } if (q == 3) { val += (xish*yish)*A[n][m]; if (n != 0){val += ((1-xish)*yish)*A[n-1][m];} if (m != 0){val += (xish*(1-yish))*A[n][m-1];} if ((m != 0) && (n != 0)){val += ((1-xish)*(1-yish))*A[n-1][m-1];} } if (q == 4) { val += (xish*yish)*A[n][m]; if (n != (Math.ceil(stageW/pix)-1)){val += ((1-xish)*yish)*A[n+1][m];} if (m != 0){val += (xish*(1-yish))*A[n][m-1];} if ((n != (Math.ceil(stageW/pix)-1))&&(m != 0)){val += ((1-xish)*(1-yish))*A[n+1][m-1];} } return (val); }//Basically takes a point and returns the weighted average of the 4 closest father pixels private function Quadrant(xish:Number,yish:Number):int { var q:int; if (xish>=0 && yish>=0) {q = 1;} if (xish<0 && yish>=0) {q = 2;} if (xish<0 && yish<0) {q = 3;} if (xish>=0 && yish<0) {q = 4;} return (q); }//Finds what quadrant of the father pixel we are in private function Fix(p:Number):Number { if (p >= 0) {p = 1 - p;} if (p < 0) {p = 1 + p;} return(p); }//Makes the values range from .5->1->.5 as we go from the left edge to the right edge, //instead of from -.5->0->.5 private function Draw(xx:int,yy:int,a:Number):void { canvas.graphics.beginFill(0x000000,a);//a is alpha value canvas.graphics.drawRect(xx,yy,1,1); canvas.graphics.endFill(); }//Draws the graph one 1x1 pixel at a time } }

Posted by
JosephKnight.com
on
May 16th, 2011

This is great. I've only skimmed it so far but I believe your sampling works different than mine. I look forward to reading thru this tomorrow. I did some more work on mine and using various levels of threshold I was able to create some pretty artistic results. Here's my latest post with an image:http://josephknight.com/the-lonely-alien-stumbles-onto-an-ancient-relic/

Posted by
h_seldon
on
May 17th, 2011

Hi,Impressive and interesting - although it seems to be a little bit more comfortable to use Flash's built-in perlin noise method. A simple example:

import flash.display.BitmapData;

import flash.display.Bitmap;

//-----------------------------------------

var bm_someNoise:Bitmap;

var someNoise:BitmapData;

var frequencyX:Number = 100;

var frequencyY:Number = 100;

var octaves:Number = 4;

var seed:Number = Math.floor(Math.random()*1000);

var stitch:Boolean = false;

var fractal:Boolean = true;

var channels:Number = 7;

var gray:Boolean = false;

//-----------------------------------------

someNoise = new BitmapData(400,300,false);

someNoise.perlinNoise(frequencyX,frequencyY,octaves,seed,stitch,fractal,channels,gray);

bm_someNoise = new Bitmap(someNoise);

addChild(bm_someNoise)

Is your method faster (I didn't test that)? Or what is the advantage in using that instead of Flash's one?

Posted by
Ben
on
May 17th, 2011

I haven't played around with Flash's native Perlin generator, but I do hope it's faster.The reason I made this method is first and foremost just so I could have a better understanding of Perlin noise from the bottom up. Also, since I get an array with a value (between 0 and 1) for each pixel in the image, it makes it easy to use this noise for other applications. For example, to make a 3D terrain map.

http://bendeitch.com/blog/3d-terrain-from-the-perlin-noise/

***I don't know if Flash's native Perlin can give an array or not, but if it does than it's probably the better solution.