Unity 上に自分で物理エンジンを実装したい
始めに断っておきますが、この記事には誤った内容や解釈が含まれているかもしれませんので注意して下さい。
先日、ふとしたきっかけで初めて Unity 4 を触ってみました。そこで、どうしても Unity の組み込みの物理エンジン (Rigidbody など) を使わずに物理演算を実現したいという欲求にかられてしまったので、自分で物理エンジンを一から実装してみました。
実はこの記事を執筆している段階ではまだ実装は完了していないのですが、とりあえず現段階で (自分の思考を整理するために) 文章化しておこうと思います。
前置き: Unity とは
Unity は今最も注目されているゲームエンジンの一つで、三次元グラフィクスを用いたゲームの開発を支援するものです。
Unity を使うことで簡単に三次元シーンを作成したり、物理エンジンを用いて物理演算を行ったり、三次元シーンをレンダリングしたり、ゲームのシナリオを組んだりすることができます。
ゲームは Win/Mac 向けにビルドしたり、Web ブラウザ上で動作するようにビルドしたりできます。
Unity では物理エンジンとして PhysX が用いられている
Unity では物理エンジンとして NVIDIA 製の PhysX を採用しているそうです。したがって、Unity の Component である Rigidbody や Cloth などは PhysX のそれと同じ挙動をするはずです。
ちなみに余談ですが、最新の PhysX の Cloth の表現では以下の技術が使われているという話を聞いたことがあります。
T. Y. Kim, N. Chentanez, M. Müller
Long Range Attachments - A Method to Simulate Inextensible Clothing in Computer Games
SCA 2012
著者のページ
なぜわざわざ自前の物理エンジンを作るのか
さて、なぜ Unity 組み込みの PhysX の機能で満足せず、自分で物理エンジンを実装しようかというと、それは Unity 上で最新の物理シミュレーション手法を動かしたいと思ったからです。PhysX を使ってしまっては、PhysX でサポートされていること以上の機能拡張はできません*2。ですので、SIGGRAPH*3 などで発表される最新の研究成果を Unity で使うためには、自分で物理エンジンを実装する必要があります*4。
実装 (未完成)
前置きが長くなりましたが、本題に移ります。
Unity では GameObject に C# または JavaScript で書かれたソースコードを Component として持たせる (アタッチする) ことでオブジェクトの性質を決めていきます。ここに物理的な振る舞いを書くことで物理演算をさせようと思います。
今回実装する手法
今回は以下の論文の手法 (Position Based Dynamics、以下 PBD) を実装することを目標にしたいと思います。
M. Müller, B. Heidelberger, M. Hennix, J. Ratcliff
Position Based Dynamics
VRIPhys 2006
著者のページ
PBD は一般的な Force-based (つまり質点に作用する力を元に質点の速度を求め、更にその速度を用いて質点の位置を決定していくような方法) なダイナミクスとは異なり、オブジェクトの位置の制約を直接的に扱う (Position-based) ことができます。これによって、シミュレーションが数値的に安定になる他、衝突の扱い (Collision handling) が容易になるという利点があります。
ちなみにこの論文では更に PBD に基づいた新しい布モデルを提案しています (この記事では説明しません。) 。
また PBD の応用例としては以下の Oriented Particles という研究が有名です。この手法を使うことによって、変形可能物体 (ゴムのような材質のものや、布や髪の毛など) を統一的な方法でリアルタイムで計算できます。まさにゲーム向きの手法と言えます。
M. Müller, N. Chentanez
Solid Simulation with Oriented Particles
SIGGRAPH 2011
著者のページ
手法の説明
以下が PBD の疑似コードになります。ここで 1 つのオブジェクトは複数の質点の集合として表されており、オブジェクトはシーン中に複数存在していると考えてください。
1ステップだけ物理時間を進める { 各質点にかかる力を計算する; 力を元に各質点の速度を更新する; 速度を元に各質点の理想位置を計算する; オブジェクト間の衝突をチェックする; loop { 衝突を正しく扱うように理想位置を弄る; 各オブジェクトの形状を維持するように理想位置を弄る; } 理想位置を元に速度を修正する。 理想位置を実際の位置として適用する。 摩擦と跳ね返りの影響を速度に反映する。 }
理想位置という見慣れない言葉が出てきていますが、これは最終的な位置の更新を安定に計算させるための一時バッファのようなものだと考えてください。
Unity での実装の方法
GameObject にアタッチしたソースコードは、毎フレーム必ず void Update(); というメソッドが呼ばれます。そこで、void Update(); が上記の「1ステップだけ物理時間を進める」関数に対応するように実装します*6。
実際には、まず PhysicsEngine という名前の GameObject を作成し、そこに PhysicsEngine.cs という C# (JavaScript でも構いません) で書かれたソースコードをアタッチします。以下は PhysicsEngine.cs の中身の一部です。
... public class PhysicsEngine : MonoBehaviour { public float deltaTime = 1.0f / 30.0f; public int stiffness = 1; ... void Update () { SimulateStep(deltaTime); Finalize(); } void SimulateStep(float dt) { CalculateForces(dt); CalculateVelocities(dt); DampVelocities(dt); CalculateEstimatedPositions(dt); GenerateCollisionConstraints(dt); for (int i = 0; i < stiffness; ++ i) { ProjectConstraints(dt); } ApplyEstimatedPositions(dt); CorrectVelocities(dt); } ... }
次に、PhysicsBase というクラスを MonoBehaviour を継承する形で作り、更にそれを継承する形で PhysicsCloth や PhysicsSoftBody クラスを作っていきます。使うときにはシーン中の GameObject に PhysicsCloth や PhysicsSoftBody をアタッチしていきます。
PhysicsEngine の個々の細かいメソッドの中では、FindObjectsOfType メソッドを使って、シーン中に存在する PhysicsBase を継承したクラスを持っている GameObject を全て探し、それぞれ更新していくようにします。
なお、PhysicsEngine の void Update(); の中で呼ばれている void Finalize(); では、それぞれの GameObject のビジュアル情報 (メッシュのスキニングなど) を物理計算結果に合わせて更新するようにします。
このアプローチでの限界
この方針で実装しようとすると、問題が一つ発生します。それは、既存の Rigidbody との衝突の扱いです。
既存の Rigidbody との衝突の検出に関しては、PhysicsCloth や PhysicsSoftBody をアタッチした GameObject に Collider をアタッチすることによって実現可能です。しかし、衝突の影響を物理演算に反映するには、どうしても Position-based ではなく Force-based を使う必要がある*7気がしています。他に良い方法を知っている人は是非ご教示下さい。
追記 (2013/06/18)
参考までに、その後の開発成果(ソースコード)は以下のページで公開しています。
衝突に関しては制限を置いて、とりあえず動作するが汎用的に動作するわけではない、という中途半端な状態になっています。
*1:Pro 版のライセンスは10万円以上します。
*2:多分できません。
*3:CG の分野で最も権威のある国際学会です。
*4:せっかくゲームエンジンを使っているのに物理エンジンを自作するなんて本末転倒ですが、、、
*5:真偽は確かめていません。誰か知っている人は是非教えて下さい。
*6:追記 (2014/02/18): 書き足し忘れていましたが、void Update(); ではなく void FixedUpdate(); を用いるべきです。後者はレンダリングのフレームレートとは関係なく一定時間毎に呼ばれる関数です。PBD も含め、ゲーム向きの物理演算アルゴリズムの多くはタイムステップ (dt) が固定であると仮定していますから、後者を用いた方が良いです。
*7:Position-based では衝突を位置の制約として扱い、その制約を Gauss-Seidel 的な手法で解きます。一方、Force-based では反発力を計算するだけ (本当はもっと大変) なので、Unity の Collider とも比較的統合が簡単そうです。