Water drop 2b – Dynamic rain and its effects

Version : 1.1 – Living blog – First version was 3 january 2013

This is the second post of a series about simulating rain and its effect on the world in game. As it is a pretty big post, I split it in two parts a and b:

Water drop 1 – Observe rainy world
Water drop 2a – Dynamic rain and its effects
Water drop 2b – Dynamic rain and its effects
Water drop 3a – Physically based wet surfaces
Water drop 3b – Physically based wet surfaces
Water drop 4a – Reflecting wet world
Water drop 4b – Reflecting wet world

Directly following the part a let’s continue with other rain effects:

Water influence on material, water accumulation and puddles

In the observation post we see that a wet surface is darker and brighter depends on its material properties. The influence of water on a material is complex and will be discuss in detail in the next post : Water drop 3 – Physically based wet surfaces. For now we will follow the guideline define by Nakamae et al in “A Lighting Model Aiming at Drive Simulators” [1]. When it rains water accumulate in ground crack, gap and deformation. With sufficient precipitation puddles can appear and stay a long time even after it stop to rain (as highlight by the observation post). To define the different states of the ground surfaces [1] introduce the following classification:

Type 1: a dry region
Type 2: a wet region; i.e., the region where the road surface is wet but no water gathers
Type 3: a drenched region; i.e., the region where water remains to some extent but no puddles are formed, or the region of the margin of a puddle
Type 4: a puddle region

When a surface is wet (type 2), the paper suggest to apply a reflection coefficient of 5 to 10 on the specular and 0.1 to 0.3 on the diffuse. In the pseudo code of this section, we will represent this water influence by a function DoWetProcess. This function take a percentage of wetting strength under the form of a shader variable we will call wet level. When wet level is 0, the surface is dry, when 1 it is wet. This value is different from the raindrop intensity of the previous sections. Wet level variable is increase when the rain starts and takes some time to decrease it after it stops. Allowing simulating some drying. Here is a simple pseudo code:

void DoWetProcess(inout float3 Diffuse, inout float Gloss, float WetLevel)
{
   // Water influence on material BRDF
   Diffuse    *= lerp(1.0, 0.3, WetLevel);
   // Not the same boost factor than the paper
   Gloss       = min(Gloss * lerp(1.0, 2.5, WetLevel), 1.0);
}

// Water influence on material BRDF
DoWetProcess(Diffuse, Gloss, WetLevel);

Note that’s there is no change apply on the normal, when the surface is wet, we simply use the original normal. Here is shot with an environment map apply:

WaterInfluenceWetDry

For puddles (type 4), the paper suggest a two layers reflection model as the photos of real rainy world show us. For now we keep it simple and just use the water BRDF parameters coupled with the diffuse attenuation of a wet surface. For the margin region of puddles (type 3) a simple weighting function between the two previous models is proposed. Here we lerp between the current material BRDF parameters (wet or not) and result of type 4 BRDF parameters to simulate the presence of accumulate water. Puddles placement need to be control by the artists and we use the alpha channel of vertex colors of a mesh for this purpose.  We provide a tool to our artists to paint vertex color in the editor directly on the mesh instance (more exactly Unreal Engine 3 provides tools). The blend weight of our lerping method is defined by the value of the vertex’s color alpha channel: 0 mean puddle and 255 mean no puddle (the default value for vertex color is often white opaque). Pseudo code:

AccumulatedWater = VertexColor.a;

// Type 2 : Wet region
DoWetProcess(Diffuse, Gloss, WetLevel);

// Apply accumulated water effect
// When AccumulatedWater is 1.0 we are in Type 4
// so full water properties, in between we are in Type 3
// Water is smooth
Gloss    = lerp(Gloss, 1.0, AccumulatedWater);
// Water F0 specular is 0.02 (based on IOR of 1.33)
Specular = lerp(Specular, 0.02, AccumulatedWater);
N        = lerp(N, float3(0, 0, 1), AccumulatedWater);

View in editor: no puddle, puddle paint with vertex color’s tools, puddle

PuddleEditing

Puddle paint can be seen in action at the begin of the puddles, heightmap and ripples youtube video.

To simulate the rain accumulation in crack and gap we could use an information already available for other effects : A heightmap.
It is common for ground in game to have some sort of parallax mapping more or less complex (simple parallax mapping, parallax occlusion mapping, relief mapping…) requiring an heightmap. We just need to define some convention for the heightmap when dealing with it. We could use the heightmap exactly as for vertex color and puddle: black mean hole where water can accumulate and white mean no water.  Then lerp BRDF parameters as before.

In both case above, the vertex color or heightmap are source of depth information for the current geometry. As we talk about accumulating water and dynamic rain, we could use these depth information to progressively fill empty holes and puddles. For this we provide two flood levels variable in the shader which represent the flooding level of hole/gap/cracks and the flooding level of puddles. This two new variables and the wet level variable give full control on surfaces state (types 2, 3 and 4 describe before). When flood levels are 0 this mean that there is no water accumulated, for 1 the water has entirely fill holes/puddles. In between we use the depth information and compare it to the current flood levels to get the level of accumulated water above the geometry. Here is a pseudo code which mixes the information from vertex color and from the heightmap (allowing artists to paint puddle on the top of geometries having a heightmap) to retrieve the accumulated water level for the current pixel:

// Get depth/accumulation water information
float2 AccumulatedWaters;
// Get the "size" of the accumulated water in a hole/cracks
AccumulatedWaters.x      = min(FloodLevel.x, 1.0 - Heightmap);
// Get the size of the accumlated water in puddle taking into account the 
// marging (0.4 constant here)
AccumulatedWaters.y      = saturate((FloodLevel.y - VertexColor.g) / 0.4);
// Mix vertex color and height map accumulated water by taking the higher
float  AccumulatedWater  = max(AccumulatedWaters.x, AccumulatedWaters .y);

The code requires some explanation. To accumulate water correctly we require knowing the volume of the free region around the pixel, the local minima, the slope etc… Of course we don’t want to provide all these information (and the extra instructions required to treat them) and instead chose a simple approximation.

First, we want to be able to simulate the shrinking behavior of puddles when they dry and their growing when it rain. For this we use the flood level as a water level. When the flood level if greater than the depth (represented by vertex color’s green channel) at the current pixel, the pixel is underwater. Painting smooth gradient at the margin of the puddles allow having growing/shrinking puddles when flood level increase/decrease. For this setup to effectively work, we set as constraint that’s all our puddles have the same depth of 0. The code will break with multiple different puddles depths where shallow puddles will appear later and disappear sooner than deeper puddles. We now require a smooth transition at the boundaries of the puddles. The rules are:

– If flood level is greater than pixel depth we are underwater (1)
– If flood level is lesser than pixel depth and a transition size (we chose 0.4 in the code), we are dry (0)
– If flood level is in between we are drenched and linearly interpolate the accumulated water value

Convert to code this give

saturate((FloodLevel.y - VertexColor.g) / 0.4)

Here is an example result. We vertex paint a mesh like on this image:

LargePuddleGround

Then we get these different results for different flood level (click for better reading):

PuddleGrowing

Second we decide to handle hole/cracks/gap differently. Holes are shallow than puddles and we must not wait for the flood level to be high before affecting them. For puddles we wanted to have a smooth gradient allowing simulating the curvature of the ground. Here we don’t care, just knowing how much water there is at the current pixel is sufficient and this can be get simply:

min(FloodLevel.x, 1.0 - Heightmap)

The benefit is that’s you have a smooth transition and you can have very small hole (control by artist in the heightmap) which will have a drenched look at most instead of being quickly underwater. Here is an example, we create a heightmap texture:

Heightmap

Then we get these different results for differents flood level:

GrowingHole

We then take the maximum of the two values describe for puddles or heightmap to allow to mix them.
A white value in the heightmap or the vertex color allows to never being underwater. A black value allows being completely underwater even when the ground is dry. Either it rain or not, when a surface is underwater (when AccumulatedWater is 1) it use the type 4. When a surface has no water above it (AccumulatedWater is 0) it use the type 2 if it rain else type 1. In transition when the surface is drench (AccumulatedWater is between 0 and 1) it uses the type 3 with some boost if it rain. For this we use a simple approximation consisting to add AccumulatedWater to the wet level variable to simulate the underwater state. Here is some pseudo code:

// WetLevel could be 0 but we can still require to have wet surface
// if there is accumulated water.
float NewWetLevel = saturate(WetLevel + AccumulatedWater);
// Calculate the surface wet BRDF parameters for the new wet level
DoWetProcess(Diffuse, Gloss, NewWetLevel);
Gloss    = lerp(Gloss, 1.0, AccumulatedWater); 
Specular = lerp(Specular, 0.02, AccumulatedWater); 
N        = lerp(N, float3(0, 0, 1), AccumulatedWater);

