LoginSignup
55
47

More than 3 years have passed since last update.

Unity の Transform のパフォーマンス最適化まとめ

Last updated at Posted at 2019-07-03

(今更ですが)毎フレーム大量の Transform を扱う機会があったので、Unity 上で Transform を更新する際の手法とパフォーマンスの比較・最適化の方法のまとめを。

毎フレーム数百かそれ以上の Transform の .position や .rotation / .eulerAngles へのアクセスや変更がある場合には考慮する意味がある、といった内容です。Transform 沼にハマっているなら。

備忘録

はじめに

面倒くさがってビルドして試さずに、すべてエディター上で確認しています。
また、.rotation / .eulerAngles を中心に確認していますが .position / .scale も同様の扱いかと思います。

  • Unity 2018.4.0f1(Unity 2018 LTS)
  • Windows 10 64bit @ Intel Core i9-9900K 3.6GHz

--

Humanoid キャラ一体の骨が 60~ 以上あったりするので、値の取得含めた Transform の操作は、油断しているとすぐ数百単位になる。そして Transform の値の取得に大きな罠があります。

【重要】 rotation / eulerAngles の xyzw へのアクセスは必ずキャッシュする

これがかなり重要

場合によっては数十%のパフォーマンスへのインパクトがあります。Transform のキャッシュでは(場合によっては)足りない。

Transform だけキャッシュするのではなく、Quaternion や Vector3 もキャッシュ

vector3.x = 0 は出来るのに、transform.position.x = 0 は出来ない、ということで、こいつらは少し特殊な扱いです。Transform をキャッシュしていたとしても、

..... = new Vector3(cache.eulerAngles.x, cache.eulerAngles.y, cache.eulerAngles.z);

など、メンバーに3回アクセスすると、3回分の Vector3 のコピーが行われる。らしいです。

↓ ↓ ↓

Transform.position や Transform.rotation、Transform.eulerAngles 等はフィールドではなくプロパティで getter がセットされていて、アクセスのたびに構造体のコピーが行われています。

(というようなことが、ネットのどこかに書いてあった気がしますが失念)

--

キャッシュだけでなく「どうやって変更を加えるか」もパフォーマンスに影響を与えます。

細かなパフォーマンス比較の前にざっくりとまとめると、

↓ ↓ ↓

localRotation が一番高速

でした。

オイラー角よりもクオータニオンの方が高速

でした。

ワールドスペースよりローカルスペースの方が高速

.rotation よりも .localRotation、.eulerAngles よりも .localEulerAngles の方が高速でした。

ルートのみワールド空間で扱う

場合によってはルートのみワールド空間の値を扱い、それ以下のオブジェクトはすべてローカル空間の値を扱うなど。

テストではローカル値を扱うようにするだけで倍近いスピードに。

localEulerAngles += よりも Transform.Rotate()

オフセットの調整等はオイラー角の方が直感的なので、.localEulerAngles += ..... としがちですが、Transform.Rotate() の方が高速です。

数が多い場合は結構なパフォーマンスへのインパクトがあります。

使えるなら HumanPoseHandler.SetHumanPose() を使う

対象が Humanoid の場合、HumanPoseHandler の SetHumanPose の方が高速な場合があります。

以下がとても参考になります。

検証動画

パフォーマンスの比較を行ったシーンは以下の通り。

フレームレートに大きな変化が出やすい数の Transform を更新してますが、場合によってはキャラ数体の場合でも、秒間 10fps 程度の変化がある可能性も。

シーン構成

95 個の muscles / Transform を1フレームに 100 回更新 = 秒間に約 10,000~ Transform の向きを更新するテストを行った動画。

※ 揺れもの無しの人型キャラクター 100 体分のボーン数、ポリゴン自体は1体分

まとめ

変更の加え方、アクセスの仕方など、各パフォーマンスの比較とまとめ。

  • 可能な限りオイラー角は扱わない。
  • .eulerAngles / .localEulerAngles に値をセットする・メンバーにアクセスすると極端にパフォーマンスが落ちる。
  • Quaternion / Vector3 のメンバーにアクセスするのはコストがかかるので必ずキャッシュする。
    • Transform ではなく、Quaternion / Vector3 をキャッシュ。

↓ 詳細 ↓

クオータニオンは高速

取得した値を直接放り込むなら、SetHumanPose よりも高速。

  • .localRotation を変更した場合のフレームレート

    • pseudo: .localRotation = Time.time;
      • 約 800fps
  • .rotation を変更した場合のフレームレート

    • pseudo: .rotation = Time.time;
      • 約 400fps

オフセットを加える場合は = Quaternion * Quaternion が一番高速

各キャラクターごとの差分の吸収など、回転のオフセットは可能な限りクオータニオンで扱う。オイラー角を扱わなければ、SetHumanPose よりもパフォーマンスが出る。

  • .rotation にクオータニオンのオフセットを加えて変更
    • pseudo: .rotation = Time.time * Quaternion
      • 約 360fps

オイラー角は厳禁

オイラー角を扱うだけで何をしても重いので、可能な限り使わない。

  1. .localEulerAngles を変更した場合のフレームレート

    • pseudo: .localEulerAngles = Time.time;
      • 約 500fps
  2. .eulerAngles を変更した場合のフレームレート

    • pseudo: .eulerAngles = Time.time;
      • 約 300fps

どうしてもオイラー角でオフセットを指定したい

ワールド空間のクオータニオン値をセットしてから、オイラー角でオフセットの数値を入力する必要がある場合は Transform.Rotate() を Space.Self で使う。

  • .rotation をセットしてから Transform.Rotate() でオフセットした場合のフレームレート
    • pseudo: .rotation = Time.time; Transform.Rotate();
      • 約 210fps
    • pseudo: .rotation = Time.time; .localEulerAngles += new Vector3() の場合
      • 約 175fps
      • 意味わからん

Quaternion / Vector3 の xyzw メンバーへのアクセス自体が重い

前述の通り transform.position.x = 0 が出来ない、思っているのと違う奴らです。
数が多い場合、軽い気持ちでメンバーにアクセスするとパフォーマンスへのインパクトが凄いことに。

複数回アクセスする場合は、Transform の .rotation や .eulerAngles は var cached = transform.rotation 等するだけで劇的に高速に。

.... transform.position.x とか気軽に使いがちだけど、アクセスする数が多い場合は厳禁。必ずキャッシュ。

  • .rotation の各メンバーにキャッシュ無しでアクセスした場合(4回のアクセス)

    • pseudo: .rotation = transform.rotation.xyzw + Time.time;
      • 約 200fps
    • キャッシュすると 約 320fps に
      • pseudo: .rotation = cachedRotation.xyzw + Time.time;
      • クオータニオンのメンバーを直接弄ることはないだろうけど、オフセットを適用するなら Quaternion * Quaternion に落とし込むのが一番高速
  • .eulerAngles の各メンバーにキャッシュ無しでアクセスした場合(3回のアクセス)

    • pseudo: .eulerAngles = transform.eulerAngles.xyz + Time.time;
      • 約 110fps
    • キャッシュすると 約 190fps に
      • pseudo: .eulerAngles = cachedEulerAngles.xyz + Time.time;

HumanPoseHandler の SetHumanPose の場合

Avatar が Humanoid で無いと動かない、Humanoid 依存のソフトウェア・コンポーネントにしたくはないので、あまりちゃんと調べてないですが…。

  • HumanPoseHandler.SetHumanPose を使用した場合のフレームレート
    • 約 250fps

--

こちらもどうぞ

55
47
5

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
55
47