~/journal/shaders

每日 shader

每天早上八点,手机上的 bot 会问我今天心情怎么样。我回一个词、几句话,或者干脆不回。一收到回复(没回的话就等到中午),它就查好埼玉当天的天气,把两样一起交给 Claude,由 Claude 写一段 WebGL fragment shader——一小段自己会画出动态画面的程序。

上线之前,代码要先过几条规则的检查(WebGL 1、循环有上限、除了时间和画布尺寸不读任何输入),不通过就退回去修。你看到的就是它最后交出来的样子。我回给 bot 的原话不会公开,标题只借用其中的意象。

最新的一幅正在动。点一下别的,就换成那一幅动起来。

北行前夜的雨窗细雨贴在车窗上,远处琥珀与青绿的光斑缓缓向后退去。 · 毛毛雨 19–23°C
源码refractive rain-on-glass droplets over drifting bokeh
2026-09-23.frag
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);
}