Skip to content

Instantly share code, notes, and snippets.

@yblein
Last active March 11, 2023 21:57
Show Gist options
  • Save yblein/2a315aa1c1314845186fdde8ce132f86 to your computer and use it in GitHub Desktop.
Save yblein/2a315aa1c1314845186fdde8ce132f86 to your computer and use it in GitHub Desktop.
WebGL Cellular Automata
license: gpl-3.0

Each color component of this animated picture is ruled by a cyclic cellular automaton that also depends on state of the other components.

<!doctype html>
<html>
<head>
<meta charset="utf-8">
<title>WebGL Cellular Automaton</title>
<style>
html, body { width: 100%; height: 100%; overflow: hidden; background-color: black }
* { margin: 0; padding: 0; }
#canvas {
position: absolute;
left: 0;
right: 0;
top: 0;
bottom: 0;
margin: auto;
width: 80%;
height: 80%;
}
</style>
<script src="https://cdnjs.cloudflare.com/ajax/libs/dat-gui/0.6.1/dat.gui.min.js"></script>
<script src="https://twgljs.org/dist/2.x/twgl.min.js"></script>
</head>
<body>
<canvas id="canvas"></canvas>
<script id="vert-passthrough" type="x-shader/x-vertex">
#ifdef GL_ES
precision mediump float;
#endif
attribute vec2 position;
void main() {
gl_Position = vec4(position, 0, 1);
}
</script>
<script id="frag-display" type="x-shader/x-fragment">
#ifdef GL_ES
precision mediump float;
#endif
uniform sampler2D buffer;
uniform vec2 size;
uniform int nbStates;
void main() {
vec4 v = texture2D(buffer, gl_FragCoord.xy / size);
// "wrong" color tranformation to brighten the result a bit
gl_FragColor = vec4(1.0 - v.rgb * float(255 / nbStates), 1.0);
}
</script>
<script id="frag-update" type="x-shader/x-fragment">
#ifdef GL_ES
precision mediump float;
#endif
uniform sampler2D buffer;
uniform vec2 size;
uniform int nbStates;
uniform int threshold;
void main() {
float dx = 1.0 / size.x;
float dy = 1.0 / size.y;
vec2 uv = gl_FragCoord.xy / size;
vec4 s = texture2D(buffer, uv);
vec4 r = s;
float ns;
float n;
ns = mod(s.r * 255.0 + 1.0, float(nbStates));
n = 0.0;
n += float(texture2D(buffer, mod(uv + vec2(dx, 0), 1.0)).r * 255.0 == ns);
n += float(texture2D(buffer, mod(uv - vec2(dx, 0), 1.0)).r * 255.0 == ns);
n += float(texture2D(buffer, mod(uv + vec2( 0, dy), 1.0)).r * 255.0 == ns);
n += float(texture2D(buffer, mod(uv - vec2( 0, dy), 1.0)).r * 255.0 == ns);
n += float(texture2D(buffer, mod(uv + vec2(dx, dy), 1.0)).r * 255.0 == ns);
n += float(texture2D(buffer, mod(uv - vec2(dx, dy), 1.0)).r * 255.0 == ns);
n += float(texture2D(buffer, mod(uv + vec2(dx, -dy), 1.0)).r * 255.0 == ns);
n += float(texture2D(buffer, mod(uv - vec2(dx, -dy), 1.0)).r * 255.0 == ns);
n += float(s.b * 255.0 == ns);
if (n >= float(threshold)) {
r.r = ns / 255.0;
}
ns = mod(s.g * 255.0 + 1.0, float(nbStates));
n = 0.0;
n += float(texture2D(buffer, mod(uv + vec2(dx, 0), 1.0)).g * 255.0 == ns);
n += float(texture2D(buffer, mod(uv - vec2(dx, 0), 1.0)).g * 255.0 == ns);
n += float(texture2D(buffer, mod(uv + vec2( 0, dy), 1.0)).g * 255.0 == ns);
n += float(texture2D(buffer, mod(uv - vec2( 0, dy), 1.0)).g * 255.0 == ns);
n += float(texture2D(buffer, mod(uv + vec2(dx, dy), 1.0)).g * 255.0 == ns);
n += float(texture2D(buffer, mod(uv - vec2(dx, dy), 1.0)).g * 255.0 == ns);
n += float(texture2D(buffer, mod(uv + vec2(dx, -dy), 1.0)).g * 255.0 == ns);
n += float(texture2D(buffer, mod(uv - vec2(dx, -dy), 1.0)).g * 255.0 == ns);
n += float(s.r * 255.0 == ns);
if (n >= float(threshold)) {
r.g = ns / 255.0;
}
ns = mod(s.b * 255.0 + 1.0, float(nbStates));
n = 0.0;
n += float(texture2D(buffer, mod(uv + vec2(dx, 0), 1.0)).b * 255.0 == ns);
n += float(texture2D(buffer, mod(uv - vec2(dx, 0), 1.0)).b * 255.0 == ns);
n += float(texture2D(buffer, mod(uv + vec2( 0, dy), 1.0)).b * 255.0 == ns);
n += float(texture2D(buffer, mod(uv - vec2( 0, dy), 1.0)).b * 255.0 == ns);
n += float(texture2D(buffer, mod(uv + vec2(dx, dy), 1.0)).b * 255.0 == ns);
n += float(texture2D(buffer, mod(uv - vec2(dx, dy), 1.0)).b * 255.0 == ns);
n += float(texture2D(buffer, mod(uv + vec2(dx, -dy), 1.0)).b * 255.0 == ns);
n += float(texture2D(buffer, mod(uv - vec2(dx, -dy), 1.0)).b * 255.0 == ns);
n += float(s.g * 255.0 == ns);
if (n >= float(threshold)) {
r.b = ns / 255.0;
}
gl_FragColor = r;
}
</script>
<script type="text/javascript">
"use strict";
var gl;
var quadBuf;
var programInfo = {};
var fbInfo = [];
var currTex = 0;
var attachments;
var running = true;
var uniforms = {
buffer: null,
size: null,
nbStates: 7,
threshold: 2,
};
// fill the current texture with random data
function randomizeCurrTex() {
var w = gl.drawingBufferWidth;
var h = gl.drawingBufferHeight;
var len = w * h * 4;
var data = new Uint8Array(len);
while (len--) {
data[len] = Math.random() * uniforms.nbStates;
}
gl.bindTexture(gl.TEXTURE_2D, fbInfo[currTex].attachments[0]);
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, w, h, 0, gl.RGBA, gl.UNSIGNED_BYTE, data);
}
function init() {
var canvas = document.getElementById("canvas");
canvas.width = canvas.clientWidth;
canvas.height = canvas.clientHeight;
gl = canvas.getContext("webgl");
var arrays = {
position: {
numComponents: 2,
data: new Float32Array([-1.0, -1.0, 1.0, -1.0, -1.0, 1.0, 1.0, 1.0]),
}
};
quadBuf = twgl.createBufferInfoFromArrays(gl, arrays);
var createProgramInfo = function(frag) {
var p = twgl.createProgramFromScripts(gl, ["vert-passthrough", "frag-" + frag]);
return twgl.createProgramInfoFromProgram(gl, p);
}
programInfo.display = createProgramInfo("display");
programInfo.update = createProgramInfo("update");
attachments = [{ format: gl.RGBA }];
fbInfo.push(twgl.createFramebufferInfo(gl, attachments));
fbInfo.push(twgl.createFramebufferInfo(gl, attachments));
randomizeCurrTex();
var gui = new dat.GUI();
gui.close();
gui.add(uniforms, 'nbStates', 3, 12, 1).onChange(randomizeCurrTex);
gui.add(uniforms, 'threshold', 2, 4, 1).onChange(randomizeCurrTex);
gui.add(window, 'running').onChange(animate);
}
function animate() {
if (!running) {
return;
}
window.requestAnimationFrame(animate, gl.canvas);
if (twgl.resizeCanvasToDisplaySize(gl.canvas)) {
twgl.resizeFramebufferInfo(gl, fbInfo[0], attachments);
twgl.resizeFramebufferInfo(gl, fbInfo[1], attachments);
randomizeCurrTex();
}
gl.viewport(0, 0, gl.drawingBufferWidth, gl.drawingBufferHeight);
uniforms.size = [gl.drawingBufferWidth, gl.drawingBufferHeight];
// update automata
uniforms.buffer = fbInfo[currTex].attachments[0];
currTex = (currTex + 1) % 2;
gl.bindFramebuffer(gl.FRAMEBUFFER, fbInfo[currTex].framebuffer);
gl.useProgram(programInfo.update.program);
twgl.setBuffersAndAttributes(gl, programInfo.display, quadBuf);
twgl.setUniforms(programInfo.update, uniforms);
twgl.drawBufferInfo(gl, quadBuf, gl.TRIANGLE_STRIP);
// render on screen
uniforms.buffer = fbInfo[currTex].attachments[0];
gl.bindFramebuffer(gl.FRAMEBUFFER, null);
gl.useProgram(programInfo.display.program);
twgl.setBuffersAndAttributes(gl, programInfo.update, quadBuf);
twgl.setUniforms(programInfo.display, uniforms);
twgl.drawBufferInfo(gl, quadBuf, gl.TRIANGLE_STRIP);
}
init();
animate();
</script>
</body>
</html> 
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment