Created
September 3, 2025 16:17
-
-
Save aza/d008e367e3159132355ebc92063894f2 to your computer and use it in GitHub Desktop.
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"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title>WebGL Ripple Filter</title> | |
| <script src="https://cdn.tailwindcss.com"></script> | |
| <style> | |
| body { | |
| margin: 0; | |
| overflow: hidden; | |
| font-family: 'Inter', sans-serif; | |
| } | |
| canvas { | |
| display: block; | |
| width: 100vw; | |
| height: 100vh; | |
| } | |
| .info { | |
| font-family: monospace; | |
| color: #ccc; | |
| background: rgba(0,0,0,0.5); | |
| padding: 0.5em 1em; | |
| border-radius: 8px; | |
| } | |
| </style> | |
| </head> | |
| <body class="bg-gray-900 flex items-center justify-center h-screen"> | |
| <!-- This canvas is where the WebGL magic happens --> | |
| <canvas id="gl-canvas"></canvas> | |
| <!-- Error message for browsers without WebGL support --> | |
| <div id="webgl-error" class="hidden absolute inset-0 bg-red-900/80 text-white flex items-center justify-center p-8 text-center"> | |
| <p class="text-lg">Sorry, WebGL is not supported or is disabled in your browser. This experience requires WebGL to run.</p> | |
| </div> | |
| <div class="absolute bottom-4 left-4 info"> | |
| WebGL Ripple Filter | |
| </div> | |
| <!-- Vertex Shader: Positions vertices --> | |
| <script id="vertex-shader" type="x-shader/x-vertex"> | |
| attribute vec2 a_position; | |
| attribute vec2 a_texCoord; | |
| varying vec2 v_texCoord; | |
| void main() { | |
| gl_Position = vec4(a_position, 0.0, 1.0); | |
| v_texCoord = a_texCoord; | |
| } | |
| </script> | |
| <!-- Fragment Shader: Colors pixels and creates the ripple effect --> | |
| <script id="fragment-shader" type="x-shader/x-fragment"> | |
| precision mediump float; | |
| uniform sampler2D u_texture; | |
| uniform float u_time; | |
| uniform vec2 u_resolution; | |
| varying vec2 v_texCoord; | |
| void main() { | |
| vec2 uv = v_texCoord; | |
| if (uv.y > 0.5) { | |
| // 'progress' goes from 0.0 at the halfway mark to 1.0 at the bottom. | |
| float progress = (uv.y - 0.5) * 2.0; | |
| // The number of waves grows as we go down the screen. | |
| float frequency = 25.0 + (600.0 * progress / progress); | |
| // The displacement grows from 0 at the center to max at the bottom. | |
| // The scale of the displacement is now 2x bigger (0.01 -> 0.02). | |
| float amplitude = 0.055 * progress * progress; | |
| // The animation speed is now 2x faster (0.6 -> 1.2). | |
| float distortion = sin(uv.y * frequency + u_time * 1.6) * amplitude; | |
| uv.x += distortion; | |
| } | |
| gl_FragColor = texture2D(u_texture, uv); | |
| } | |
| </script> | |
| <script type="module"> | |
| // IMPORTANT: Replace this with the URL of your image. | |
| const imageUrl = 'https://placehold.co/2048x1024/FFFFFF/000000?text=ESP&font=inter'; | |
| function main() { | |
| const canvas = document.getElementById('gl-canvas'); | |
| const gl = canvas.getContext('webgl'); | |
| if (!gl) { | |
| document.getElementById('gl-canvas').classList.add('hidden'); | |
| document.getElementById('webgl-error').classList.remove('hidden'); | |
| console.error('WebGL is not supported by your browser.'); | |
| return; | |
| } | |
| // 1. Setup GLSL program | |
| const vsSource = document.getElementById('vertex-shader').text; | |
| const fsSource = document.getElementById('fragment-shader').text; | |
| const program = createProgram(gl, vsSource, fsSource); | |
| // 2. Look up attribute and uniform locations | |
| const positionLocation = gl.getAttribLocation(program, 'a_position'); | |
| const texCoordLocation = gl.getAttribLocation(program, 'a_texCoord'); | |
| const textureLocation = gl.getUniformLocation(program, 'u_texture'); | |
| const timeLocation = gl.getUniformLocation(program, 'u_time'); | |
| const resolutionLocation = gl.getUniformLocation(program, 'u_resolution'); | |
| // 3. Create buffers for a quad that fills the canvas | |
| 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); | |
| const texCoordBuffer = gl.createBuffer(); | |
| gl.bindBuffer(gl.ARRAY_BUFFER, texCoordBuffer); | |
| gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([0,1, 1,1, 0,0, 0,0, 1,1, 1,0]), gl.STATIC_DRAW); | |
| // 4. Load the image and create a texture | |
| const texture = createTextureFromURL(gl, imageUrl); | |
| // 5. Render loop | |
| function render(time) { | |
| resizeCanvasToDisplaySize(gl.canvas); | |
| gl.viewport(0, 0, gl.canvas.width, gl.canvas.height); | |
| gl.clearColor(0.1, 0.1, 0.12, 1.0); | |
| gl.clear(gl.COLOR_BUFFER_BIT); | |
| gl.useProgram(program); | |
| gl.enableVertexAttribArray(positionLocation); | |
| gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer); | |
| gl.vertexAttribPointer(positionLocation, 2, gl.FLOAT, false, 0, 0); | |
| gl.enableVertexAttribArray(texCoordLocation); | |
| gl.bindBuffer(gl.ARRAY_BUFFER, texCoordBuffer); | |
| gl.vertexAttribPointer(texCoordLocation, 2, gl.FLOAT, false, 0, 0); | |
| gl.uniform1f(timeLocation, time * 0.001); | |
| gl.uniform2f(resolutionLocation, gl.canvas.width, gl.canvas.height); | |
| gl.uniform1i(textureLocation, 0); | |
| gl.activeTexture(gl.TEXTURE0); | |
| gl.bindTexture(gl.TEXTURE_2D, texture); | |
| gl.drawArrays(gl.TRIANGLES, 0, 6); | |
| requestAnimationFrame(render); | |
| } | |
| requestAnimationFrame(render); | |
| } | |
| function createTextureFromURL(gl, url) { | |
| const texture = gl.createTexture(); | |
| gl.bindTexture(gl.TEXTURE_2D, texture); | |
| gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, 1, 1, 0, gl.RGBA, gl.UNSIGNED_BYTE, new Uint8Array([0, 0, 255, 255])); | |
| const image = new Image(); | |
| image.crossOrigin = "anonymous"; | |
| image.onload = () => { | |
| gl.bindTexture(gl.TEXTURE_2D, texture); | |
| gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, image); | |
| gl.generateMipmap(gl.TEXTURE_2D); | |
| gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE); | |
| gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE); | |
| gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR); | |
| }; | |
| image.onerror = () => { | |
| console.error("Failed to load image texture from URL:", url); | |
| } | |
| image.src = url; | |
| return texture; | |
| } | |
| // --- WebGL Helper Functions --- | |
| function createShader(gl, type, source) { | |
| const shader = gl.createShader(type); | |
| gl.shaderSource(shader, source); | |
| gl.compileShader(shader); | |
| if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) { | |
| console.error('An error occurred compiling the shaders: ' + gl.getShaderInfoLog(shader)); | |
| gl.deleteShader(shader); | |
| return null; | |
| } | |
| return shader; | |
| } | |
| function createProgram(gl, vsSource, fsSource) { | |
| const vertexShader = createShader(gl, gl.VERTEX_SHADER, vsSource); | |
| const fragmentShader = createShader(gl, gl.FRAGMENT_SHADER, fsSource); | |
| const program = gl.createProgram(); | |
| gl.attachShader(program, vertexShader); | |
| gl.attachShader(program, fragmentShader); | |
| gl.linkProgram(program); | |
| if (!gl.getProgramParameter(program, gl.LINK_STATUS)) { | |
| console.error('Unable to initialize the shader program: ' + gl.getProgramInfoLog(program)); | |
| return null; | |
| } | |
| return program; | |
| } | |
| function resizeCanvasToDisplaySize(canvas) { | |
| const displayWidth = canvas.clientWidth; | |
| const displayHeight = canvas.clientHeight; | |
| if (canvas.width !== displayWidth || canvas.height !== displayHeight) { | |
| canvas.width = displayWidth; | |
| canvas.height = displayHeight; | |
| return true; | |
| } | |
| return false; | |
| } | |
| main(); | |
| </script> | |
| </body> | |
| </html> | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment