Water simulation demo in WebGL
- Rotate the camera by dragging the mouse.
- Zoom in and out with the mouse wheel
- Adjust the waves using the form below
The water is a simple sum of eight 2D sine waves. Every parameter of every wave can be adjusted using the form above. The vertex shader uses the first four rows (big waves) to compute the height (Z coordinate) of the water at each vertex. The water mesh itself is a 256x256 grid, which is not high enough resolution for smaller waves. The fragment shader uses all eight rows to compute the normal and the reflection vector based on the derivative of the waves and the camera position. The reflection vector is used to access a regular 2D tiled sky texture. Try turning off all the waves to see it. You could also use a skybox for this, but this works well enough for me.
The reflectance of the water is determined by the steepness of the camera angle with respect to the surface of the water. This is done per pixel, and it takes the waves into account. I originally implemented Fresnel equations for this. Although it gave physically accurate results, they didn't really look the way I wanted. I ended up settling on a quick approximation using the sine of the angle.
You'll notice that as the water gets deeper, the terrain gets less visible. This is a fog effect computed when the terrain is drawn. You can still see it if you turn off the water rendering. The amount of fog is proportional to the underwater distance between the camera and the terrain. You could probably simplify this by just using the water depth, but I think this looks better.
The terrain itself is drawn using a simple 256x256 height map I made in GIMP and a pre-generated normal map. The terrain mesh is just a grid of 2D points; the Z coordinate is pulled out of the height map by the vertex shader. I also made the sand and grass textures in GIMP.
- vector.js - a basic 3-vector library
- matrix.js - a basic 4x4 matrix library with some useful transformations
- camera.js - manages a camera which rotates around a sphere
- shader.js - extracts and compiles GLSL vertex and fragment shaders. Also locates uniforms and attributes.
- buffer.js - manages vertex buffer objects
- texture.js - loads textures from images
- mesh.js - ties it all together
I encourage you to hit "View Source" in your browser and look at
initWaterMesh to see how these are used. Basically, you create a new
IndexedMesh object by giving a WebGL primitive (e.g.,
TRIANGLES), a shader, and dictionaries of uniforms, attributes, and textures. After that, you just call
mesh.prepare() to set up everything and
mesh.draw() to draw the mesh.
I hope you enjoyed this demo. I think technologies like WebGL are really exciting. There are many other emerging HTML5 standards like WebWorkers and WebSockets which are really increasing the capability of web apps. I don't think web apps will ever entirely replace traditional native apps, but I still think they have a bright future.