Email Updates RSS Subscribe
Line

This blog is created and maintained by the technical team at Hook in an effort to preserve and share the insights and experience gained during the research and testing phases of our development process. Often, much of this information is lost or hidden once a project is completed. These articles aim to revisit, expand and/or review the concepts that seem worth exploring further. The site also serves as a platform for releasing tools developed internally to help streamline ad development.

Launch
Line

Hook is a digital production company that develops interactive content for industry leading agencies and their brands. For more information visit www.byhook.com.

Line

Squirming Pixels

Line
Posted on February 27th, 2013 by Parker
Line

Download the source for this demo here.
Check out the full-screen demo.

Conway’s Game of Life is well known in programming circles. Abstractly, it’s a set of rules applied to a grid of boolean (either on or off) cells. On each iteration, the cells in the grid evaluate their neighbors and decide whether to turn on or off. From Wikipedia,

  • Any live cell with fewer than two live neighbours dies, as if caused by under-population.
  • Any live cell with two or three live neighbours lives on to the next generation.
  • Any live cell with more than three live neighbours dies, as if by overcrowding.
  • Any dead cell with exactly three live neighbours becomes a live cell, as if by reproduction.

The rules are delightfully simple, considering the results. Since it’s inception in 1970, enthusiasts have devised carefully balanced “organisms” that are arranged to cycle, move, explode, grow, shrink, or even replicate. One such pattern was selected as the unofficial hacker emblem because of how endearing demo scene and hobbyist programmers find it.

So what is that pulsing pink thing?

The demo above is a WebGL and CoffeeScript implementation of a generic cellular automaton, with some slight deviations to make it extra trippy. Let’s break that down.

  • It’s a generic cellular automaton. That means it can be Conway’s Game of Life, but it can also be lots of other things by changing three variables: the lower bound for survival, the upper bound for survival, and the birth rate. Conway’s rule was {2, 3, 3} but the demo above starts with rule {2, 5, 3} for a higher rate of survival (and growth).
  • It uses WebGL, so the game logic is done on the graphics card with a pixel shader. This means that there is a constant performance based on the resolution of the demo. Oh, and it’s lightning fast. In this demo I used the goodness that is three.js to speed up development and reduce headaches.
  • It uses CoffeeScript, but this is just my preference. The golden rule of CoffeeScript is: “It’s just JavaScript”.
  • The pixel shader has a tweak to make the demo look…cooler. Over time, it fades the alpha of each cell. Because of the way GLSL works, once the alpha is less than 0, it snaps back up to 1. This creates a pulsating effect that really makes it feel squirmy.

The Code

There are two interesting bits of code worth talking about. The first is from the pixel shader that makes it all tick.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
// state of current cell
vec4 c = texture2D(buffer, vuv);
 
// distance to step in each direction to check neighbor cells
float dx = 1.0 / size.x;
float dy = 1.0 / size.y;
 
// sum of neighbors of current cell
float n;
n += ceil( texture2D( buffer, mod( vuv + vec2( dx,   0), 1.0) ).a );
n += ceil( texture2D( buffer, mod( vuv - vec2( dx,   0), 1.0) ).a );
n += ceil( texture2D( buffer, mod( vuv + vec2(  0,  dy), 1.0) ).a );
n += ceil( texture2D( buffer, mod( vuv - vec2(  0,  dy), 1.0) ).a );
n += ceil( texture2D( buffer, mod( vuv + vec2( dx,  dy), 1.0) ).a );
n += ceil( texture2D( buffer, mod( vuv - vec2( dx,  dy), 1.0) ).a );
n += ceil( texture2D( buffer, mod( vuv + vec2( dx, -dy), 1.0) ).a );
n += ceil( texture2D( buffer, mod( vuv - vec2( dx, -dy), 1.0) ).a );
 
if (c.a > 0.0)
{
    gl_FragColor = (n >= rules.x && n <= rules.y) ? vec4(color, c.a) : vec4(0.0); 
}
else
{
    gl_FragColor = (n == rules.z) ? vec4(color, 1.0) : vec4(0.0);
}

I pass in the previous frame as a uniform sampler2D. The pixel shader then sums the alpha channel (I’m using it as a poor-man’s data texture) of neighboring pixels to find how many neighboring “alive” cells exist, and then evaluates the correct rule based on whether the cell was on or off in the previous frame. Note that I apply ceil to the alpha values I sample, since I would like to end up with a whole number of neighbors (although there are continuous cellular automata). I also intentionally summed the alpha values instead of having eight if statements, as branching in WebGL is bad for performance.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
# Configure the front and back render targets.
 
@frontrt = new THREE.WebGLRenderTarget 2, 2, bufferparams
@backrt  = new THREE.WebGLRenderTarget 2, 2, bufferparams
 
# Build the shader texture, mesh, and scene.
 
@uniShader = 
  rules  : { type: 'v3', value: new THREE.Vector3(@alpha, @beta, @gamma) }
  size   : { type: 'v2', value: new THREE.Vector2()                      }
  mouse  : { type: 'v2', value: new THREE.Vector2()                      }
  buffer : { type: 't',  value: @backrt                                   }
  color  : { type: 'c',  value: new THREE.Color(0xF54173)                }
 
@uniShader.buffer.value = @backrt # is this necessary?
 
mat = new THREE.ShaderMaterial
  uniforms       : @uniShader
  vertexShader   : $('#passthru-vertex').text()
  fragmentShader : $('#automaton-fragment').text()
 
geo  = new THREE.PlaneGeometry 2, 2
mesh = new THREE.Mesh geo, mat
@sceneShader.add mesh
 
# Build the screen texture, mesh, and scene.
 
@uniScreen = 
  buffer : { type: 't', value: @frontrt }
 
@uniScreen.buffer.value = @frontrt # is this necessary?
 
mat = new THREE.ShaderMaterial
  uniforms       : @uniScreen
  vertexShader   : $('#passthru-vertex').text()
  fragmentShader : $('#passthru-fragment').text()
 
geo  = new THREE.PlaneGeometry 2, 2
mesh = new THREE.Mesh geo, mat
@sceneScreen.add mesh
1
2
3
4
5
6
7
8
9
10
# Update the shader buffer textures. The automaton shader renders the back 
# buffer onto the front buffer, which is then rendered to the screen.
@uniShader.buffer.value = @backrt
@uniScreen.buffer.value = @frontrt
 
@renderer.render( @sceneShader, @camera, @frontrt )
@renderer.render( @sceneScreen, @camera ) 
 
# Swap the buffers with a destructuring assignment.
[ @frontrt, @backrt ] = [ @backrt, @frontrt ]

The second bit of code worth talking about is the texture swapping used to create the feedback loop necessary for cellular automata. The first code block above is from the scene setup, and the second is from the rendering function. I create two scenes, two render targets, two ShaderMaterials and two planes to apply the materials to. The screen scene is very simple, and just renders the front render target to the scene. The shader scene is where all of the magic happens. It takes the back render target and renders it onto the front render target through our special pixel shader. My favorite bit about this code is at the end of the render function, where I swap the render targets with a CoffeeScript destructuring assignment. One of the many reasons I love CoffeeScript. Now the front render target is the back render target and vice versa, and the loop repeats.

Download the source for this demo here.

Line

Leave a Reply

*

Line
Line
Pony