Porting Perlin Turbulence

As part of a project I’m working on right now, I’ve been looking at this set of SVG effects for HTML elements.

SVG distortion effects

For the purposes of this particular project, it’s desirable to re-create this effect in THREE.js using post-processing fragment shaders.

This allows us to use the turbulence effect in a broader context than simple HTML elements with relatively primitive SVG shapes, which is desirable for reasons that will be clear once the project is published and I’m able to share.

Looking at the source for the aforementioned article, I can see these distortion effects are powered by an old friend of mine – Perlin noise.

It’s been quite some time since I used Perlin noise, so after dusting off some old articles and refreshing myself with the methods and terminology, I’m equipped to run at this.

There are existing Perlin shaders out there, which I have had a play with, however none of these quite meet my needs in terms of the configurability offered by the SVG filters. I specifically need two-channel (X & Y) animated noise to pull these effects off in a post-processing context.

Shadertoy demonstration

Luckily for us, the SVG specification provides the code used to power the turbulence effect in plain C.

Yesterday I attempted to port this into ES6 and a GLSL fragment shader. Whilst I was able to get something that looks sensible in memory and compiles properly, there are problems with the current setup.

  • The seeded pseudo-random number function provided in the C code states that the function can be tested by stating what the 10,000th output from the function should be. My JavaScript equivalent does not give the specified output.
  • The gradient mapping array contains a lot of consecutive zero’s towards the end of the array. I wouldn’t expect to see this in a data set which is supposed to map random, smooth noise.
  • The biggest problem of all is that running the shader gives me a black screen. Clearly this isn’t working as intended. I should be seeing a mix of values on the red and green channels.

The shader itself is relatively quite simple, so at the moment I’m assuming that the problem lies in the pre-computations that are done in JavaScript. Luckily for us, this makes things significantly easier to debug.

I need to run the C code from the SVG specification with a debugger and some breakpoints, whilst simultaneously running the JavaScript port with the debugger open, and compare the operations on each line to figure out where they diverge.

This will make the problem point clear and from there I can establish how to make the JavaScript behave in an identical way to the C source.

I’ll need to grab Visual Studio C++ 2019 for this, which these days totals 8GB with all the accompanying packages.

Meanwhile, check out this quick experiment I wrote whilst waiting, perhaps this can be put to use somewhere!

I ran the code from the SVG spec in Visual Studio and I can see that I get the expected output from the random function.

Testing the random function

We’ll start from the beginning with the JavaScript port and first figure out why these two functions give different outputs when they should be the same. Maybe that will even solve subsequent issues!

Next we check the lattice selectors match.

And fix an issue to get the gradient maps to match.

Once we’ve got the exact same lookup table and gradient map going to the graphics card, a quick once over reveals a couple of missing lines due to human error on my part.

Solving the missing code finally completes this task.

This exactly matches the output of the SVG distortion functions, I’ve also added gain and lacunarity into the mix.

And that’s how you port effects written in C from the SVG spec into JavaScript and a WebGL shader!

Leave a Reply