Having two depth editing paths with two flood levels allow to let the accumulated water of puddles stay longer than water in small gap/cracks/gap. These flood levels are control on the CPU by the amount of rain precipitation. The rain precipitation is growing base on the time spend and intensity of the rain (Rain intensity). A light rain will take more time to increase the flooding levels than a heavy rain. The flooding levels do not decrease when the rain stop. They decrease with time. You could base the speed of decrement on the hot temperature but we don’t need this level of detail, player will not notice it. As puddles disappear more slowly, their flood level (the one use for vertex color) will decrease more slowly than the flood level of hole/cracks/gap (the one use for heightmap). Here is a diagram showing the relationship between all the describe variable based on the time.

RainValueRelationship

At an arbitrary moment, it starts to moderately rain (Black line (i.e. Rain intensity) increasing to 0.5). Surface is rapidly wet (Wet level in blue increase to 1). After a certain time raining, the flood levels have increase sufficiently to flood all surface and puddle (Flood levels in green and orange at 1). When it stops to rain it require some time to dry the surface (wet level decrease to 0). It requires more time for the water in hole to evaporate (Flood level Heightmap decrease to 0) and even more time for puddle to disappear (Flood level vertex color). With such control, if you want to simulate a drizzle rain where nothing should flood, you just have to put a speed increase of flood levels 0.

I have made a RenderMonkey project (see at the end of the post) with all the code presented here. I do a video capture from this application to show the flooding of both the puddle (vertex color) and the heightmap in action with a timeline animating variables similar to what we just describe, the video also show ripples of the next section: puddles, heightmap and ripples youtube video (For the ripples, better to see the HD version).

An interesting benefit of puddles is that’s they are like decals and allow to break repeating pattern use on ground. This is good for modularity.

Even if dynamic accumulation is a cool feature most of the time you don’t need it. First you must have the need of a dynamic rain. Then the player should be able to go to the same place at different time. In a linear game where you always go forward the player will never notice the accumulation. In an open world game you have much chance that the player run around the same place and notice a difference. In our game we don’t use a dynamic water accumulation system but it still useful for previewing what an area could be when wet, good when design decision change often.

There is few optimizations to do when you already know the weather of a particular area. All dynamic stuffs in this case are useless. You can prebake all your textures with the wet level influence of the area by using the previous wet code, or better have artists creating the wet textures directly. Also interesting to know even if this not always an optimization, for holes/puddles you could use a standard blend material. One layer is setup as the dry surface, the other layer is set as water properties mixed with the wet surfaces.

Another thing when talking about wet and dry surface is that only exposed surface to the rain is wet. Dry surface are not only the one protected from rain, other surface like wall which are perpendicular to the rain direction are mostly dry. A depth map generated from the rain direction, like the one use for the rain splashes, is a good candidate for this problem. Stalker – Clear sky use this kind of approach [3]. Using the depth map like a shadow map, the visible pixels through the depth map will be wet. And this will automatically discard surface not facing the rain direction (think about an orthogonal top view of a wall). To avoid having too hard transition between wet and dry surface, some jittering and filtering technique can be use.  Also this doesn’t handle complex case of wet propagation like on a tree. The main problem of the shadow map is the limited distance/resolution it covers.
Another way we prefer for performance reasons is to take some artists’ time to paint dry surface in vertex color’s blue channel of a mesh instance. Tagged vertices will attenuate the wet level value: 0 mean always dry, 1 mean let the wet level handle the wetness:

DoWetProcess(Diffuse, Gloss, NewWetLevel * VertexColor.b);

Of course, instead of painting all wall in blue we chose to only allow wet effects on ground parallel surfaces. Ground surfaces have their own dedicated shaders. Dynamic object could be handled with the CPU by reusing the depth map of rain splashes (or by doing line check with the world  in the opposite direction of the rain but this can be costly). The current position of the dynamic object is tested against the shadow map on the CPU. We can track the time since the dynamic object is under cover to simulate a drying process. In this case, each dynamic object has its own wet level value which increment when under water and decrement when under cover.

To conclude with puddles, it should have impact on sounds and footstep. When the player walks in a puddle, it expects some splash effect with its associated sound. Using vertex color to paint puddle fit actually well for this kind of effect. On our mesh we have physical materials allowing defining the kind of material we are actually walking on. When walking on the mesh, we retrieve the contact vertex color (instead of looking up in a texture like Unreal Engine does). If the vertex color is black, we return the water material. We also perform a splash effect when there is enough water to allow it based on the Flood level values. The splash effect is not perform in dry area detected by throwing a ray in opposite rain direction and testing collision with the world or by reusing the depth map of the rain splashes. At the end of the rain splashes youtube video you can see some foot splashes.

Added Note:

To go further, puddles are not always clear and [1] describe how to take into account muddy puddles with a specific phase function in a scattering model.
There is also an interesting post to read about wet surface in Moto GP 08 by Angelo Pesce [4]. Most of the post is about reflections which are the subject of another water drop post but I want to quote a small part showing the similar behavior with the water normal influence than the system we describe here :

Water will have the same normals as the underlying asphalt if the water layer is thin, but it will “fill” asphalt discontinuities if it thick enough. That’s easy if the asphalt has a normalmap, we simply interpolate that with the original geometry normal proportionally with the water level.

As a last note, many games tend to have water stream on walls or vertical part of objects. This is not something expected in real world as see in the observation post. There is circumstance where this happen but this is the exception not the standard.

Ripple in puddles and thin water layer

Ripples are a consequence of the impact between a raindrops and a layer of water on top of the world geometry. The impact generates a circle wave propagating in the water media as can be seen in the observation post. If there is not enough water there is no ripples, this is an important point. As for rain splashes there is no need to sync the raindrops effect to the ripple effect, the eye can’t see the difference as you may see in the video in the observation section.

Most games use a dynamic ripple normal map to perform this effect. Modifying vertex geometry is a too heavy process for a game and requires detailed meshes. At each frame a new ripple normal map is generated. A single ripple normal map  is use for the entire world for performance reasons. The texture will be sample with xy world coordinate of the object (with z up) meaning that’s the ripple normal map must tile. The ripple normal and the normal of the water are then combined. As water normal are always Z up the combination consist to take only the ripple normal map into account.

A physical way to generate the ripple normal maps is to perform a true simulation of ripple like in the Toy shop demo [2] either on the GPU or the CPU. Raindrops are use as seed  in a water simulation texture. Each raindrop generating a dampened sine wave. Each ripple interacts with others. The result of the simulation is a heightmap which will be converting to a ripple normal map.
The simpler way is to use an animated texture, like the tile volume texture use in Stalker – Clear sky [3]. The main weakness of this method is the obvious pattern in ripple.
We chose a procedural way as a solution between the two above. Good to avoid repetition and more stable than a simulation at a lower cost. As the number of ripple should increase with the rain intensity it is better if the method chose allow generating more ripple.

The procedural way we chose consists of creating animated circle of different size from a texture. This texture has been generated procedurally. This work have been done by Antoine Zanuttini. An algorithm spawn circles with different sizes and no overlapping then for the red channel: calculate the distance inversed and normalized from the circle center, for the green/blue channel: store the direction from the circle center, for the alpha: take random grey value constant for the circle. Picture below show in order: RGB texture, red channel, green/blue channel, alpha channel.

CircleMap

Each circle in the texture will be use to create an animated circle in the ripple map. We get the size variety because this is bake in a texture. To work, the texture must tile, mean that’s if we want to scale the texture coordinate, we must scale by an integer number. The green and blue channel is the perturbation in XY of the normal. The alpha channel is a time offset factor to allow “starting” the circle animation at different time, so all circles of the texture won’t grow together. The red channel is use to attenuate the ripple when going at boundary of a circle. Here is the pseudo code to generate an animation of circle with this texture:

float3 ComputeRipple(float2 UV, float CurrentTime, float Weight)
{
    float4 Ripple = tex2D(RippleTexture, UV);
    Ripple.yz = Ripple.yz * 2 - 1; // Decompress perturbation

    float DropFrac = frac(Ripple.w + CurrentTime); // Apply time shift
    float TimeFrac = DropFrac - 1.0f + Ripple.x;
    float DropFactor = saturate(0.2f + Weight * 0.8f - DropFrac);
    float FinalFactor = DropFactor * Ripple.x * 
                        sin( clamp(TimeFrac * 9.0f, 0.0f, 3.0f) * PI);

    return float3(Ripple.yz * FinalFactor * 0.35f, 1.0f);
}

