Cloud Cover

Cloud Cover


Requires DOS4GW
The Cloud Cover program was written for the Educated Programming Contest (now since closed down), on the subject of clouds. Against stiff competition from various marshmallows and cottonwool, we (Matt and I) won. It was written in two weeks flat, and coincided with our second year Maths revision, which may explain how come we managed to fail so many exams. We were supposed to win 25, but It's never turned up. Never mind.

I coded the realtime Perlin Noise to generate the clouds, and all the rest of the graphics stuff, and Matt designed the overall program, and wrote the all important inner loop which actually makes the clouds as good as they are.

Quite a few people have expressed an interest in the program, and asked how it was done. I promised to write an article on the subject, so here it is.

I intend to be a little vague in this article. This is for a couple of reasons, but mainly because I don't want anyone to feel they have to follow our program exactly. The way we did it was just one possible way of many (and not an especially good way at that). I would much rather people experimented themselves, rather than just blatently copied our algorithms. So I shall go as far as telling you why we made each step, and how it was based on real clouds, but probably won't give a lot of pseudocode.


General Information

We thought a good deal about what screen mode to render the clouds in, eventually coming to the decision that an 8-bit mode would be very restrictive, and not really do the clouds justice. We settled, eventually, for a truecolour (24-bit) mode, rather than Hicolour (16-bits). Firstly it would look better, and secondly it would be easier/faster to program.

Not being a wizard, and being unable to communicate with the VESA 1.5 BIOS from within the 32-bit .EXE, I initially had some trouble getting the program to autodetect the graphics card. Eventually I gave up, and decided to place the burden on the user. The trouble is this. Truecolour modes come in two flavours, 24-bit or 32-bit, but it's up to the graphics card which it supplies you with. It's possible to query the number of bits per pixel, but I was having serious trouble calling the Real Mode VESA functions. In the end, we had to write two inner loops, one to render to a 24-bit screen, and one to render to a 32-bit screen, and let the user choose whichever one doesn't look like nonsense.

As I said before, this program was written in just two weeks, so it's a bit of a mess, and many things could have been done a lot better. Everything is software rendered, and little attempt was made to optimise it. It uses a good deal of look up tables, so the more cache you've got, the faster it'll run. However, I expect that it would be possible to speed it up a great deal with 3D graphics hardware. It should even be possible to make a version that allows you to look around freely. Anyway, I'll leave that up to you, and get on with the article.

About Cloudscapes

The first thing you should do if you're going to simulate clouds is to rush outside and look at some real clouds. Make as many observations about the clouds as you can. However, if you're anything like me, you're probably reading this at night, so I'll tell you some things about clouds.

1. The sun is very bright and it is making you squint !
2. The clouds near the sun are glowing much brighter than clouds far from the sun.
3. Luckly the clouds are moving across the sky, and occasionally block out the sun, making it easier to see, so you don't have to squint as much.
4. The clouds are also changing shape as they move across the sky.
5. Not all the sky contains cloud. Some areas of sky are completely cloud free.
6. The clouds do not continue into the distance forever. The furthest clouds are often obscured by haze.

All of these things happen in a real life cloudscape, and lots more besides. The more things we can simulate, the more realistic our clouds will look.

Creating Clouds

If you have not already, I suggest you read the article on Perlin Noise.

Perlin Noise is great for creating cloudy type things, and can produce very realistic results. However, it is far too slow to be used for a realtime graphics application like this, and a faster alternative must be found.

In the end, we settled for comopsition of four octaves of animated noise at various frequencies and amplitudes.

Each octave (four in total) was calculated seperately, and they were all added together to create a cloud texture. A sheet of noise was handled like this:

Create an array of noise
Smooth it
Resample it up.


This was done for four arrays of noise to create noise textures of size 32x32, 64x64, 128x128 and 256x256.

Imagine now that these noise textures are tiled together to create four maps each of size 256x256. (see left).

The white lines have been added as a guide just to help you see the tiling.

Now, add these four maps together:

	CloudMap = Map1 + (Map2 / 2) + (Map3 / 4) + (Map4 / 8)
