Skip to content

Instantly share code, notes, and snippets.

@wipfli
Created April 7, 2026 11:54
Show Gist options
  • Select an option

  • Save wipfli/c377ad6dd9d8c7623a6ad8fbf6c72349 to your computer and use it in GitHub Desktop.

Select an option

Save wipfli/c377ad6dd9d8c7623a6ad8fbf6c72349 to your computer and use it in GitHub Desktop.
SDF WebGL Demo
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>SDF WebGL Demo</title>
<style>
body {
margin: 0;
background: #111;
display: flex;
justify-content: center;
align-items: center;
height: 100vh;
}
canvas {
image-rendering: pixelated;
border: 1px solid #444;
}
</style>
</head>
<body>
<canvas id="glcanvas" width="512" height="512"></canvas>
<script>
// Created with ChatGPT
const canvas = document.getElementById("glcanvas");
const gl = canvas.getContext("webgl");
// --- Create 16x16 SDF texture ---
const size = 16;
const radius = 4;
const sdfData = new Uint8Array(size * size);
for (let y = 0; y < size; y++) {
for (let x = 0; x < size; x++) {
const dx = x - size / 2 + 0.5;
const dy = y - size / 2 + 0.5;
const dist = Math.sqrt(dx * dx + dy * dy) - radius;
// Map distance to 0..255 (128 = surface)
const maxDist = 8;
let v = dist / maxDist;
v = Math.max(-1, Math.min(1, v));
sdfData[y * size + x] = Math.floor((v * 0.5 + 0.5) * 255);
}
}
// --- Upload texture ---
const texture = gl.createTexture();
gl.bindTexture(gl.TEXTURE_2D, texture);
gl.texImage2D(
gl.TEXTURE_2D,
0,
gl.LUMINANCE,
size,
size,
0,
gl.LUMINANCE,
gl.UNSIGNED_BYTE,
sdfData
);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
// --- Shaders ---
const vertexShaderSource = `
attribute vec2 aPos;
varying vec2 vUV;
void main() {
vUV = (aPos + 1.0) * 0.5;
gl_Position = vec4(aPos, 0.0, 1.0);
}
`;
const fragmentShaderSource = `
precision mediump float;
varying vec2 vUV;
uniform sampler2D uTex;
void main() {
float d = texture2D(uTex, vUV).r;
// Convert back to signed distance (-1 to 1)
d = d * 2.0 - 1.0;
vec3 color;
if (d < 0.0) {
color = vec3(0.0, 0.0, 0.0);
} else {
color = vec3(1.0, 1.0, 1.0);
}
gl_FragColor = vec4(color, 1.0);
}
`;
// --- Compile shader ---
function compile(type, source) {
const shader = gl.createShader(type);
gl.shaderSource(shader, source);
gl.compileShader(shader);
return shader;
}
const vs = compile(gl.VERTEX_SHADER, vertexShaderSource);
const fs = compile(gl.FRAGMENT_SHADER, fragmentShaderSource);
const program = gl.createProgram();
gl.attachShader(program, vs);
gl.attachShader(program, fs);
gl.linkProgram(program);
gl.useProgram(program);
// --- Fullscreen quad ---
const buffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([
-1, -1,
1, -1,
-1, 1,
1, 1
]), gl.STATIC_DRAW);
const aPos = gl.getAttribLocation(program, "aPos");
gl.enableVertexAttribArray(aPos);
gl.vertexAttribPointer(aPos, 2, gl.FLOAT, false, 0, 0);
// --- Draw ---
gl.viewport(0, 0, canvas.width, canvas.height);
gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);
</script>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment