Yar Woo
Project: Sandstorm

Original Project Proposal

The ultimate goal of my project will be to render a sandstorm blowing in the desert, as shown in the picture. One of the main motivations behind this project is that I find the concept of modeling natural weather systems like rain, snow, or in this case, sand, intriguing. I am also interested in figuring out the best way to graphically display large-scale particle systems like this one. Is it best to model each particle separately, and update them based on some sort of external wind field? Or should I attempt to render the sandstorm volumetrically, using perhaps level sets to update its position? I don't really know yet.

Technical Approach and Difficulties

As I mentioned in the previous paragraph, I can see two immediate ways to model the storm. One is to model each grain of sand, and update each grain at each time step using some sort of external field. After figuring out how to calculate an external field that would provide good results, the problem then becomes how to configure the raytracer to efficiently render many, many small particles.

The other way I see it is to render the sandstorm volumetrically, like steam. The problems with this would be how to distinguish it from steam -- more specifically, how to convey a sense of distinct graininess to the sand.

There would be a ton of other extensions to this that I could add if this turns out to be too easy (somehow I get the feeling it won't). I could add bump mapping to the sand on the ground. I could make a fractal tree. If I were feeling super-duper ambitious, I could model the ground as geometry and have the sandstorm change it as it passed over.

Final Results

Well, it's a few weeks later, and I'm done. I didn't get to do any of the extensions that I talked about, but I'm pretty happy with what I accomplished, especially the animations. Here's the picture that is probably closest to my original image:

I ended up going with a volumetric technique as opposed to a particle system, partially because there was a lot of literature in the field as to how to go about ray tracing volume densities. I'll try to outline the two main sections of my project in the next few paragraphs.

Part 1 - Rendering the Density Cloud

This ended up being the easier of the two parts. I modeled the sandstorm as a relatively low-resolution (10x10x10) grid of densities. My first step was to trace each eye ray through this grid, sum up the densities at discrete points, and use that as the opacity of the cloud at that point. Then I would just blend whatever the ray would have hit if not for the cloud with this opacity.

Later, I added some other effects. Using Kajiya's paper[1] on ray tracing as a guideline, I added the effects of light on the cloud, in particular attenuation effects and light scattering. I treated the light as being straight overhead, so you can see the attenuation effects as the cloud gets darker near the bottom, because so much light has been absorbed from the cloud on top. I used Mie scattering (the Heyney-Greenstein approximation to it, anyway) to simulate the light scattering... I don't know how obvious that is from the picture.

Part 2 - Moving the Density Cloud

This was the hard part - updating the density cloud's position at each time step in a way that looked realistic. There were no papers that I could find on dust-modeling (unsurprising), but there were lots of papers on smoke, steam, and, in particular, clouds that looked helpful. I saw some cloud animations and thought that I could probably tweak them to look like sandstorms.

I finally settled on Stam's paper[2] on stable fluids, which promised an "easy implementation"... yeah right. Stam's paper revolved around creating a grid of velocities and a grid of matter. The grid of matter at each time step, predictably, gets changed by the velocity at that point in the grid, but the velocity at each point in the grid also gets advected by itself. In addition, both the matter and the velocities diffuse a little bit at each step, and the velocities are tweaked so that mass is conserved throughout the density grid. I coded this whole thing in C, experienced problems, threw it out, recoded it in Matlab, and exported density grids for lrt to read in.

Stam's implementation allows you to specify matter "sources" from which matter comes a bubblin'. I put all of these sources off camera, to the left of the screen.

I wanted the sandstorm to move across the screen, but in a swirl. When I tried to set the velocity grid to push the matter across the screen, it looked stupid... it looked like the cloud was this straight block just uniformly marching across the screen. When I tried to make the velocities random numbers in a certain range, that didn't work either, because the many density cells the eye ray had to traverse through, when summed up, would come out to some average value, giving the same kind of "marching-across-the-screen" effect.

I solved this problem by keeping most of my velocities in a random positive range so that the storm would move across the screen. However, I introduced a number of random 3x3x3 "bubbles" of counter-velocities dispersed throughout the grid (their locations change every timestep). In these bubbles, matter actually flows backwards. I think this leads to a nicer overall effect.

Part 3 - Future Work

I wish I had had more time to do the peripheral stuff. I made a simple noise texture for the background sand, but it's not that great. As for the storm itself, I wish I had made a way for it to look "grainy". I couldn't really figure out a way to texture the density clouds I was creating.

Part 4 - Results First, the movies:
a heavy sandstorm
a much lighter sandstorm
somewhere in the middle

And, finally, some still shots:

Before the storm...

A lighter sandstorm, halfway across the screen.

The light sandstorm, completely covering the screen.

Part 5 - Source Code

The source code modifications to lrt are here. I mostly made changes to densitygrid.cc, with some changes to scene.cc as well.
The MATLAB code I used to generate the densities are here. The main function is update_density(t,dt) which takes in a total time and the length of each timestep.