Yohan Chalier Blog RSS

Randomart

I have a repository where I put random bits of code 'generating aesthetically pleasing stuff', called generative-art. Every now and then, I stumble upon a cool technique I want to try and implement, so I add it to this repo.

This is how, when I recently watched Tsoding's replay Implementing Scientific Paper in C, I tried to replicate it in JavaScript leveraging the power of a GPU through WebGL. I recently dug into shaders for an upcoming project — before next year hopefully — and this really is a neat application.

The idea of the paper is to be able to generate random artworks from a seed — an encryption key hash — to visually check whether the key has been altered or not, which is more fitted with human capabilities. Random generation is based on a functionnal grammar, a set of rules that may reference eachother, with associated probabilities. All nodes are mathematical functions taking a color mapped between -1 and 1 and outputting another color. By controlling the available nodes, their probabilities of appearance and the general depth of the tree, one may produce really nice drawings from pure mathematical functions.

expression tree rendered with Tikz
Example tree that produces a nice gradient.

Moreover, this kind of processing is especially suited for GPU rendering: each pixel is entirely determined by its UV coordinates — slightly scaled and offset — and transformation functions are very common and available in GLSL. The generated tree can thus be converted to a GLSL script and compiled into a WebGL shader, for immediate rendering. Here is a sample code for the tree presented above:

precision lowp float;
varying vec2 uv;
void main() {
  vec3 node9 = vec3(-0.26889400370419025, 0.10063280304893851, -0.00119680399075150);
  vec3 node11 = 2.0 * vec3(uv.x, uv.x, uv.x) - 1.0;
  vec3 node13 = 2.0 * vec3(uv.x, uv.x, uv.x) - 1.0;
  vec3 node14 = mix(node11, node13, (node9 + 1.0) / 2.0);
  vec3 node17 = 2.0 * vec3(1.0 - uv.y, 1.0 - uv.y, 1.0 - uv.y) - 1.0;
  vec3 node19 = 2.0 * vec3(uv.x, uv.x, uv.x) - 1.0;
  vec3 node21 = 2.0 * vec3(uv.x, uv.x, uv.x) - 1.0;
  vec3 node22 = mix(node19, node21, (node17 + 1.0) / 2.0);
  vec3 node24 = 2.0 * vec3(uv.x, uv.x, uv.x) - 1.0;
  vec3 node25 = mix(node22, node24, (node14 + 1.0) / 2.0);
  vec3 node27 = 2.0 * vec3(1.0 - uv.y, 1.0 - uv.y, 1.0 - uv.y) - 1.0;
  vec3 node29 = vec3(-0.62939488142728806, 0.76759272674098611, 0.34022910147905350);
  vec3 node30 = vec3(node25.x, node27.y, node29.z);
  gl_FragColor.xyz = (node30 + 1.0) / 2.0;
  gl_FragColor.w = 1.0;
}

An here is how it looks:

gradient wallpaper with tints of green, purple, pink and yellow
Bitmap output of the above tree. You can try it yourself.

It appears that with shaders, rendering takes way less time than the compilation of the WebGL program. Thus, it is possible to animate the canvas by adding a time-dependant node — the most basic one being a simple parameter t that can be specified as a uniform float in the shader. For looping, the value is passed to a sin function, which also scales its between -1 and 1, which suits our framework nicely. Here is how it looks:

Video loop example. Try it yourself.

If you want to have fun with it, it is available on my website. And the source code is available on GitHub, if you want to copy it or improve it!