1 Comment

Rendering snow on the ground

I recently made a few performance improvements with my snow rendering system, so I figured it would be a good time to talk about the challenges I’ve faced in getting nice-looking snow cover. I’m still not overly satisfied with the way it looks or performs, but it seems interesting to talk about anyway.

SnowResult

Snow as it stands currently.

Tracking snow accumulation

The snow is part of my dynamic weather system, which tracks precipitation “fronts” and hot/cold “fronts” across the game world. This isn’t anywhere close to modelling real weather, but it’s sufficient for a game.

WeatherSystems

From L to R: Precipitation, Temperature and Wind (which is based on precip gradient)

The weather simulation is run each frame on the GPU, currently in a 256 x 256 grid for the 1024 x 1024 world. So each 4 x 4 chunk in my world gets its own precipitation and temperature value.

The snow cover simulation is also run on the GPU. It tracks snow accumulation and melting based on the results of the current frame’s weather and temperature maps.

The precipitation and temperature maps themselves are generated from scratch each time by drawing a few “front” blobs. However, the snow accumulation map needs to persist across time. This means that we need to store this information in a reliable place. Unfortunately, the GPU’s memory is not such a place, as the graphics device can be reset at any time, making you lose all your graphics resources (on Windows, anyway – this is not an issue on the Xbox). So in general, you need to be able to recreate all your resources at any time. XNA handles much of this automatically, but it is not possible to do so for render targets.

So that means we need to keep a copy of the data on the CPU. Reading back from the GPU is generally expensive since it will cause a stall. Though that’s the main issue, we still want to limit how much data we need to read back. Originally, I had the snow cover map the same size as the other weather system maps (256 x 256), but I realized I could still get a decent effect with a 32 x 32 map. A small snow cover map is also good for save games. The snow cover is tracked with 16 bits per pixel, so a 256 x 256 map would add 128KB to each save game, while a 32 x 32 map only adds 2KB.

The snow cover map looks like this:

SnowCoverMap

L: Snow cover map. R: Snow cover map as sampled by the pixel shader

Now, there are some constraints imposed by such a small map for the entire world. While I can use point sampling for actually calculating the snow cover map, linear sampling is absolutely necessary when sampling from the snow cover map while rendering the terrain. This ensures smooth gradients in snow cover. Otherwise, you’d end up with very noticeable boundaries:

PointSamplingSnow

Problems with point sampling the snow cover map

Linear sampling means that (in XNA) we are limited to using a non-floating point surface format for our snow accumulation render target, such as Rgba32. However, using a single 8 bit component isn’t really enough to track snow accumulation. We need to be able to “accumulate” a sufficient amount of snow to snow melting when the temperature warms. We also don’t want noticeable “jumps” in the amount of snow at one point. This means we need more discrete values than 8 bits can provide.

For now, I think 16 bits is fine. So I store the snow depth in both the R and G components by scaling the snow value amount so that it lies between 0 and 255, and storing (snowAmount / 255) in R and frac(snowAmount) in G. When sampling snow depth to determine how much snow to draw, I only look at the R component, since combining R and G make no sense when linear sampling.

Rendering the snow

Now that I’ve discussed the format of the snow cover map, I’ll talk about how I render the snow itself.

The snow affects the “diffuse” color of an object. Basically it replaces it with white (or whatever color we decide snow should be). The final color is either the original, white, or somewhere in between. Snow generally isn’t transparent, so the amount “somewhere in between” has to be chosen judiciously, as it doesn’t typically look realistic. Basically, there is a small range of snow depth for which “somewhere in between” is chosen, so hopefully the ground changes from its original color to completely white over a short distance. I also have tried using some Perlin noise to adjust this value – more on that later on in this post.

In addition to the snow depth, I also take into account the world normal of the object. Snow doesn’t accumulate on steep ground as much as it does on flat ground, so this looks very natural. This significantly alleviates the “somewhere in between” problem mentioned in the previous paragraph, and is the main thing that makes snow look (somewhat) realistic.

Here’s a screenshot of the same region with and without snow cover:

SnowNoSnow

Top: without snow. Bottom: with snow.

Since I’m using deferred rendering, the G Buffer provides a natural way to do this. I have a buffer that contains the normals, so I can sample this when rendering the snow. I can allocate a bit (or 8) in one of the buffers to indicate whether an object accumulates snow (terrain does, but water and animated characters don’t, for instance).

The problem is, rendering the snow essentially changes the diffuse value. This means that all the lighting passes that use the G Buffer need to use an alternate version of the diffuse buffer that was generated by the snow pass. So the logic goes:

  1. Render scene to G-buffer (3 render targets: Diffuse, Normal and Depth)
  2. Do a snow pass that renders to a new DiffuseWithSnow render target. For every pixel, it needs to sample Diffuse (to know which color to blend with the snow), Normal (to know much snow to apply here), and Depth (depth allows us to get the original world position), along with the snow cover map.
  3. Do all the deferred lighting using DiffuseWithSnow, Normal and Depth.
