MagicLeapのハンドトラッキング実装の備忘録
基本的にはここの通りに進めていけば問題ない
前提となるもの
- MagicLeapのmpkをビルドできる段階までのセッティングが完了している
- The LabでのZeroIterationが可能になっている
開発環境
Windows10
Unity 2019.3.7f1
MagicLeapUnityPackage 0.24.1
出来上がり
こんな感じに各関節
手首、親指 ~ 小指までの座標の取得
ジェスチャの取得( サンプルの中では利用していない )
MagicLeapのハンドトラッキング実装してみた pic.twitter.com/ZSYl1BVXay
— 松本隆介 (@matsumotokaka11) July 30, 2020
実装方法
下準備
シーンに配置するGameObject
大体のVRデバイスとかで主流な構成の配置にしてます
CameraRig
ただのルートオブジェクトとして利用しています
Head
MagicLeapパッケージ内 Core > Assets > Prefabs > MainCamera
のプレハブをCameraRigの子オブジェクトにしてHeadと名称を変えたものです、この中にDirectionalLightを子オブジェクトとして配置していますが配置するか否かはお好みで
Controller
MagicLeapパッケージ内 Examples > Assets > Prefabs > Controller
のプレハブをCameraRigの子オブジェクトとして配置しています
こちらの記事で紹介したコントローラの入力でホームボタンでアプリを閉じるために利用してます
LHand, RHand
今回のメイン
CameraRigの子オブジェクトとしてGameObjectを作成( Emptyとして )
Thumb( 親指 ), Index( 人差し指 ), Middle( 中指 ), Ring( 薬指 ), Pinky( 小指 )をそれぞれEmptyのGameObjectで作成し、
各指の子オブジェクトThumb, Index, Middleは3個、Ring, Pinkyは2個、SphereObjectを生成、スケールは0.01くらいに設定
スクリプト
このスクリプトはMagicLeap公式サンプルのものに手を加えたものです
以下のスクリプトをLHand, RHandにアタッチ
using UnityEngine;
using UnityEngine.XR.MagicLeap;
/// <summary>
/// ハンドトラッキング.
/// </summary>
public class HandController : MonoBehaviour
{
[System.Serializable]
public class HandJointData
{
[SerializeField] GameObject wrist;
[SerializeField] GameObject[] thumb;
[SerializeField] GameObject[] index;
[SerializeField] GameObject[] middle;
[SerializeField] GameObject[] ring;
[SerializeField] GameObject[] pinky;
[SerializeField] Material handMaterial;
[SerializeField] Color color;
public Vector3 Wrist { get; private set; }
public Vector3[] Thumb { get; private set; }
public Vector3[] Index { get; private set; }
public Vector3[] Middle { get; private set; }
public Vector3[] Ring { get; private set; }
public Vector3[] Pinky { get; private set; }
MLHandTracking.Hand hand;
LineRenderer[] lines;
public void Initialize(
MLHandTracking.Hand _hand)
{
hand = _hand;
Wrist = Vector3.zero;
// 各関節 + 手首の分, 配列をとる.
Thumb = new Vector3[thumb.Length + 1];
Index = new Vector3[index.Length + 1];
Middle = new Vector3[middle.Length + 1];
Ring = new Vector3[ring.Length + 1];
Pinky = new Vector3[pinky.Length + 1];
lines = new LineRenderer[5];
lines[0] = thumb[0].transform.parent.gameObject.AddComponent<LineRenderer>();
lines[0].positionCount = 4;
lines[1] = index[0].transform.parent.gameObject.AddComponent<LineRenderer>();
lines[1].positionCount = 4;
lines[2] = middle[0].transform.parent.gameObject.AddComponent<LineRenderer>();
lines[2].positionCount = 4;
lines[3] = ring[0].transform.parent.gameObject.AddComponent<LineRenderer>();
lines[3].positionCount = 3;
lines[4] = pinky[0].transform.parent.gameObject.AddComponent<LineRenderer>();
lines[4].positionCount = 3;
// LineRendererの初期セッティング.
foreach (var line in lines)
{
line.material = handMaterial;
line.startColor = color;
line.endColor = color;
line.startWidth = 0.01f;
line.endWidth = 0.01f;
}
}
public void UpdatePositions()
{
if (hand == null) return;
Thumb[0] = hand.Wrist.KeyPoints[0].Position;
Thumb[1] = hand.Thumb.KeyPoints[0].Position;
Thumb[2] = hand.Thumb.KeyPoints[1].Position;
Thumb[3] = hand.Thumb.KeyPoints[2].Position;
for (var i = 1; i < Thumb.Length; ++i)
{
thumb[i - 1].transform.position = Thumb[i];
}
lines[0].SetPositions(Thumb);
Index[0] = hand.Wrist.KeyPoints[0].Position;
Index[1] = hand.Index.KeyPoints[0].Position;
Index[2] = hand.Index.KeyPoints[1].Position;
Index[3] = hand.Index.KeyPoints[2].Position;
for (var i = 1; i < Index.Length; ++i)
{
index[i - 1].transform.position = Index[i];
}
lines[1].SetPositions(Index);
Middle[0] = hand.Wrist.KeyPoints[0].Position;
Middle[1] = hand.Middle.KeyPoints[0].Position;
Middle[2] = hand.Middle.KeyPoints[1].Position;
Middle[3] = hand.Middle.KeyPoints[2].Position;
for (var i = 1; i < Middle.Length; ++i)
{
middle[i - 1].transform.position = Middle[i];
}
lines[2].SetPositions(Middle);
Ring[0] = hand.Wrist.KeyPoints[0].Position;
Ring[1] = hand.Ring.KeyPoints[0].Position;
Ring[2] = hand.Ring.KeyPoints[1].Position;
for (var i = 1; i < Ring.Length; ++i)
{
ring[i - 1].transform.position = Ring[i];
}
lines[3].SetPositions(Ring);
Pinky[0] = hand.Wrist.KeyPoints[0].Position;
Pinky[1] = hand.Pinky.KeyPoints[0].Position;
Pinky[2] = hand.Pinky.KeyPoints[1].Position;
for (var i = 1; i < Pinky.Length; ++i)
{
pinky[i - 1].transform.position = Pinky[i];
}
lines[4].SetPositions(Pinky);
}
}
// ジェスチャ.
public enum HandPoses
{
Ok,
Finger,
Thumb,
OpenHand,
Fist,
NoPose,
NoHand,
}
public enum HandId
{
RightHand,
LeftHand
}
[SerializeField] HandPoses handPose = HandPoses.NoPose;
[SerializeField] HandJointData handData;
[SerializeField] HandId handId;
MLHandTracking.HandKeyPose[] gestures;
MLHandTracking.Hand hand;
void Start()
{
// HandTrackingを開始する.
MLHandTracking.Start();
hand = handId == HandId.LeftHand ? MLHandTracking.Left : MLHandTracking.Right;
handData.Initialize(hand);
gestures = new MLHandTracking.HandKeyPose[6];
// 各ジェスチャを登録.
gestures[0] = MLHandTracking.HandKeyPose.Ok;
gestures[1] = MLHandTracking.HandKeyPose.Finger;
gestures[2] = MLHandTracking.HandKeyPose.OpenHand;
gestures[3] = MLHandTracking.HandKeyPose.Fist;
gestures[4] = MLHandTracking.HandKeyPose.Thumb;
gestures[5] = MLHandTracking.HandKeyPose.NoHand;
MLHandTracking.KeyPoseManager.EnableKeyPoses(gestures, true, false);
}
void OnDestroy()
{
MLHandTracking.Stop();
}
void Update()
{
handData.UpdatePositions();
if (GetGesture(hand, MLHandTracking.HandKeyPose.Ok))
{
handPose = HandPoses.Ok;
}
else if (GetGesture(hand, MLHandTracking.HandKeyPose.Finger))
{
handPose = HandPoses.Finger;
}
else if (GetGesture(hand, MLHandTracking.HandKeyPose.OpenHand))
{
handPose = HandPoses.OpenHand;
}
else if (GetGesture(hand, MLHandTracking.HandKeyPose.Fist))
{
handPose = HandPoses.Fist;
}
else if (GetGesture(hand, MLHandTracking.HandKeyPose.Thumb))
{
handPose = HandPoses.Thumb;
}
else
{
handPose = HandPoses.NoPose;
}
}
/// <summary>
/// ジェスチャの取得.
/// </summary>
/// <param name="hand"></param>
/// <param name="type"></param>
/// <returns></returns>
private bool GetGesture(
MLHandTracking.Hand hand,
MLHandTracking.HandKeyPose type)
{
if (hand == null) return false;
return 0.9f < hand.HandKeyPoseConfidence && hand.KeyPose == type;
}
}
アタッチしたら以下の画像のように各関節のオブジェクトをセット、配列の添え字が若い方が根元に来るように設定
各パラメータの説明
HandPose : ジェスチャのポーズ名( 今回はInspectorに表示しているだけです )
HandData : 各関節のオブジェクトを保持するクラス、HandCenterは利用していません( うまくトラッキングできなかったので外しました )
HandMaterial : 各関節オブジェクトの球をつなぐ線の描画用マテリアルです、今回はMagicLeapパッケージ内のUIBeamを利用しました
Color : 各関節オブジェクトの球をつなぐ線の色です、今回は左は赤、右は緑で設定しています
HandId : 手の左右を決定する識別子
Unity の設定
この状態でTheLabでMagicLeapと接続してPlayModeに入るとハンドトラッキングされたオブジェクトの様子が確認できると思います
ただしmpkファイルとして出力する際は以下の設定を行わないと実機ではエラーが出てハンドトラッキング及びジェスチャの取得はできません
公式のチュートリアル通りにやれば設定の仕方まで説明されてたけど必要なメソッドとか確認したらすぐ実行したくなっちゃうのよね
Edit > ProjectSettigs > MagicLeap > ManifestSettings の項目を開き
GestureConfig, GestureSubscribeにチェックを入れる ( 公式チュートリアルだと明示的にLowLatencyLightwearにもチェックを入れるように説明されているが現バージョンでは自動で入ってる? )
これでmpkを出力して実機でテストするとハンドトラッキングが実装できているはずです
あとがき
これはTheLabからDLしてきたMagicLeapUnityPackageでのサンプルです、MagicLeapToolKitでのハンドトラッキングはまだ触ったことがないので後日記事にできればと思います