Beklager, du har ikke den krævede version af Flash Player. Du kan hente den fra http://www.adobe.com/go/getflash/

Intro

Denne guide er beregnet som en introduktion til og nem opsætning af et animeret Perlin noise. Den er baseret på arbejdet af Seb Lee-Delisle, Ron Valstar og selvfølgelig Ken Perlin.

Perlin noise er et specialiseret slags noise som har en smoothing/gradient:

Denne type noise kan bruges til mange ting, både direkte visuelt, men også til at styre visuelle elementer eller pathing, flocking og animation af objekter. Da Perlin noise består af gradienter egner det sig godt til at generere organiske og flydende animationer.

Perlin noise som 3D terræn
Perlin noise til at lave flocking

Princippet

Her i guiden vil jeg fokusere på at lave et animeret Perlin noise ligesom det Seb har lavet.

Princippet bag et animeret Perlin noise er at generere et 3D Perlin noise og så animere ved at tage "slices" på z-aksen, dette giver en flydende animation, hvorimod hvis man bare gentegnede et 2D Perlin noise med et nyt seed (tilfældighed) ville det give et meget hakkende resultat.

Flash understøtter som standard kun 2D Perlin noise med BitmapData.perlinNoise(), men heldigvis har Ron Valstar konverteret Ken Perlin's originale 3D Perlin Noise til AS3 hvorefter Mario Klingemann har lavet nogle optimeringer af klassen.

Her er et eksempel lavet af Ron Valstar hvor klassen er brugt, den viser også de forskellige parametre man kan stille på:

Beklager, du har ikke den krævede version af Flash Player. Du kan hente den fra http://www.adobe.com/go/getflash/

Inden jeg viser hele koden der skal til for at få et animeret Perlin Noise til at køre så lad os lige kigge på et par kodestumper.

Perlin settings

Først de settings vi laver på OptimizedPerlin klassen(den vi downloadede), det er de samme parametre som vi havde i ovenstående eksempel.

1
2
3
4
5
6
7
// settings for the perlin noise renderer
// size of areas, integers, higher = smaller areas = more detailed
OptimizedPerlin.octaves = 3;
// smoothing, values: 0-1, lower = more smooth
OptimizedPerlin.falloff = 0.5;
// generate perlin seed(randomness)
OptimizedPerlin.seed = Math.round(Math.random() * 123);

Octaves er mængden/størrelsen af "områder" og dermed også detaljegraden, højere betyder flere "områder" mere detaljeret. Falloff er mængden af smoothing(gradient graden), lavere betyder mere smoothing. Seed er hvilken "opsætning" man får, så man sætter den til et random tal hver gang til at få et forskelligt resultet.

1
2
3
4
5
// create bitmapdata that we will draw perlin map onto, keep size small for fast render
_perlinData = new BitmapData(50,25,false,0x000000);
 
// create bitmap for perlin bitmapdata, smoothing is important cause it will be scaled
var perlinBitmap:Bitmap = new Bitmap(_perlinData,"auto",true);

Det bitmapData vi genererer vores Perlin noise på skal være meget lille(her 50x25px) for at vi kan generere vores noise hurtigt nok til at animere det, men heldigvis kan vi bare skalere vores bitmap op med smoothing på og stadig få et godt resultet.

Draw Perlin

Her er metoden som genererer og tegner vores Perlin noise:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
// DRAW PERLIN
private function drawPerlin():void
{
  // lock the bitmapdata (block updates)
  _perlinData.lock();
  
  // run through each pixel in the bitmapdata
  for (var i:Number = 0; i < _perlinData.width; ++i) 
  {
    for (var j:Number = 0; j < _perlinData.height; ++j) 
    {
      // get value from perlin noise (0-1)
      var noiseValue:Number = OptimizedPerlin.noise( i/_perlinData.width, j/_perlinData.height, _zValue );
      // convert value to color amount (0-255)
      var noiseColorValue:int = int( (.5+1.*(noiseValue-.5))*255 );
      // create blue color between full blue and full white
      var color:uint = noiseColorValue << 16 | noiseColorValue << 8 | 255;
      // set color on bitmapdata pixel
      _perlinData.setPixel(i, j, color);
    }
  }
  
  //unlock the bitmapdata
  _perlinData.unlock();
}

