Published
Edited
Nov 22, 2019
1 star
Also listed in…
Generative Art
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
{
const t1 = newTexture(seed);
const t2 = newTexture();
const fb1 = newFramebuffer(t1);
const fb2 = newFramebuffer(t2);
for (let i = 0; true; ++i) {
gl.useProgram(timestepProgram);
gl.viewport(0, 0, W, H);
for (let j = 0; j < 32; ++j) {
gl.bindTexture(gl.TEXTURE_2D, t1);
gl.bindFramebuffer(gl.FRAMEBUFFER, fb2);
gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);
gl.bindTexture(gl.TEXTURE_2D, t2);
gl.bindFramebuffer(gl.FRAMEBUFFER, fb1);
gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);
}
gl.useProgram(renderProgram);
gl.viewport(0, 0, canvas.width, canvas.height);
gl.bindTexture(gl.TEXTURE_2D, t2);
gl.bindFramebuffer(gl.FRAMEBUFFER, null);
gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);
yield i;
}
}
Insert cell
Insert cell
Insert cell
Insert cell
seed = {
restart;
const a = new Float32Array(4 * W * H);

for (let y = 0; y < H; ++y) {
for (let x = 0; x < W; ++x) {
const i = W * y + x << 2;
a[i + 0] = -0.3;
}
}

function dot(cx, cy, r = 5) {
const r2 = r ** 2;
for (let y = cy - r; y < cy + r; ++y) {
for (let x = cx - r; x < cx + r; ++x) {
if ((x - cx) ** 2 + (y - cy) ** 2 < r2) {
const i = W * y + x << 2;
a[i + 0] = 0.6;
}
}
}
}

for (let i = 0; i < 2; ++i) {
dot(W * Math.random(), H * Math.random());
}

return a;
}
Insert cell
function newTexture(source) {
const texture = gl.createTexture();
gl.bindTexture(gl.TEXTURE_2D, texture);
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, W, H, 0, gl.RGBA, gl.FLOAT, source);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.REPEAT);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.REPEAT);
return texture;
}
Insert cell
function newFramebuffer(texture) {
const framebuffer = gl.createFramebuffer();
gl.bindFramebuffer(gl.FRAMEBUFFER, framebuffer);
gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, texture, 0);
return framebuffer;
}
Insert cell
Insert cell
timestepShader = newShader(gl.FRAGMENT_SHADER, `
precision mediump float;
uniform sampler2D u_image;
uniform vec2 u_size;

// Parameters from Ready: Patterns > FitzHugh-Nagomo > ising_regime.vti
const float A0 = -0.01;
const float A1 = 2.0;
const float EPSILON = 0.1;
const float DELTA = 7.0;
const float TIMESTEP = 0.03;
const float SCALE = 1.0;

void main() {
vec2 position = gl_FragCoord.xy;

vec2 value = texture2D(u_image, position / u_size).xy;

vec2 laplacian = - 4.0 * value
+ texture2D(u_image, (position + vec2(0.0, 1.0)) / u_size).xy
+ texture2D(u_image, (position + vec2(1.0, 0.0)) / u_size).xy
+ texture2D(u_image, (position + vec2(0.0, -1.0)) / u_size).xy
+ texture2D(u_image, (position + vec2(-1.0, 0.0)) / u_size).xy;

vec2 delta = vec2(
value.x - value.x * value.x * value.x - value.y + laplacian.x * SCALE,
EPSILON * (value.x - A1 * value.y - A0) + DELTA * laplacian.y * SCALE
);

gl_FragColor = vec4(value + delta * TIMESTEP, 0.0, 0.0);
}`)
Insert cell
renderShader = newShader(gl.FRAGMENT_SHADER, `
precision mediump float;
uniform sampler2D u_image;
uniform vec2 u_size;

void main() {
float c = texture2D(u_image, gl_FragCoord.xy / u_size).y;
float v = smoothstep(-0.10, -0.05, c);
gl_FragColor = vec4(0.5, 0.8, 0.0, v);
}`)
Insert cell
vertexShader = newShader(gl.VERTEX_SHADER, `
attribute vec2 a_position;

void main() {
gl_Position = vec4(a_position, 0.0, 1.0);
}`)
Insert cell
function newShader(type, source) {
const shader = gl.createShader(type);
gl.shaderSource(shader, source);
gl.compileShader(shader);
if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) throw new Error("cannot compile shader");
return shader;
}
Insert cell
Insert cell
renderProgram = newProgram(vertexShader, renderShader)
Insert cell
timestepProgram = newProgram(vertexShader, timestepShader)
Insert cell
function newProgram(vertexShader, fragmentShader) {
const program = gl.createProgram();
gl.attachShader(program, vertexShader);
gl.attachShader(program, fragmentShader);
gl.linkProgram(program);
if (!gl.getProgramParameter(program, gl.LINK_STATUS)) throw new Error("cannot link program");
const a_position = gl.getAttribLocation(program, "a_position");
const u_size = gl.getUniformLocation(program, "u_size");
gl.useProgram(program);
gl.bindBuffer(gl.ARRAY_BUFFER, gl.createBuffer());
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([-1, -1, 1, -1, -1, 1, 1, 1]), gl.STATIC_DRAW);
gl.enableVertexAttribArray(a_position);
gl.vertexAttribPointer(a_position, 2, gl.FLOAT, false, 0, 0);
gl.uniform2f(u_size, W, H);
return program;
}
Insert cell
gl = {
const gl = canvas.getContext("webgl");
if (!gl) throw new Error("WebGL unsupported");
if (!gl.getExtension("OES_texture_float")) throw new Error("OES_texture_float unsupported");
if (gl.getParameter(gl.MAX_TEXTURE_SIZE) < W) throw new Error("big textures unsupported");
return gl;
}
Insert cell

Purpose-built for displays of data

Observable is your go-to platform for exploring data and creating expressive data visualizations. Use reactive JavaScript notebooks for prototyping and a collaborative canvas for visual data exploration and dashboard creation.
Learn more