Monday, May 25, 2009

An Oscar Algorithm - Silverlight Real-time 3D Perlin Noise

Perlin Noise Cloud SkyPerlin Noise is a computer graphics algorithm, which is widely used in computer games and movies as a visual effect. It was developed by Ken Perlin in the early 1980's and was used in the movie TRON. In 1997 Perlin received a Technical Achievement Award from the Academy of Motion Picture Arts for his work. So Perlin Noise is an Oscar-winning algorithm and I'm pretty sure almost everyone has seen it before.
Perlin Noise could produce pseudo-random semi-natural textures with fractal characteristics and is mostly used for procedural image generation. The algorithm can be controlled with various parameters and is able to produce good looking cloud / sky, fire, smoke, wood or marble textures and height maps, just to name a few.
In short, the Perlin Noise algorithm generates random noise functions with various frequencies and amplitudes, sums them up and smoothes the result. I don't want to bore you with more mathematical details, which were already explained several times on the web. If you are interested, I refer you to the Wikipedia article and the link section, Ken Perlin's site and to this excellent explanation. 

Live
My Silverlight implementation is the three dimensional improved Perlin Noise algorithm.

The initial Frequency and the Amplitude of the noise function can be changed. The Octaves parameter defines how many iterations of the noise function are summed up. Like in music the frequency is doubled in every subsequent octave. The Persistence controls how much the amplitude changes after each iteration. Press the Randomize button to reinitialize the noise function with new pseudo-random seeds.

How it works
The Perlin Noise computing loop:
for (int i = 0; i < this.Octaves; i++)
{
   noise += Noise(x * freq, y * freq, z * freq) * amp;
   freq  *= 2;
   amp   *= Persistence;
}

Each frame the Perlin Noise for every pixel of the 2D destination image is computed in real-time and is used as the alpha value for blending the noise color over the base color, then the z-value is increased. The noise's third dimension, the z-value, is actually used as time for the animation. So each frame the next slice of the 3D noise cube is computed and shown, thus resulting in a smooth animation.
For the source of the destination image the great RawPngBufferStream from the open source GameEngine Balder is used.
int off = 0;
for (int y = 0; y < bufferStream.Height; y++)
{
   for (int x = 0; x < bufferStream.Width; x++)
   {
      float a = perlinNoise.Compute(x, y, perlinZ);
      float ai = 1 - a;
      bufferStream[off + aOff] = (byte)(a * noiseColor.A + ai * baseColor.A); 
      bufferStream[off + rOff] = (byte)(a * noiseColor.R + ai * baseColor.R);
      bufferStream[off + gOff] = (byte)(a * noiseColor.G + ai * baseColor.G);
      bufferStream[off + bOFF] = (byte)(a * noiseColor.B + ai * baseColor.B);
      off += 4;
   }
   off++;
}
if (Chk3D.IsChecked.Value)
{
   perlinZ++;
}
imgSource.SetSource(bufferStream);


Source code
You can download the Silverlight 2 version here. Feel free to use or modify it as you like. If you do so please drop me a line, because I'm curious to know where (you want to) use it. Have fun!

Update 07-26-2009
I updated the project to Silverlight 3. Now it uses the WriteableBitmap instead of Balder's RawPngBufferStream. You can download the updated Visual Studio 2008 solution from here.

18 comments:

  1. I like it. Thxs.

    ReplyDelete
  2. Very nice effect. It probably will be much faster if you port it to Silverlight 3.

    ReplyDelete
  3. Thanks.
    I wanted that as many as possible can watch, but Silverlight 3 and it's WriteableBitmap would surely run faster. I'm looking forward to the 10th of July, when SL3 will probably be released.

    ReplyDelete
  4. You have a random var... for us to create heightmaps we have to have the same output to the same input vars...

    ReplyDelete
  5. I think you have to use a seed parameter

    ReplyDelete
  6. @nfBarata If you always need the same noise, then you should use this Random class ctor: http://msdn.microsoft.com/en-us/library/ctssatww.aspx, this will result in a identical permutation vector.

    ReplyDelete
  7. Yes, I did that, I added a get/set parameter to the class called seed and used it as a parameter to the random constructor

    ReplyDelete
  8. @nfBarata Glad you like and use it. You use it for heightmap generation? Is it live somewhere?

    ReplyDelete
  9. Hi Rene!
    I was just about to drop you a comment, when I saw yours on the Living Noise :) Thanks!
    Your perlin noise sample is very cool.

    ReplyDelete
  10. Very nice. Have you tried porting it to Silverlight 3, using WriteableBitmaps? I'd love to see that if you have.

    ReplyDelete
  11. Thanks.
    I've updated it to SL3 shorly after SL3 came out, please read the last paragraph: "Update 07-26-2009".

    ReplyDelete
  12. This is really beautiful!

    Is it possible to overlay this animation over an image?

    If so, can you please post a sample?

    ReplyDelete
  13. Hi Piet,

    this is possible without any problems.

    Just set the BaseColor to #00000000 which is fully transparent and lay the Image control where the effect is rendered to over another picture.

    ReplyDelete
  14. Nice, but a couple of comments. First, the noise function returns positive and negative values centered around 0 so when you clamp between 0 and 1 you're throwing at least half your generated data away. Also, the fractal algorithm returns values whose absolute value has a max of Amplitude * (1 - persistence ^ octaves) / (1 - persistence) so you're also throwing away anything between 1 and this max. I divided the output of Compute by this value (which I update in the setters for amplitude, persistence and octaves) which gives me values from -1 to 1 which I then map to the range 0 to 1 and then don't have to clamp any more plus I get all the noise rather than some fraction of it.

    Also, I think most machines these days take a performance hit when using floats rather than doubles. I modified it to use doubles and fps went from 20 to 24 or so.

    Finally, I haven't thought about it really carefully, but the Grad function which I see you took directly from Perlin's reference implementation is a bit opaque to me. I know he's trying to pick essentially random normalized vectors at each grid point, but in the explanations of his I've read he talks about generating points in a sphere and normalizing them, but then he uses this rather odd function in his reference implementation which he doesn't seem to explain anywhere that I see. Do you know what he's trying to do there?

    Anyway, thanks for the implementation!

    ReplyDelete
  15. Oh, BTW, with regard to my previous post, the Amplitude is just a scaling factor so when I mapped everything to the range 0 to 1 I essentially eliminated that scaling so I ended up tossing Amplitude as a parameter also.

    ReplyDelete
  16. I had a test yesterday , the final answer was bufferStream[off + aOff] = (byte)(a * noiseColor.A + ai * baseColor.A);
    bufferStream[off + rOff] = (byte)(a * noiseColor.R + ai * baseColor.R);
    bufferStream[off + gOff] = (byte)(a * noiseColor.G + ai * baseColor.G);
    bufferStream[off + bOFF] = (byte)(a * noiseColor.B + ai * baseColor.B);

    ReplyDelete
  17. hello, i think that this post is very good beacause has useful information.

    ReplyDelete