【Unity】オブジェクトを指定した向きでターゲット方向に向かせる方法

投稿日: 2026年3月14日

はじめに:関節の向きを制御する際の課題

この記事の対象読者:UnityでMediaPipeなどの姿勢推定結果を3Dキャラクターに反映させたい開発者、またはボーンの回転制御に苦労している方。

3Dモデルのポーズをプログラムから動的に制御する際、最も直感的な方法は Transform.LookAtQuaternion.LookRotation を使用して目標座標(ターゲット)へ関節を向けることです。

しかし、Unityの標準機能は基本的に「Z軸(ローカルの正面)」をターゲットに向ける仕様になっています。一方で、Blender等のモデリングソフトからインポートされたヒューマノイドモデルの多くは、「Y軸」が骨の長手方向(ボーンの伸びる方向)に設定されています。

そのため、単純に LookRotation を適用すると、脚や腕が90度ねじれたような状態になってしまいます。本記事では、この「Y軸をターゲットに向けつつ、関節の曲がる方向(Z軸)を外積を用いて厳密に制御するロジック」について、Pose Mirrorの実装例を交えて解説します。

解決する問題を整理すると、以下の2つです。

  1. Y軸が長手方向のボーンを、正しい方向へ向ける(軸の変換
  2. 膝や肘が「どちらに曲がるか(ロール軸)」を正確に決定する(外積による直交基底の生成

基本的なY軸のターゲット指向(軸の変換)

まず、Y軸をターゲットに向ける最も基本的なアプローチを確認します。Z軸をターゲットに向けた後、ローカルのX軸を基準に90度回転させることで、Y軸をターゲットに向けることができます。

transform.LookAt(target.transform.position);
transform.rotation *= Quaternion.AngleAxis(90, Vector3.right);

この手法は非常に強力で、単純なオブジェクト(円柱など)を特定の座標に向けるだけであればこれで十分です。しかし、人体モデルの「脚」や「腕」に適用する場合、これだけでは不十分です。なぜなら、「膝や肘がどちらに曲がるか(ロール軸の制御)」が定まらないからです。

LookRotation は「前方向」と「上方向」の2ベクトルで回転を決定します。前方向だけを指定した場合、ボーンのロール(長軸周りの回転)は不定となり、フレームごとにぶれが生じます。膝が前ではなく横に飛び出すなどの問題が発生します。

外積(Cross Product)を用いた空間ベクトルの直交化

膝の曲がる方向を正確に計算するために、外積(Cross Product)を利用します。外積は、2つのベクトル $\vec{A}$ と $\vec{B}$ の両方に直交する新しいベクトル $\vec{C}$ を生成する演算です。

$$\vec{C} = \vec{A} \times \vec{B}$$

脚の関節において、この外積を以下のように適用します。

  1. 大腿のベクトル($\vec{V}_{upper}$):股関節から膝へ向かうベクトル。
  2. 下腿のベクトル($\vec{V}_{lower}$):膝から足首へ向かうベクトル。

人間が膝を曲げるとき、大腿と下腿は必ず「ひとつの平面上」に存在します。この2つのベクトルの外積をとることで、「膝の側面の軸(横方向ベクトル)」を導き出すことができます。

$$\vec{V}_{side} = \vec{V}_{upper} \times \vec{V}_{lower}$$

側面の軸($\vec{V}_{side}$)が分かれば、もう一度外積を利用して、大腿ベクトルに対して垂直な「正面(前向き)ベクトル」を求めます。

$$\vec{V}_{up} = \vec{V}_{upper} \times \vec{V}_{side}$$

この二段階の外積計算は、線形代数におけるグラム・シュミット直交化に相当します。これにより LookRotation(forward, up) に渡すための、完全に直交した2ベクトルが安定して得られます。

外積の順序と左右の問題

外積には反交換律があります。すなわち、

$$\vec{A} \times \vec{B} = -(\vec{B} \times \vec{A})$$

つまり、計算順序を逆にすると向きが反転します。脚の場合、右脚と左脚では膝の「横方向」が体の内側か外側かが逆になります。

Vector3.Cross(upperDir, lowerDir) は、右手系の法則に従い「大腿→下腿の向きで右手の指を曲げたときに親指が向く方向」にベクトルを生成します。自然な立ち姿勢では、右脚・左脚のどちらも正しい向き(膝の側面方向)が自動的に得られます。

⚠️ ただし、膝が完全に伸び切っているとき($\vec{V}_{upper}$ と $\vec{V}_{lower}$ が平行なとき)は外積が零ベクトルになり正規化できません。このケースはフォールバックとして別途ハンドリングが必要です(後述のコード参照)。

実装例:脚のポーズ適用ロジック

上記の理論を実際のC#コードに落とし込んだものが以下の関数です。MediaPipe等から取得した関節の3D座標(pUpLeg, pLowLeg 等)を元に、Unity上のボーンを回転させます。

/// <summary>
/// 脚全体のポーズを適用します。
/// </summary>
/// <param name="upperBone">大腿のトランスフォーム</param>
/// <param name="lowerBone">下腿のトランスフォーム</param>
/// <param name="ankleBone">足首のトランスフォーム</param>
/// <param name="toeBone">つま先のトランスフォーム</param>
/// <param name="pUpLeg">股関節の目標座標</param>
/// <param name="pLowLeg">膝の目標座標</param>
/// <param name="pAnkle">足首の目標座標</param>
/// <param name="pHeel">かかとの目標座標</param>
/// <param name="pToe">つま先の目標座標</param>
/// <param name="isRight">右脚かどうかのフラグ</param>
private void ApplyLeg(
    Transform upperBone, Transform lowerBone, Transform ankleBone, Transform toeBone,
    Vector3 pUpLeg, Vector3 pLowLeg, Vector3 pAnkle, Vector3 pHeel, Vector3 pToe,
    bool isRight
)
{
    // 1. ガード節(どれか一つでも欠けていたら実行しない)
    if (upperBone == null || lowerBone == null || ankleBone == null) return;

    // 2. 脚の基本方向ベクトルを計算
    Vector3 upperDir    = (pLowLeg - pUpLeg).normalized;
    Vector3 lowerDir    = (pAnkle  - pLowLeg).normalized;
    Vector3 footForward = (pToe    - pHeel).normalized;
    Vector3 footUp      = (pAnkle  - pHeel).normalized;

    // 3. 外積を利用した直交ベクトルの生成
    // 大腿ベクトル × 下腿ベクトル → 膝の側面(横軸)ベクトルを算出
    Vector3 kneeSideDir = Vector3.Cross(upperDir, lowerDir);

    // 膝が完全に伸び切っている場合(ベクトルが平行 → 外積がほぼ零)はフォールバック
    if (kneeSideDir.sqrMagnitude < 0.0001f)
        kneeSideDir = isRight ? characterRoot.right : -characterRoot.right;
    else
        kneeSideDir.Normalize();

    // 二段階目の外積:側面軸と骨の進行方向から「正面ベクトル」を算出(グラム・シュミット)
    Vector3 lowerUp = Vector3.Cross(lowerDir, kneeSideDir);
    Vector3 upperUp = Vector3.Cross(upperDir, kneeSideDir);

    // 4. 回転の適用(モデル固有の軸補正はRotate系の各関数内で実施)
    if (isRight)
    {
        RotateRightUpperLeg(upperBone, upperDir, upperUp);
        RotateRightLowerLeg(lowerBone, lowerDir, lowerUp);
        RotateRightToe(ankleBone, footForward, footUp);
    }
    else
    {
        RotateLeftUpperLeg(upperBone, upperDir, upperUp);
        RotateLeftLowerLeg(lowerBone, lowerDir, lowerUp);
        RotateLeftToe(ankleBone, footForward, footUp);
    }
}

/// <summary>
/// 右大腿の回転を適用し、軸の補正を行います。
/// </summary>
private void RotateRightUpperLeg(Transform bone, Vector3 boneDir, Vector3 boneUp)
{
    // Z軸がboneDir(膝方向)を向き、Y軸がboneUp(正面)を向くようにLookRotationで設定
    bone.rotation = Quaternion.LookRotation(boneDir, boneUp);

    // モデル固有の軸補正:Z軸 → Y軸への変換(X軸周り90度)などを適用
    bone.rotation = rig.FixRightUpperLeg(bone.rotation);
}

/// <summary>
/// 軸補正ロジックの例(Y軸をボーンの長手方向に合わせる)
/// モデルのボーン初期設定に合わせて AngleAxis の引数を調整する。
/// </summary>
public override Quaternion FixRightUpperLeg(Quaternion rot)
    => rot * Quaternion.AngleAxis(90, Vector3.right);

実装のポイントと独自の工夫

この実装で最も重要なのは、「方向計算」と「モデル固有の軸補正」を明確に分離している点です。

ApplyLeg 関数内では、一切の「モデル依存の補正」を行っていません。ここでは純粋に Vector3.Cross を用いて、現実の物理骨格に基づく絶対座標系(骨の伸びる方向・骨の正面方向)を計算します。

補正は最後に FixRightUpperLeg などの関数内で行います。Quaternion.AngleAxis(90, Vector3.right) を後から掛けることで「Z軸で計算した結果をY軸方向に変換」します。この設計により、VRoidモデルやMixamoモデルなどボーンの初期軸設定が異なる様々な3Dモデルに対しても、Fix 関数だけ差し替えれば対応できる高い拡張性を持たせています。

また、縮退ケース(膝の完全伸展)のフォールバックに characterRoot.right を使用することで、自然なT字ポーズへのフォールバックを実現しています。

まとめ

この記事のポイント
  • Unityの LookRotation はZ軸基準のため、Y軸が長手方向のボーンには AngleAxis(90, right) による後補正が必要
  • 膝・肘のロール軸は「大腿ベクトル × 下腿ベクトル → 側面軸 → さらに外積で正面軸」という二段階の外積(グラム・シュミット直交化)で安定して求められる
  • 外積が零になる縮退ケース(関節の完全伸展)には明示的なフォールバックが必要
  • 「方向計算(モデル非依存)」と「軸補正(モデル依存)」を分離することで、複数モデルへの対応が容易になる

本記事の実装は、姿勢推定ライブラリ(MediaPipe等)から得た3D関節座標を Unity上の3Dキャラクターに適用する Pose Mirror で実際に使用しています。ぜひ実際の動作を確認してみてください。