>_htmlhub
library/3d & webgl/envato-liquid-distortion

Liquid Distortion Shader

Live previewPerformance varies by device
envato-liquid-distortion.html

No account needed to copy or download. Check dependencies and thelicense status before publishing.

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Liquid Distortion Shader</title>
    <style>
        body {
            margin: 0;
            overflow: hidden;
            background-color: #000;
        }
        canvas {
            display: block;
            width: 100vw;
            height: 100vh;
        }
    </style>
</head>
<body>
    <canvas id="glcanvas"></canvas>
    <!-- Vertex Shader -->
    <script id="v-shader" type="x-shader/x-vertex">
        attribute vec2 a_position;
        varying vec2 v_uv;
        void main() {
            v_uv = a_position * 0.5 + 0.5;
            v_uv.y = 1.0 - v_uv.y;
            gl_Position = vec4(a_position, 0.0, 1.0);
        }
    </script>
    <!-- Fragment Shader -->
    <script id="f-shader" type="x-shader/x-fragment">
        precision mediump float;
        varying vec2 v_uv;
        uniform float u_time;
        uniform vec2 u_resolution;
        uniform vec2 u_mouse;
        // Configuration colors (passed as uniforms in a real app, hardcoded here for simplicity)
        const vec3 color1 = vec3(0.0, 0.4, 1.0); // #0066ff
        const vec3 color2 = vec3(1.0, 0.0, 0.5); // #ff0080
        // Inigo Quilez Simplex Noise 2D
        vec3 permute(vec3 x) { return mod(((x*34.0)+1.0)*x, 289.0); }
        float snoise(vec2 v){
            const vec4 C = vec4(0.211324865405187, 0.366025403784439, -0.577350269189626, 0.024390243902439);
            vec2 i  = floor(v + dot(v, C.yy) );
            vec2 x0 = v -   i + dot(i, C.xx);
            vec2 i1;
            i1 = (x0.x > x0.y) ? vec2(1.0, 0.0) : vec2(0.0, 1.0);
            vec4 x12 = x0.xyxy + C.xxzz;
            x12.xy -= i1;
            i = mod(i, 289.0);
            vec3 p = permute( permute( i.y + vec3(0.0, i1.y, 1.0 )) + i.x + vec3(0.0, i1.x, 1.0 ));
            vec3 m = max(0.5 - vec3(dot(x0,x0), dot(x12.xy,x12.xy), dot(x12.zw,x12.zw)), 0.0);
            m = m*m;
            m = m*m;
            vec3 x = 2.0 * fract(p * C.www) - 1.0;
            vec3 h = abs(x) - 0.5;
            vec3 ox = floor(x + 0.5);
            vec3 a0 = x - ox;
            m *= 1.79284291400159 - 0.85373472095314 * ( a0*a0 + h*h );
            vec3 g;
            g.x  = a0.x  * x0.x  + h.x  * x0.y;
            g.yz = a0.yz * x12.xz + h.yz * x12.yw;
            return 130.0 * dot(m, g);
        }
        void main() {
            vec2 st = gl_FragCoord.xy / u_resolution.xy;
            float aspect = u_resolution.x / u_resolution.y;
            st.x *= aspect;
            vec2 mouse = u_mouse / u_resolution.xy;
            mouse.x *= aspect;
            // Liquid distortion calculation
            float noiseTime = u_time * 0.2;
            vec2 noiseCoord = st * 3.0;
            float n = snoise(noiseCoord + vec2(noiseTime));
            float n2 = snoise(noiseCoord * 2.0 - vec2(noiseTime * 1.5));
            vec2 distortedUV = st + vec2(n, n2) * 0.1;
            // Mouse interaction
            float distToMouse = distance(st, mouse);
            float mouseInfluence = smoothstep(0.4, 0.0, distToMouse);
            distortedUV += (st - mouse) * mouseInfluence * 0.2;
            // Color mixing
            float mixer = snoise(distortedUV * 2.0 + u_time * 0.1) * 0.5 + 0.5;
            vec3 finalColor = mix(color1, color2, mixer);
            // Add some bright specular highlights
            float highlight = smoothstep(0.8, 1.0, snoise(distortedUV * 5.0 - u_time));
            finalColor += vec3(highlight * 0.5);
            gl_FragColor = vec4(finalColor, 1.0);
        }
    </script>
    <script>
        const canvas = document.getElementById('glcanvas');
        const gl = canvas.getContext('webgl');
        if (!gl) {
            console.error('WebGL not supported');
            document.body.innerHTML = '<div style="color:white;text-align:center;padding:50px;">WebGL is required for this animation.</div>';
        } else {
            // Compile shader
            function compileShader(type, id) {
                const source = document.getElementById(id).text;
                const shader = gl.createShader(type);
                gl.shaderSource(shader, source);
                gl.compileShader(shader);
                if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
                    console.error('Error compiling shader:', gl.getShaderInfoLog(shader));
                    gl.deleteShader(shader);
                    return null;
                }
                return shader;
            }
            const vertexShader = compileShader(gl.VERTEX_SHADER, 'v-shader');
            const fragmentShader = compileShader(gl.FRAGMENT_SHADER, 'f-shader');
            // Create program
            const program = gl.createProgram();
            gl.attachShader(program, vertexShader);
            gl.attachShader(program, fragmentShader);
            gl.linkProgram(program);
            if (!gl.getProgramParameter(program, gl.LINK_STATUS)) {
                console.error('Error linking program:', gl.getProgramInfoLog(program));
            }
            gl.useProgram(program);
            // Set up buffer
            const positionBuffer = gl.createBuffer();
            gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
            gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([
                -1.0, -1.0,
                1.0, -1.0,
                -1.0, 1.0,
                -1.0, 1.0,
                1.0, -1.0,
                1.0, 1.0
            ]), gl.STATIC_DRAW);
            const positionLocation = gl.getAttribLocation(program, 'a_position');
            gl.enableVertexAttribArray(positionLocation);
            gl.vertexAttribPointer(positionLocation, 2, gl.FLOAT, false, 0, 0);
            // Uniforms
            const timeLocation = gl.getUniformLocation(program, 'u_time');
            const resolutionLocation = gl.getUniformLocation(program, 'u_resolution');
            const mouseLocation = gl.getUniformLocation(program, 'u_mouse');
            let mouseX = window.innerWidth / 2;
            let mouseY = window.innerHeight / 2;
            function resize() {
                canvas.width = window.innerWidth;
                canvas.height = window.innerHeight;
                gl.viewport(0, 0, canvas.width, canvas.height);
                gl.uniform2f(resolutionLocation, canvas.width, canvas.height);
            }
            window.addEventListener('resize', resize);
            window.addEventListener('mousemove', (e) => {
                mouseX = e.clientX;
                mouseY = window.innerHeight - e.clientY; // Flip Y for WebGL
            });
            window.addEventListener('touchmove', (e) => {
                mouseX = e.touches[0].clientX;
                mouseY = window.innerHeight - e.touches[0].clientY;
            });
            resize();
            // Render loop
            function render(time) {
                gl.uniform1f(timeLocation, time * 0.001);
                gl.uniform2f(mouseLocation, mouseX, mouseY);
                gl.drawArrays(gl.TRIANGLES, 0, 6);
                requestAnimationFrame(render);
            }
            requestAnimationFrame(render);
        }
    </script>
</body>
</html>

About this animation

A WebGL displacement shader creating a liquid, glassy refraction effect over neon gradients.

Source Code
Copied to clipboard