There is a weight factor in the code to allow more puddle with rain intensity increasing. Now we get our different size and shifted time circle animation, we could add variety by merging several layers of this same texture at different time/frequency. As we want to increase the number of ripple with rain intensity we will add layer based on it. On the C++ side we init some Times variable at different frequency and shifted:

// This are just visually tweak value
float4 TimeMul = float4(1.0f, 0.85f, 0.93f, 1.13f); 
float4 TimeAdd = float4(0.0f, 0.2f, 0.45f, 0.7f);
float4 Times = (Time * TimeMul + TimeAdd) * 1.6f;
Times = frac(Times);

Then in the main shader:

// Generate four shifted layer of animated circle
float3 Ripple1 = ComputeRipple(UVRipple + float2( 0.25f,0.0f), Times.x, Weights.x);
float3 Ripple2 = ComputeRipple(UVRipple + float2(-0.55f,0.3f), Times.y, Weights.y);
float3 Ripple3 = ComputeRipple(UVRipple + float2(0.6f, 0.85f), Times.z, Weights.z);
float3 Ripple4 = ComputeRipple(UVRipple + float2(0.5f,-0.75f), Times.w, Weights.w);

// We enable one layer by quarter intensity and progressively blend in the
// current layer
float4 Weights = RainIntensity - float4(0, 0.25, 0.5, 0.75);
Weights = saturate(Weights * 4);
// Compose normal of the four layer based on weights
float4 Z = lerp(1, float4(Ripple1.z, Ripple2.z, Ripple3.z, Ripple4.z), Weights);
float3 Normal = float3( Weights.x * Ripple1.xy +
                        Weights.y * Ripple2.xy + 
                        Weights.z * Ripple3.xy + 
                        Weights.w * Ripple4.xy, 
                        Z.x * Z.y * Z.z * Z.w);
// return result                             
return float4(normalize(Normal) * 0.5 + 0.5, 1);

Sample result for RainIntensity 1, 0.6, 0.3:

RippleMapResult

For performance and quality we try to generate mipmap for the ripple texture. Either by regenerating the texture procedurally or by filtering the base level. In both case we get improved performance but we are not totally satisfying with the visual result. With the procedural way the low resolution don’t give good rasterize result and with the mipmap the ripple just disappear. This could be a new area of research link to the hot topic of filtering normal map.

Now that’s we generate our ripple normal map, we just need to replace the water normal by the ripple normal in our previous code base on the rain intensity:

float NewWetLevel = saturate(WetLevel + AccumulatedWater);
DoWetProcess(Diffuse, Gloss, NewWetLevel);

float3 RippleNormal = tex2D(RippleTexture, WorldPos.xy * ScaleFactor)*2.0-1.0;
// For clarity add this extra line, but in practice it is not
// require, RippleNormal is neutral when rain is off
float3 WaterNormal = lerp(float3(0, 0, 1), RippleNormal, RainIntensityOn);

Gloss = lerp(Gloss, 1.0, AccumulatedWater); 
Specular = lerp(Specular, 0.02, AccumulatedWater); 
N     = lerp(N, WaterNormal, AccumulatedWater);

The RainIntensityOn is 1 when its raining and 0 otherwise. The smooth graduation in the ripple (number, frequency) is control at the ripple normal map generation time. The ripple normal map can even be flat normal. The code above allow to make ripple appear only where there is enough accumulated water. A video show ripples in action: puddles, heightmap and ripples youtube video.

Here is two shots of ground without ripples and with ripples:
RippleEditor

Note that’s with a thin layer of water (which can be control by settings the maximum value of a heightmap to 0.8), you can still have some light ripple effect. In real world, ripples are often only visible when there is enough lighting. Here we get the same effect, we need to have a bright light (either direct or from an environment map) to be able to see ripples.
To avoid to get ripple on undesirable surface, typically vertical surface attach to ground (like sidewalk) which are difficult for artists to separate, we can test the world normal orientation of the surface and attenuate the ripple. As this has shader’s instructions cost, we prefer to paint a weight to attenuate the ripple in a vertex color’s red channel: 0 no ripple allowed, 1 : ripple allowed.

N = lerp(N, WaterNormal, AccumulatedWater * VertexColor.r);

