Files
zeekay 91fbe784d0 film/models: each model's concept, drawn by a shader and cut into a loop
Nine concepts to the owner's mockup, grey on true black, the subject right
of centre and the left 40% kept dark for a card's words: Enso a brushed
ensō shedding particles over water and mist, Kai three panes of glass
threaded by falling light, Zen a sphere in raked sand, Iro mountains and
cloud spilling out of a frame, Koma film frames running into depth, Oto a
mirrored waveform, Koe twisting ribbons, Uta a fan rising over strings,
Mori a wireframe peak.

Each is one fragment shader over film/models/gl.js, a pure function of the
loop's phase, so the frame after the last is the first. film.mjs steps it
on the GPU, refuses a seam, and cuts public/models/<name>.{webp,webm,mp4}:
1200x1500 for every model and a 1500x1500 Enso, 30 fps, each loop under
700 KB. Frames stay in film/models/frames (ignored).
2026-10-30 07:49:48 -07:00

55 lines
2.0 KiB
HTML

<!doctype html>
<html data-seconds="6">
<meta charset="utf-8" />
<title>Koe</title>
<style>html,body{margin:0;background:#000}canvas{display:block}</style>
<body>
<canvas></canvas>
<!--
Koe: a voice drawn as ribbons. Forty fine sine lines, each a blend of two
waves, so together they twist like one ribbon turning in the light, rising
out of the dark on the left and swelling to the right. In the loop both
waves travel, one twice as fast as the other, and the ribbon turns over.
-->
<script type="x-shader/x-fragment">
const int N = 34;
// One ribbon: N lines, each a blend of two waves, so together they twist.
float ribbon(vec2 f, float x, float A, float dA, float grow, float k1, float k2, float m2, float off, float gain){
float H = R.y;
float L = 0.;
for (int i = 0; i < N; i++) {
float t = float(i) / float(N - 1);
float p1 = k1 * x - TAU * P + off;
float p2 = k2 * x - TAU * P * m2 + off * 1.7 + 1.9 + t * 1.4;
float w = mix(sin(p1), .8 * sin(p2), t);
float dw = mix(k1 * cos(p1), .8 * k2 * cos(p2), t);
float yi = .5 * H + A * w;
float slope = (A * dw + dA * w) / H;
float d = abs(f.y - yi) / sqrt(1. + slope * slope);
float edge = abs(t - .5) * 2.;
float bright = (.3 + .5 * (1. - edge * edge)) * grow * gain;
L += bright * (1. - smoothstep(.25, 1.05, d)) + exp(-d / 6.) * .025 * grow * gain;
}
return L;
}
float scene(vec2 f){
float H = R.y;
float x = f.x / H;
float x0 = .3 * R.x / H;
// The ribbons swell out of nothing on the left and hold to the right.
float grow = smoothstep(x0, x0 + .36, x);
float A = .23 * H * grow;
float dA = .23 * H * (smoothstep(x0, x0 + .36, x + .001) - grow) / .001;
float L = ribbon(f, x, A, dA, grow, 7.2, 4.6, 2., .3, 1.)
+ ribbon(f, x, A * .78, dA * .78, grow, 5.6, 8.4, 1., 2.6, .6);
// The left of the frame is the words': keep it dark.
L *= mix(.1, 1., smoothstep(.22, .45, f.x / R.x));
return pow(tone(L * 1.05), .9);
}
</script>
<script src="./gl.js"></script>
</body>
</html>