ハクソク

世界を動かす技術を、日本語で。

波動関数崩壊を用いた手続き型六角マップの構築

概要

  • 本記事は手続き型マップ生成の技術的詳細を解説
  • **Wave Function Collapse (WFC)**アルゴリズムの応用事例
  • マルチグリッド構成やバックトラッキングによる解決策
  • 装飾や水表現などWFC以外の技法も紹介
  • Three.jsやWebGPU/TSLシェーダの実装例

手続き型マップ生成へのこだわり

  • すべてのマップが異なる形状、かつシード値に基づく決定論的生成
  • AD&Dのランダムダンジョン表に影響を受けた体験
  • Three.js WebGPUとTSLシェーダによる実装
  • 4,100ヘックスセル19グリッドに分割し、約20秒で生成
  • Carcassonneのようなタイル配置をコンピュータが自動で実施

Wave Function Collapse (WFC) アルゴリズムの概要

  • Maxim Gumin考案のWFCアルゴリズムを採用
  • Carcassonneのようにエッジ同士が一致するタイルを配置
  • ヘックスタイルは1枚につき6辺(通常の50%増し制約)
  • 30種類のタイル、6回転、5段階の標高で1セルあたり900状態

タイル定義とエッジマッチング

  • 各タイルは6辺の地形タイプと重みを定義
  • 例:3方向道路タイル { name: 'ROAD_D', ... edges: { NE: 'road', ... }, weight: 2 }
  • ルールは隣接辺が一致すること

WFCの進行手順

  • 初期状態:全セルが全状態の重ね合わせ(純粋な可能性)
  • 最も制約の厳しいセルを選び、ランダムに1状態へコラプス
  • 隣接セルへ制約伝播、不一致の状態を除外
  • 全セル解決まで繰り返し、失敗時はバックトラック

マルチグリッド問題とその解決

  • 大規模グリッドではデッドエンド発生率が急増
  • 19個のヘックスグリッドに分割し、各グリッド独立解決
  • 境界セルは隣接グリッドの配置を制約として固定
  • バックトラッキングで最大500回まで巻き戻し可能

リカバリーシステム

  • Layer 1: Unfixing 隣接セルの矛盾時、固定を解除し再解決
  • Layer 2: Local-WFC 問題領域周辺だけを再WFC(半径2セル、最大19セル)
  • Layer 3: Drop and hide 最終手段で問題セルを山タイルで隠蔽

三次元(標高)への拡張

  • 5段階の標高:海・草原がレベル0、斜面や崖で上下移動
  • 標高不一致で道路や川が崖で途切れる問題が発生
  • 2次元制約から3次元制約問題へ拡張

自然なデバッグカラー

  • 標高ごとに色分けし、TSLシェーダでパレット間をブレンド
  • 低地は夏色、高地は冬色

ヘックス座標の難しさ

  • 6方向のため、単純なx,y座標では隣接計算が困難
  • **キューブ座標系(q, r, s=-q-r)**を採用し、隣接セル計算を単純化
  • WFC自体はエッジの一致のみを気にするため、座標系は描画や配置にのみ使用

森や建物の配置はWFCに不向き

  • WFCは局所的なパターン生成には強いが、大域的な分布には不向き
  • Perlinノイズで森や建物の密度を決定し、自然なクラスタリングを実現
  • 建物は道路端や海岸、丘の上などにロジック配置

水表現の工夫

  • 海はアニメーション付きカースティック海岸波を重ねて表現
  • カースティックは小さなテクスチャ+ノイズで低負荷化
  • 海岸波はマスク画像を使い、波の幅を地形に応じて調整
  • 入り江問題はCPUで「囲まれ度」を計算し、波を細くするマスクで解決

Blenderによるタイル作成

  • KayKitのMedieval Hexagonパックをベースに、不足コネクタをBlenderで自作
  • すべてのタイルは2ワールド単位幅、エッジのUVも厳密に調整

ビジュアル強化とレンダリング

  • Three.js + WebGPU/TSLでレンダリング
  • TSLシェーダでカスタム表現を実装
  • ポストプロセスでGTAOなどの効果を追加し、雰囲気あるビジュアルを実現

このように、WFCによる地形生成ノイズやシェーダ表現を組み合わせることで、手続き型で美しい中世風の島マップを効率的に生成する手法を実現しています。

Hackerたちの意見

