Development Diaries, Volume 7

Posted by Alex Jordan on

Happy 50th post, everyone! I have a lot to show today, for a relatively small amount of work. Observe:

This is another one that's better off being viewed as non-embedded, in HD.

What you're looking at is some pretty impressive wave refraction, although the 2D art it's refracting is still, ah... subpar. (I deployed a debug version of my handiwork to the Xbox 360 last night and could barely see the low-contrast refraction effect, so I plopped the 2D art back into Photoshop and pumped up the contrast. Still, it's nothing to write home about.)

But anyway, that's only what's being refracted, not how it's being refracted.

In yesterday's Development Diary, I mentioned that I was using a 3D model with undulating waves to compute the refraction angle. Here's a render of it just as a model, not as pure refraction data:

Noise map

Now you can see how the fun-house mirror effect of the previous videos was achieved, through a few relatively modest waves.

In doing my research on what kind of water effect to implement, I noticed that almost every XNA tutorial assumed that the programmer would want the actual surface of the water to undergo huge, moving swells... gigantic, oceanic waves. But yesterday's Monkey Island video didn't show huge waves... it was a simple 2D effect (whereas surface waves are 3D), and yet it looked gorgeous.

That meant that I should aspire to have a flat, 2D water surface (which is more efficient to render, anyway) and generate small wave-style refraction from a different source. That source? Normal maps!

If you recall, I discussed normal maps way back in my first Development Diary. Initially, I was using normal maps to generate per-pixel shadowing on my models. That's one use for them. But since a normal map is actually an image that indicates the surface angle of any given pixel, that means I can use it for more than shadows. I can also use it to determine an angle of refraction!

So, since I'm not going for huge, 3D waves, here we see a flat, 2D plane with the normal map pasted onto it:

Flat map

The normal map's primary color is blue, but you'll also notice some red and green highlights. Yes, it looks weird, since it's basically color data that will eventually be interpreted as surface data. And no, that weirdly blue-ish, wavelike image will never show up in game. I'll read the data from it, compute refraction, and then refract the background image instead.

This flat surface is far easier to use than the wave surface in the previous image. Since I'm still tweaking my effect - playing with the level of refraction, as well as the scale of the effect - it's a relatively easy matter to resize this flat surface, as it will only resize in two dimensions (X and Y). If I were to resize the wave surface, it would resize in all three dimensions, and eventually eclipse and obscure my world model, which it has to share 3D space with. And anyways, like I said, I don't need huge waves.

The "glistening", gently undulating effect that's shown in the video at the top of this post is achieved by slowly moving this plane from left to right while leaving the image behind it (the one to be refracted) completely still. Easy, right? If, like in the image above, there were a specific image attached to the plane, you'd easily notice that image moving. But since there's no specific image, only the invisible normal map which is used to refract the non-moving background, the movement doesn't look like real movement. It looks like waves!

So, that part's done. Yesterday's goals still apply, however. More tweaking is needed, and I really, really need to generate a nice water background to generate waves on. You know... one that doesn't look like a blue raspberry popsicle melted onto a Rand McNally map.