With this tagging method,  large puddle half protected from the rain can be simulated. To help artists with this painful process, we develop a tools to automatically paint red channel of mesh instance. Enabling the detection feature in our interface (Left image) allow to quickly remove surface which can’t receive ripple from the red channel. This is base on line check with the world in the rain direction and normal orientation (Right image show display of the vertex color’s red channel of objects) . See how the bottom object has its top black as there is another object above him:

VertexColorAutoEdit

Added note:

If you use the blend material way to create puddles/hole by binding the blend weights on the color channel use for puddles weight you can get the ripples effects on the water surface.

It could be possible to add even more interaction in ripple simulation like taking into account object falling in puddle or footstep. But in this case we can’t use the generated ripple normal map everywhere on the world. The simulation should be performing for a small area around the player like a small grid and require extra instruction to handle it. Two textures will be requiring in this case. Bioshock [5] uses a screen space buffer to perform ripple simulation and water interaction similar to a deferred renderer.

Fog and misty glow

Fog and rain have in common that they are both composed of water droplets, only the size of drop differ. Fog is really a complex phenomena and can have many different sources. We try to get the relationship between fog and rain:  what are the conditions to get fog and rain at the same time, because in real world having both phenomena is not systematic. But we fail to get an answer, it seems it still currently not really defines. The closest kind of fog due to rain seems to be the precipitation fog. According to Wikipedia definition [7]:

Precipitation fog (or frontal fog) forms as precipitation falls into drier air below the cloud, the liquid droplets evaporate into water vapor. The water vapor cools and at the dewpoint it condenses and fog forms.

For our game, we go away from this. Fog is a necessary feedback for the player. We keep the fog phenomena really simple and rather artistic. Our game defines a fog amount and a fog color by area. For each area artit provide a desaturation factor. When it rain, we smoothly interpolate the fog color with it’s desaturate version until we reach the desaturation factor specified:

