たべもののつくりかた.

目次/第03章

03Noise

焼きムラは、なめらかな乱数でできている

value noise / fBm — 値ノイズと重ね合わせ

この章の3行まとめ
  • ふつうの乱数はとなりと無関係なので、そのまま模様にすると砂嵐になります。
  • 格子の点にだけ乱数を置いて、あいだをなめらかにつなぐ。これが値ノイズです。
  • 細かさを半分ずつにして何枚も重ねると(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);
};
14本すべてに、ほぼこの形で入っている

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;
};
オクターブを増やすほど細かい粒が乗る。4〜6枚でだいたい足りる

実験 — オクターブを重ねる

左が乱数そのもの(となりと無関係)、右が値ノイズ。 「焼き色にする」を押すと、同じノイズがトーストの焼きムラに化けます。

ぐるっと一周してつながるノイズ

ウィンナーや秋刀魚のように筒の表面に模様を巻くとき、 ふつうのノイズを使うと、一周して戻ってきたところで模様が切れて縦の線が入ります。 そこで、横方向(u)だけ格子の番号を周期で折り返した専用のノイズを使います。 14本のうち筒状のものには、必ずこの v2u が入っています。

つまずきポイント

fBm の平均は 0.5 のあたりに集まります。 オクターブを重ねるほど、極端な値(0 に近い/1 に近い)は出にくくなります。 「しきい値 0.8 以上を焦げにする」と書いたのに焦げが1点も出ない、というのはこれが原因です。 焼き魚(サンマ).js には、しきい値を実際の分布に合わせ直した記録が残っています。

やってみよう
  1. オクターブを1から6まで動かし、どこから「焼きムラらしく」見えるか決める。
  2. 減り方を 0.85 にすると何が起きるか観察する(細かい粒が消えないので砂っぽくなる)。
  3. 「焼き色にする」で、大きさを 1 と 14 にしたときの見え方の違いを言葉にする。
  4. トースト.js の中で fbm を呼んでいる箇所を全部探し、それぞれ何に使っているか書き出す。