Published
Edited
Jul 18, 2021
8 forks
Importers
65 stars
Insert cell
Insert cell
shader({width: 640, height: 100})`
void mainImage(out vec4 fragColor, in vec2 fragCoord) {
fragColor = vec4(vec2(fragCoord.xy / iResolution.xy), 0.0, 1.0);
}`
Insert cell
shader({width: 640, height: 100})`
const float size = 25.0;

void mainImage(out vec4 fragColor, in vec2 fragCoord) {
vec2 p = fragCoord.xy;
float k = float(mod(p.x, size * 2.0) < size == mod(p.y, size * 2.0) < size);
fragColor = vec4(vec3(k), 1.0);
}`
Insert cell
Insert cell
Insert cell
shader({height: 100, iTime: true, visibility})`
const float size = 25.0;

mat2 rotate2d(float a) {
return mat2(cos(a), -sin(a), sin(a), cos(a));
}

void mainImage(out vec4 fragColor, in vec2 fragCoord) {
vec2 p = (fragCoord.xy - iResolution.xy / 2.0) * rotate2d(iTime / 10.0);
float k = float(mod(p.x, size * 2.0) < size == mod(p.y, size * 2.0) < size);
fragColor = vec4(vec3(k), 1.0);
}`
Insert cell
Insert cell
canvas = shader({height: 100, uniforms: {angle: "float"}})`
const float size = 25.0;

mat2 rotate2d(float a) {
return mat2(cos(a), -sin(a), sin(a), cos(a));
}

void mainImage(out vec4 fragColor, in vec2 fragCoord) {
vec2 p = (fragCoord.xy - iResolution.xy / 2.0) * rotate2d(angle);
float k = float(mod(p.x, size * 2.0) < size == mod(p.y, size * 2.0) < size);
fragColor = vec4(vec3(k), 1.0);
}`
Insert cell
canvas.update({angle: now / 10000.0 % (2 * Math.PI)})
Insert cell
Insert cell
Insert cell
Insert cell
shader({height: 100, inputs: {angle: viewof angle, size: viewof size}})`

mat2 rotate2d(float a) {
return mat2(cos(a), -sin(a), sin(a), cos(a));
}

void mainImage(out vec4 fragColor, in vec2 fragCoord) {
vec2 p = (fragCoord.xy - iResolution.xy / 2.0) * rotate2d(angle);
float k = float(mod(p.x, size * 2.0) < size == mod(p.y, size * 2.0) < size);
fragColor = vec4(vec3(k), 1.0);
}`
Insert cell
Insert cell
function shader({
width = 640,
height = 480,
devicePixelRatio = window.devicePixelRatio,
invalidation, // when the canvas is considered disposed
visibility, // if present, only draw when resolves
uniforms = {}, // declare your uniforms here
inputs = {}, // bind inputs to uniforms
iMouse = false, // not yet supported!
iTime = false, // enable built-in iTime uniform
sources = [], // extra sources (lib functions etc.)
preserveDrawingBuffer = false // if you want “Download PNG” to work
} = {}) {
uniforms = new Map(Object.entries(uniforms).map(([name, type]) => [name, {type}]));
inputs = new Map(Object.entries(inputs));
for (const {type} of uniforms.values()) if (type !== "float") throw new Error(`unknown type: ${type}`);
for (const name of inputs.keys()) if (!uniforms.has(name)) uniforms.set(name, {type: "float"});
if (iTime && !uniforms.has("iTime")) uniforms.set("iTime", {type: "float"});
if (visibility !== undefined && typeof visibility !== "function") throw new Error("invalid visibility");
return function() {
const source = String.raw.apply(String, arguments);
const canvas = document.createElement("canvas");
canvas.width = width * devicePixelRatio;
canvas.height = height * devicePixelRatio;
canvas.style = `max-width: 100%; width: ${width}px; height: auto;`;
const gl = canvas.getContext("webgl", {preserveDrawingBuffer});
const fragmentShader = createShader(gl, gl.FRAGMENT_SHADER, `precision highp float;
${Array.from(uniforms, ([name, {type}]) => `uniform ${type} ${name};`).join("\n")}
const vec3 iResolution = vec3(
${(width * devicePixelRatio).toFixed(1)},
${(height * devicePixelRatio).toFixed(1)},
${(devicePixelRatio).toFixed(1)}
);
`, ...sources, source, `
void main() {
mainImage(gl_FragColor, gl_FragCoord.xy);
}
`);
const vertexShader = createShader(gl, gl.VERTEX_SHADER, `
attribute vec2 a_vertex;

void main() {
gl_Position = vec4(a_vertex, 0.0, 1.0);
}
`);
const vertexBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, vertexBuffer);
gl.bufferData(gl.ARRAY_BUFFER, Float32Array.of(-1, -1, +1, -1, +1, +1, -1, +1), gl.STATIC_DRAW);
const program = createProgram(gl, vertexShader, fragmentShader);
gl.useProgram(program);
gl.bindBuffer(gl.ARRAY_BUFFER, vertexBuffer);
const a_vertex = gl.getAttribLocation(program, "a_vertex");
gl.enableVertexAttribArray(a_vertex);
gl.vertexAttribPointer(a_vertex, 2, gl.FLOAT, false, 0, 0);
for (const [name, u] of uniforms) u.location = gl.getUniformLocation(program, name);
const ondispose = invalidation === undefined ? Inputs.disposal(canvas) : invalidation;
let frame;
let disposed = false;
ondispose.then(() => disposed = true);
async function render() {
if (visibility !== undefined) await visibility();
frame = undefined;
gl.drawArrays(gl.TRIANGLE_FAN, 0, 4);
}
Object.assign(canvas, {
update(values = {}) {
if (disposed) return false;
for (const name in values) {
const u = uniforms.get(name);
if (!u) throw new Error(`unknown uniform: ${name}`);
gl.uniform1f(u.location, values[name]);
}
frame || requestAnimationFrame(render);
return true;
}
});
for (const [name, input] of inputs) {
const u = uniforms.get(name);
if (!u) throw new Error(`unknown uniform: ${name}`);
gl.uniform1f(u.location, input.value);
const update = () => {
gl.uniform1f(u.location, input.value);
frame || requestAnimationFrame(render);
};
input.addEventListener("input", update);
ondispose.then(() => input.removeEventListener("input", update));
}
if (iTime) {
frame = true; // always rendering
const u_time = gl.getUniformLocation(program, "iTime");
let timeframe;
(async function tick() {
if (visibility !== undefined) await visibility();
gl.uniform1f(u_time, performance.now() / 1000);
gl.drawArrays(gl.TRIANGLE_FAN, 0, 4);
return timeframe = requestAnimationFrame(tick);
})();
ondispose.then(() => cancelAnimationFrame(timeframe));
} else {
gl.drawArrays(gl.TRIANGLE_FAN, 0, 4);
}
return canvas;
};
}
Insert cell
function createShader(gl, type, ...sources) {
const shader = gl.createShader(type);
gl.shaderSource(shader, sources.join("\n"));
gl.compileShader(shader);
if (gl.getShaderParameter(shader, gl.COMPILE_STATUS)) return shader;
throw new Error(gl.getShaderInfoLog(shader));
}
Insert cell
function createProgram(gl, ...shaders) {
const program = gl.createProgram();
for (const shader of shaders) gl.attachShader(program, shader);
gl.linkProgram(program);
if (gl.getProgramParameter(program, gl.LINK_STATUS)) return program;
throw new Error(gl.getProgramInfoLog(program));
}
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