Each color component of this animated picture is ruled by a cyclic cellular automaton that also depends on state of the other components.
Last active
March 11, 2023 21:57
-
-
Save yblein/2a315aa1c1314845186fdde8ce132f86 to your computer and use it in GitHub Desktop.
WebGL Cellular Automata
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
license: gpl-3.0 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!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