Water surface rendering

Hello.

I have been doing some research on water surface rendering, and I am having great difficulty finding good information on it. What I am primarily interested in is the coloring and lighting effects on the water surface. Also, I have two restrictions: the renderer MUST NOT use pixel shaders, and it must not require an extremely finely tesselated mesh.

Probably the best source is the Gamasutra article by Jensen and Golias, which most people who are doing water rendering seem to be aware of. The link is http://www.gamasutra.com/gdce/2001/jensen/jensen_01.htm .

I have a few problems with this article. First of all, I dont have a degree in mathematics, and when they start using the Greek letters, well… its all Greek to me. Not to mention, I havent studied differential equations. The other problem I have with it is that it is largely theoretical, and does not discuss the specifics of rendering with a 3D API such as OpenGL or Direct3D.

Nonetheless, the article got me really excited when it showed this screenshot: http://www.gamasutra.com/gdce/2001/jensen/fig_3,4.gif
along with its wireframe version: http://www.gamasutra.com/gdce/2001/jensen/fig_3.5.gif . However, the author does not explain the technique used to achieve that in an detail.

Here is another water engine I found, which is ideally what I’d like to achieve: http://www.geocities.com/davidl1_2000/index.htm . If you look at the top screenshot, you can tell that the color of water is dependent on the viewing angle. Where the angle is greater, the water is more whitish-greenish, and when you are looking directly at it, the water is more darkish-bluish, which is an important property of water.

Finally, here is another demo I found: http://www.people.fas.harvard.edu/~reichard/water/
His demo does not run directly out of the zip, but after making a few changes I got it to run. He uses vertex colors to make the color of the water, along with an animated bump map which appears to be calculated in real time (havent looked deep enough into the code to confirm this though). Since he uses vertex colors, the water looks really great for a finely tesselated mesh, but looks terribly blocky for a coarse mesh. His demo also accounts for the fact that water looks more blue when looking directly at it, and more white-green when looking at it at an angle.

At this point, I dont have a specific question, but I am hoping someone can lead me in the right direction (discuss techniques, or point me to a website). I have not made up my mind on any particular technique. What I want to achieve is a realistic coloring/lighting of water surfaces that will work with a coarse mesh and doesnt require pixel shaders. Also, it must account for the view dependent nature of water coloring (e.g. more blue when looking directly at it, more white-green when looking at an angle).

Many thanks in advance.

[This message has been edited by ioquan (edited 09-10-2003).]

What I want to achieve is a realistic coloring/lighting of water surfaces that will work with a coarse mesh and doesnt require pixel shaders.

Well, you’re not going to get it.

Realistic water requires complicated computations. Now, you can either do this via vertex programs running on a finely-tesselated mesh, or you have to resort to relatively complex per-fragment operations. Even simple bump mapping techniques require some form of moderately complex per-fragment ops.

You say that you aren’t willing to use pixel shaders. Which OpenGL extensions precisely are you wanting to avoid? You can do some pretty good stuff with NV_register_combiner or ATI_fragment_shader. Even ARB_texture_env_combine can do some interesting stuff, especially with the crossbar extension.

In short, what tools are you allowing yourself?

My video card is a GeForce 4 MX, which does support GL_NV_register_combiners. However, I’d like to avoid using that because it is not supported on ATI cards, and I dont want to have to write completely different renderers for different cards. GL_ARB_texture_env_combine would be fine since it is supported on most cards (though I’m not sure what it does). GL_ATI_fragment_shader is totally out of the question since my hardware doesnt support it and I wouldnt be able to test it.

I should clarify when I say “realistic”. I dont mean something that is a perfectly physically correct model. I mean something that looks reasonably good. Even if I decide to use fragment programs or something later, I still need to be able to render water without them. I want to get the best looking water possible without them.

You say I will not be able to achieve the results I want without pixel or vertex shaders. However, one of the demos I posted above does not use any vertex or pixel shaders, and achieves excellent results (however, it does use a very fine mesh). If I recall correctly, even Waverace 64 for the Nintendo 64 had good looking water (but its been a while since I’ve seen it, so I’m not sure).

At this point I’m basically working on the minimum renderer; something that I can be sure will work on most cards and will produce good results. I think there must be a way to obtain results close to the ones in the demos I posted above without pixel or vertex shaders.

