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).
90 lines
3.3 KiB
HTML
90 lines
3.3 KiB
HTML
<!doctype html>
|
|
<html data-seconds="8">
|
|
<meta charset="utf-8" />
|
|
<title>Zen</title>
|
|
<style>html,body{margin:0;background:#000}canvas{display:block}</style>
|
|
<body>
|
|
<canvas></canvas>
|
|
<!--
|
|
Zen: a matte stone sphere resting in raked sand, the grooves drawn round it
|
|
in rings. One low light from behind and to the left catches the crest of
|
|
every groove and the stone's shoulder, and falls off into the dark. In the
|
|
loop the rings breathe: they swell and settle, and move out by one groove.
|
|
-->
|
|
<script type="x-shader/x-fragment">
|
|
const float RHO = 1.;
|
|
|
|
// The sand's height and slope at distance r from where the stone rests.
|
|
float rake(float r){ return smoothstep(1.32, 1.75, r) * exp(-r * .05); }
|
|
float k(){ return TAU / .34; }
|
|
float groove(float r){
|
|
float breathe = .85 + .15 * sin(TAU * P);
|
|
return rake(r) * .045 * breathe * sin(k() * r - TAU * P);
|
|
}
|
|
float slope(float r){
|
|
float breathe = .85 + .15 * sin(TAU * P);
|
|
float e = .002;
|
|
return (groove(r + e) - groove(r - e)) / (2. * e);
|
|
}
|
|
|
|
float scene(vec2 f){
|
|
vec2 uv = (f - vec2(.67 * R.x, .5 * R.y)) / R.y;
|
|
uv.y = -uv.y;
|
|
vec3 target = vec3(0., RHO * .9, 0.);
|
|
float el = radians(24.);
|
|
vec3 eye = target + 15. * vec3(-.18, sin(el), -cos(el));
|
|
vec3 fw = normalize(target - eye);
|
|
vec3 rt = normalize(cross(vec3(0, 1, 0), fw));
|
|
vec3 up = cross(fw, rt);
|
|
vec3 rd = normalize(fw * 1.45 + rt * uv.x + up * uv.y);
|
|
vec3 light = normalize(vec3(-.75, .42, .55));
|
|
float L = 0.;
|
|
|
|
// The stone.
|
|
vec3 oc = eye - vec3(0., RHO, 0.);
|
|
float b = dot(oc, rd);
|
|
float c = dot(oc, oc) - RHO * RHO;
|
|
float disc = b * b - c;
|
|
float tS = disc > 0. ? -b - sqrt(disc) : 1e9;
|
|
// The sand.
|
|
float tG = rd.y < 0. ? -eye.y / rd.y : 1e9;
|
|
|
|
if (tS < tG) {
|
|
vec3 p = eye + rd * tS;
|
|
vec3 n = normalize(p - vec3(0., RHO, 0.));
|
|
float dif = max(dot(n, light), 0.);
|
|
// Matte: a broad soft fall from the lit shoulder, a whisper of rim from behind, the sand's bounce below.
|
|
float wrap = max((dot(n, light) + .25) / 1.25, 0.);
|
|
float rim = pow(1. - max(dot(n, -rd), 0.), 3.) * .25 * max(dot(n, light) + .4, 0.);
|
|
float bounce = max(-n.y, 0.) * .05;
|
|
float grain = .92 + .08 * gn(n.xz * 40. + n.y * 13.);
|
|
L = (pow(wrap, 1.6) * .95 + rim + bounce) * grain;
|
|
} else if (tG < 1e8) {
|
|
vec3 p = eye + rd * tG;
|
|
float r = length(p.xz);
|
|
vec2 dir = p.xz / max(r, 1e-4);
|
|
float s = slope(r);
|
|
vec3 n = normalize(vec3(-s * dir.x, 1., -s * dir.y));
|
|
// The grooves catch the low light on their crests.
|
|
float dif = max(dot(n, light), 0.);
|
|
float crest = pow(dif, 5.) * 2.2 + dif * .35;
|
|
// Sand grain, fixed in the sand.
|
|
float grain = .8 + .4 * h21(floor(p.xz * 420.)) * smoothstep(9., 2., tG - 13.);
|
|
// The pool of light the stone stands in, and the stone's shadow across it.
|
|
float pool = exp(-pow(length((p.xz - vec2(1.4, 1.2)) / vec2(6.2, 6.)), 2.) * 1.6);
|
|
vec3 toC = vec3(0., RHO, 0.) - p;
|
|
float along = dot(toC, light);
|
|
float off = length(toC - light * along);
|
|
float shade = along > 0. ? smoothstep(RHO * .75, RHO * 1.35, off) : 1.;
|
|
float ao = smoothstep(.9, 1.9, r) * smoothstep(.0, .5, r - .2);
|
|
L = crest * grain * pool * mix(.18, 1., shade) * mix(.25, 1., ao) * .55;
|
|
}
|
|
// The left of the frame is the words': keep it dark.
|
|
L *= mix(.08, 1., smoothstep(.2, .5, f.x / R.x));
|
|
return pow(tone(L * 1.2), .95);
|
|
}
|
|
</script>
|
|
<script src="./gl.js"></script>
|
|
</body>
|
|
</html>
|