This page shows the graphic effect of a water ripples caused by water drops.
Above the implementation of a water ripple effect is shown. It is implemented in Typescript using HTML5 canvas.
Feel free to download the source code at the top of the page to check out how it works.
Move the mouse cursor on above black canvas to create water drops under the cursor. Alternatively, switch the mode to random water drop creation
for the effect of falling water drops onto the canvas.
After the background image is loaded, it is possible to switch to image mode so that the water drop effect is not only black and white, but creates
a ripple effect on an image.
The damping factor defines how fast the water drop waves are soften.
The underlying concept with the base algorithm (for black and white) is quite easy and very elegant. It is taken from [1], thus it is not my invention.
Without repeating the whole article, here a short summary of the key points:
In short, the new current height value is an average value (smoothened value) of the previous four neighbour values (multiplied by factor 2) combined with the vertical velocity (see [1] for a deeper explanation).
The new current value is multiplied by a factor < 1 so that is slowly damped (and will vanish after some time).
At the end of one cycle, the CurrentBuffer and the PreviousBuffer are swapped, so both buffers exchange their roles in each cycle.
In the following, the complete algorithm for propagating the water ripples is shown:
In the following, the first few cycles of the height map evolution are shown, after one value in the center of the height map is set to a value unequal to zero:
0 | 0 | 0 | 0 | 0 |
0 | 0 | 0 | 0 | 0 |
0 | 0 | -10 | 0 | 0 |
0 | 0 | 0 | 0 | 0 |
0 | 0 | 0 | 0 | 0 |
Cycle 0
0 | 0 | 0 | 0 | 0 |
0 | 0 | 0 | 0 | 0 |
0 | 0 | 10 | 0 | 0 |
0 | 0 | 0 | 0 | 0 |
0 | 0 | 0 | 0 | 0 |
Cycle 1
0 | 0 | 0 | 0 | 0 |
0 | 0 | 5 | 0 | 0 |
0 | 5 | 0 | 5 | 0 |
0 | 0 | 5 | 0 | 0 |
0 | 0 | 0 | 0 | 0 |
Cycle 2
0 | 0 | 2 | 0 | 0 |
0 | 5 | 0 | 5 | 0 |
2 | 0 | 0 | 0 | 2 |
0 | 5 | 0 | 5 | 0 |
0 | 0 | 2 | 0 | 0 |
Cycle 3
Drawing the water ripples in black and white only is easy as only gray-scale pixels are drawn. By default, each pixel is black. If a height value is unequal to 0, it is clampled into range [0, 255] and this value is used for each component R, G and B to get a gray-scale color respresenting the height of a water ripple.
Overlaying the water ripples on an image is more interesting. It raises the question how to modify the existing image pixels by using the current height map?
The height map is used to calculate the gradient for each pixel, for x-direction and y-direction. Here, the gradient is defined as the difference between the left and right neighbor (x-gradient) and between the top and bottom neighbot (y-gradient).
If a gradient is unequal to zero, meaning there is water ripple causing the difference in the height, this gradient value is used as offset value: After checking for value for valid bounds, it is used to as offset to the actual current position access the RGBA value of the image.
Here the simple procedure in pseudo code that produces the nice ripple effect on an image:
That's it for now, hope you learned something new. Go on and check out the source code for more details.
2023/12/03: Initial release.