目次/第03章
焼きムラは、なめらかな乱数でできている
value noise / fBm — 値ノイズと重ね合わせ
- ふつうの乱数はとなりと無関係なので、そのまま模様にすると砂嵐になります。
- 格子の点にだけ乱数を置いて、あいだをなめらかにつなぐ。これが値ノイズです。
- 細かさを半分ずつにして何枚も重ねると(fBm)、焼きムラや生地の肌理になります。
トーストの焼き色を、1ピクセルごとに rnd() で決めたらどうなるか。
白と茶色の点が入り乱れた砂嵐になります。実物の焼きムラは、
濃いところの隣はだいたい濃い。となり合う値がつながっていないといけません。
そこで、まず大きな格子を用意し、格子の交点にだけ乱数を置きます。
あいだの場所は、四隅の値を混ぜて求める。ただの直線で混ぜると格子が四角く見えてしまうので、
混ぜ具合を t·t·(3−2t) というなめらかな曲線(スムーズステップ)に通します。
これで、格子の形が見えない、なめらかな乱数の面ができます。
// 2. Noise : 2D値ノイズ const v2 = (x, y, seed) => { const xi = Math.floor(x), yi = Math.floor(y); const xf = x - xi, yf = y - yi; // 直線ではなく S 字で混ぜる。格子の目が見えなくなる const u = xf * xf * (3 - 2 * xf); const v = yf * yf * (3 - 2 * yf); const a = h2(xi, yi, seed), b = h2(xi + 1, yi, seed); const c = h2(xi, yi + 1, seed), d = h2(xi + 1, yi + 1, seed); return mix(mix(a, b, u), mix(c, d, u), v); };
1枚では足りない。だから重ねる
値ノイズを1枚だけ使うと、雲のようにぼんやりしたムラにしかなりません。 実物の焼きムラは、大きな濃淡の中に細かい斑があって、その中にもっと細かい粒がある。 そこで、細かさを2倍にして高さを半分にしたものを何枚も足します。 これを fBm(fractional Brownian motion/非整数ブラウン運動)と呼びます。 枚数のことをオクターブといいます。
const fbm = (x, y, seed, oct) => { let s = 0, a = 0.5, f = 1, n = 0; for (let o = 0; o < oct; o++) { s += a * v2(x * f, y * f, seed + o * 131); n += a; f *= 2; // 細かさは2倍 a *= 0.5; // 効き目は半分 } return s / n; };
実験 — オクターブを重ねる
左が乱数そのもの(となりと無関係)、右が値ノイズ。 「焼き色にする」を押すと、同じノイズがトーストの焼きムラに化けます。
ぐるっと一周してつながるノイズ
ウィンナーや秋刀魚のように筒の表面に模様を巻くとき、
ふつうのノイズを使うと、一周して戻ってきたところで模様が切れて縦の線が入ります。
そこで、横方向(u)だけ格子の番号を周期で折り返した専用のノイズを使います。
14本のうち筒状のものには、必ずこの v2u が入っています。
fBm の平均は 0.5 のあたりに集まります。 オクターブを重ねるほど、極端な値(0 に近い/1 に近い)は出にくくなります。 「しきい値 0.8 以上を焦げにする」と書いたのに焦げが1点も出ない、というのはこれが原因です。 焼き魚(サンマ).js には、しきい値を実際の分布に合わせ直した記録が残っています。
- オクターブを1から6まで動かし、どこから「焼きムラらしく」見えるか決める。
- 減り方を 0.85 にすると何が起きるか観察する(細かい粒が消えないので砂っぽくなる)。
- 「焼き色にする」で、大きさを 1 と 14 にしたときの見え方の違いを言葉にする。
- トースト.js の中で fbm を呼んでいる箇所を全部探し、それぞれ何に使っているか書き出す。