Qiita Teams that are logged in
You are not logged in to any team

Log in to Qiita Team
Community
OrganizationAdvent CalendarQiitadon (β)
Service
Qiita JobsQiita ZineQiita Blog
39
Help us understand the problem. What is going on with this article?
@sator_imaging

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

(今更ですが)毎フレーム大量の 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

--

こちらもどうぞ

39
Help us understand the problem. What is going on with this article?
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
sator_imaging
作業用のメモや、読んだ資料のまとめが置いてあります。 アメドラ好き。 最近は ・プリプロ ・演出シーンやアセットの制作 が半々ぐらいです。 ◆ 短編アニメシリーズ ◆ VR進撃の巨人 THE HUMAN RACE ◆ 出資向けVTuber ◆ 版権物スマホゲー

Comments

No comments
Sign up for free and join this conversation.
Sign Up
If you already have a Qiita account Login
39
Help us understand the problem. What is going on with this article?