インスピレーションを与える内容だね。下の方にOGたちへの素晴らしいリファレンスがたくさんあって、ソースもあるし。これ、https://heredragonsabound.blogspot.com/ の雰囲気と合わせられないかな? ;)
これ大好き!余談だけど、もし作者がこれを読んでいたら、スーパーポジション状態(つまり、タイルにどんなオプションがあるか)にビットフィールドを使うことを考えたことある?前にWFCの実装をしたとき、しばらくしてからビットフィールドに移行したんだけど、速度がすごかったよ。バックトラックするより、最初からチャンクを再計算した方が速くなった。内側のループがほぼ完全にブランチレスだったからね。私のチャンクは100タイル立方体くらいだったかな。
Dorfromantikを思い出すなぁ。[0] https://store.steampowered.com/app/1455840/Dorfromantik/
同名のボードゲームが元になってるよ。https://boardgamegeek.com/boardgame/370591/dorfromantik-the-...
OPは知ってるかもしれないけど、このサイトにはコード例付きの六角形の数学の良い例がたくさんあるよ - https://www.redblobgames.com/grids/hexagons/
投稿内でそのサイトにリンクしてるよ。
オスカー・スタールバーグは、タウンスケーパーを含むいくつかのゲームで波動関数崩壊を使ってるよ。ここで話してるよ: https://www.youtube.com/watch?v=Uxeo9c-PX-w&pp=ygUhdG93bnNjY... (SGC21- オスカー・スタールバーグ - タウンスケーパーを超えて)。
ジャスパー・フリックのユニティの六角形地形チュートリアルを思い出すなぁ[0]。これもすごく詳細で面白いよ。面白い対比だけど、このプロジェクトは既製のタイルと制約解決を使ってタイルの境界を合わせてるのに対して、あっちは動的にタイルの境界(ジオメトリやブレンドなど)を生成してる。どちらも楽しい読み物だよ![0] https://catlikecoding.com/unity/tutorials/hex-map/
投稿は「バックトラッキング」を軽く流していて、500ステップに制限しているだけだけど、実際には制約プログラミングはすごく面白くて複雑な分野で、クールなアルゴリズムやトリックがたくさんあるんだよね。この場合、KnuthのアルゴリズムXを使ってダンシングリンクで解決できるはず。これは特別なバックトラッキングの一種なんだ。理論的には、アルゴリズムXは記事の「レイヤー2」で説明されている境界領域を86%よりも高い成功率で解決できるはずだよ。それに、さまざまなヒューリスティックを使えば、ブルートフォースアプローチに比べてバックトラッキングをかなり速くできるんだ。数独ソルバーを実装したことがある人なら分かると思うけど、ブルートフォースのバックトラッキングは実装は簡単だけど、すぐに遅くなっちゃうんだよね。
こういう制約のある組合せ最適化問題には、専用の制約プログラミングソルバーや高レベルのモデリング言語もたくさんあるよ。例えば、https://www.minizinc.org/ はいくつかの異なるソルバーバックエンドをターゲットにできる高レベルのモデリング言語を提供していて、カスタムアルゴリズムを書くのを完全に無視して、既存の産業グレードの制約プログラミングソルバーを使って、プロシージャル生成の問題を高レベルの言語でモデル化し、既存のソルバーを使ってランダムな解を見つけたり(または徹底的に列挙したり)するのがいい結果を生むかもしれないね。そうすれば、ソルバーを書くのに時間を取られる代わりに、問題定義を変更してもっと面白いマップを作ることにもっと時間を使えるから。
難しさの多くは、制約を満たす配置を見つけることにあるみたい。代わりにSATソルバーを使うアプローチはどうかな?そのアプローチの問題は、ソルバーがいつも「簡単な」解を見つけてしまって、ランダムに見えないことかもしれないね。一部のSATソルバーは変数の初期割り当てをランダムにすることができるけど、それがランダムな解を得られるわけじゃないし。誰か似たようなアプローチを試したことある?
SATソルバーの問題は、計算が複雑で、形式的手法を勉強していない人には理解しづらいことだと思う。WFCはブルートフォースでシンプルだけど、シンプルだから計算コストがかなり安いんだ(たくさんの行き止まりにぶつからない限り)。だから、SATソルバーよりも早く適切な解を見つけられることが多いんじゃないかな。少なくとも、ゲームの場合は、結果が完璧である必要はなくて、十分良ければいいからね。
何年も前(スマホが普及する前)に、モバイルマップとナビゲーションの製品を作ったことがあるんだ。街のラベリングは面白いサイドクエストの一つで、見つけた解決策は多くの候補を生成して、一つの解を選んで反復するという似たアプローチだった。実際にはかなりうまくいったよ。
「WebGPUはあなたのデバイスやブラウザでは利用できません。」でも、他の3Dデモは問題なく動くよ。モバイルでFirefoxとOperaを試したけどね。