<!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>
<canvas id="glcanvas"></canvas>
<!-- Vertex Shader -->
<!-- Fragment Shader -->
body {
margin: 0;
overflow: hidden;
background-color: #000;
}
canvas {
display: block;
width: 100vw;
height: 100vh;
}
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);
}