struct Params { time: f32, progress: f32, aspect: f32, padding: f32, } @group(0) @binding(0) var params: Params; fn thread(value: f32, width: f32) -> f32 { return pow(0.5 + 0.5 * cos(value), width); } @fragment fn fs_main(@location(0) uv: vec2f) -> @location(0) vec4f { let p = vec2f(uv.x * params.aspect, uv.y); let drift = sin(p.y * 18.0 + params.time * 0.42) * 0.018; let warp = thread((p.x + drift) * 310.0, 7.0); let weft = thread((p.y + sin(p.x * 13.0) * 0.006) * 280.0, 8.0); let crossing = step(0.5, fract(floor(p.x * 49.0) * 0.5 + floor(p.y * 46.0) * 0.5)); let overUnder = mix(warp * 0.58 + weft, warp + weft * 0.55, crossing); let irregularity = sin(p.x * 127.0 + sin(p.y * 31.0)) * 0.025; let revealEdge = params.progress + sin(uv.y * 9.0 + params.time * 0.55) * 0.035; let woven = 1.0 - smoothstep(revealEdge - 0.15, revealEdge + 0.04, uv.x); let looseYarn = thread((p.x + sin(p.y * 8.0 + params.time) * 0.035) * 74.0, 10.0); let wovenMask = smoothstep(0.16, 0.72, overUnder + irregularity); let looseMask = smoothstep(0.24, 0.78, looseYarn); let threadMask = mix(looseMask * 0.72, wovenMask, woven); let slider = smoothstep(0.0, 1.0, params.progress); let dyed = 1.0 - smoothstep(slider - 0.025, slider + 0.025, uv.x); let greige = vec3f(0.69, 0.62, 0.51); let madder = vec3f(0.72, 0.24, 0.12); let indigo = vec3f(0.08, 0.34, 0.58); let dyeColor = mix(madder, indigo, smoothstep(0.15, 0.9, uv.x)); let threadColor = mix(greige, dyeColor, dyed); let shadow = vec3f(0.105, 0.10, 0.082); let highlight = mix(vec3f(0.90, 0.84, 0.72), dyeColor * 1.35, dyed); var color = mix(shadow, threadColor, threadMask); color += highlight * pow(max(warp, weft), 10.0) * 0.14 * woven; let vignette = smoothstep(0.82, 0.28, distance(uv, vec2f(0.5))); color *= 0.7 + vignette * 0.3; return vec4f(color, 1.0); }