Weight = lerp(0, MaxWeight, saturate(TimeSinceRainStart / TimeTransition))
RainFogColor = lerp(FogColor, dot(FogColor, flot3(0.299, 0.587, 0.114), Weight);

Our “rain fog” color is based on localization not the kind of rain, and it is controlled by artists.
Moreover, the heavier the rain is the denser is the fog (fog amount). This is because raindrops reduce the visibility for far away objects producing a haze-like effect.

Another important scattering effect from our point of view, that’s we haven’t implemented,  is the appearance of misty glow well describe in [8]:

Water particles in the atmosphere during the rain increase the amount of light scattering around objects. Multiple scattering effects are responsible for the appearance of glow around light sources in stormy weather.

The toy shop demo implements such an effect with following hack and with a Kawase filtering [8]:

float4 vFogParams;
float4 ComputeFoggedColor(float3 cFinalColor, // Pixel color 
                          float glow,  // Glow amount 
                          float fFog) // Vertex shader computed fog
{
    float4 cOut;
    // Foggy factor
    float fogFactor = fFog * (1-(SiGetLuminance(cFinalColor)/10));
    fogFactor = min (fogFactor, vFogParams.z);
    // First figure out color
    cOut.rgb = lerp(cFinalColor, cFogColor, fogFactor);
    // Then alpha (which is the glow)
    cOut.a = lerp(glow, fogFactor*vFogParams.w + glow, fogFactor);
    return cOut;
}

The toyshop demo use the current luminance of the pixel to tweak the fog factor (no fog on bright pixel) and add it to the glow amount simulating a larger halo.
All current game engines have a bloom postprocess system, so simulate misty glow when it rain could be as easy as increase the blur kernel size but this has a cost…

Rain effects control panel (bis)

Taking the definition defines in Rain effects control panel of part a, we can now add the parameters of this post. The value of these tabs are just to illustrate how the parameters are link togethers. There is some more parameters to include here. Values need to be tweak for game context, performance, game design needs etc…

Rain type Rain Intensity Number splashes Flood puddle speed (Inc/Dec) Flood heightmap speed (Inc/Dec) Wet level speed (Inc/Dec) Fog amount
Light 0.33 20 0.1 / 0.05 0.2 / 0.1 0.6 / 0.2 0.15
Moderate 0.66 40 0.3 / 0.05 0.5 / 0.1 0.75 / 0.2 0.4
Heavy 1.0 60 0.7 / 0.05 0.85 / 0.1 1.0 / 0.2 1.0

Other important rain effects not covered

Rainy world as has been show in the observation post imply many more than the described rain effects. To be realistic it is not sufficient to take into account raindrops and rain splashes, but also many other effects. Missing one of this effects break the rain feeling. Production time constraint and performance are the principal difficulties met when working on rain effects. Lopez’s master thesis on Real-time Realistic Rain Rendering [9] show on page 22 a nice summary of the essentials properties of rain effects algorithms covered by several papers (Tab is not show here, reader should refer to the mentioned thesis). Here is example of some of them:

– Cloud in the sky: Cloud are participating media where light suffers a high degree of scattering and absorption. The density of droplets suspended in the cloud determines the way light is distorted, and variations of this density may produce lighting conditions with a wide difference in coloring and attenuation.
– Rainbow: When rain is mild or has just finished, small droplets suspended in the air produce rainbows by refracting the sunlight.
– Lightning: A lightning is an atmospheric discharge of electricity, typically originating in a cloud and hitting the ground (…) Simulation of this phenomenon must produce pseudo-random paths and change the illumination of the whole scene to respond to its varying-intensity flashes.
– Dripping water: When raindrops fall on non horizontal surfaces they do not accumulate on it but trickle down following the forces they receive, mainly gravity and wind.
– Wind.
Source [9]

Physically based rainbow is describe in depth in a recent paper by Disney research [10].
Several other effects like misty rain halo, water stream, swirling water, windshield with dynamically cleaned raindrops are also describe in the toy shop demo paper [8]. The list is endless.

Conclusions

As any game effects, dynamic rain need to be budgeted to be able to have a playable game. Here we are not a technical demo with a high-end PC. Getting an eye-candy look within console performance constraint is the real challenge of video games. A budget of 3ms (on a base of a 30 fps game) for all the rain core effects is reasonable. As rain is dynamic, you must reserve this 3ms in your frame time. Artists use to edit the world with rain off, so they must stay under the 30ms (even less in practice). Here is some performance numbers (all in milliseconds) for the core rain effects of this post for 1280×720 screen:

Platform PS3 XBOX360
Depth map (Dominated by characters) (256×256) ~0.32 ~0.20
Raindrops (Separate in two passes) ~0.40 + 1.29 ~0.34 + 1.38
Texture ripples generation (256×256) 0.14 0.15
Rain splashes (heavy rain) ~0.33 ~0.25
Camera droplets ~0.32 ~0.54
Total rain core ~2.8 ~2.86

We call them core rain effects because this is the effects you generally have enabled when rain is on. Remark: Given the rain budget it is rather difficult to take into account  the lighting in the core effects like in the Toy Shop demo [2],  that’s why we have not handle it . Other misc FX artists’ effects add to this, some like foot splashes are almost free. The ripples themselves with the dynamic rain material increase the cost of objects rendering (in forward renderer). These enhance features are always active in the shader to be able to support dynamic rain at all time, so this cost (not negligible) is included in the 30ms frametime limit that’s artists must respect when dealing with dry objects. So all cost summed we effectively go above the 3ms rain budget, dynamic rain is a costly features!
In our game we don’t include every features presented in this post. Performance, design decision change, artists/level designers training, lack of documentation are some factors at the origin of this.

To conclude, rain is a very complex phenomena and rainy world is really difficult to get right. The result we get still far from reality. This post present some common rain effects found in game and we hope you enjoy reading us. Other rain effects like misty objects or droplets glissing on glass can be found in the conference on the Toy shop demo [2] that I really recommend. But there still even more complex effects like those imply by wind (wave on puddle), or even caustic on wall due to puddle perturbation by rain. Whatever you chose to implement for increasing the rainy mood of your game try to link it with the gameplay. It is a better return on investment for such costly features :). As an example, in Far Cry 2/3 when the jungle is wet, it is more difficult to burn it [6].

I write a small RenderMonkey project containing most of the code presented in this b part if you want to play with it. RenderMonkey is an awesome tool from AMD available here : http://developer.amd.com/resources/archive/archived-tools/gpu-tools-archive/rendermonkey-toolsuite/ but sadly it is not supported anymore (But is should be!). The application itself contains a ground with vertex color tagged for one puddle and a heightmap with bump offset. The ground is lit by a direct point light and an indirect specular cubemap. It uses a PBR lighting model, see Adopting a physically based shading model. Variables (Wet level, rain intensity, flood levels…) are animated within a texture to show  the water accumulation and the drying process. By default an animation of 60 seconds (that’s you have seen for the drying of puddles) is play. You can disable the animation and play with the parameters by changing code in pixel shader of Main Pass and GenerateRippleTexture to

