物理エンジンと数値積分
剛体がめり込まず、跳ねすぎず、爆発もしない安定した物理を実装したい人へ。semi-implicit Euler・Verlet・逐次インパルスの原理を押さえれば、ゲーム物理の挙動を狙って設計できます。
- 剛体は力→加速度→速度→位置と積分する。ゲームでは速度を先に更新する半陰的Eulerが標準で、陽的Eulerより安定する。位置ベースのVerletは拘束を扱いやすい。
- 接触・関節の拘束は逐次インパルスで各接触点を反復補正する。前フレーム結果を初期値にするwarm startで収束を速め、貫入はBaumgarteやソフト拘束で直す。
- 固定タイムステップ+積分器の選択で安定性を確保し、動かない物体はスリープでCPUを節約する。同じ入力から同じ結果を得る決定性には、固定Δt・演算順序の固定・浮動小数点の再現性が要る。
物理エンジンがやっていること:力から位置への積分ループ
横にスクロール
ゲームの物理エンジンは、毎フレーム剛体の状態(位置・姿勢・速度・角速度)を微小時間 Δt だけ進める数値積分器が中心です。ニュートンの運動方程式は連続時間の微分方程式ですが、コンピュータは離散的にしか計算できないため、力 → 加速度 → 速度 → 位置 の連鎖を Δt きざみで近似積分します。並進は a = F/m、回転は 角加速度 = I⁻¹ τ(I は慣性テンソル、τ はトルク)で求め、これを積分して速度・角速度、さらに位置・姿勢へと反映します。
問題は、この積分のやり方で挙動の安定性がまるで変わることです。素朴に「今の速度で位置を進め、今の加速度で速度を進める」陽的(explicit / forward)Euler は、単振動のような系でエネルギーがフレームごとに増え、振動が発散します。ゲーム物理が「爆発する」典型がこれです。
剛体は並進と回転の2系統を持ちます。並進側は位置 x と線速度 v、回転側は姿勢(クォータニオン q または回転行列)と角速度 ω です。1ステップでは (1) 重力・入力・拘束からの力とトルクを集計し、(2) v += (F/m)Δt、ω += I⁻¹ τ Δt で速度を更新し、(3) x += v Δt、姿勢を ω で回してから、(4) 接触・関節の拘束を解いて速度(と位置)を補正します。慣性テンソル I はワールド空間では姿勢に応じて回転させて使う点に注意が必要です。
semi-implicit Euler と Verlet:なぜこの積分器なのか
ゲーム物理で事実上の標準になっているのが semi-implicit Euler(別名 symplectic Euler、semi-explicit Euler)です。陽的Eulerとの違いはたった一つ、速度を先に更新し、その新しい速度で位置を進めることです。
陽的 Euler(発散しやすい・使わない):
x_new = x + v * Δt ← 古い v で位置を進める
v_new = v + (F/m) * Δt
semi-implicit / symplectic Euler(ゲーム標準):
v_new = v + (F/m) * Δt ← 先に速度を更新し
x_new = x + v_new * Δt ← 新しい v で位置を進める
たった1行の順序差ですが、semi-implicit Euler はシンプレクティック積分器の一種で、保存系の疑似エネルギーを長時間ほぼ一定に保ちます。陽的Eulerがエネルギーを注入して発散し、逆に「後退(backward)Euler」がエネルギーを散逸させて減衰しすぎるのに対し、semi-implicit はその中間で、実装コストは陽的Eulerと同じ。これがゲームで選ばれる理由です。
もう一つの系統が Verlet積分で、速度を陽に持たず現在位置と1つ前の位置から次の位置を求めます。
位置Verlet:
x_new = x + (x - x_prev) + (F/m) * Δt²
(速度は v ≈ (x - x_prev) / Δt として暗黙に持つ)
Verletは位置を直接動かすため、「点間の距離を一定に保つ」といった位置レベルの拘束を、動かした後に位置をそのまま射影(クランプ)するだけで扱えるのが強みです。布・ロープ・ソフトボディ(3Dグラフィックスの原理 と隣接する分野)の質点系で広く使われます。半面、Δt を途中で変えると速度推定が壊れるため、固定タイムステップと相性が強く結びついています。
| 観点 | semi-implicit Euler | 位置Verlet |
|---|---|---|
| 状態変数 | 位置と速度を明示的に持つ | 現在位置と前位置(速度は暗黙) |
| 安定性 | シンプレクティックでエネルギー保存が良い | 同様に安定、位置ベースで頑健 |
| 拘束の扱い | 速度レベルのインパルスと相性が良い | 位置を直接射影でき距離拘束が容易 |
| 向く対象 | 剛体(接触・関節) | 質点系(布・ロープ・ソフトボディ) |
| 可変Δt | 比較的許容しやすい | 速度推定が崩れるため固定推奨 |
| 実装コスト | 陽的Eulerと同等に軽い | 軽い(速度を持たない) |
逐次インパルスによる拘束解決:接触と関節を速度レベルで解く
積分だけでは物体は互いにすり抜けます。床にめり込まない、関節が伸びない、といった条件は拘束(constraint)として別途解きます。現代のゲーム物理エンジン(Box2D・PhysXなど)の主流は、拘束を速度レベルで表現し、逐次インパルス(Sequential Impulse)で反復的に解く方式です。
各拘束は「相対速度が満たすべき条件」として書けます。接触なら「接触法線方向の相対速度が離れる向き(0以上)であれ」(非貫入)、関節なら「相対速度が0であれ」(等式)です。これを満たすように、物体対に瞬間的なインパルス(力積、速度の不連続な変化)を与えます。全拘束を同時に解く連立問題(LCP)は重いため、逐次インパルスは1つの拘束ずつ順番にインパルスを適用して速度を補正し、これを全拘束にわたって数回反復します。反復ごとに他の拘束の影響が伝播し、Gauss–Seidel的に全体の解へ収束していきます。
逐次インパルスの1フレーム:
for 各拘束 c: # warm start
前フレームの蓄積インパルスを適用(速度に反映)
repeat イテレーション回(例 4〜10回):
for 各拘束 c:
相対速度を評価
条件を満たすのに必要なインパルス Δλ を計算
λ_total をクランプ(接触は Δλ ≥ 0、摩擦は錐/箱で制限)
Δλ を両物体の速度に適用
x += v * Δt # 補正後の速度で位置を積分
接触状態はフレーム間でほとんど変わらないため、前フレームで各拘束に与えたインパルスの累積値を保存しておき、今フレームの反復の初期値として先に適用します(warm starting)。これにより反復は「ゼロから」ではなく「ほぼ正解から」始まり、わずか数反復で安定した接触力に収束します。積み上がった箱がプルプル震えずに静止するかは、warm start と反復回数がほぼ決めます。摩擦は法線インパルスに比例したクーロン摩擦の上限(|摩擦| ≤ μ|法線|)でクランプして解きます。
貫入補正・安定性・スリープ・決定性
速度を合わせても、すでに起きてしまった貫入(めり込み)は消えません。これを位置レベルで押し戻す代表手法が Baumgarte安定化で、貫入深さに比例した補正速度(bias)を拘束条件に加え、数フレームかけて押し出します。ただしbiasを強くするとエネルギーが注入されて跳ねるため、係数は控えめにするか、split impulse(押し出し用インパルスを本来の速度と分離)や位置ベース補正で、反発を汚さずに貫入だけ直す実装が好まれます。
安定性の土台は固定タイムステップです。可変フレームレートのまま Δt を積分に使うと、重い場面で Δt が伸びて貫入・すり抜け・発散を招きます。実務では描画フレームレートと切り離し、1/60秒 などの固定 Δt で物理を進め、必要なら1フレームに複数回サブステップします(アキュムレータで端数を持ち越す)。Δt を小さくすれば拘束反復の収束も速く安定しますが、その分ステップ数が増えCPU負荷が上がる、というトレードオフです。
静止した大量のオブジェクトを毎フレーム積分・拘束解決するのは無駄で、しかも微小な数値誤差で「プルプル」揺れ続ける原因にもなります。線速度・角速度が一定時間しきい値を下回った剛体をスリープ状態にし、積分と拘束解決の対象から外します。近くで別の物体が動いた、力が加わった等のイベントでウェイクして復帰させます。島(island、接触で連結した剛体群)単位でスリープ判定するのが一般的で、片方が起きたら島全体を起こします。スリープはCPU節約と「静止しているものは完全に止まって見える」安定性の両方に効きます。
決定性(determinism)は、同じ初期状態と同じ入力列から、いつ・どの環境で走らせてもビット単位で同一の結果を得る性質で、リプレイ、ロックステップ方式のネットワーク対戦(ネットワークの原理 と関わる同期モデル)、デバッグ再現に不可欠です。これを保つには、(1) 固定 Δt(可変にしない)、(2) 物体・接触の処理順序を固定(Gauss–Seidel系の逐次インパルスは適用順で結果が変わるため、ID順など安定なソートで毎回同じ順に解く)、(3) 浮動小数点演算の再現性(同一命令列・同一丸め・SIMDやfmaの使い方・コンパイラ最適化の固定、プラットフォーム跨ぎではしばしば固定小数点を採用)が要ります。マルチスレッド化は並列度と引き換えに順序を乱しやすく、決定性と両立させるには島の分割や結果マージの順序まで固定する設計が必要です。
- 積分器の選択: 陽的Eulerはエネルギー発散で「爆発」する。速度を先に更新して位置に使う semi-implicit(symplectic)Euler が剛体の標準。位置Verletは速度を持たず距離拘束を射影で扱え、質点系向き。
- 拘束は速度レベル+逐次インパルス: 接触(非貫入・摩擦)や関節を相対速度の条件として、1拘束ずつ反復補正(Gauss–Seidel)。warm start で前フレームのインパルスを初期値にすると数反復で収束。
- 貫入は位置補正: Baumgarte/split impulse/位置ベースで、反発を汚さずめり込みだけ押し戻す。bias過大はエネルギー注入で跳ねる。
- 安定性は固定Δt+サブステップ: 描画と物理を分離し固定タイムステップで積分。Δtを小さくすると安定だがCPU負荷増。
- スリープ: 速度がしきい値未満の物体(島単位)を積分・解決から除外しCPUと微振動を抑制、イベントでウェイク。
- 決定性の3条件: 固定Δt・処理順序の固定・浮動小数点の再現性。ロックステップ対戦やリプレイで必須、マルチスレッドは順序管理が鍵。
まとめ
物理エンジンは、剛体の力・トルクから加速度・速度・位置を Δt きざみで数値積分し、接触や関節の拘束を解いて状態を毎フレーム進めるループです。積分器は、陽的Eulerがエネルギーを注入して発散するのに対し、速度を先に更新してから位置に使う semi-implicit(symplectic)Euler がエネルギー保存に優れ剛体の標準となり、位置ベースのVerletは距離拘束を射影で扱えるため布やロープの質点系に向きます。めり込みや関節の条件は速度レベルの拘束として表し、逐次インパルスで1拘束ずつ反復補正し、warm start で前フレームのインパルスを初期値にすることで数反復で安定接触へ収束させます。すでに生じた貫入はBaumgarteやsplit impulse、位置ベース補正で反発を汚さず押し戻します。安定性は固定タイムステップとサブステップが支え、静止物体はスリープで積分から外してCPUと微振動を抑えます。そして固定Δt・処理順序の固定・浮動小数点の再現性という3条件を満たせば、リプレイやロックステップ対戦に不可欠な決定性が得られます。積分の順序と拘束解決の反復、そしてタイムステップの扱いこそが、ゲーム物理の「めり込まず・跳ねすぎず・爆発しない」挙動を決める設計軸です。
ゲーム開発の記事ガイド
物理エンジンと数値積分を実務で読む
TL;DRは入口です。実際に選ぶ・使う段階では、何を解決するか、何と比較するか、導入後にどこで詰まるかまで見る必要があります。
解決すること
ゲーム開発
比較で見る軸
難易度: advanced / カテゴリ: ゲーム開発 / タグ数: 6
導入後に効く点
接触・関節の拘束は逐次インパルスで各接触点を反復補正する。前フレーム結果を初期値にするwarm startで収束を速め、貫入はBaumgarteやソフト拘束で直す。
先に潰すリスク
用語だけ覚えても、設計・実装・運用でどこに効くかを確認しないと判断を誤る。
- 難易度
- advanced
- カテゴリ
- ゲーム開発
- タグ数
- 6
判断チェックリスト
- 自社の用途が「ゲーム開発 / 物理エンジン」に近いか確認する。
- 強みである「剛体は力→加速度→速度→位置と積分する。ゲームでは速度を先に更新する半陰的Eulerが標準で、陽的Eulerより安定する。位置ベースのVerletは拘束を扱いやすい。」が本当に評価軸になるか確認する。
- 注意点の「用語だけ覚えても、設計・実装・運用でどこに効くかを確認しないと判断を誤る。」を運用で吸収できるか確認する。
- 公開値や仕様値は、対象プラン・対象機種・対象リージョンまで確認する。
- 既存システム、ID、ネットワーク、監視、バックアップとの接続方法を先に洗い出す。
- 小さく試してから、本番移行、権限設計、障害時手順、コスト監視を決める。