学習用にAgentCodingで生成したデモです
128×64×128の地形上に、少数の植物ボクセルを生成します。植物は光からエネルギーを得て上や横へ伸び、他の植物を遮光します。日陰では隣接する植物からの供給と維持費の釣り合いによって生存が決まり、枯れた場所には再び光が差し込みます。森林の生理を再現するモデルではなく、光を奪い合う群体の簡略モデルです。
- 「実行」で開始、「停止」で更新と連続描画を止めます
- ドラッグで回転、ホイールや2本指のピンチで接近・後退します。カメラは透視投影です
- シードには0〜4,294,967,295の整数を入力し、「再生成」で適用します。「別シード」は新しい値を選んで再生成します
- 「最初から」で適用済みシードの初期状態へ戻します。再生成時は植物と経過時間も初期化します
- 表示は通常の「植物」、保持量を色にする「エネルギー」、真上からの遮光を示す「日照」を選べます
- 新規発生率は、日向の地形直上にある空セルごとの毎秒の確率です。初期値は0.002%、範囲は0〜0.02%で、0では実行中の新規発生を止めます。初期配置と既存植物からの成長には影響しません。変更は次の更新から適用されます
- 寿命は0〜600秒で調整でき、0では寿命による枯死を無効にします。実行中の変更は次の更新から適用されます。エネルギー不足や接続切れによる枯死は有効です
- 時間倍率は1〜16倍です。表示される秒数はシミュレーション内の経過時間です
- 青緑が地形、緑が植物です。日照表示は黄色が受光、暗い青が遮光です
GPUでの更新
状態はGPUのStorage Bufferに保持します。CPUは操作入力、時間の管理、Dispatchと描画の指示を担当し、シミュレーション状態や描画件数を毎フレーム読み戻しません。パーティクル、掘削、堆積は一旦削除しています。
シードからの地形生成
integers.set([Number(viewControl.value),activeSeed,0,0],24);
シードはFloat32ではなくUniform Bufferのu32領域へ書きます。大きな整数をFloat32へ変換して下位ビットが失われることを避け、uint32の全範囲を入力として扱います。植物の初期配置と新しい種の発生にも、同じ整数シードを使います。
地形は、シードで変わる3D Value Noiseを使う陰関数で生成します。低周波の地形に座標の歪みと高さ方向の凹凸を加えて、岩の張り出しを作ります。さらに位置・半径・曲がり方・高さがシードで変化する4本の通路をくり抜きます。通路と地表が交わる場所は開口やアーチになり、地表下を通る場所はトンネルになります。個別の地形座標やシードごとの特例は持ちません。
真の距離関数ではなく、内外を判定する密度表現です。生成結果をボクセルへ格納し、その後の描画・植物更新はボクセルを参照します。最下部2層は床として残し、生成形状の安定性や全ての通路の接続までは保証しません。ノイズのスケールや通路数などは、生成器の調整パラメータです。
地形の書き込みと植物の初期配置は別Dispatchにします。植物は確定した地形配列を読み、地形直上の空セルから初期の種を選びます。同じシード・同じ実装と実行環境では同じ初期状態を再現できます。GPUの浮動小数点演算を使うため、異なるGPU間で境界セルまで完全一致することは保証していません。
セルと空間の大きさ
struct Plant {
energy: f32, age: f32, parent: u32,
birth: u32, parentBirth: u32, colony: u32,
upProbability: f32, generation: u32
}
植物の状態は1セル32バイトです。地形は別の整数配列に保持します。植物配列は2面で64 MiB、そのほか地形・日照・更新後エネルギー・描画セル一覧を各4 MiB確保し、主要な状態バッファは合計84 MiB(種の競合用4 MiBを含む)です。描画先とDepthは別途必要です。
const world={x:128,y:64,z:128};
空間サイズの定義はTypeScriptのこの一か所です。Uniform Bufferで全シェーダーへ渡し、座標変換、Dispatch数、ボックス境界にも同じ値を使います。WGSLの共通宣言はこの実験内のworld.wgslにまとめ、各シェーダーのコンパイル時に先頭へ連結します。
真上からの日照
sunlight[i]=select(0u,1u,open);
if(terrain[i]!=0u || plants[i].energy>0.0) { open=false; }
XZ列ごとに1つのInvocationを割り当て、上から下へ走査します。地形または植物に遮られるまでは受光できます。植物自身を評価した後で遮光するため、一番上の植物は光を受け、その下は陰になります。斜めの太陽光、透過、散乱は扱いません。
エネルギーの獲得・消費・輸送
exchange+=(other.energy-plant.energy)*transportRate;
6近傍の植物とのエネルギー差に比例して交換します。双方が同じ前ステップの状態から逆向きの流量を計算するため、輸送だけでエネルギーは増えません。群体が異なっていても、隣接していれば交換します。根や茎に相当する輸送路の専用モデルはまだありません。
受光中は毎秒2.4を獲得し、全植物が毎秒0.16 + 0.0015 × 高さを消費します。保持上限は12です。輸送係数は毎秒0.4、固定時間刻みは0.25秒で、6 × 0.4 × 0.25 = 0.6 ≤ 1として拡散更新で負の量を作らない設定です。上限を超えた獲得分は破棄し、枯死による消失もあるため、全処理を通じた保存系ではありません。
成長先の競合
if(destination<settings.world.w && winner(destination)==i) {
next.energy-=birthCost;
}
上限まで溜まった植物は、上か水平4方向の空セルへ1件だけ成長を提案します。上へ伸びる確率を遺伝子upProbability(0〜1)として保持し、残りの確率を水平4方向へ均等に割り当てます。初期配置と自然発生の遺伝子は一様乱数です。選んだ先が塞がっていればその更新では成長しません。枝には遺伝子と世代をそのまま継承します。複数の親が同じセルを提案した場合は、そのセルが乱数の優先度とセル番号で勝者を決めます。親も同じ規則で勝敗を計算し、成功した場合だけ4を支払います。子は2を受け取り、差分の2が形成コストになります。
親と子は前状態・更新後エネルギーを読むだけで決定し、それぞれ自分の次状態にだけ書きます。Atomicによる勝者の偶然の先着順には依存しません。初期の植物と、日向の地形直上にUIで指定した確率で生じる新しい種は、地形に直接接続する根元として扱います。
種による繁殖と突然変異
受光中で更新後エネルギーが8以上の植物セルを、種を作れる葉として扱います。各葉は毎秒0.05相当の確率(0.25秒刻みでは1.25%)で種の落下を試みます。70%は真下、30%は水平4方向のいずれかへ1セルずらします。
旧状態を真下へ走査し、最初の障害物が地形ならその直上へ発芽します。植物に当たる場合や、横ずれ先が既に塞がっている場合は失敗します。植物や地形を貫通せず、休眠や落下アニメーションはありません。
atomicMax(&seeds[landing],i+1u);
落下先の競合を別Dispatchで確定させます。同じセルではセル番号が大きい葉を採用し、GPUの実行順には依存しません。この優先順位には位置による偏りがあります。枝の成長と競合した場合は枝を優先し、発芽が成立した親だけがエネルギー4を消費します。子は2で始まり、新しい群体として地形に接続します。
種は親の遺伝子を継承し、10%の確率で±0.1以内の一様乱数を加算して0〜1へ制限します。世代は親から1増やします。「遺伝子」表示では青が横志向、赤が上志向、「世代」表示では0世代が暗色、10世代以上が黄色です。自然発生率を0にすれば初期個体からの系統だけを観察できます。
親の真下は陰になりやすく、発芽後の生存は通常のエネルギー規則に従います。個体の適応度を集計して選別する処理や交叉はなく、生存と繁殖を通じた選択を観察するモデルです。
枯死と接続
return source.energy>0.0 && source.birth==plant.parentBirth
&& source.colony==plant.colony;
エネルギーが尽きるか、UIで指定した寿命に達すると消滅します。寿命は全植物共通で、初期値は105秒です(従来の65〜145秒の個体差は廃止)。0では寿命判定だけを無効にします。年齢は無効中も加算するため、再び有効にすると指定寿命を超えた植物は次の更新で枯死します。根元は直下の地形に、枝は生成元の植物に接続します。親のセル番号だけでなく生成ステップと群体番号も照合し、同じセルに後から生えた別の植物には接続しません。
親が消えた枝は次の更新で消え、その先へ順に伝わります。一度に群体全体を探索する処理ではなく、1更新に1接続ずつ枯死が伝播します。周囲からエネルギーを受け取れても、構造上の接続が切れた枝は残り続けません。
時間と描画
日照、エネルギー計算、種の落下先競合、成長・発芽・枯死を順番にDispatchし、植物の読み取り面と書き込み面を交換します。更新は固定0.25秒で、時間倍率は実行するステップ数を変えます。遅延時は処理を一度に無制限に追い付かせず、実行速度が低下する場合があります。非表示タブでは自動停止します。
描画する表面ボクセルとIndirect Drawの件数もCompute Shaderで生成します。内部セルは描画から外しますが、シミュレーションは空間全体を更新します。カメラ操作だけでは表面一覧を作り直さず、状態更新のあった場合だけ再抽出します。DepthはReversed Zです。
現在の範囲
植物の形は全てボックスで、葉・幹・根を別の種類にはしていません。季節、水分、栄養、重力による倒木、保存と再開は未実装です。維持費・輸送・寿命による更新はありますが、常に多様な景観が続くことや、実在の植生に近い振る舞いを保証するものではありません。