Generating a water effect, part 1. SVG and Canvas

Ada Rose Cannon
Feb 10 · 6 min read

This is a 3 part breakdown of how this water effect works, you can view the finished demo here:

Source code for this first part is at the bottom of the article.

It was partly inspired by the water effects in Zelda Wind Waker.

Screenshot of the finished demo.

The first thing we need to do is make a good texture for the water. This is a picture to make these lines on the surface of the water. If you look carefully you’ll see it is a repeating pattern, but the picture is carefully designed to hide that fact and there are tricks we can use later on to hide it further.

When I began making this effect I used a dummy image for the water, but making our own is probably the best place to start since we will need to use it in later steps.

The finished texture should

  • Tile seamlessly
  • appear pretty random and organic
  • not obviously tesselate

Step 1. Generate Shapes

There is a type of diagram known as a Voronoi Diagram it describes areas which are closest to a particular point.

Voronoi diagram from Wikipedia

This is often used for working out catchment areas for public utilities like schools and doctors by finding the area closest to that point.

It generates nice looking cells which seemed like they could be a good starting point.

I used this library to do the calculations. For this project I combined it with some helper functions from some of the demos in the source code and put them into an ES6 module.

I tested it out by running it on 15 random points.

Doing this returned a set of cells and described their edges. Which is hard to examine without some kind of visual output.

There are a few ways I could render this output. My initial instinct was to draw it to a HTML Canvas but I knew that later I would want to run some fancy SVG filters on it, so I decided to turn these points into SVG polygons to view them.

First step is to generate the SVG and give it a white background and add a group element to put each polygon into.

This just makes a white SVG rectangle we now want to draw our cells, each cell will be a new in the SVG.

The end result shows it is working as expected:

Our rendered SVG, I have added circles at each site location.

Unfortunately this does not tile. To make it tile we can duplicate all the points on the 8 adjacent squares. That’s 8 times above, below, left, right and the diagonals, before we compute the cells.

Most of the polygons will be rendered off the edges of the SVGs viewBox but they will be cut off by the boundaries of the .

Now it tiles.

Next we will increase the spacing between each polygon by moving the vertices of each polygon a certain amount towards the center. First we will write a linear interpolation (lerp) function which returns a point that lies on a line between two other points.

Then we will apply this to move each vertex a little bit between it’s current location and the cell site (marked as a cyan circle).

This looks okay, but some lines are too thick and some are too thin. So instead of doing a constant lerp we will make it try to be a consistent distance.

This new interpolation looks a little more even and aesthetically pleasing.

Lerp applied

This is looking better but still too angular. To fix this we are going to use a trick I found on CSS Tricks for Gooey Effects. I love this effect.

If we apply it as a filter to each polygon we get nice rounded corners.

Getting closer.

This is looking even better, but some of the fine lines look a little too fine so we can then apply a slightly modified goo effect to the group as well.

The final result looks pretty good!

Now we just need to make it black and white to use it as a texture for WebGL.

Unfortunately SVG can’t be used in WebGL directly — it has to be rasterised to a canvas which involves a little trickery:

This will encode the SVG we made to a URL and assign it to the image. Once the image is loaded we can write it to a canvas. We use THREE.CanvasTexture to then load the image back.

It’s now ready to use in WebGL.

Next we will look at making this as a shader for use with AFrame.

Final Image Generator Source (without texture loading bit)

Samsung Internet Developers

Writings from the Samsung Internet Developer Relations Team. For more info see our disclaimer:

Thanks to Laura Morinigo

Ada Rose Cannon

Written by

Co-chair of the W3C Immersive Web Working Group, Developer Advocate for Samsung.

Samsung Internet Developers

Writings from the Samsung Internet Developer Relations Team. For more info see our disclaimer:

Welcome to a place where words matter. On Medium, smart voices and original ideas take center stage - with no ads in sight. Watch
Follow all the topics you care about, and we’ll deliver the best stories for you to your homepage and inbox. Explore
Get unlimited access to the best stories on Medium — and support writers while you’re at it. Just $5/month. Upgrade