Pages

Tuesday, March 25, 2008

Volumetric Clouds

Last summer I did some experiments with an unusual way of rendering volumetric clouds. The usual way to render volumetric clouds is with a bunch of quads/voxels/slices. But this approach is much simpler, and at least for the exterior, gives really good results.

The problem came when rendering multiple clouds in a field. Due to the way the cloud is rendered, overlapping clouds would produce artifacts, and short of rendering each cloud by itself I couldn't figure out how to eliminate the artifacts. But then again, I didn't spend too much time on them either.

The general approach is as follows:
  • Draw your scene as usual
  • Then render a low poly cloud mesh - this could be created in Max/Maya with a group of spheres/ellipsoids in the shape of a cloud
  • Copy the backbuffer to a render target, and clear the alpha channel
  • Render the clouds with full alpha and your lighting of choice (I only implemented simple phong shading, a better lighting algorithm would have helped). We'll call this the cloud map.
  • Blur the cloud map using a Gaussian filter, or another if you like.
  • After blurring we need to distort the blurred cloud map. To do this, we place a quad at the center of each cloud and billboard each vertex to the camera (make sure the billboard covers the entire cloud from any angle).
  • Now we render this quad to distort the cloud map. In the pixel shader for the quad we use the projected position as the texture coordinates and shift the texture coordinates based on the angles to the X and Y axis. We then sample a 2-channel fractal/noise texture with these texture coordinates to obtain our distortion offset. Next, we sample the blurred cloud map using the texture coordinates distorted by the distortion offset and the distance from the quad to the camera:
  • float4 distortedColor = tex2D(BlurredCloudSampler, texC + offset/ dist);
  • Optionally, after we have distorted our cloud map, we can perform a radial blur for a softer look.
  • Finally we merge our render target with the back buffer.
A visual representation of the process:


A really simple algorithm that produces a pretty good result. And as you can see, it is essentially a post processing technique. However, it seems that this approach is more suitable to large volcanic or nuclear plumes rather than a dense field of many cumulus clouds (e.g. a large cloud model with many mega particles, as can be seen in the author's images).

My experiment:



Author's images:




There's an article on this technique in Shader X5 and you can find the slides here. If you're thinking of trying this technique out, you really don't need the book. The slides are more than you need to implement it. The only detail is that you don't actually use a fractal cube like the slides say, you really just use a billboarded quad.

17 comments:

Ultrahead said...

Great read!

Kyle Hayward said...

Thanks! I'm glad you liked it.

Charles Humphrey said...

Yet, to try it but why does it not lend it's self to cloud fields, looks like it would do the job well...

Kyle Hayward said...

Unfortunately I lost all of my images and project except for those two when my computer died on me. So I cannot show the exact artifact.

But when rendering multiple clouds, the distortion billboards interfere with one another causing noticeable artifacts where one billboard would affect another cloud.

Now, I rendered all the billboards in one pass for performance, and short of rendering each billboard individually (which would be really really inefficiently) I couldn't get the artifacts to disappear.

You'll also notice that in the author's images, both pictures are one cloud model with many mega particles.

Charles Humphrey said...

Hmm, so the issue was to do with the draw order??

In the volumetric cloud system I am creating I put all my billboards into a single vertex stream as you did, but prior to the draw, if the cameras position has changed I then sort the draw order; I know killer on perf, but it has to be done. The only other way around it is with a double pass in the shader like in the Creators Club sample, but when you (like I do) want a semi transparent billboard texture it does not work too well. At th moment I am using a crap sorting routine and am sure I can optimize it, guess you will see what I did in the code when I put it up on the blog.

I may still have a go at this method, see how it pans out and if indeed it is a draw order issue.

Thanks for sharing.

Kyle Hayward said...

I tried sorting the clouds Front to Back based on the distance from the camera to the center of the cloud to alleviate the artifacts, but it didn't get rid of all of them.

Example: Cloud A's center is closer to the camera than Cloud B's center. But Cloud B's billboard can affect a particle from cloud A that is farther from the camera than Cloud B's center.

I probably could have spent more time on a smarter sorting though. I finished the clouds right before school started up again and didn't have time to work on them again. And then my computer died and I lost the most recent version of the project.

Charles Humphrey said...

Ouch!

I guess an extra step and possible one to many would have been to measure the distance of the nearest billiard corner. Guess you could use center * scale rather than just the center.

Shame you lost the project. When I have time I will open up that chapter and have a play. Cool to see your implementation.

Kyle Hayward said...

Hmmm... I didn't think of doing that. That might help.


One aspect that I really like about this technique is that there isn't a problem of depth intersections since you're rendering the clouds like any other piece of geometry. And it's extremely fast.

I wish I had time to work on clouds right now. Seeing your posts is really getting my interest. But my research on Real-time Reflections/Refractions is taking up all my time. I'll be posting about it in a couple of days hopefully. We're trying to submit to Eurographics and the deadline is Apr. 7th.

Charles Humphrey said...

That would be interesting. I just wish I had time to do more XNA.

Must be great being able to just doe development in this area all the time.... most of my days is taken up by fixing bugs in retail systems.... how crap is that lol

Ho hum, won't flood this post with any more of my moaning :P

Kyle Hayward said...

Ha ha. I did that last summer as an internship. At least it was c#, and mostly gui related though.

Hopefully I can get a job in the industry when I graduate.

Sharky said...

This is really awesome.

I've had your post bookmarked for a while but only just got round taking a look at it.

I'd so love to do something like this for my XNA game's clouds ("Sharky's Air Legends" at http://sharky.bluecog.co.nz). Each of my clouds is currently just a single billboarded quad + cloud photo texture. Now that my game features a more dynamic camera it's really starting to show its flaws.

I can kind of follow some of the technique, but struggling to know how to actually code it for XNA.

Is your source code available somewhere as a sample?

Kyle Hayward said...

Unfortunately, I lost the source for the volumetric clouds. But if you need help, I would be glad to help you out.

You also might want to look at randomchaos's blog as he has some pretty good looking volumetric clouds too.

Sharky said...

Thanks Kyle.

I'll take a look and see where I get to.

AndersO said...

Would be nice with some demo or video or something to see it "live". Interested to see if the "shower door" effect is annoying enough to not try this.

Great stuff anyway.

Anonymous said...

Can you write some lines of you PS, where you compute UVs for distorting

Kyle Hayward said...

The quad to distort the cloud is setup with standard uvs[0,1].

You then find the angle of the camera's position to the x axis and the up-axis( y-axis in my case).

You then shift the uvs by these respective angles.

u -= xangle;
v -= yangle;

I did this on the cpu side btw. But could be done in the vertex/pixel shader.

All this is really aiming to do is allow each pixel of the cloud to have a unique distortion. If you always apply the same distortion at the same pixel then you get slightly weird results.

So your pixel shader looks something like:

float2 offset = tex2d( noiseTex, shiftedUVs ).

offset -= .5f;
offset /= distanceToCamera;

return tex2d( blurredCloudTex, ProjTexCoord + offset );

Kyle Hayward said...

I think the original implementation used camera view angles to shift the billboard uvs, but this means that the cloud displacement changes based on the viewing angle ( which means the cloud is constantly changing based on the direction you're looking and looks completely wonky ). So that's why I based the angle on position rather than view.