diff --git a/shader/playground.glsl b/shader/playground.glsl index bee2109..ce4246d 100644 --- a/shader/playground.glsl +++ b/shader/playground.glsl @@ -64,18 +64,6 @@ float fillPx(float d) { return 1.0 - smoothstep(-px, px, d); } -// レンガの壁(第7章のタイル) -vec3 bricks(vec2 p) { - vec2 size = vec2(0.4, 0.2); - vec2 q = p / size; - q.x += 0.5 * mod(floor(q.y), 2.0); // 1段おきに半分ずらす - vec2 id = floor(q); - vec2 f = (fract(q) - 0.5) * size; - float d = sdRoundBox(f, size * 0.5 - 0.012, 0.02); - float shade = 0.85 + 0.15 * cos(id.x * 2.7 + id.y * 5.1); - return mix(vec3(0.55, 0.53, 0.50), vec3(0.70, 0.32, 0.22) * shade, fillPx(d)); -} - // 角度の繰り返し:pを一番近い区画の中心(x軸上)に回す vec2 repeatAngle(vec2 p, float count) { float sector = 6.2832 / count; @@ -83,54 +71,142 @@ vec2 repeatAngle(vec2 p, float count) { return rot(-k * sector) * p; } -// 針:12時の方向を向いたカプセルを、時計回りに回す -// 座標をrot(+a)で回すと、図形は-a、つまり時計回りに回る -float sdHand(vec2 p, float turn, float back, float len, float width) { - vec2 q = rot(turn * 6.2832) * p; - return sdSegment(q, vec2(0.0, -back), vec2(0.0, len)) - width; +// ---- 第8章:乱数とノイズ ---- + +// 整数ハッシュ(Chris Wellonsのlowbias32):32ビットをよく掻き混ぜる +uint hashU(uint x) { + x ^= x >> 16u; + x *= 0x7feb352du; + x ^= x >> 15u; + x *= 0x846ca68bu; + x ^= x >> 16u; + return x; } -// 時針と分針を1つの距離に(影にも使う) -float sdHands(vec2 p, float t) { - float h = sdHand(p, fract(t / 43200.0), 0.08, 0.36, 0.028); // 時針:12時間で1周 - float m = sdHand(p, fract(t / 3600.0), 0.10, 0.54, 0.018); // 分針:1時間で1周 - return min(h, m); +// 格子点(整数の座標)から0〜1の乱数を1つ +float hash21(vec2 cell) { + uvec2 q = uvec2(ivec2(cell)); // 負の数もビットのまま変換される + return float(hashU(q.x + hashU(q.y)) >> 8u) / 16777216.0; } -void main() { - // 中心が原点、縦が-1〜1になる座標(第5章) - vec2 p = (2.0 * gl_FragCoord.xy - uResolution) / uResolution.y; +// 格子点から0〜1の乱数を2つ +vec2 hash22(vec2 cell) { + uvec2 q = uvec2(ivec2(cell)); + uint h = hashU(q.x + hashU(q.y)); + return vec2(float(h >> 8u), float(hashU(h) >> 8u)) / 16777216.0; +} + +// 5次の補間曲線 6t^5 - 15t^4 + 10t^3(両端で1階と2階の微分が0) +vec2 quintic(vec2 t) { + return t * t * t * (t * (t * 6.0 - 15.0) + 10.0); +} - // 10時10分0秒から始まる時刻(秒) - float t = uTime + 10.0 * 3600.0 + 10.0 * 60.0; +// バリューノイズ:格子点の乱数を滑らかに補間(0〜1) +float valueNoise(vec2 p) { + vec2 i = floor(p); + vec2 f = fract(p); + float a = hash21(i); + float b = hash21(i + vec2(1.0, 0.0)); + float c = hash21(i + vec2(0.0, 1.0)); + float d = hash21(i + vec2(1.0, 1.0)); + vec2 u = quintic(f); + return mix(mix(a, b, u.x), mix(c, d, u.x), u.y); +} - // 背景:少し暗くしたレンガの壁 - vec3 color = bricks(p) * 0.55; +// 格子点のランダムな単位ベクトル +vec2 gradient(vec2 cell) { + float a = hash21(cell) * 6.2832; + return vec2(cos(a), sin(a)); +} - // 時計の影 - float shadow = sdCircle(p - vec2(0.04, -0.06), 0.8); - color *= 1.0 - 0.6 * (1.0 - smoothstep(-0.05, 0.1, shadow)); +// グラディエントノイズ(パーリンノイズの仲間):約-0.7〜0.7 +float gradientNoise(vec2 p) { + vec2 i = floor(p); + vec2 f = fract(p); + float a = dot(gradient(i), f); + float b = dot(gradient(i + vec2(1.0, 0.0)), f - vec2(1.0, 0.0)); + float c = dot(gradient(i + vec2(0.0, 1.0)), f - vec2(0.0, 1.0)); + float d = dot(gradient(i + vec2(1.0, 1.0)), f - vec2(1.0, 1.0)); + vec2 u = quintic(f); + return mix(mix(a, b, u.x), mix(c, d, u.x), u.y); +} - // 枠と文字盤 - float rim = sdCircle(p, 0.8); - float face = sdCircle(p, 0.72); - color = paint(color, vec3(0.20, 0.18, 0.22), rim); - color = paint(color, vec3(0.96, 0.93, 0.85), face); +// fBm(非整数ブラウン運動):オクターブを重ねる +float fbm(vec2 p, int octaves) { + float sum = 0.0; + float amp = 0.5; + for (int i = 0; i < octaves; i++) { + sum += amp * gradientNoise(p); + p = rot(0.6) * p * 2.0 + vec2(17.1, 9.3); // 周波数を2倍に(回してずらし、格子を隠す) + amp *= 0.5; // 振幅を半分に + } + return sum; +} - // 目盛り:60個の小さい物と12個の大きい物 - float minuteTick = sdBox(repeatAngle(p, 60.0) - vec2(0.65, 0.0), vec2(0.025, 0.004)); - float hourTick = sdBox(repeatAngle(p, 12.0) - vec2(0.62, 0.0), vec2(0.06, 0.014)); - color = paint(color, vec3(0.25), minuteTick); - color = paint(color, vec3(0.12), hourTick); +// セルラー(ボロノイ)ノイズ:一番近い特徴点と2番目に近い特徴点までの距離 +vec2 voronoi(vec2 p, float t) { + vec2 i = floor(p); + vec2 f = fract(p); + float d1 = 8.0; + float d2 = 8.0; + for (int y = -1; y <= 1; y++) { + for (int x = -1; x <= 1; x++) { + vec2 o = vec2(float(x), float(y)); + vec2 h = hash22(i + o); + vec2 pt = o + 0.5 + 0.4 * sin(t + 6.2832 * h); // 隣のマスの特徴点(時間で揺れる) + float d = length(pt - f); + if (d < d1) { + d2 = d1; + d1 = d; + } else if (d < d2) { + d2 = d; + } + } + } + return vec2(d1, d2); +} - // 針の影、時針と分針 - color *= 1.0 - 0.3 * (1.0 - smoothstep(-0.02, 0.03, sdHands(p - vec2(0.015, -0.025), t))); - color = paint(color, vec3(0.12, 0.12, 0.15), sdHands(p, t)); +// 山の稜線の高さ:xだけの関数(1Dのノイズとして、yに層毎の番号を入れる) +float ridge(float x, float layer) { + return fbm(vec2(x, layer * 7.3), 5); +} - // 秒針:1秒毎にカチッと動く - float second = sdHand(p, fract(floor(t) / 60.0), 0.15, 0.6, 0.006); - second = min(second, sdCircle(p, 0.035)); - color = paint(color, vec3(0.85, 0.15, 0.15), second); +void main() { + // 中心が原点、縦が-1〜1になる座標(第5章) + vec2 p = (2.0 * gl_FragCoord.xy - uResolution) / uResolution.y; + float t = uTime; + + // 空:夕焼けのグラデーション + vec3 color = mix(vec3(0.98, 0.55, 0.30), vec3(0.12, 0.10, 0.30), smoothstep(-0.3, 1.0, p.y)); + + // 星:画素の格子毎の乱数で、ごく一部だけ光らせる + vec2 starCell = floor(gl_FragCoord.xy / 3.0); + float star = step(0.996, hash21(starCell)); + float twinkle = 0.6 + 0.4 * sin(t * 3.0 + 6.2832 * hash21(starCell + 71.0)); + color += star * twinkle * smoothstep(0.3, 0.9, p.y) * vec3(0.9, 0.9, 1.0); + + // 太陽:円と、距離で弱くなる光彩(第6章) + vec2 sunPos = vec2(0.45, -0.05); + float sun = sdCircle(p - sunPos, 0.18); + color += vec3(1.0, 0.6, 0.3) * 0.35 * exp(-4.0 * max(sun, 0.0)); + color = paint(color, vec3(1.0, 0.92, 0.70), sun); + + // 雲:ゆっくり流れる、ずらしたfBm + vec2 cq = vec2(p.x * 1.2 + t * 0.03, p.y * 3.0); + vec2 warp = vec2(fbm(cq + vec2(0.0, t * 0.05), 4), fbm(cq + vec2(3.7, 1.9), 4)); + float cloud = smoothstep(-0.05, 0.4, fbm(cq + 1.5 * warp, 6) + 0.25 * (p.y - 0.3)); + cloud *= smoothstep(0.0, 0.35, p.y); // 地平線の近くには出さない + vec3 cloudColor = mix(vec3(0.95, 0.55, 0.45), vec3(0.35, 0.25, 0.45), smoothstep(0.2, 0.9, p.y)); + color = mix(color, cloudColor, cloud * 0.85); + + // 山:奥から手前へ3層。手前程暗く、大きく、ギザギザに + for (int i = 0; i < 3; i++) { + float k = float(i); + float h = -0.15 - 0.22 * k + (0.35 - 0.05 * k) * ridge(p.x * (1.0 + 0.6 * k) + 3.1 * k, k); + vec3 hill = mix(vec3(0.45, 0.30, 0.45), vec3(0.06, 0.05, 0.10), k / 2.0); + hill = mix(hill, vec3(0.98, 0.55, 0.30), 0.25 * (1.0 - k / 2.0)); // 遠くは空の色が混ざる(霞) + color = paint(color, hill, p.y - h); + } out_color = vec4(color, 1.0); }