Vi starter og slutter med at låse vores bitmapdata så der ikke bliver sendt updates til vores bitmap for hver pixel vi løber igennem. Efter den er låst så løber vi igennem hver pixel vi har i vores bitmapdata og får Perlin noise værdien med OptimizedPerlin.noise(), som tager imod x, y og z parametre som alle skal være meget lave tal. Herefter konverterer vi værdien fra 0-1 til 0-255 så vi kan bruge det til at lave en farve, så laver vi en blå farve mellem helt blå og helt hvid, og til sidst tegner vi farven på den pixel i vores bitmapdata som vi er kommet til.

Enter Frame

Til sidst har vi den funktion der sørger for at animere vores Perlin noise, den bliver kaldt hver frame.

1
2
3
4
5
6
7
8
9
// ENTER FRAME HANDLER
private function enterFrameHandler(event:Event):void
{
  // draw perlin map
  drawPerlin();
  
  // increment z value used for perlin map
  _zValue += 0.04;
}

Her tegner vi vores Perlin noise og hæver z værdien.

Dette er stort set alt der skal til for at få lavet et animeret Perlin noise, den fulde kode som jeg bare har sat som document class i Flash CS4 kan ses her:

Kildekode

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
package 
{
  // IMPORTS
  import flash.display.Sprite;
  import flash.display.StageScaleMode;
  import flash.display.StageAlign;
  import flash.display.BitmapData;
  import flash.display.Bitmap;
  import flash.events.Event;
  import nl.ronvalstar.math.OptimizedPerlin;
  
  // CLASS
  public class PerlinAnim extends Sprite
  {
    //PRIVATE VARS
    private var _perlinData:BitmapData;
    private var _perlinHolder:Sprite;
    private var _zValue:Number = 1;
    
    // CONSTRUCTOR
    public function PerlinAnim()
    {
      // set stage scale and align
      stage.scaleMode = StageScaleMode.NO_SCALE;
      stage.align = StageAlign.TOP_LEFT;
      
      // initialize
      init();
    }
    
    // INIT
    private function init():void
    {
      // create the perlin bitmaps etc.
      setupPerlin();
      
      // listen for stage resize and enter frame
      stage.addEventListener(Event.RESIZE, stageResizeHandler, false, 0, true);
      addEventListener(Event.ENTER_FRAME, enterFrameHandler, false, 0, true);
    }
    
    // SETUP PERLIN
    private function setupPerlin():void
    {
      // settings for the perlin noise renderer
      // size of areas, integers, higher = smaller areas = more detailed
      OptimizedPerlin.octaves = 3;
      // smoothing, values: 0-1, lower = more smooth
      OptimizedPerlin.falloff = 0.5;
      // generate perlin seed(randomness)
      OptimizedPerlin.seed = Math.round(Math.random() * 123);
      
      // create bitmapdata that we will draw perlin map onto, keep size small for fast render
      _perlinData = new BitmapData(50,25,false,0x000000);
      
      // create bitmap for perlin bitmapdata, smoothing is important cause it will be scaled
      var perlinBitmap:Bitmap = new Bitmap(_perlinData,"auto",true);
      
      // setup holder and add to stage
      _perlinHolder = new Sprite();
      _perlinHolder.addChild(perlinBitmap);
      addChild(_perlinHolder);
      
      // scale perlin holder to stage size
      stageResizeHandler(null);
    }
    
    // DRAW PERLIN
    private function drawPerlin():void
    {
      // lock the bitmapdata (block updates)
      _perlinData.lock();
      
      // run through each pixel in the bitmapdata
      for (var i:Number = 0; i < _perlinData.width; ++i) 
      {
        for (var j:Number = 0; j < _perlinData.height; ++j) 
        {
          // get value from perlin noise (0-1)
          var noiseValue:Number = OptimizedPerlin.noise( i/_perlinData.width, j/_perlinData.height, _zValue );
          // convert value to color amount (0-255)
          var noiseColorValue:int = int( (.5+1.*(noiseValue-.5))*255 );
          // create blue color between full blue and full white
          var color:uint = noiseColorValue << 16 | noiseColorValue << 8 | 255;
          // set color on bitmapdata pixel
          _perlinData.setPixel(i, j, color);
        }
      }
      
      //unlock the bitmapdata
      _perlinData.unlock();
    }
    
    // ENTER FRAME HANDLER
    private function enterFrameHandler(event:Event):void
    {
      // draw perlin map
      drawPerlin();
      
      // increment z value used for perlin map
      _zValue += 0.04;
    }
    
    // STAGE RESIZE HANDLER
    private function stageResizeHandler(event:Event):void
    {
      // scale perlin holder to stage
      _perlinHolder.scaleX = stage.stageWidth/_perlinData.width;
      _perlinHolder.scaleY = stage.stageHeight/_perlinData.height;
    }
  }
}