なつねこメモ

主にプログラミング関連のメモ帳 ♪(✿╹ヮ╹)ノ 書いてあるコードは自己責任でご自由にどうぞ。記事本文の無断転載は禁止です。

SteamVR + Unity で、アバターのサイズを人間の大きさに合わせたい

前回までの記事で、せっかく自分の動きでアバターを動かせるようにしたけど、
大きさがあっていないので、若干の違和感が発生してしまっていました。

ということで、この記事では自己流でサイズを合わせてみました。

いつもの通り、前提環境は以下の通りです。

  • Unity Personal 2019.2.16f1
  • Windows 10
  • Index Controller
  • SteamVR が設定済みのシーン

使用するアセットは以下の通りです。

使用した 3D モデルは、以下の 2 種類です。

アバターのサイズを人間に合わせるには、いくつかの手段があります。
私が知っている限りは、アバターのサイズを変えるのが一般的でした。

しかし、私は世界観的に VR 空間内のオブジェクトの大きさ比は維持されて欲しいので、
VR 空間そのものの大きさを変えることにしてみました。

まず初めに、 World という空の GameObject を設置します。
次に、 SteamVRObjects という空の GameObject を設置します。

f:id:MikazukiFuyuno:20191221024936p:plain

画像のように配置する

設置したら、その中にそれぞれ対応する GameObject を入れていきます。
World には世界に関連するもの (鏡やアバターなどなど) を、
SteamVRObjects には SteamVR 関連のものを入れていきます。

今回の場合、最終的には下のような構成になりました。

f:id:MikazukiFuyuno:20191221025041p:plain

これで前準備は完了です。
次は、大きさをどのように変えるかを C# Script で組みます。

いくつかの VR ゲームをプレイすると分かるのですが、
ユーザーに身長を選択もしくは入力させることがあります。
これをヒントに、以下の流れで拡大率を決定しました。

  1. ユーザーの身長を元に、腕の長さを計算する
  2. VRIK に設定されているアバターの腕を元に、アバターの腕の長さを計算する
  3. ユーザーの腕の長さをアバターの腕の長さで割る
  4. その値で World.transform.localScale を設定する

個体差はあるものの、人間の形は大体にたようなものなので、
身長さえ分かれば、あとは大体計算で求めることが可能です。

今回、両腕の長さ (実際には手首まで) を 身長 * .78 として算出しました。
これは、標準的な人間の身長に対して、以下の法則がある (らしい) からです。

  • 腕を横に伸ばした場合、その長さはほぼ身長と等しい
  • 身長の長さに対して、手のひらの長さは大体 11% 程度
  • よって、 100 - (11 * 2) = 78%

今回の場合、私の身長は 157cm なので、両腕の長さはだいたい 122cm で計算します。
次に、1 つめのアバターのシャペルちゃんは、両腕の長さが 90cm 程度だったので、
世界の大きさをだいたい 1.38 倍にしてあげると、ちょうど良い感じになります。

それを行っているのが、以下のスクリプトです。

using RootMotion.FinalIK;

using UnityEngine;

#pragma warning disable 649

namespace SteamVR_Sandbox.Scripts
{
    public class AvatarCalibrator : MonoBehaviour
    {
        private const float PlayerHandDistanceByHeight = .78f;

        // Start is called before the first frame update
        private void Start()
        {
            if (World == null)
                return;

            var avatarHandDistance = Vector3.Distance(IK.references.leftHand.position, IK.references.rightHand.position);
            var playerHandDistance = PlayerHeight * PlayerHandDistanceByHeight;
            var worldScale = playerHandDistance / avatarHandDistance;

            World.transform.localScale = Vector3.one * worldScale;
        }

        [SerializeField]
        private GameObject World;

        [SerializeField]
        private VRIK IK;

        [SerializeField]
        [Tooltip("Player Real Height (m)")]
        private float PlayerHeight;
    }
}

これを適当な GameObject にくっつけて、 World に先ほど作成した World を、
IK にはアバターにくっつけている VRIK を、 PlayerHeight には身長を入れます。

f:id:MikazukiFuyuno:20191221025138p:plain

私の場合の設定例

最後に、デバッグモードで起動してあげれば、身長、コントローラーの位置ともに、
ちょうど良い感じになります (若干まだ調整はいりそうですが)。

ということで、ではでは~(╹⌓╹ )