[This message has been edited by ioquan (edited 09-10-2003).]

An idea that springs to mind is to use a technique similar to celshading.

Compute the dot product of the vertex normal and the normal from each vertex to the eye position.

Use this value as a lookup in a 1d texture. This texture holds the colors you want the water to have.

I have no idea if this will work or how it will look but it might be worth a try.

You can find a celshading demo over at nehe.gamedev.net

Regards!
/hObbE

P.S. It will probably not be as visually stunning as the sample pictures. At least not without a pretty dense mesh.

[This message has been edited by tobiaso (edited 09-10-2003).]

Thanks for your reply.

Your suggestion is similar to one of the demos I posted above. The problem is that it requires a very fine mesh. With a coarse mesh, using vertex colors in such a way gives you very blocky and ugly results.

Here is a thought that comes to mind… maybe someone can tell me if its good. What if I were to make a cube map that has an animated texture? Near the bottom part of the cube map, it would be more white, and near the top it would be more blue. When drawing the water, I bounce rays off of each vertex to get the texture coordinates in the cube map. Because the bottom of the cube map is more white, and the top is more blue, when you are looking at an angle, you will get more whitish colors, and when looking straight down, you will get more bluish colors. One problem that comes to mind with this technique is that the water might look weird if the entire texture changes whenever the view changes. But then again, it might look really good. Any thoughts?

The cg SDK (from memory) contained a very nice water simulation. I believe it ran entirely on the GPU so it most likely used “pixel shaders”. The effect was available through the effects viewer and consisted of a rotating shape under the water (sorry, I am being kind of vague here - I don’t have it in front of me). At any rate you could probably implement the same in software but performance will most likely suffer. One drawback with this method will be the colouring of your surface (the nVidia example looks more like mercury than water).

Of course if you absolutely mustn’t use “Pixel Shaders” then I believe you’re in luck because I think that’s an exclusively Dx term.

Pixel shaders, fragment programs, whatever they are called. Sorry if my terminology is incorrect… I havent messed around with that stuff yet.

Anyway, using a cube map, I managed to achieve results that are closer to what I want. Here are some screenshots:
http://users.ms11.net/~ioquan/screenshots/envmap0.jpg http://users.ms11.net/~ioquan/screenshots/envmap1.jpg http://users.ms11.net/~ioquan/screenshots/envmap2.jpg http://users.ms11.net/~ioquan/screenshots/envmap3.jpg

It still has a long way to go I think. The surface looks more like mercury or oil than water.

I have a question about cube mapping. Why is it that when I use GL_REFLECTION_MAP_EXT to generate tex coords, I only ever see three of the faces of the cube map, and when I use GL_NORMAL_MAP_EXT, I only ever see 2 of the faces of the cube map? Also are there any tricks I can use that might help improve the cube mapping on the water surface? (like using the texture matrix or something)

Thanks.

DAMN! How did you do that effect? I haven´t done much about water-surfaces, but that looks pretty good, and from your statement i would think it isn´t very hard to do, is it?

Well, about the cubemap thing. You have to rotate your cubemap acording to the rotation of the viewer. You do this by simply rotating the texture matrix of the cubemap. Either in the same direction the viewer was rotated, or in the reverse, i can´t remember, but it should be easy to find out.

As long as you don´t do that, it is absolutely normal, that you only see 2 or 3 faces of the cubemap.

Jan.

Jan2K is right. You have to apply your camera rotations (not the inverse) to the texture matrix so that your cubemap rotates with your viewpoint.

I would think that the reason your water looks a bit “metallic” is that water doesn’t always reflect light off it’s surface. If you look at the examples you gave in your original post, the closer the water is to the viewer, the less it reflects (ie. you can see into the water). And individual waves further off will also exhibit this property (on their near side).

Presumably this effect could be achieved with multitexturing or multiple passes, but I haven’t any ideas as to how it could be done. Even if you just managed the effect for “nearer to the viewpoint” rather than on individual waves. You could probably use a reflectivity mask (eg. a grayscale image that modifies the amount of reflection) and combiners - but I don’t know how successful this would be (it might look worse than what you already have - or it might be too expensive for adequate performance)

Any chance of a wireframe image of what you’ve done? I’m interested in how you have done the water (presumably it’s bump mapping).

Thanks for the info on the texture matrix. Once I do that it should look better when the camera is in motion (the cube map wont seem to follow you).

Jan2000, the effect is not too difficult to achieve. All I did was make a cube map that represents the approximate color I want the water to be at different angles, and draw the water with GL_REFLECTION_MAP_EXT tex coord generation. Once I get my water engine to a more satisfactory state, I intend to write a tutorial(s) explainging the different techniques I used.

rgpc,
I think I might be able to get the effect you mentioned by adding an alpha channel to the cube map textures. Also, by using another cube map for my second texture unit, I might be able to achieve better results.

The only texture on the water right now is the one cube map. Here is the front face texture I am using: http://users.ms11.net/~ioquan/cube5.jpg . Since I’m not rotating the texture matrix, it is always in front of the viewer. By messing around with the cube textures, I have gotten a much less metallic look. Here is a new screenshot: http://users.ms11.net/~ioquan/screenshots/water_island.jpg .

Here is a wireframe shot, along with a corresponding filled version. http://users.ms11.net/~ioquan/screenshots/water_wire.jpg http://users.ms11.net/~ioquan/screenshots/water_fill.jpg

[This message has been edited by ioquan (edited 09-12-2003).]

The effect mentioned by rgpc is known as Fresnel effect. You would blend between reflection and refraction by a value which is the cosine between the vector to eye and surface normal, raised to a power (I think, could be wrong about something there). It’s essential when you can see through the water. You can approximate this a number of ways, a bad approximation can still look pretty good. Of course, it’s already an approximation as is.

Originally posted by ioquan:
[b]Thanks for the info on the texture matrix. Once I do that it should look better when the camera is in motion (the cube map wont seem to follow you).

Jan2000, the effect is not too difficult to achieve. All I did was make a cube map that represents the approximate color I want the water to be at different angles, and draw the water with GL_REFLECTION_MAP_EXT tex coord generation. Once I get my water engine to a more satisfactory state, I intend to write a tutorial(s) explainging the different techniques I used.

rgpc,
I think I might be able to get the effect you mentioned by adding an alpha channel to the cube map textures. Also, by using another cube map for my second texture unit, I might be able to achieve better results.

The only texture on the water right now is the one cube map. Here is the front face texture I am using: http://users.ms11.net/~ioquan/cube5.jpg . Since I’m not rotating the texture matrix, it is always in front of the viewer. By messing around with the cube textures, I have gotten a much less metallic look. Here is a new screenshot: http://users.ms11.net/~ioquan/screenshots/water_island.jpg .

Here is a wireframe shot, along with a corresponding filled version. http://users.ms11.net/~ioquan/screenshots/water_wire.jpg http://users.ms11.net/~ioquan/screenshots/water_fill.jpg

[This message has been edited by ioquan (edited 09-12-2003).][/b]

Have you considered using an EMBM type effect? This would allow for per-pixel water surface normals rather than the per-vertex normals that you are using to index the cubemap. Scrolling bump maps across the mesh would allow you to greatly reduce the complexity of your geometry (maybe scroll in two different directions and adverage the two). All of this can be done with multi-pass, fixed function (non-shader) hardware.

Question, how is the refraction applicable to deep water when you cant see through the water surface?

Yes, I am considering bump mapping. I am looking into various techniques for it now. Can someone tell me which extensions I should be looking into for bump mapping?

Just wanted to say that the water is really comming along! Great work! Maybe you could post a tutorial or something explaining your technique when you are satisfied!

Wirting a tut or article is a great way to force yoursel to really think about, and understand, what you are doing!

Regards!
/hObbE

The trick in ioquan’s water is that the ‘bump’ effect that is visible on his screen shots comes from the cube map.

To get the most realistic water, you should use a cube map for reflection (skybox one is perfect), an animated bump map to perform true reflective bump mapping, vertex color, and diffuse and decal textures. This is kinda hard to perform without fragment programs. [PROPAGANDA] Fragment program makes it easy. Use fragment and vertex program. This is easy, a lot easier than fixed function TexEnv calls. [/PROPAGANDA]
Adding high dynamic range texture for the cube map would be a huge improvment to fresnel reflection as sun light is always fully reflected.
Last add-ons could be to animate diffuse and decal textures, as is made in the nvidia water effect cited above, and mesh animation for big waves.

