地図/気まぐれ/プロシージャル生成

ノイズで高さ場をつくる

地形そのものを生やすには、各マスに高さの値を持たせて、海面のしきい値で陸と海を分ける。高さを作る素直な道具がノイズで、なめらかに変化する値の場をそのまま標高として読む。

なめらかに変化する一枚の場 (x, y) → 値 を標高として読むと、しきい値の上が陸、下が海に分かれる。場の周波数(座標にかける係数)が地形の縮尺で、低いほど大きな大陸、高いほど群島になる。Perlin ノイズや、周波数を倍々にしたサインの和(fBm)が、雲や煙と同じ統計の起伏を作るので地形の高さ場に使われる。塗りなら値を明度に写すところを、地形では値をしきい値で陸海に量子化する。

高さ場のいる所は、座標を入れたら標高が返る関数。周波数を倍々にした sin/cos を4枚重ねた擬似ノイズで代用する。重ねるたびに振幅を半分に落とすので、大きなうねりの上に細かい凹凸が乗った、フラクタルな起伏になる。最後に 0〜1 へ寄せて標高として読む。

// 座標 → 標高。倍々の周波数を4枚、振幅を半分ずつ重ねる
const height = (gx, gy, t) => {
  let v = 0
  let amp = 1
  let f = 0.09
  for (let o = 0; o < 4; o++) {
    v += amp * Math.sin(gx * f + t) * Math.cos(gy * f * 1.3 - t * 0.7 + o)
    amp *= 0.5 // 重ねるごとに弱く
    f *= 2.0 // 重ねるごとに細かく
  }
  return (v + 1.7) / 3.4 // ざっくり 0..1 に寄せる
}

amp *= 0.5f *= 2.0 の組が、大きい起伏に小さい起伏が入れ子になる粗さを作る。t を足し引きしているのは時間で、入れると場全体がゆっくり流れる。

この標高をそのまま明度にするとグラデーションになる。しきい値で一段だけ切ると境界がぼやけるので、海・浅瀬・陸・山と何段かのしきい値で帯に分けると、地形図に近づく。

// 標高を4段の帯に量子化。しきい値を動かすと海岸線が前後する
const sea = 0.42
const coast = 0.5
const land = 0.72
const band = (e) => {
  if (e < sea) return colors.tint(70) // 深い海(暗い)
  if (e < coast) return colors.tint(130) // 浅瀬
  if (e < land) return colors.tint(195) // 陸
  return colors.tint(238) // 山(明るい)
}

sea を上げると水位が上がって陸が沈み、下げると干上がって大陸が太る。下は、この height を全マスに引いて band で塗り分けたもの。t をゆっくり進めると、海岸線が呼吸するように前後する。

しきい値で切った瞬間、なめらかなグラデが島の形に変わる。band の段数としきい値を増やすと、もっと細かい地形帯(砂浜・草原・森・雪山)に分けられる。height の周波数 0.09 を上げると大陸が割れて群島になり、下げると一枚の大きな陸になる。