Friday, November 6, 2015

Simulating Rain Effects

It has been raining here in San Jose this week, so it seems like a good time to talk about rain. I finally rewrote the rain system in 3DWorld to be faster and also look better, with denser raindrops, animated splashes, and wet surface lighting effects. I was originally planning to make a post about precipitation, including both rain and snow. However, it would probably be too long, and instead I'll talk about rain first, then add a later post on snow during the winter.

Rain can be toggled using the "B" hotkey in 3DWorld. The "m" and "M" keys control precipitation rate. The user can also enable "auto time advance", which moves the sun and moon to simulate changing time of day and enables random temperature change and weather effects such as rain, snow, clouds, wind, etc.

The old rain system simulated and drew individual raindrops as ellipsoids (spheres stretched vertically), and had no splash effects other than water ripples. This was slow on the CPU as every raindrop was checked for collision with the scene geometry every frame. The max number of droplets I could have in the scene before the framerate started to drop was around 40,000. That's not enough for a heavy rain effect, so I decided to take a different approach on the second attempt.

I found the following series of online articles helpful for creating realistic rain effects: here and here. I actually started working on the "lines" method of drawing raindrops before reading these articles, but they did lead to some improvements. Lines look better than small spheres when they're moving because they simulate motion blur, an effect our eyes see when observing fast moving, small objects. Lines are also more efficient to draw, since they cover more of the scene than points and therefore require fewer of them for a given perceived density of rain. Also, they're only two vertices, rather than many vertices used to draw a nearby sphere.

The lines move in the direction of the rain, which depends on the wind but is mostly downward. Rain/wind speed and direction can be changed in realtime using hotkeys or the onscreen display slider. Collision detection doesn't have to be very exact: nearby rain lines move too fast to see clearly, and distant rain lines are too thin/small to tell exactly what they're intersecting. Instead of checking each raindrop/line for collision with the exact scene geometry every frame, the detail intersection test is only done if the line intersects a rough grid-based (voxel) approximation to the scene. If a collision is found, the raindrop is respawned somewhere else above the player's field of view. Any raindrops that fall outside the simulation region (a cylinder around the player) are also respawned within the cylinder. The improved collision performance allows for up to 100,000 raindrops in the scene. This results in both a heavier apparent rain density and higher frame rate than 40,000 "old" raindrops.

Animated splashes are created whenever a raindrop's line intersects the scene geometry, at the exact point of intersection. These are small, partially transparent billboards rotated to face the player. They increase in size and fade to transparent over the course of several frames to give the effect of an expanding surface of water. I tried to capture these in the screenshots and video below, but they're small and short lived, which makes them difficult to see at lower resolution. There are typically a few thousand intersections per frame, but only a few hundred may be visible to the player. If the animation lasts for 10 frames then there will be a few thousand splashes to draw each frame, which has a negligible impact on frame rate.

Here is a screenshot comparison before rain vs. during rain. The rain lines and clouds are obvious, the splashes can be seen if you look closely, and the ground is darker and shiny/reflective.

Building scene before rain.
Building scene during rain with raindrops, splashes, and a wet surface lighting effect.

The bottom (rainy) image shows some procedural rain clouds in the sky. These were generated by rendering thousands of partially transparent 2D billboard sprites distributed in 3D space into a 2D texture. The texture is projected onto the sky and moved with the camera to get correct parallax. Lighting is computed per-cloud particle on the CPU every time the sun moves by ray marching through the cloud volume from the sun's position to the particle center and integrating cloud density along the ray. This makes the bottom of the clouds appear darker than the top.

Here is an overhead screenshot of several materials showing the specular/glossy wet surface. The lighting is mostly determined by the normal maps of each material (concrete, stone, brick, and wood).

View of wet objects from above where specular reflections from the sun can be seen

It took a bit of work to make the wet lighting effect work correctly and look convincing. I had to run a preprocessing step to tag scene objects as indoors vs. outdoors by casting vertical rays through all of the upward facing polygon vertices and center points to determine which objects were visible to the sky. If any rays reached the sky, that surface could receive raindrops and would be (at least partially) wet. If all rays were blocked, the polygon was likely indoors and would stay dry. The indoor/outdoor state also needed to be updated when objects moved or parts of the scene were destroyed. This information was passed to the shader to control the lighting diffuse and specular weights, and the glossiness (specularity). This method worked well for the sun and moon, which always shine down from above in a similar direction to the rain, and mostly light outdoor areas rather than indoor areas. It didn't work as well for dynamic light sources, in particular for large polygons that were half occluded from ran and should have been only half wet. It also didn't work well for surfaces that didn't work up, so I restricted dynamic lighting wetness to upward facing surfaces only.

Here is a screenshot with two dynamic light sources reflecting off the ground. There is a floating, moving green emissive sphere that produces green light. This sphere also casts soft shadows from the sun. There are actually 100 spheres in the scene, though only a few are in view. The second dynamic light source is the player flashlight, which casts the yellow circle of light seen in the foreground.

Dynamic light sources (green glowing sphere and yellow flashlight) on wet bricks. Note the green sphere's soft shadow.

Here is a screenshot of heavy rain, viewed from above. The framerate is still around 100 FPS. The brick floor, tile fountain, and wooden benches all show sparkly wet surfaces. The trees are not specular (the leaf shader doesn't support this effect yet).

Scene with heavy rain, viewed from above, showing a high density of raindrops rendered as lines

Here is a short video showing rain, splashes, and specular reflections of the sun on wet surfaces.

Do you see any differences between this video and previous 3DWorld videos that I have uploaded? This one was created by a new, internal video recording system rather than with Fraps. My implementation creates a pipe from 3DWorld to a ffmpeg process and feeds video frames to ffmpeg using a thread safe locking queue. There is no time limit or Fraps watermark, and I have more control over the recording parameters. In addition, the video is compressed in realtime in multiple background threads and requires no postprocessing. The only disadvantage is that there is no sound. I haven't figured out how to either capture or record OpenAL audio. So sorry, no rain or wind sounds this time.

I can record at 720p resolution (1280x720) with no problems, and the MPEG compression running in the background can keep up with little impact on the framerate. However, recording at the default 1080p full screen monitor resolution (1920x1080) I use to view 3DWorld fills the video buffer after a few seconds and starts to reduce the framerate so that it can keep up. If I use more threads for compression, I get an overall lower frame rate as this competes with 3DWorld for CPU resources. If I use fewer threads, the video buffer fills and stalls the pipeline for a few second every so often trying to keep up. Fortunately, recording at 720p is fine, since uploading the video will further reduce the resolution anyway. This should allow me to record longer videos in the future (though with no sound).

Update: Here is a new video showing a thunderstorm in tiled terrain mode, including rain, thunder, lighting, and wind. It's hard to see at this low resolution though.

Update 2: I recorded another lightning video in the house scene using Fraps (so that there can be sound).

No comments:

Post a Comment