Before working on the model viewer I actually wanted to create a “water slice”. I found the idea pretty interesting both as a visual concept as well as an interesting challenge. Water in general is always an interesting rendering problem and I have not dealt with that one before.
What is it?
- A FFT transformed height field used as base for the water surface
- A water cylinder below
- both have fresnel and light absorption functions applied
- combined with some stencil shading
Above: First iterations, with some fake green subsurface scattering
How is it done?
Well the whole thing went through several iterations and in the end I stopped working on it, since it didn’t seem like something that would be interesting enough to release without having fishes and boats etc. Plus the rendering obviously needed more work.
The basic idea for the heighmap transformation is derived from J. Tessendorf’s paper about simulating ocean behaviour by statistical analysis of waves and work out the current heightmap values with fast fourier transformations. You can find the link below.
For realistic rendering I combined that with a fresnel map that is generated on startup during runtime for realistic reflection behaviour as well as a water color map based on light absorption, which i generate with some real world values.
I had to combine that with calculating the volume traversal for each pixel, which was interesting to solve, but I did something similar for volumetric lights before.
Basically you would want to know the entry and exit point in the water surface for each pixel. That can be done by either reading the pixel depth (for pixels on the water surface) or calculating intersection points for a line / circle (basic math).
Along with the knowledge of how far we travel through the water we also have to know how much light reaches the water depth in the first place and then integrate these together.
This poses an interesting issue where if we have a water cylinder like this we have to decide whether or not the light outside the cylinder but below the water surface travels as if it’s in air or in water which we just can’t see.
Depending on which one we chose the water gets darker faster or slower depending on depth from above and from the sides.
The depth also depends on the dynamic surface height of the waves. You can see that pretty well in the first gif.
Another interesting challenge is to simulate actual refraction for meshes inside the water.
This is everything but trivial:
The game approach to refraction is usually to use a post-process effect
Render the scene “as is” without any refraction to a buffer
On the objects that refract (glass, water etc.) we usually have a distortion texture or use normals to get some distortion value
read the buffer with the distortion offset and present the data
This looks plausible enough in many cases, but has the major drawback of not being accurate at all.
The problem lays in the fact that depending on how far away the mesh point is from the point of refraction the mesh will appear more skewed to one side for the viewer.
E.g. a fish right in front of the glass of an aquarium more or less has the same position as if there was no water/glass. A fish at the back end however will have a great offset from what his position would be like without refraction. This is not accounted for with the post-process approach.
So I attempted to “solve” the issue for a cylinder in the vertex shader and transform the position to where it should be after the refraction. Some of it I could do analytical (because of the cylinder assumption) and some I had to do numerical.
Either way, it works *somewhat*, but it does have bugs from certain perspectives and I’d have to fix that if i wanted to release the project.
- Ocean FFTs
J Tessendorf’s Paper
Simulating Ocean Water – CiteSeerX
- Water light absorption values
- Reflection, Refraction –> Wikipedia