Public
Edited
Feb 5, 2024
Fork of Shader
4 forks
Importers
4 stars
Insert cell
Insert cell
shader({ width: 640, height: 200 })`
void mainImage( out vec4 fragColor, in vec2 fragCoord ) {
// Normalized pixel coordinates (from 0 to 1)
vec2 uv = fragCoord/iResolution.xy;
// varying pixel color
vec3 col = 0.5 + 0.5*cos(uv.xyx+vec3(0,2,4));
// Output to screen
fragColor = vec4(col,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({ width: 640, height: 100, iTime: true, visibility })`
void mainImage( out vec4 fragColor, in vec2 fragCoord ) {
// Normalized pixel coordinates (from 0 to 1)
vec2 uv = fragCoord/iResolution.xy;
// Time varying pixel color
vec3 col = 0.5 + 0.5*cos(iTime+uv.xyx+vec3(0,2,4));
// Output to screen
fragColor = vec4(col,1.0);
}
`
Insert cell
Insert cell
shader({ height: 100, iMouse: true })`
void mainImage( out vec4 fragColor, in vec2 fragCoord ) {
if (distance(fragCoord, iMouse.xy) < 50.) {
fragColor = vec4(1.,0.,0.,1.);

// Left Click
} else if (iMouse.z == 1.) {
fragColor = vec4(1.,1.,.2,1.);

// Right Click
} else if (iMouse.w == 1.) {
fragColor = vec4(1.,.2,1.,1.);

} else {
fragColor = vec4(0.1,0.1,0.1,1.);
}
}
`
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,
preserveDrawingBuffer = false,
visibility, // if present, only draw when resolves
inputs = {}, // bind inputs to uniforms
iTime,
iMouse,
sources = [],
uniforms = {}
}) {
uniforms = new Map(
Object.entries(uniforms).map(([name, value]) => {
let [_, type, dims] = value.match(/([^[]+)((?:\[[\s0-9]+\])*)*/);
return [name, { type, dims }];
})
);
for (const { type } of uniforms.values())
if (type !== "float") throw new Error(`unknown type: ${type}`);
if (iTime && !uniforms.has("iTime")) uniforms.set("iTime", { type: "float" });
if (iMouse && !uniforms.has("iMouse"))
uniforms.set("iMouse", { type: "vec4" });

inputs = new Map(Object.entries(inputs));
for (const name of inputs.keys())
if (!uniforms.has(name)) uniforms.set(name, { type: "float" });

return function () {
const source = String.raw.apply(String, arguments);
const canvas = DOM.canvas(
width * devicePixelRatio,
height * devicePixelRatio
);
const gl = canvas.getContext("webgl2", { preserveDrawingBuffer });
canvas.style = `max-width: 100%; width: ${width}px; height: auto;`;

const fragmentShader = createShader(
gl,
gl.FRAGMENT_SHADER,
`#version 300 es
precision highp float;

${Array.from(
uniforms,
([name, { type, dims }]) => `uniform ${type} ${name}${dims || ""};`
).join("\n")}
const vec3 iResolution = vec3(
${(width * devicePixelRatio).toFixed(1)},
${(height * devicePixelRatio).toFixed(1)},
${devicePixelRatio.toFixed(1)}
);`,
...sources,
source,
`
out vec4 fragColor;
void main() {
mainImage(fragColor, gl_FragCoord.xy);
}`
);

const vertexShader = createShader(
gl,
gl.VERTEX_SHADER,
`#version 300 es
in vec4 a_position;
void main() {
gl_Position = a_position;
}`
);

const program = createProgram(gl, vertexShader, fragmentShader);
const positionAttributeLocation = gl.getAttribLocation(
program,
"a_position"
);
const vao = gl.createVertexArray();
gl.bindVertexArray(vao);
for (const [name, u] of uniforms)
u.location = gl.getUniformLocation(program, name);
const positionBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
gl.bufferData(
gl.ARRAY_BUFFER,
new Float32Array([-1, -1, 1, -1, -1, 1, -1, 1, 1, -1, 1, 1]),
gl.STATIC_DRAW
);
gl.enableVertexAttribArray(positionAttributeLocation);
gl.vertexAttribPointer(positionAttributeLocation, 2, gl.FLOAT, false, 0, 0);
gl.viewport(0, 0, gl.canvas.width, gl.canvas.height);
gl.useProgram(program);
gl.bindVertexArray(vao);

async function render() {
if (visibility !== undefined) await visibility();
frame = undefined;
gl.drawArrays(gl.TRIANGLES, 0, 6);
}

const ondispose =
invalidation === undefined ? Inputs.disposal(canvas) : invalidation;
let disposed = false;
ondispose.then(() => (disposed = true));

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));
}

let u_mouse;
let mouse = [0, 0];
let leftDown = 0;
let rightDown = 0;
if (iMouse) {
document.body.addEventListener("mousedown", (e) => {
if (e.button === 0) {
leftDown = 1;
} else if (e.button === 2) {
rightDown = 1;
}
gl.uniform4f(u_mouse, ...mouse, leftDown, rightDown);
render();
});
document.body.addEventListener("mouseup", (e) => {
if (e.button === 0) {
leftDown = 0;
} else if (e.button === 2) {
rightDown = 0;
}
gl.uniform4f(u_mouse, ...mouse, leftDown, rightDown);
render();
});
canvas.addEventListener("mousemove", (e) => {
mouse = [
e.offsetX * devicePixelRatio,
height * devicePixelRatio - e.offsetY * devicePixelRatio
];
gl.uniform4f(u_mouse, ...mouse, leftDown, rightDown);
render();
});

u_mouse = gl.getUniformLocation(program, "iMouse");
gl.uniform4f(u_mouse, ...mouse, leftDown, rightDown);
}

let frame;
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.TRIANGLES, 0, 6);
return (timeframe = requestAnimationFrame(tick));
})();
ondispose.then(() => cancelAnimationFrame(timeframe));
} else {
gl.drawArrays(gl.TRIANGLES, 0, 6);
}
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