毎日のシェーダー
毎朝八時、スマホの bot が「今日の気分は?」と聞いてきます。ひとことで返すことも、数行書くことも、返さないこともあります。返事が来たら(来なければお昼に)bot は埼玉のその日の天気を調べ、両方を Claude に渡します。Claude が書くのは WebGL のフラグメントシェーダー——それだけで動く絵を描く、小さなプログラムです。
公開する前に、コードはいくつかのルール(WebGL 1、ループに上限、時刻とキャンバスの大きさ以外は入力しない)でチェックされ、引っかかれば直しに戻されます。ここにあるのは、最後に出てきたそのままの姿です。bot に返した言葉は公開しません。タイトルはそのイメージを借りているだけです。
この一枚は、お使いのブラウザでコンパイルできませんでした。
ソースrefractive rain-on-glass droplets over drifting bokeh
precision mediump float;
uniform vec2 u_resolution;
uniform float u_time;
float h(vec2 p){ return fract(sin(dot(p, vec2(127.1, 311.7))) * 43758.5453); }
float h1(float x){ return fract(sin(x * 91.345) * 47453.123); }
float n(vec2 p){
vec2 i = floor(p); vec2 f = fract(p);
f = f * f * (3.0 - 2.0 * f);
return mix(mix(h(i), h(i + vec2(1.0, 0.0)), f.x), mix(h(i + vec2(0.0, 1.0)), h(i + vec2(1.0, 1.0)), f.x), f.y);
}
float fbm(vec2 p){
float s = 0.0; float a = 0.5;
for (int i = 0; i < 4; i++) { s += a * n(p); p *= 2.03; a *= 0.5; }
return s;
}
vec3 drops(vec2 uv, float sc, float t){
vec2 p = uv * sc;
vec2 id = floor(p);
vec2 f = fract(p) - 0.5;
float r = h(id);
float slide = fract(t * 0.04 * (0.6 + r) + r * 5.0);
vec2 c = vec2((fract(r * 7.3) - 0.5) * 0.4, 0.3 - slide * 0.6);
vec2 q = (f - c) * vec2(1.0, 0.85);
float d = length(q);
float rad = 0.13 + 0.1 * fract(r * 13.1);
float life = sin(slide * 3.14159);
float m = smoothstep(rad, rad - 0.06, d) * life;
return vec3(q / rad * m, m);
}
vec3 scene(vec2 uv, float t){
float g = uv.y * 0.7 + 0.5;
vec3 col = mix(vec3(0.30, 0.27, 0.24), vec3(0.34, 0.42, 0.44), clamp(g, 0.0, 1.0));
col += 0.06 * vec3(0.9, 0.8, 0.6) * fbm(uv * 1.5 + vec2(t * 0.015, 0.0));
for (int i = 0; i < 22; i++) {
float fi = float(i) + 1.0;
float a = h1(fi); float b = h1(fi * 2.17); float c = h1(fi * 3.71);
float x = mod(a * 2.0 - t * 0.025 * (0.5 + b), 2.0) - 1.0;
float y = (b - 0.5) * 0.55 - 0.08 + 0.02 * sin(t * 0.2 + fi);
float r = 0.05 + 0.08 * c;
float d = length(uv - vec2(x, y));
float disk = smoothstep(r, r * 0.82, d);
float rim = smoothstep(r * 0.55, r * 0.95, d) * disk;
vec3 lc = mix(vec3(1.0, 0.68, 0.34), vec3(0.72, 0.82, 0.42), step(0.55, h1(fi * 5.3)));
lc = mix(lc, vec3(0.55, 0.78, 0.80), step(0.85, h1(fi * 7.9)));
float pulse = 0.85 + 0.15 * sin(t * 0.3 + fi * 1.7);
col += lc * (disk * 0.22 + rim * 0.12) * pulse * (0.6 + 0.4 * c);
}
return col;
}
void main(){
vec2 uv = (gl_FragCoord.xy - 0.5 * u_resolution) / u_resolution.y;
float t = u_time;
vec3 d1 = drops(uv, 5.0, t);
vec3 d2 = drops(uv + vec2(0.31, 0.17), 7.5, t * 1.2);
vec2 off = d1.xy * 0.09 + d2.xy * 0.06;
float m = max(d1.z, d2.z);
float fog = 0.32 + 0.18 * fbm(uv * 2.2 + vec2(t * 0.02, -t * 0.01));
vec3 mistC = vec3(0.60, 0.63, 0.62);
vec3 outside = mix(scene(uv, t), mistC, fog);
vec3 inside = mix(scene(uv * 0.92 - off * 1.8, t), mistC, fog * 0.25) * 1.05;
vec3 col = mix(outside, inside, m);
vec2 hl = d1.xy + d2.xy;
col += vec3(0.9, 0.92, 0.9) * smoothstep(0.2, 0.7, dot(hl, vec2(-0.5, 0.6))) * m * 0.18;
col -= vec3(0.05) * smoothstep(0.3, 0.8, dot(hl, vec2(0.4, -0.7))) * m;
float v = 1.0 - 0.35 * dot(uv * vec2(0.9, 1.2), uv * vec2(0.9, 1.2));
col *= v;
col = mix(col, vec3(dot(col, vec3(0.33))), 0.12);
gl_FragColor = vec4(clamp(col, 0.04, 0.92), 1.0);
}