[Unity] 空気抵抗を独自に実装してみる

Unityでは空気抵抗を受けるシミュレーションが可能です。
対象のゲームオブジェクトにRigidbodyをアタッチし、Dragの値を0以外に設定すれば実現できます。

air-resist1

しかし、この空気抵抗の動作には大きな問題があります。

通常、物体に及ぼす空気抵抗は力であるため、質量が異なる同じ形状・大きさの物体を落下するとき、質量の軽い物体のほうが終端速度が低くゆっくりと落下します。(ニュートンの運動方程式より)

Unityでは上記物体は同じ速度で落下します。
すなわち、物体の質量にかかわらず空気抵抗が及ぼされる挙動は変わりません。
花びらや鉄くずも同じ速度で落下します。

上記のままでは扱いづらいと思う人もいるでしょう。(私がそうです)
もっと厳密に空気抵抗による動きを再現したい場合は独自に物理計算処理を実装するしかありません。
今回は以下のような簡単な運動方程式で実現してみます。

F = -kv
 
Fは物体に及ぼす空気抵抗の抗力、vは物体の移動速度、kは空気抵抗係数(>0)です。
スクリプトで実装すると以下のような感じになります。

using UnityEngine;
using System.Collections;

public class AirResistance : MonoBehaviour {
    public float coefficient;   // 空気抵抗係数

	void FixedUpdate() {
	    // 空気抵抗を与える
        rigidbody.AddForce(-coefficient * rigidbody.velocity);
	}
}

このスクリプトを空気抵抗を受けるゲームオブジェクトにアタッチします。
アタッチしたらcoefficientに適当な値を設定します。
(RigidbodyのMassが1の場合は5くらいを設定すれば効果を確認できます)
RigidbodyのDragは0にします。

air-resist2

これでRigidbodyの質量によって落下速度が変わるようになりました。

fall-ball

左端の球体が最も軽く、右に行くほどMassを1ずつ大きくしています。

今回は簡単な運動方程式で実装しましたが、これは厳密に成り立つものではなく弾丸や飛行機など高速で移動する物体に対しては以下のような式を実装する必要があります。

F = \cfrac{\rho C S v^2}{2}
 
ρは空気密度、Cは空気抵抗係数、Sは断面積、vは物体に対する空気の相対速度です。
設定するパラメータが増えて面倒になる反面、より正確な動きを再現できます。
数式が変わるだけでFixedUpdate内でRigidbodyに対してAddForceで上式を突っ込んでやるだけです。

断面積を計算するのが面倒ですが、これができると板のような平べったい物体がひらひら舞い落ちる挙動などが実現できます。

これはまたの機会に書きたいと思います。

COMMENTS & TRACKBACKS

  • Comments ( 3 )
  • Trackbacks ( 0 )
  1. By 匿名

    これ、Massに係数かけたものをDragに代入するスクリプトアタッチしたほうが良くないでしょうか?

    • By ftvoid

      コメントありがとうございます。
      返信が遅くなり申し訳ありません。

      恐らく、RigidbodyのMassと係数を使って最初にDragの値を指定しておく方法と思われますが、それらしく見せるだけであれば問題ないと思います。
      記事中のスクリプトと厳密に動きは一致しませんが、以下の初期化コードで対応できるようです。

      using System.Collections;
      using System.Collections.Generic;
      using UnityEngine;

      public class AirResistance : MonoBehaviour {
      public float coefficient; // 空気抵抗係数

      private void Awake() {
      var rigidbody = GetComponent();
      rigidbody.drag = coefficient / rigidbody.mass;
      }
      }

      もしかしたら、厳密に一致させる方法があるかもしれませんが、Dragの計算がブラックボックス化されているため、これ以上は分かりません…
      もしそのような方法があれば、別途ブログ記事に纏めたいと思います。

  2. By 匿名

    参考になりました!
    パラシュート実装してみたかったんでちょっと数式弄ったらうまくいきました。

    void FixedUpdate()
    {
    var resistance = GetComponent().velocity;
    resistance.Set(resistance.x * -coefficient*0.1f, resistance.y * -coefficient, resistance.z * -coefficient * 0.1f);
    GetComponent().AddForce(resistance);
    }