I can’t think of more than this, except for that little white stuff that I don’t know the name in english but in french is called ‘ecume’.

Ho yes, maybe you could put some refraction when in shallow water …

SeskaPeel.

I am still having difficulty getting the color to be how I want it. I’ve decided to work on the wave geometry in the meantime.

In Jensen and Golias’s paper, they mentioned that it would be possible to use a precomputed animation which will repeat after a certain number of frames by making the wave frequencies a multiple of the same basic frequency. I dont understand the math behind this, and I was wondering if someone might be able to explain that a little better.

One of the demos I posted above comes with source code, and I would like to modify it to write out a binary file with a precomputed animation. However, in order to make this work, I need to modify it so the animation repeats. If someone could help me understand this, it would be very helpful.

I can explain the reasoning about the frequencies, if that’s what you need.

Take two sine waves; they each repeat with a given period, call it T1 and T2.

Now, add the two sine waves; what is the new period (and does it even exist)? It is the smallest period T such that N1T1 == N2T2 for two integers N1 and N2. If T1 / T2 is a rational number, then such integers N1 and N2 exist and the summed function is also periodic with a period T. (Note that if T1 and T2 are both rational numbers then T1 / T2 is guaranteed to be rational itself.) If T1 / T2 is not rational, then there is no pair of integers N1 and N2, and the summed function is not periodic.

That’s what they mean by the frequencies. Frequency is just the inverse of the period (ie, f = 1/T). If you don’t massage the frequencies, the ratio of frequencies (and hence the ratio of periods) is not a rational number, so the summed function is not periodic. This means, by definition, that it never repeats in time. But, if you fudge the frequencies so that they are all a multiple of a common frequency, then the ratio of their frequencies is in fact a rational number, and hence the summed function is periodic in time.

Incidentally, Waverace64 used a localized mesh to simulate the waves using spring equations. It was most certainly not highly tesselated. They used a flat background gradiant to simulate the fall-off in color as the water approached the horizon. The water ‘closest’ to you was then rendered on top. The look of the water was achieved using specular reflections and nothing else.

The N64 didn’t have much power, yet the game was very impressive in that the water effects were dynamically pleasing and contributed beautifully to the gameplay.

I’ve never done any actualy jet-skiing, but that game was a helluvalotta fun.

If you happen to own the game, get yourself a copy of UltraHLE with a 3dfx wrapper (or simply find a computer with a good ol’ Voodoo), download the ROM and set the thing to wire-frame mode. You can learn a lot of tricks from those old N64 games.

Good luck

Coriolis, thanks a bunch for your explanation, that helps a lot.

This might be a dumb question, but how can I determine the time T that it takes for a given sine wave to repeat?

not sure if this is what you mean, but you can count polarity changes or zero crossings - every two crossings equals one cycle (as the waveform goes from + to - and then back again)… works well for most waveforms (apart from asymmetrically distorted or dc-biased ones).

on the other hand, this is a form of digital sampling so there are loads of potential issues if you’re thinking of doing this per-frame (same as very low and highly unstable sampling rate) - because of this your frequency detector will be fairly inaccurate and unreliable… search for dsp guide if you want a thorough grounding (could well be too much/irrelevant if all you want to do is use detuned/harmonic sines ).

what is in your mind though? tbh if you want to go with this approach, generally the idea is to pick as many sines as you like (or can afford, in terms of cpu time) and assign different frequencies to them - by frequency i mean “the rate of change in phase [per second]”, i.e.

phase += frequency * last_frame_time
out = sin(phase)

as mentioned, frequency == 1/t, so as long as you keep frame time and frequency in like units (so that 1.0f = 1 second, say) this’ll work reliably. for very rhythmic results use pure harmonics (sines which are all perfect multiples of each other), for slow modulation use sine waves with nearly identical frequencies. you could add some noise too (with appropriate smoothing). btw, remember to keep phase in range (so that sin() doesn’t break after 30 minutes )

add these sine waves or modulate them in some fashion (try varying amplitude and frequency over time), and you can get masses of smooth motion (especially with frequency modulation and/or different waveforms). hope this is what you had in mind…

(btw, you’ll probably want to use 1d lookup textures, which means you can have arbitrary waveforms…)