Jump to content

  • Log In with Google      Sign In   
  • Create Account


Like
0Likes
Dislike

Simple Clouds Part 1

By Francis "AK 47" Huang | Published Apr 13 2004 11:34 AM in Game Programming

If you find this article contains errors or problems rendering it unreadable (missing images or files, mangled code, improper text formatting, etc) please contact the editor so corrections can be made. Thank you for helping us improve this resource



Have you ever noticed how procedural many things in nature appear? Clouds are a perfect example of this. Even though all clouds are unique, they really just look like slight variations of one
another. Wanna make some clouds so you can stay glued to your computer without having to go outside to look at them? I will show you how to procedurally generate clouds and render them using
OpenGL.

We will use Perlin Noise to make clouds. Ken Perlin developed Perlin Noise in the 80s. A Perlin Noise function is a seeded pseudo random number generator. The noise function will always give the
same result for the same seed. The random values between two seeds will also smoothly interpolate between one another. These two features make Perlin Noise perfect for the procedural generation of
anything with a pseudo random appearance. Clouds are an ideal example! Ken Perlin has written a great tutorial on Perlin Noise. It is available at "http://www.noisemachine.com/talk1/">http://www.noisemachine.com/talk1/.

I will not explain too much math in this tutorial. The code will be without any optimization or unusual techniques, so you can grasp the general steps involved in a fast breezy manner. I am
assuming you know how to initialize OpenGL and already understand texture mapping. The code presented is easily portable.

Let us get started!

A basic noise map 32x32


float map32[32 * 32];

We start by declaring an array of size 32*32 as our basic noise map. This noise map is a building block to form…..

The cloud map


float map256[256 * 256];

The cloud map will hold our cloud.

Random noise generator

Next, we set random noise values ranging from -1 to 1 into our 32*32 map. First, we need a noise generator. Here is a popular one.


float Noise(int x, int y, int random)

