Created
April 7, 2026 11:54
-
-
Save wipfli/c377ad6dd9d8c7623a6ad8fbf6c72349 to your computer and use it in GitHub Desktop.
SDF WebGL Demo
This file contains hidden or 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 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