mirror of
https://github.com/hanzo-apps/hanzo.ai.git
synced 2026-10-30 14:58:08 +00:00
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).
114 lines
4.3 KiB
HTML
114 lines
4.3 KiB
HTML
<!doctype html>
|
|
<html data-seconds="6">
|
|
<meta charset="utf-8" />
|
|
<title>Kai</title>
|
|
<style>html,body{margin:0;background:#000}canvas{display:block}</style>
|
|
<body>
|
|
<canvas></canvas>
|
|
<!--
|
|
Kai: three panes of glass stacked in isometric depth, and a curtain of thin
|
|
vertical lines of light threaded through them — a decision passing down
|
|
through its layers. In the loop the panes breathe up and down out of step,
|
|
pulses of light run down the threads, and each one flares where it meets a
|
|
pane.
|
|
-->
|
|
<script type="x-shader/x-fragment">
|
|
const float C30 = .8660254;
|
|
const float S30 = .5;
|
|
|
|
// A pane's top face at height y0, half-width a: its (x, z), and how far f is
|
|
// from its edge in pixels (negative inside).
|
|
vec2 face(vec2 p, float y0){
|
|
float u = p.x / C30;
|
|
float v = (p.y + y0) / S30;
|
|
return vec2(u + v, v - u) * .5;
|
|
}
|
|
|
|
// The light a pane gives at p: its edges, its two faces, its sheen.
|
|
float pane(vec2 p, float y0, float a, float t, float px, inout float cover){
|
|
vec2 xz = face(p, y0);
|
|
float dTop = (max(abs(xz.x), abs(xz.y)) - a) / 1.1547;
|
|
vec2 xzb = face(p, y0 - t);
|
|
float dBot = (max(abs(xzb.x), abs(xzb.y)) - a) / 1.1547;
|
|
float L = 0.;
|
|
// The rims: the top face sharp, the bottom face dimmer through the glass.
|
|
L += line(dTop / px, .6) * 1.25 + exp(-abs(dTop) / (px * 6.)) * .22;
|
|
L += line(dBot / px, .5) * .45 + exp(-abs(dBot) / (px * 5.)) * .08;
|
|
// The edge of the slab between them, where the two faces' outlines part.
|
|
float slab = step(dTop, 0.) + step(dBot, 0.);
|
|
bool inTop = dTop < 0.;
|
|
if (inTop) {
|
|
// The face: a cool glass sheen, brighter toward the far edge, with a soft reflection crossing it.
|
|
float far = smoothstep(a, -a, xz.x + xz.y);
|
|
float sweep = exp(-pow((xz.x - xz.y) / a * .9 + .9 * sin(TAU * P) - .2, 2.) * 2.);
|
|
float grain = .5 + .5 * gn(xz / a * 3. + loop(.3));
|
|
L += (.05 + .09 * far + .07 * sweep) * (.75 + .25 * grain);
|
|
// A faint lattice in the glass.
|
|
vec2 g = abs(fract(xz / a * 3. + .5) - .5) * a / 3. / 1.1547;
|
|
L += (line(g.x / px, .35) + line(g.y / px, .35)) * .035;
|
|
}
|
|
if (dBot < 0. && !inTop) L += .035;
|
|
L *= cover;
|
|
if (inTop) cover *= .72;
|
|
return L;
|
|
}
|
|
|
|
float scene(vec2 f){
|
|
float px = 1. / R.y;
|
|
vec2 C = vec2(.655 * R.x, .5 * R.y);
|
|
vec2 p = (f - C) / R.y;
|
|
float a = .108;
|
|
float t = .012;
|
|
float gap = .13;
|
|
float L = 0.;
|
|
float cover = 1.;
|
|
|
|
// The threads: vertical lines of light through the stack, densest at its middle.
|
|
float threads = 0.;
|
|
float flares = 0.;
|
|
for (int i = 0; i < 46; i++) {
|
|
float fi = float(i);
|
|
vec2 h = h22(vec2(fi, 2.3));
|
|
// A thread's place in the panes' plan, mostly inside the footprint.
|
|
vec2 xz = (h - .5) * 2. * a * .95;
|
|
float x = (xz.x - xz.y) * C30;
|
|
float dx = abs(p.x - x);
|
|
if (dx > .03) continue;
|
|
float base = (xz.x + xz.y) * S30;
|
|
float top = base - gap * 1.9 - .06 - .12 * h11(fi * 3.7);
|
|
float bot = base + gap * 1.6 + .05 + .14 * h11(fi * 5.1);
|
|
float along = smoothstep(top, top + .08, p.y) * (1. - smoothstep(bot - .1, bot, p.y));
|
|
float weight = .35 + .65 * h11(fi * 8.3);
|
|
// Pulses run down each thread, one or two per loop.
|
|
float k = 1. + floor(2. * h11(fi * 4.4));
|
|
float head = top + (bot - top) * fract(P * k + h11(fi * 6.6));
|
|
float pulse = exp(-pow((p.y - head) / .035, 2.)) + .35 * exp(-max(head - p.y, 0.) / .09) * step(p.y, head) * step(head - .25, p.y);
|
|
float core = line(dx / px, .45);
|
|
threads += along * weight * (core * (.32 + 1.4 * pulse) + exp(-dx / (px * 4.)) * .05 * (1. + 2. * pulse));
|
|
// Where a thread meets a pane it flares, and brightest as a pulse passes.
|
|
for (int j = 0; j < 3; j++) {
|
|
float fj = float(j);
|
|
float y0 = (1. - fj) * gap + .007 * sin(TAU * (P + fj / 3.));
|
|
float yy = base - y0;
|
|
float dd = length(vec2(p.x - x, (p.y - yy) * 1.6));
|
|
float near = exp(-pow((head - yy) / .05, 2.));
|
|
flares += weight * (exp(-dd * dd / (px * px * 6.)) * (.5 + 1.8 * near) + exp(-dd / (px * 14.)) * .12 * (1. + 2. * near));
|
|
}
|
|
}
|
|
|
|
for (int j = 0; j < 3; j++) {
|
|
float fj = float(j);
|
|
float y0 = (1. - fj) * gap + .007 * sin(TAU * (P + fj / 3.));
|
|
L += pane(p, y0, a, t, px, cover);
|
|
}
|
|
L += threads + flares;
|
|
|
|
// The left of the frame is the words': keep it dark.
|
|
L *= mix(.15, 1., smoothstep(.2, .45, f.x / R.x));
|
|
return pow(tone(L * 1.1), .9);
|
|
}
|
|
</script>
|
|
<script src="./gl.js"></script>
|
|
</body>
|
|
</html>
|