Two of the eight sheets carry fish instead of only plasma. Each fish cuts straight across the slab once per cycle (thirty seconds, period preserved), three per sheet, sizes spread by pow(hash, 2.6) so most are small and now and then one is huge. Sheets sit near the front (0 and 1) so the silhouettes are not buried under the layers in front of them; their alpha carries coverage instead of depth. The fish ride screen space, since the sheets themselves stretch up to four times towards the frame edges, and are bent by the smooth swell of the surface only: the crack steps flip the slope within a pixel and would slice a fish to bits. Eyes are a small cold blue glow, the only light they have. debug_fish.frag renders the shapes flat for inspection.
62 lines
2.9 KiB
GLSL
62 lines
2.9 KiB
GLSL
// Debug view: the fish silhouette alone, on flat grey, big enough to judge the
|
|
// geometry. Not part of the pages; build with
|
|
// python3 build_plasma.py 6_ice_plasma.frag debug_fish.frag debug_fish.html
|
|
precision highp float;
|
|
uniform vec2 u_res;
|
|
uniform float u_time;
|
|
|
|
float hash(vec2 p) { return fract(sin(dot(p, vec2(127.1, 311.7))) * 43758.5453); }
|
|
float smin(float a, float b, float k) {
|
|
float h = clamp(0.5 + 0.5 * (b - a) / k, 0.0, 1.0);
|
|
return mix(b, a, h) - k * h * (1.0 - h);
|
|
}
|
|
float sdCapsule(vec2 p, vec2 a, vec2 b, float r) {
|
|
vec2 pa = p - a, ba = b - a;
|
|
float h = clamp(dot(pa, ba) / dot(ba, ba), 0.0, 1.0);
|
|
return length(pa - ba * h) - r;
|
|
}
|
|
float sdTriangle(vec2 p, vec2 p0, vec2 p1, vec2 p2) {
|
|
vec2 e0 = p1 - p0, e1 = p2 - p1, e2 = p0 - p2;
|
|
vec2 v0 = p - p0, v1 = p - p1, v2 = p - p2;
|
|
vec2 q0 = v0 - e0 * clamp(dot(v0, e0) / dot(e0, e0), 0.0, 1.0);
|
|
vec2 q1 = v1 - e1 * clamp(dot(v1, e1) / dot(e1, e1), 0.0, 1.0);
|
|
vec2 q2 = v2 - e2 * clamp(dot(v2, e2) / dot(e2, e2), 0.0, 1.0);
|
|
float s = sign(e0.x * e2.y - e0.y * e2.x);
|
|
vec2 d = min(min(vec2(dot(q0, q0), s * (v0.x * e0.y - v0.y * e0.x)),
|
|
vec2(dot(q1, q1), s * (v1.x * e1.y - v1.y * e1.x))),
|
|
vec2(dot(q2, q2), s * (v2.x * e2.y - v2.y * e2.x)));
|
|
return -sqrt(d.x) * sign(d.y);
|
|
}
|
|
// Half-height of the outline at x: pointed nose, deepest just behind the head,
|
|
// a narrow peduncle, then the tail fan opening toward the rear. One function, so
|
|
// the whole animal is guaranteed to be one connected shape.
|
|
// Half-height of the body at x: pointed nose, deepest a quarter of the way back,
|
|
// then down to a narrow peduncle. Sweeping a circle along it keeps the body one piece.
|
|
float fishWidth(float x) {
|
|
float t = clamp((1.15 - x) / 2.30, 0.0, 1.0); // 0 at the nose, 1 at the peduncle
|
|
return 0.34 * pow(sin(3.14159 * pow(t, 0.55)), 0.8) + 0.05 * smoothstep(0.70, 1.0, t);
|
|
}
|
|
float sdFish(vec2 p) {
|
|
float d = length(vec2(max(abs(p.x) - 1.15, 0.0), p.y)) - fishWidth(p.x); // body
|
|
// tail: two lobes fanning back from the peduncle, with a notch between them
|
|
d = smin(d, sdTriangle(p, vec2(-1.02, 0.03), vec2(-1.62, 0.09), vec2(-1.46, 0.52)), 0.08);
|
|
d = smin(d, sdTriangle(p, vec2(-1.02, -0.03), vec2(-1.62, -0.09), vec2(-1.46, -0.52)), 0.08);
|
|
// dorsal and pectoral fins
|
|
d = smin(d, sdTriangle(p, vec2(0.22, 0.20), vec2(-0.06, 0.46), vec2(-0.40, 0.16)), 0.05);
|
|
d = smin(d, sdTriangle(p, vec2(0.46, -0.16), vec2(0.26, -0.08), vec2(0.10, -0.28)), 0.04);
|
|
return d;
|
|
}
|
|
|
|
void main() {
|
|
vec2 p = (gl_FragCoord.xy - 0.5 * u_res) / u_res.y * 3.0;
|
|
vec3 col = vec3(0.16, 0.17, 0.18);
|
|
for (int i = 0; i < 3; i++) {
|
|
float id = float(i);
|
|
float size = mix(1.15, 0.28, id / 2.0);
|
|
vec2 at = vec2(0.0, 0.75 - 0.72 * id);
|
|
float d = sdFish((p - at) / size);
|
|
col = mix(col, vec3(0.01), clamp(0.5 - d / (0.005 / size), 0.0, 1.0));
|
|
}
|
|
gl_FragColor = vec4(col, 1.0);
|
|
}
|