Sator Imaging - はてなブログ

作業用のメモや、読んだ資料のまとめが置いてあります。 アメドラ好き。 最近は ・プリプロ ・演出シーンやアセットの制作 が半々ぐらいです。 ◆ 短編アニメシリーズ ◆ VR進撃の巨人 THE HUMAN RACE ◆ 出資向けVTuber ◆ 版権物スマホゲー ◆ https://twitter.com/sator_imaging

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

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

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

備忘録

はじめに

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

--

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

--

こちらもどうぞ