Saturday, October 16, 2010

Ocean

Bit.Trip Runner is probably the happiest game I've ever played. Seriously, what other game have you played where you run around collecting gold while leaving behind freaking rainbows in an atari-like world? Not that it has anything to do with this post, of course.

But implementing oceans into my engine was so much easier than I thought. It took me one day to do it. Probably because I was following Riemer's tutorials, and I suggest that you do the same thing (www.riemers.net)  I figured that the water he's done was simple enough for me to implement quickly, and just the right type of water for this type of project.

Think of water as a plane. A HUGE plane that stretches over the playing field of the game... with waves and reflections and refractions and stuff. Sounds complicated, but as far as geometry goes, it's just a single plane.

I didn't have any wave maps on hand, so I started with mirroring and basic dirty color. Dirty color is what we'll call the base color of the ocean.


At first, when you want mirror, all you need is a "reflection map". To create the reflection map, you first must create the clipping plane. The plane must be a horizontal plane set a little bit below the ocean. There should be a little offset, because once waves are implemented, it may reflect incorrectly. Next, you render your scene as normal with everything below the clipping plane culled away. But instead of rendering the scene from the view of the camera, you must render from below the ocean. To do this, you take the camera's y coordinate, and flip it over the ocean. For example, if it's y coordinate is 4 and the ocean height is 1, the reflected y coordinate is -2.

By the way, if you want to save a couple of milliseconds, you could probably get away with making the reflection map 1/2 or 1/4 the size of the screen, so it would render faster. This will result in a pixelated reflection, but blurring can be applied to the map afterwards.

Then you take the reflection map, and in the ocean shader make it sample from the map using the reflected camera's view matrix as projected onto the screen.

Adding the dirty color is easy. Simply add it into the color you get from the reflection map.

Of course, what's water without waves?


I thought I was finished at this point, since this requires making normal maps, but then I found a Normal Map plug-in for GIMP here (http://registry.gimp.org/node/69) By rendering a tiled turbulence cloud in GIMP, followed by using the normal map plugin, I got this picture:


If you're wondering what a normal map is, a normal map contains data for which way light will reflect. The rgb values of the picture represents xyz values of rays going from point (0, 0, 0) to (x, y, z). These values are in the range of [-1, 1], but a picture contains values ranging from 0 to 255. So normal maps have 0 = -1, 127 = 0, and 255 = 1. This is why this image is mostly blue, by default a texture should reflect light forward (perpendicular from it's orientation), so that value is (0, 0, 1), with the normal map equivalent being (r = 127, g = 127, b = 255).

This normal map won't be used just for lighting, though. It's used also to displace pixels in the reflection map. To do that you would first find the color of the normal map at the specified coordinates. When you find that, you must get its "perturbation", or the offset, by using that color. Multiply by the height of the wave, and you have the offset of the reflection of the map. Now when you find the coordinates of the pixel to sample from in the reflection map, just add the perturbation to that coordinate.

All that's left is refraction.



To do this, I made a refraction map the same way I did with the reflection map, only this time I'm rendering from the camera's view and not the reflected camera's. I will also cull away everything above the water. I'll get the color of the refraction the same way I get the color of the reflection, too. But this time, I'll need to know how much of the reflection color and how much of the refraction color to use. Something called the Fresnel term will be used to find this out. To get the fresnel term, you find the dot product of the normal vector (from the wave normal map) and the camera-to-pixel vector. I get the correct color value by "lerping" between the refractive and reflective colors with the fresnel term.

Lerping's a weird word. But it's so useful. I also created a texture for the ocean to replace having a single dirty color. I sample the texture using the coordinates from the bump map, and blend that in as the "dirty color", by lerping the refract/reflect color and the dirty color with the alpha of the dirty color.


And so with that, and the specular highlight of the water, I'm done. I think I'll enjoy the pixelated look to my water texture. I still have the sun to add in, which I'll probably draw into my sky cylinder. Lens flare, maybe?

No comments:

Post a Comment