#define USE_TIMELINE 0

The default viewpoint doesn’t show anything, so you need to rotate the camera to see something. The default loaded ground mesh has two puddles (black vertex colors):

Twopuddleground
I made a zip file containing the RM project, the textures and the ground mesh. As WordPress don’t support zip file, I rename the zip in pdf. So to download the application right click on the following link : RenderMonkey-RainEffects, save target as… and then change the extension to “.zip”.

RenderMonkeyProject

Reference

[1] Nakamae, Kaneda, Okamoto, Nishita, “A Lighting Model Aiming at Drive Simulators”, http://dl.acm.org/citation.cfm?id=97922
[2] Tatarchuck, “Artist directable real-time rain rendering in city environments ” http://www.ati.com/developer/gdc/2006/GDC06-Advanced_D3D_Tutorial_Day-Tatarchuk-Rain.pdf
[3] Lobanchikov, Gruen, “GSC Game World‘s S.T.A.L.K.E.R : Clear Sky –a showcase for Direct3D 10.0/1” http://developer.amd.com/gpu_assets/01GDC09AD3DDStalkerClearSky210309.ppt
[4] Pesce, “Impossible is approximatively possible” http://c0de517e.blogspot.fr/2008/10/impossible-is-approximatively-possible.html
[5] Alexander, Johnson, “The Art and Technology Behind Bioshock’s Special Effects”, http://gdcvault.com/play/289/The-Art-and-Technology-Behind
[6] Levesque, “Far Cry: How the Fire Burns and Spreads”, http://jflevesque.com/2012/12/06/far-cry-how-the-fire-burns-and-spreads/
[7] Wikipedia, “Fog”, http://en.wikipedia.org/wiki/Fog
[8] Tatarchuck, “Artist-Directable Real-Time Rain Rendering in City Environments”, http://developer.amd.com/wordpress/media/2012/10/Tatarchuk-Isidoro-Rain%28EGWNph06%29.pdf
[9] Lopez, “Master Thesis – Real-time Realistic Rain Rendering”, http://upcommons.upc.edu/pfc/bitstream/2099.1/11303/1/Carles%20Creus%20Lopez.pdf
[10] Sadeghi, Muñoz, Laven,Jarosz, Seron, Gutierrez, Wann Jensen, “Physically-based Simulation of Rainbows”, http://zurich.disneyresearch.com/~wjarosz/publications/sadeghi11physically.html

9 Responses to Water drop 2b – Dynamic rain and its effects

  1. Nathan Reed says:

    Very nice ideas about the ripple maps!

  2. Teck Lee Tan says:

    I’ve had some pretty decent results overlaying the perfect circle alphas with a render cloud (or just running a difference cloud over it). Doesn’t do much other than ensure you don’t have perfectly-circular ripples, but that alone adds a little bit of visual interest/chaos to the effect, if that floats your boat.

    • seblagarde says:

      Interesting remark, we already debated here about the perfect circle case. We also have some circle distortion in our original code but this was not totally satisfying me. For this post, I chose to let the perfect circles. Your way to add chaos is a good idea, I will try it, thank you!

  3. seblagarde says:

    v1.1: Add “Other important rain effects not covered” section with a link to the nice Master thesis “Real-time Realistic Rain Rendering” of Carles Creus López http://upcommons.upc.edu/pfc/bitstream/2099.1/11303/1/Carles%20Creus%20Lopez.pdf. + Refactor fog and misty glow section and add toyshop misty glow code.

  4. Charles says:

    You could also use flow map to add more variation to the rain on horyzontal surface : http://www.youtube.com/watch?v=80Ce1i0ODGU.

    Very nice article and very inspiring !

    • seblagarde says:

      This is a nice idea! However I will use it based on slope of the ground as flat ground should not have any water flow (unlike in the video). There is also an extra cost to use such a feature that need to be measure.

      Thank you for the feedback!

  5. Pingback: S.T.A.L.K.E.R.: Clear Sky – Review – GND-Tech

  6. Pingback: dry and wet look study – drhein blog

  7. Pingback: 3DCG [Introduction] – Site-Builder.wiki

Leave a comment