GBuffer

Top left: Diffuse. Top right: Normal. Bottom left: Depth. Bottom right: DiffuseWithSnow

Step 2 is fairly costly. It uses much texture bandwidth (sampling from 3 full screen textures), and involves a render target resolve. This was my initial implementation, and on the Xbox, step 2 cost me nearly 3ms per frame for a 1280 x 720 image.

Unfortunately there isn’t a straightforward way to avoid the extra resolve. Snow could be “blended” into the original Diffuse buffer – but unfortunately the snow amount requires sampling Normal and Depth. In order to resolve Normal and Depth, I also need to resolve Diffuse.

There a few other options: Instead of a distinct snow pass (which is clean architecturally), I could apply the snow value at every light. That means every light needs to sample from the snow-cover map. Another alternative is to render the actual snow amount to the G-Buffer. So the “original” Diffuse buffer will already have the snow applied. That means every object (or every object that has snow) as it is rendered to the G-Buffer needs to sample from the snow-cover map.

The latter seemed like a reasonable alternative, so I tried it out – just on terrain to start with. It results in a not-too-shabby performance improvement: down to an extra per-frame cost of 1.8ms on the Xbox compared to the same scene without snow. Having each shader know about snow is a little hacky, but it wasn’t very difficult to implement since every shader that writes to the G-Buffer reuses the same shader header file.

Realistic edges

The edges of the snow pack look different when they are melting vs when they are accumulating. Melting snow tends to have a sharp well-defined edge, and greater local variation (i.e. it’s “patchy”). I can simulate this by sampling from a couple of perlin noise textures (using differently scaled texture coordinates) and using that value to modify the perceived snow accumulation at a particular point.

PerlinNoise

Perlin noise texture

This lets me define a unique snow edge for nearly any spot in the world – i.e., it looks like there is much more detail than there actually is. The perlin noise texture repeats throughout the world, but almost always with a different base snow cover amount, so you can’t tell.

Here’s a far away screenshot comparing smooth snow edges vs the Perlin-modified sharper edges. Snow depth (from the snow cover map) increases gradually from left to right in this image.

PatchyVsSmooth

Patchy snow (created by sampling from a couple of Perlin noise textures) vs smooth snow.

In the end though, I’m not sure I really like the patchy look. It may be more realistic for melting snow, but that would mean I need to track whether a particular spot is melting or accumulating. On top of that, it ignores topography. It looks weird when the patches extend across ridges and such:

PatchySnowLooksWeirdAcrossHills

Patchy snow extending across a ridge top.

So in the end, I’ll probably just use smooth snow.

Other objects

I’m not completely certain how I’ll handle objects other than terrain.

Most objects will be pretty straightforward. They’ll be tagged as either accumulating snow or not, and the shader constants adjusted accordingly.

Vegetation is the tricky one. It looks unrealistic if it doesn’t accumulate snow (see the above images). On the other hand, it blows in the wind, so it also looks unrealistic if it accumulates too much snow. I also don’t currently support normal maps for the vegetation – normal maps are pretty key to good-looking “speckle-y” snow.

Sparkles

I haven’t done any “sparkle research” yet, but it is possible I could modify the normal and specular power in random ways to achieve some sort of glitter effect on freshly fallen snow.

About these ads

One comment on “Rendering snow on the ground

  1. [...] talk a bit about how the weather system is implemented in this post about snow cover, but I’ll go over it briefly [...]

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

- Woolfe -

Developer's blog for IceFall Games

Ferrara Fabio

Game & Application Developer, 3D Animator, Composer.

Clone of Duty: Stonehenge

First Person Shooter coming soon to the XBOX 360

Low Tide Productions

Games and other artsy stuff...

BadCorporateLogo

Just another WordPress.com site

Sipty's Writing

Take a look inside the mind of a young game developer.

Jonas Kyratzes

Writer, game designer, filmmaker.

Indie Gamer Chick

Indie Game Reviews Without Mercy

The Witness

Developer's blog for IceFall Games

Developer's blog for IceFall Games

Developer's blog for IceFall Games

game producer blog

Developer's blog for IceFall Games

A Random Walk Through Geek-Space

Brain dumps and other ramblings

The ryg blog

When I grow up I'll be an inventor.

Developer's blog for IceFall Games

T-machine.org

Developer's blog for IceFall Games

Ocean Quigley's Projects:

Developer's blog for IceFall Games

Wolfire Games Blog

Developer's blog for IceFall Games

The Witness

Developer's blog for IceFall Games

Follow

Get every new post delivered to your Inbox.

Join 283 other followers

%d bloggers like this: