Hello! I’m Echo. I push the pixels and twiddle the bits that keep Skull Theatre running. In this incarnation of our blog, I’m going to showcase the technical side of the project. I’ll be the yang to Boone’s yin, if you will. I’m going to start by talking about, of all things, the weather. Now I should warn you that this post is going to be a little technical. If that’s not your bag, then here’s a bunch of pictures of cats in sinks instead. Anyways, let’s get started!
If you don’t live in the Great Northwest, you might be surprised to learn that it hasn’t properly rained in our hometown of Seattle since early July. We’ve seen some drizzle once or twice since then, but nothing befitting of our reputation as the soggy city. That’s all about to change with a pretty epic storm pushing through right now and promising to leave us with a good amount of the wet stuff. The lingering golds will soon be replaced with the tell-tale greens that the Emerald City is famous for.
So in celebration of this momentous occasion, I wanted to share with you some of the things that we’re doing with rain in our game. Rain is an interesting effect to create in a video game because, when done properly, it’s not just a single effect but rather a clever combination of effects that synergize to produce a convincing image. But when most people think of rain effects, they think of raindrops so I’ll focus on that for now, but I’ll also showcase one particular way that we’re combining our raindrop effect with another to build that ‘convincing image’ that I was going on about.
There are really two main ways to render raindrops in a game. I’m oversimplifying the problem here, but bear with me. The first method involves creating a whole lot of individual droplets (usually particles) and simulating them falling from the sky in the world. This method has the advantage of being fairly realistic. Since the droplets are in the world, you can simulate collisions with other objects and no extra effort is needed on your part as the engineer to make the droplets appear where you would expect them to be. The downside of this method is that it can get very expensive if you need to render a lot of rain. For example, if you were to simulate a heavy downpour, you would need many thousands of raindrop particles, which can quickly become prohibitively expensive to render.
The second method involves creating a texture that looks like a sheet of rain and scrolling it down the screen. The main advantage of this method is that the cost of rendering the effect is independent of the intensity of the rain. The graphics hardware doesn’t particularly care if your rain texture has two drops on it or two thousand – it’s just going to draw it over your scene at the same cost. The big downside of this method is that it isn’t realistic, and your engine will probably be spending a lot of time trying to convince the viewer that they aren’t just looking at a texture scrolling down the screen (which they are). There are way too many games out there with rain effects that lose all realism as soon as you look straight up. So preserving realism is a challenge here, and a big one.
So what’s the best method? As with most graphical effects, it depends on the situation. However, the second method has been gaining a lot of favor over recent years (actually, for quite a while) due to the fact that it lets you render heavy rain efficiently. And it turns out that there are a lot of tricks that you can play to make the effect not only seem realistic but actually interact with the 3D environment. Our game will be using the second method of rendering ‘screen space’ rain.
As I mentioned, I’ve extremely oversimplified the two methods above. If you’re going for high quality, you can’t just blend a scrolling sheet of rain over your scene and send it out the door. If the viewer is standing on a cliff overlooking a distant city, then your effect might look fine as is, but as soon as they walk up to a wall, it’ll look silly. How can there be thousands of raindrops falling between the viewer and the distant city, and the same number of raindrops between them and the nearby wall? Luckily, you can use the scene’s depth buffer to help out. Raindrops can test against the scene’s depth to determine if they’re in front of or behind objects that have already been rendered.
So now I can start describing the ‘anatomy’ of our raindrop. It needs to have an intensity, or opacity, to indicate how much it should blend with the scene. I’ve concluded that it should also have a depth which can be tested against the scene depth to determine if it should render. What other information can be loaded into a rain texture? I’ve got four color channels at my disposal, and so far I’ve only accounted for two. How about lighting information? One way to make your rain effect look more realistic is to realistically light it. Raindrops have a nonzero surface area, so therefore they should have a surface normal like everything else, right? So let’s give it one.
For my last channel, I’m going to add a ‘blip’ at the actual location of the raindrop. Raindrops are rendered as streaks due to perceptual motion blur, but an actual raindrop is close to spherical. If you want to simulate collisions with other objects, then you need a point-based representation of it (more on this later).
Combining all of this together, I get a texture that looks a little something like this:
Pretty retro, huh? Luckily, this isn’t what you see (perhaps next project).
This is what the ‘intensity’ channel looks like, which is a better representation of what the rain effect will look like in-game:
And this is the depth channel:
And the surface normal channel:
And finally, the droplet channel:
As you can see, my simple ‘rain sheet’ texture has gotten quite complex. But what can I do with all of that complexity? For one, I can render a properly lit depth-accurate rain effect that looks like this:
Once you have a depth channel in your rain texture, you can scale it and change the intensity of the rain effect. For example, you can have your rain shader ignore all but the top 50% of your depth range and simulate a light rainfall. The image below is the exact same rain shader and the exact same texture, with only the intensity shader constant adjusted:
This is super-useful when you want to make a rainstorm slowly pick up or die down. You can also further scale and offset the depth value to project the rain into the scene. One big problem with screen-space sheet rain is that if the raindrops are all the same size and moving at the same speed, then it still looks fake, even if it’s accurately clipping with the scene. To solve this, I have three ‘sheets’ of rain that use the same source texture, but project into different depth ranges of the scene. The closer rain sheet is scaled up and moves faster to enhance the appearance of the raindrops being closer to the camera. I also rotate the rain sheet slightly so you get a nice criss-cross effect of the three rain sheets scrolling in different directions across each other.
This is the same shader/texture, with all three rain layers set to render between ten and eleven meters from the camera:
It looks pretty silly, but it illustrates how drastically you can change the effect by just tweaking shader constants.
One last trick that I want to share is an easy way to make the rain not follow the camera. I hate it when you turn the camera side to side in a game and the rain turns with you – as if each raindrop is determined to fall right in front of you. Well, there’s an easy solution to this problem: scroll the rain texture in the opposite direction of the camera. To do this, take the horizontal angle of the camera’s view direction, scale it appropriately, negate it, and use it as a u coordinate offset for your rain texture. With this addition, my rain texture UV offsets end up looking like this:
float fUOffset = atan2( vViewDir.y, vViewDir.x ) * ( -fHorizScrollConst * fRainLayerScale );
float fVOffset = fmod( fCurrentTime * fRainLayerSpeed, fMaxTime );
And the rain no longer follows the camera. So now I’ve used the depth, normal, and intensity channels, but what about that last channel – the raindrop ‘blip’ channel? I’m super glad you asked that, Mr. Theoretical Reader, because this is the really cool part. We’re going to have a lot of water in our game, and we’d like the water to be interactive. There are plenty of well-documented ways of rendering water ripples and we aren’t planning anything particularly novel in that department, so suffice to say what we’re doing is simulating displacement across the surface of the water volume. But what if we wanted the raindrops to actually create water ripples when they impacted the surface of the water?
“Shenanigans!”, you exclaim. ”Water ripples are simulated in world space and rain is a screen space effect. Never the twain shall meet!” But it can be done. The trick is to rasterize into “water ripple UV space” (which is basically 2D world space where the water ripples are simulated) while rendering to the water ripple texture, but at the same time keep track of where each pixel is located in screen space. Then, each pixel in the water ripple texture can look up its current position in the rain texture to determine if a raindrop is colliding with the water surface at that location, in exactly the same way that we would do it in screen space. If it is, and we have a raindrop ‘blip’ at that position, then we can add a ripple to the ripple texture. The result is that every time a water droplet hits the surface of the water, a ripple appears in the world and persists even if the camera looks away and back again – even though the rain is just a 2D texture scrolling across the screen. The best part is, this can all be done with DirectX 9 hardware. No fancy compute shaders needed here!
So to summarize, the process of generating new water ripples from raindrops goes like this:
1. Bind the water ripple texture as your render target.
2. Draw a fullscreen quad representing the water’s surface using a special shader described below.
3. In the vertex shader:
3a. The output position is equal to the water ripple UV value of that vertex, projected into clip space.
3b. The vertex position is also transformed into your 3D scene’s screen space (not the ripple texture’s screen space) and passed along to the pixel shader.
4. In the pixel shader:
4a. Use the scene screen space position from the vertex shader to sample from the rain texture at that pixel.
4b. Perform a depth comparison between the scene depth from the vertex shader and the scene depth from the rain texture sample.
4c. If the two depth values are within a certain threshold AND the rain sample has a raindrop ‘blip’, then render a positive value to the ripple texture. Otherwise, leave the ripple texture as-is.
5. Update the water ripple simulation as you would normally.
I’ll leave you with a short video showing the water ripples in action. Pay close attention to the water ripples as the rain intensity is decreased. Notice that they always appear as a raindrop hits the surface of the water.
Until next time. Echo out.