| 1 | #version 300 es |
| 2 | precision highp float; |
| 3 | |
| 4 | in vec2 v_texcoord; |
| 5 | uniform sampler2D tex; |
| 6 | uniform float TIME; |
| 7 | out vec4 fragColor; |
| 8 | |
| 9 | // --- CONFIGURATION --- |
| 10 | const float wobble_speed = 3.0; // Animation speed |
| 11 | const float wobble_frequency = 15.0; // Wave density |
| 12 | const float wobble_amplitude = 0.025; // Displacement amount |
| 13 | const float edge_fade = 0.1; // Fade near edges (0.0 to disable) |
| 14 | const bool organic_motion = true; // Multi-frequency for natural look |
| 15 | const bool prevent_edge_artifacts = true; |
| 16 | // --------------------- |
| 17 | |
| 18 | void main() { |
| 19 | // Use local time to prevent floating point precision issues with large TIME values |
| 20 | float t = mod(TIME, 628.318); // Wrap at 2*PI*100 |
| 21 | |
| 22 | vec2 new_uv = v_texcoord; |
| 23 | |
| 24 | // Calculate edge fade mask (reduces artifacts at screen borders) |
| 25 | float edge_mask = 1.0; |
| 26 | if (edge_fade > 0.0) { |
| 27 | vec2 dist_to_edge = min(v_texcoord, 1.0 - v_texcoord); |
| 28 | float min_dist = min(dist_to_edge.x, dist_to_edge.y); |
| 29 | edge_mask = smoothstep(0.0, edge_fade, min_dist); |
| 30 | } |
| 31 | |
| 32 | float amplitude = wobble_amplitude * edge_mask; |
| 33 | |
| 34 | float h_offset, v_offset; |
| 35 | |
| 36 | if (organic_motion) { |
| 37 | // Layer multiple frequencies for organic, natural motion |
| 38 | // Different speeds prevent repetitive patterns |
| 39 | |
| 40 | // Horizontal wobble (based on Y position) |
| 41 | h_offset = 0.0; |
| 42 | h_offset += sin(v_texcoord.y * wobble_frequency * 1.0 + t * wobble_speed * 1.00) * 0.50; |
| 43 | h_offset += sin(v_texcoord.y * wobble_frequency * 2.1 + t * wobble_speed * 1.37) * 0.30; |
| 44 | h_offset += sin(v_texcoord.y * wobble_frequency * 0.5 + t * wobble_speed * 0.71) * 0.20; |
| 45 | |
| 46 | // Vertical wobble (based on X position) |
| 47 | v_offset = 0.0; |
| 48 | v_offset += cos(v_texcoord.x * wobble_frequency * 0.9 + t * wobble_speed * 1.13) * 0.50; |
| 49 | v_offset += cos(v_texcoord.x * wobble_frequency * 1.7 + t * wobble_speed * 0.83) * 0.30; |
| 50 | v_offset += cos(v_texcoord.x * wobble_frequency * 0.4 + t * wobble_speed * 1.41) * 0.20; |
| 51 | |
| 52 | // Add subtle interaction between axes |
| 53 | h_offset += sin(v_texcoord.x * wobble_frequency * 0.3 + t * wobble_speed * 0.5) * 0.1; |
| 54 | v_offset += cos(v_texcoord.y * wobble_frequency * 0.3 + t * wobble_speed * 0.6) * 0.1; |
| 55 | |
| 56 | } else { |
| 57 | // Simple single-frequency wobble |
| 58 | h_offset = sin(v_texcoord.y * wobble_frequency + t * wobble_speed); |
| 59 | v_offset = cos(v_texcoord.x * wobble_frequency + t * wobble_speed); |
| 60 | } |
| 61 | |
| 62 | new_uv.x += h_offset * amplitude; |
| 63 | new_uv.y += v_offset * amplitude; |
| 64 | |
| 65 | // Prevent sampling outside texture bounds |
| 66 | if (prevent_edge_artifacts) { |
| 67 | // Clamp with small margin to prevent edge bleeding |
| 68 | new_uv = clamp(new_uv, 0.002, 0.998); |
| 69 | } |
| 70 | |
| 71 | fragColor = texture(tex, new_uv); |
| 72 | } |