Ream-time Interactive Water Surface Simulation
A program is implemented to simulate water surface behavior in real-time. This program is written in C++ with support of OpenGL.
- Heightfield water simulation
Water surface is simulated by a quad with a 128 by 128 vertices. Its height is stored in a texture with 128 by 128 pixels called height map. At each frame update, a new height value of each vertex will be calculated according to its neighbors' current height and all new height value will be calculated simultaneously. Some decay constant is also applied to the new height value to avoid non-stop diffusion of water wave.
A normal map of size 128 by 128 is also calculated from the height map for some other functions. Each vertex has two displacement vector from its position to its up vertex's position and its right vertex's position, respectively. Then the normal is calculated as the cross product of those two vectors. The normal map also updates once for every frame.
- Environmental mapping
Reflection and refraction on water surface are pre-calculated every frame and are stored in reflection texture and refraction texture, respectively. The reflection texture is captured by placing the camera under the water surface and only render objects above the water surface. The refraction texture is captured in a similar way, despite that the camera is placed in the same direction but closer to the water surface than the original camera. UV coordinate is calculated from water surface's NDC (Normalized Device Coordinate) space coordinate and its normal, which represents distortion generated by water wave. This method simulates reflection and refraction pretty well, and is cheaper than physically based ray-tracing in real-time.
Caustics are beautiful patterns generated when light is bent by the wavy water surface or other curvy transparent surfaces. A simplified photon mapping technique is used to implement the caustic effects. A 128-by-128-vertices mesh is used to represent light and the area of each triangle represents the light intensity. Light intensity is calculated to generate a caustic texture, which is also with size of 128 by 128. Each vertex of the light mesh is projected onto the floor along the light direction, reflected by the water surface according to its height and normal. The ratio of the reflected area to the original area is stored in the texture as light intensity. When applying this texture on the floor/wall, the pixel is projects along the inverse refracted light direction to the water surface to get the UV coordinate. The color at this pixel is added/subtracted by some fraction according to the light intensity.
- Soft shadow
Soft shadow is implemented in a similar way as how caustics is implemented. Each pixel on the floor/wall is projected onto the ceiling along the inverse light direction, refracted and then un-refracted, to check if the pixel hit the open ceil or the walls. Some portion of the color of the pixels that hit the wall will be subtracted to represent shadow. A linear filter is applied at the edge to simulate soft and smooth shadow.
- User interaction
User can click left bottom of the mouse to generate small waves on the water surface. A ray is cast from the camera through the mouse clicking position. If the ray goes across the water surface, a small wave is generated at the intersection. An interval timer is set to prevent continuous input which leads to dramatic and unrealistic effects.
Later in the project, three textures, i.e., height map, normal map and caustic area ratio map, are combined into one texture of vec4 (4-tuple floating point vector) structure. In the vector, one element is used to represent the height value, one is the caustic area ratio, and the other two are the x and y component of the normal vector. The z component of the normal vector is calculated from the x and y component by cross-product. This optimization reduces the texture number by 2 and prevent frequent read and write to different textures.
- Video demo