It doesn't look a great deal like the cloud cover in the program yet, but it's getting there. Importantly, observation number 5 (above) has not been satisfied. This entire texture contains cloud to some extent. What we really want is areas of cloud, and areas of totally empty sky.
There is a fairly simple function you can use to transform the plasma into something that looks a lot like clouds. The exponential function is the mother of all functions. You will find uses for it everywhere.

  function CloudExpCurve(v)
      c = v - CloudCover
      if c < 0 then c=0
 
      CloudDensity = 255 - ((CloudSharpness c) * 255) 

      return CloudDensity
  end function
Values of CloudCover between 0 and 255 give total cloud cover and empty sky respecitvely. CloudSharpness controls how fuzzy or sharp the clouds are. Lower values give sharper, denser clouds, and values closer to 1.0 give fuzzier, thinner clouds. Do not use values any greater than 1.0.

You should agree that these look a lot more like clouds than the previous image.

Putting the clouds in the sky

If you hadn't already noticed, the sky is curved. It's amazing how many computer generated images of the sky fail to take this into account. If you have a totally flat sky, the clouds will appear to go forever into the distance. You will notice that this does not happen with the real sky, so it should not happen with our simulation.

So, we shall take our little square of clouds, and lay it over a sphere to represent the curvature of the earth.

The camera is then positioned, not in the centre of the sphere, as this is not realistic. We live on the surface of the earth, not in the centre. So, put the camera somewhere near the top of the sphere, and point it at the clouds. Sorry that the diagram doesn't really show the correct position of the camera, but it would have been too cluttered.

Colouring it in nicely

Right, now that the clouds are in place, they're going to need to be coloured like clouds. You might be tempted to colour the sky blue, and the clouds white, and leave it at that. You're more than welcome, but it would look lame.
Firstly, we'll render the backdrop to the clouds. The sky directly overhead is blue, but if you look into the distance, you will see that it becomes a pale blue / grey.

Blue Sky
To do this, we needed the know the distance to the cloud dome at each pixel. Knowing this information, we kind of bodged together an algorithm to produce a blue/grey gradient across the sky. I'm sure you can figure out something similar, so I won't go into detail here.

Glow from the Sun
Since the light from the sun scatters in the atmosphere, you get a bright glow around it. We modeled this glow with an exponential function. See there it is again. Also, a constant value was added to light up the atmosphere to make the atmosphere calculations work correctly.

This was stored as a 320x150x8bit texture map to be used later to make the clouds glow near the sun.

In the pseudocode at the end of this page, I shall refer to this as the Glow map.

Mixing the two
Now, the blue sky and the glow were combined to create a bright glowing atmosphere.

Since the camera never moved, this was stored as a 320x150x24bit texture map.

In the pseudocode at the end of this page, I shall refer to this as the BlueSky map.

Lens Flare
Lastly, a lens flare texture was created. You can use any lens flare map you like. This one was created in much the same way as the glow map, but six lines were added as well. The lines are the width of the sun, and fade off exponentially.

In the pseudocode at the end of this page, I shall refer to this as the LensFlare map.

Combining it all into a finished image

There are a couple of things going on every frame. Firstly, the cloud map must be animated. Then the blue sky, sun glow, cloud map, and lens flare are all combined to create the final image.

Animating the clouds

If you read and understood my very brief explanation of how the clouds were created, then it is a simple task to animate them. All you need to do it to animate the original noise maps.
You will remember that there are basically 4 noise maps. The ones we used were 32x32 pixels each. Animating them is simply a matter of interpolating them from one noise map to another.

Animating a single noise map

50% Noise 1
50% Noise 2
Noise map 1 Crossfade between Noise 1 and Noise 2 Noise map 2


  NoiseMap1 : map of 32x32 pixels
  NoiseMap2 : map of 32x32 pixels
  AnimMap   : map of 32x32 pixels
 
  Initialise NoiseMap1
  Initialise NoiseMap2

  time = 0
  timestep = something less then 1

  loop forever

      if time >= 1 then
	  time = time - 1
	  NoiseMap1 = NoiseMap2
	  Initialise NoiseMap2
      end if

      p2 = time
      p1 = 1 - time

      AnimMap = (NoiseMap1 * p1) + (NoiseMap2 * p2)  

      time = time + timestep

  end loop