{

    int n = x + y * 57 + random * 131;

    n = (n<<13) ^ n;

    return (1.0f - ( (n * (n * n * 15731 + 789221) +

            1376312589)&0x7fffffff)* 0.000000000931322574615478515625f);

Note the function takes in a random integer to generate different noise patterns.

Set noise to map

Now our function to set noise for the 32*32 noise map:


void SetNoise(float  *map)

{

  float temp[34][34];

We declare a temporary array to make our function cleaner. Why does the array hold 34*34 elements instead of 32*32? This is because we will need extra elements for side and corner mirroring, as we
shall see soon.


  int random=rand() % 5000;



  for (int y=1; y<33; y++) 

  for (int x=1; x<33; x++)

  {

    temp[x][y] = 128.0f + Noise(x,  y,  random)*128.0f;

  }

Here we insert the noise values one by one into the temporary array. Each time the function is called, a different set of noise values is generated. The color values of our cloud range from 0 to
256.

Seamless cloud


  for (int x=1; x<33; x++)

  {

    temp[0][x] = temp[32][x];

    temp[33][x] = temp[1][x];

    temp[x][0] = temp[x][32];

    temp[x][33] = temp[x][1];

  }

  temp[0][0] = temp[32][32];

  temp[33][33] = temp[1][1];

  temp[0][33] = temp[32][1];

  temp[33][0] = temp[1][32];

We mirror the side and corner elements so our final cloud will be seamless without any ugly borders showing.

Smooth…


  for (int y=1; y<33; y++)

    for (int x=1; x<33; x++)

    {

      float center = temp[x][y]/4.0f;

      float sides = (temp[x+1][y] + temp[x-1][y] + temp[x][y+1] + temp[x][y-1])/8.0f;

      float corners = (temp[x+1][y+1] + temp[x+1][y-1] + temp[x-1][y+1] + temp[x-1][y-1])/16.0f;



      map32[((x-1)*32) + (y-1)] = center + sides + corners;

    }

}

Finally, we take the values from the center, corners and sides. We weigh and average them up to give the noise a smoother look. This is our first smoothing process.

Making the noise less blocky

The current noise map consists of a bunch of pixels with random values. There is too much noise change between neighboring pixels, though. To solve this problem we will use a second smoothing
process known as interpolation. We will average the value of each pixel value with that of its neighbors' values.


float Interpolate(float x, float y, float  *map)

{

  int Xint = (int)x;

  int Yint = (int)y;



  float Xfrac = x - Xint;

  float Yfrac = y - Yint;

Parameter x and y are float indices between two neighboring integer indices. We obtain them by scaling integer indices with an octave factor, this we shall see later.


  int X0 = Xint % 32;

  int Y0 = Yint % 32;

  int X1 = (Xint + 1) % 32;

  int Y1 = (Yint + 1) % 32;

Next, we define neighboring integer indices. We applied modulus because the noise map row and column consist of only 32 dots.


  float bot = map[X0*32 + Y0] + Xfrac * (map[X1*32 + Y0] - map[X0*32 + Y0]);

  float top = map[X0*32 + Y1] + Xfrac * (map[X1*32 +  Y1] - map[X0*32 + Y1]);



  return (bot + Yfrac * (top - bot));

}

Finally we get noise values from the neighboring indices, average them with delta and return a blunted noise value located at float x and y indices.

Overlap the octaves

Now we will make a couple of noise layers called octaves. The first octave is a blowup of a single 32*32 noise map to a 256*256 map. The second octave is a blowup of four 32*32 maps to four
128*128 maps which are tiled together. This process goes on for higher octaves.

The octaves are then overlapped together to give our cloud more turbulence. We will use four octaves for our cloud. You can use more octaves if you like.


void OverlapOctaves(float  *map32, float  *map256)

{

  for (int x=0; x<256*256; x++)

  {

    map256[x] = 0;

  }

We start working with the 256*256 map by clearing its old values


  for (int octave=0; octave<4; octave++)

    for (int x=0; x<256; x++)

      for (int y=0; y<256; y++)

      {

        float scale = 1 / pow(2, 3-octave);

        float noise = Interpolate(x*scale, y*scale , map32);

Here we scale the x and y indices with the values of 1/8, 1/4, 1/2 and 1 consisting of four octaves. The scaled x, y indices and 32*32 map are then sent as parameters for interpolation to return a
smoother noise value.


        map256[(y*256) + x] += noise / pow(2, octave);

      }

}

The octaves are added together with the proper weight factors.

You could replace pow(2, i) with 1<<i for faster computation.

Filter the noise with exponential function

This is the last function we need to get a cloud! We use an exponential filter to make the 256*256 noise map look more like a cloud.


void ExpFilter(float  *map)

{

  float cover = 20.0f;

  float sharpness = 0.95f;



  for (int x=0; x<256*256; x++)

  {

    float c = map[x] - (255.0f-cover);

    if (c<0)     c = 0;

    map[x] = 255.0f - ((float)(pow(sharpness, c))*255.0f);

  }

}

Putting it all together


float map32[32 * 32];

float map256[256 * 256];



void Init()

{

  SetNoise(map32);

}



void LoopForever()

{

  OverlapOctaves(map32, map256);

  ExpFilter(map256);

}

Moving & rendering the clouds in OpenGL

At last the code to render the cloud in OpenGL:


void DrawGLScene()

{

  glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

  glLoadIdentity();



  LoopForever();                   //Our cloud function  

    

  char texture[256][256][3];       //Temporary array to hold texture RGB values 



  for(int i=0; i<256; i++)         //Set cloud color value to temporary array

  for(int j=0; j<256; j++) 

  {

    float color = map256[i*256+j]; 

    texture[i][j][0]=color;

    texture[i][j][1]=color;

    texture[i][j][2]=color;

  }



  unsigned int ID;                 //Generate an ID for texture binding                     

  glGenTextures(1, &ID);           //Texture binding 

  glBindTexture(GL_TEXTURE_2D, ID);



  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);

  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);

  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);

  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);

   

  gluBuild2DMipmaps(GL_TEXTURE_2D, GL_RGB, 256, 256, GL_RGB, GL_UNSIGNED_BYTE, texture);



  glMatrixMode(GL_TEXTURE);        //Let's move the clouds from left to right

  static float x;    

  x+=0.01f;

  glTranslatef(x,0,0);



  glEnable(GL_TEXTURE_2D);         //Render the cloud texture

  glBegin(GL_QUADS);

  glTexCoord2d(1,1); glVertex3f(0.5f, 0.5f, 0.);

  glTexCoord2d(0,1); glVertex3f(-0.5f, 0.5f, 0.);

  glTexCoord2d(0,0); glVertex3f(-0.5f, -0.5f, 0.);

  glTexCoord2d(1,0); glVertex3f(0.5f, -0.5f, 0.);

  glEnd(); 



  SwapBuffers(hDC);

}

Animating the clouds

That will be in part 2. I hope you enjoyed this tutorial. Let us take a break and play some games!







Comments

Note: Please offer only positive, constructive comments - we are looking to promote a positive atmosphere where collaboration is valued above all else.




PARTNERS