Pseudocode to animate an octave of noise

Hopefully this small fragment of code will help to convey something which I am finding very hard to explain in words. The idea is to use this code to maintain four octaves of animating noise. The four octaves will need to animate at different speeds. The noise map responsible for producing the the largest amplitude of noise should animate the slowest.

The Initialise Noisemap procedure should fill a noise map with random pixels, then apply a smoothing filter.

The noise maps are thes used as explained in the Creating Clouds paragraph.

Rendering the Image

Any finally, you will have to actually take all this information (BlueSky, Glow, Lensflare and Clouds), and combine them into an image. Now, we took a look at the original code, and we can't figure out what some of it does, so we haven't included it here, for the sake of simplicity. This fragment of pseudocode will probably not produce nice clouds straight off, so don't write to me complaining. It's only really meant as a guide to how we achieved it, to point you in the right direction. You may have to do a lot of fiddling around and rearranging, and lots of optimising. Basically, don't copy out this code. read it, try and figure out vaguely what's going on, and write your own version from scratch.

Take a look at the Exposure article for information on the ExposureFunction.


  function HazeExpCurve(d)

      c = a couple of hundred. Depends on how far away the sky dome is
      Hazyness = about .95
 
      p = d2 / c
      return Hazynessp

  end function


  FlareStrength = Cloud Density over sun

  for each pixel

    c  = pixel from CloudMap
    gl = pixel from GlowMap
    fl = (pixel from FlareMap) * FlareStrength
    h  = HazeExpCurve(Distance to cloud dome)

    Start with Blue Sky Map
    HazeRed   = Red Component of pixel from BlueSky map
    HazeGreen = Green Component of pixel from BlueSky map
    HazeBlue  = Blue Component of pixel from BlueSky map

    Add simple shading to clouds
    shading = simple bump mapping on cloud

    Apply the bright glow on the clouds from the sun
    shading = shading * gl

    Fade the amount of cloud by the haze (according to distance)
    CloudVal = c * h

    Alpha-Blend the clouds with the blue sky using CloudVal
    Red   = Red   + (CloudCol-Red)  * CloudVal
    Green = Green + (CloudCol-Green)* CloudVal
    Blue  = Blue  + (CloudCol-Blue) * CloudVal

    Add Lens Flare
    Red   = Red   + fl 
    Green = Green + fl	
    Blue  = Blue  + fl

    Now, use the exposure function, since values of Red, Green and Blue may be quite high
    Red   = ExposureFunction(Red)
    Green = ExposureFunction(Green)
    Blue  = ExposureFunction(Blue)
    
    Draw this pixel

  end loop


References

LIGHT (in C++) ! : http://math1.uibk.ac.at/~werner/light/

Interesting sky / clouds / landscape renderer written in C++.

Mapping Reality: Simulating Clouds : http://patate.virtualave.net/clouds/index.html

Information on rendering plasma clouds.

Convective Clouds : http://www-imagis.imag.fr/Membres/Fabrice.Neyret/clouds/index-eng.html

Research into models of convecting clouds.

Radiant lense flare render engine : http://www.geocities.com/SiliconValley/Ridge/7251/radiant.htm

Pretty self explanatory.

Intel Developer Site : http://developer.intel.com/software/idap/games/index.htm

Kim Pallister has written an article, based on this one, explaining some methods of using hardware acceleration to speed up the rendering of clouds. However, I get the feeling that hardware acceleration is not appropriate for all aspects of these clouds. I think that, until graphics cards get more advanced, or allow you to program your own routines, some of this will still have to be done in software.

Noise Machine : http://www.noisemachine.com/

Ken Perlin's course notes from the GDCHardCore gamers workshop, San Francisco, Dec 9, 1999.


Return to the Good Looking Textured Light Sourced Bouncy Fun Smart and Stretchy Page.