2022年 8月、Metaがハンドトラッキングのデモアプリ「First Hand」を公開しました。
App Lab にてデモアプリが DL できます。
- First Hand
GitHub にてソースコードが公開されています。
- Unity-FirstHand
非常に素晴らしい技術デモです。このプロジェクトの中身が見られるなんて…感激です!
しかしデモで使用されている SDK のバージョンは古く、最新 SDK でこのまま真似て開発することはできません。
今回は 2022年 11月現在 最新の SDK で、ハンドトラッキング演奏アプリを開発してみました。開発中に得られた知見をここに備忘録として残したいと思います。
- ハンドトラッキング・トランペット
開発環境
Windows 11
Unity 2022.1.16f1
Oculus Integration Version 46.0
Oculus Quest 2
導入
基本的な VR 開発の Project 設定を済ませ、Oculus Integration をインポートします。
開発に必須なのは VR と Interaction のみです。他は必要に応じて追加して下さい。

Interaction SDK に関してはほとんど記事も出回っておらず、真っ更な状態から自力で組み立てるのは難しそうです。
まずは Demo Scene を確認し、設計を見てみましょう。
Assets > Oculus > Interaction > Samples > Scenes > Examples > HandGrabExamples シーンを開きます。

大まかな理解としては、OVRCameraRig の子に OVRInteraction をアタッチし、
OVRInteraction の子に OVRHands(ハンドトラッキング用の手) と OVRControllerHands(コントローラー操作用の手)をアタッチし、
それらのコンポーネントの参照を渡し合って接続しているようです。
この完成された OVRCameraRig をそのまま自分のシーンへ持って行けば、すぐに使えます。
今回はせっかくなので、設計を理解するために自力で組み立ててみたいと思います。
自力で組み立てない場合は スキップして【Grab されるオブジェクトの作成】へ進んでください。
Grab 時に指先が黄色くなる機能が邪魔になる場合は、HandGrabInteractor の GrabStrengthIndicator コンポーネントを削除して下さい。
OVRCameraRig を組み立てる
Project ビューで検索してプレハブアセットを探しながら、下記画像を参考にヒエラルキー上に配置していきます。1つずつ、順を追って見ていきましょう。

1 . Scene上に OVRCameraRig を配置し、MainCamera を削除します。
2 . OVRCameraRig > TrackingSpace > LeftHandAnchor の子に OVRHandPrefab を入れます。Right も同様にします。
3 . 後述の HandSynthetic 内の HandVisual を手の表示に使用する為、OVRHandPrefab で必要なのは OVRHand コンポーネントと OVRSkelton コンポーネントのみです。他を削除します。Right も同様にします。
4 . デフォルトではOVRHandPrefab は左手用に設定されています。右手のコンポーネントの設定を2箇所直し、右手用にします。

5 . OVRCameraRig の子に OVRInteraction を入れます。
6 . 先程の OVRInteraction の子に、OVRHands と OVRControllerHands を入れます。
7 . 先程の OVRHande の子に、LeftHandSynthetic と RightHandSynthetic を入れます。
ここまでで、ヒエラルキー上に並ぶオブジェクトが先に載せた画像のようになります。似た名前のプレハブアセットが複数あるのでご注意下さい。
インスペクター上で参照を渡して機能を繋ぎ合わせます。
8 . OVRInteraction > OVRCameraRigRef の OvrCameraRig に、OVRCameraRig をアタッチします。

ハンドトラッキング用の手を仕上げます。
9 . 次の画像のように、HandFeatures に参照を渡します。Right も同様にします。

10 . 次の画像のように、OVRHandDataSource > FromOVRHandDataSorce に参照を渡します。Right も同様にします。

11 . 今回は HandSynthetic 内の HandVisual を使用するため、LeftHandVisual と RightHandVisual は使いません。非アクティブにします。(次の画像参照)
12 . LeftHandSynthetic に、画像のように参照を渡します。Right も同様にします。

コントローラー操作用の手を仕上げます。
13 . OVRControllerHandDataSource に参照を渡します。Right も同様にします。

Grab(掴む) Poke(突く) 機能を手に実装します。
14 . HandInteractorsLeft の子に HandPokeInteractor と HandGrabInteractor を入れます。Right も同様にします。

15 . 先程の HandPokeInteractor と HandGrabInteractor に HandRef コンポーネントがあるので、画像のように手の参照を渡します。左右で合計4箇所です。

16 . HandPokeInteractor の孫にある HandPokeLimiter が非アクティブになっています。これをアクティブ化し、画像のように参照を渡します。Right も同様にします。
これは「Poke している間、指がボタンを貫通しないようにする」手の見た目を合成するための機能を担います。

17 . HandGrabInteractor も、16. と同じ工程で機能をアクティブ化・接続します。Right も同様にします。

以上で掴む・突くことのできる OVRCameraRig が完成しました!
工程が多くて少し大変ですが、自力で組み立てることで設計が見えてきますね。


使用されている「3種類の手」
組み立てた OVRCameraRig には、3種類も手のオブジェクトが登場します。
設計理解のため読み解いてみたところ、下記のような機能が実装されているようです。
OVRHandPrefab
おなじみの、ハンドトラッキングに使う基本のプレハブ。Interaction SDK を使わない場合はこの手のみ。
今回は見た目を表示するためのコンポーネントを削除。
OVRHands
OVRInteraction の中に配置した手。Interaction SDK の機能を使うためのプレハブ。OVRInteraction を参照し、OVRInteraction は OVRCameraRig を参照し、手のインタラクションを実装している。
今回は見た目を表示するためのコンポーネントを削除。
LeftHandSynthetic・RightHandSynthetic
「合成手」の訳の通り、Grab や Poke をした時に本来の手の形・位置ではない表示をするために使用している。
今回手の見た目を表示しているのはこのオブジェクト。
手の機能を実験して比較
上記を踏まえて、1つ実験をしてみます。
Q. OVRHands と LeftHandSynthetic・RightHandSynthetic 、両方の手の見た目を表示したらどうなるでしょうか?
違いがわかりやすいよう、左手だけ両方表示・右手は LeftHandSynthetic・RightHandSynthetic のみ表示してテストしてみました。結果は次の動画のようになります。
本来の手の位置に Visual が表示される OVRHands と、リアルに見せるために合成した Visual が表示される LeftHandSynthetic・RightHandSynthetic 。2種類の手の機能の違いが、よくわかる結果となりました。
Grab されるオブジェクトの作成【基礎】
HandGrabExamples シーン等で見本を見ることができるので、まずはそちらを確認し完成イメージを掴みます。
最終的に、下記画像のようにオブジェクトをヒエラルキー上に配置することになります。
1つずつ解説していきます。

GrabbableRoot
1 . Grab されるオブジェクト全体の Root オブジェクトとして、空のオブジェクトを作成します。わかりやすいよう、GrabbableRoot に名前を変更しました。Position や Rotation の調整は、このオブジェクトで行います。
2 . GrabbableRoot にアタッチするコンポーネントは下記です。必要に応じて組み合わせて下さい。
Grabbable (必須)
ものを持つための基本コンポーネント。Transfer On Second Selection にチェックを入れると、左右の手で持ち替えることが出来るようになります。他はそのままでOK。
RigidBody (必須)
お好みで設定して下さい。
PhysicsGrabbable
物理挙動のあるものを Grab するための基本コンポーネント。物理計算に変化が出るが、問題なければ基本的にアタッチしておく。実は無くても持てるが、左右の手で持ち替えるのは難しい。(時々出来る)
Grabbable・RigidBody の後にアタッチすると、自動で参照が渡される。
PointableUnityEventWrapper
ものを持ったとき(WhenSelect) や離した時(WhenUnselect) などにイベントを発火できる、UnityEvent コンポーネント。Pointable に自身の Grabbable コンポーネントをアタッチして使用する。

Visuals
3 . 先程の GrabbableRoot の子に、Grabされる見た目のオブジェクトを入れます。わかりやすいよう、Visuals に名前変更しました。
GrabbableRoot の中のどこかに Collider を1つ以上付ける必要があるので、このオブジェクトに Collider を付けておくのが基本的な実装方法です。
Scale の調整は、このオブジェクトで行います。GrabbableRoot や HandGrabInteractable の Scale を変えてしまわないように注意して下さい。
HandGrabInteractable
このオブジェクトは、Grab する際の手の形を表現する機能を担います。
基本的に、Interaction SDK に含まれる「Hand Grab Pose Record」を使用して作成します。
Oculus Link プレイ中にハンドトラッキングで ものを持つ手の形を演技し、それを記録し、読み込むという流れで作成できます。
4 . 上のメニューバーの Oculus > Interaction > Hand Grab Pose Record から、ウィンドウを開きます。

5 . 先程のウィンドウ内に、画像のように参照を渡します。ものを持つ手の形を演技することになるので、やりやすい方の手をアタッチして下さい。
「3」には、レコード済のコレクションが表示されます。初回は None になっています。

6 . 再生モードに入り、実際に掴んでいるかのような手の形を演じます。
手の形が出来たら再生モードのまま「2」の Record HandGrabPose をクリックします。
記録に成功すると下記画像のように白い手のゴーストが表示され、先程 None になっていた「3」にコレクションが表示されます。Assets 内に HandGrabInteractableDataCollection フォルダが生成され、そこにデータが書き出されています。

7 . 再生モードを終了し、「4」の Load From Collection をクリックします。
先程記録した手の形を表現するためのオブジェクトである HandGrabInteractable が、GrabbableRoot の子に作成されます。
8 . HandGrabInteractable オブジェクトを選択していると、白いゴーストの手が表示されます。指の関節部分にリングがあるので、それを Scene 上で操作することで指の角度を調整することができます。

9 . 指の自由を制限するかどうか、HandGrabInteractable の HandGrabPose で設定することができます。デフォルトでは小指が Free になっていますが、Constrained にするのが自然な表現になりオススメです。
Free
- 制限なく、自由に動かせる。
Constrained
- 指を閉じる方向へは、登録した位置までしか動かせなくなる。そのため、貫通することがなくなる。開く方向へは自由に動かせる。
Locked
- Grab している間、その指は動かせない。

10 . 左右どちらの手でも同じように持てるようにしたい場合、HandGrabInteractable コンポーネントの Create Mirrored HandGrabInteractable をクリックします。反対の手用に、左右反転された HandGrabInteractable が作成されます。

これで、設定した手の形で掴むことができるようになりました。
Grab されるオブジェクトの作成【応用】
【基礎】と同じ流れで、1つの GrabbableRoot に複数の掴み方を登録することが出来ます。
HandGrabInteractable を複数作成することで、次のような実装が出来ます。
こちらのハンドベルは、下記のような構成になっています。

Poke されるオブジェクトの作成【基礎】
PokeExamples シーン等で見本を見ることができるので、まずはそちらを確認し完成イメージを掴みます。
最終的に、下記画像のようにオブジェクトをヒエラルキー上に配置することになります。
1つずつ解説していきます。

PokeRoot
1 . Poke されるオブジェクト全体の Root オブジェクトとして、空のオブジェクトを作成します。わかりやすいよう、PokeRoot に名前を変更しました。
PokeBase
2 . ボタンの台座となるビジュアルのオブジェクトを、PokeRoot この子に配置します。今回は Cube にしました。
PokeInteractable
3 . PokeInteractable プレハブを PokeRoot の子に配置します。PokeInteractable コンポーネントが、押す動作「Poke」を実装するための基本コンポーネントです。
4 . PokeInteractable を選択していると、水色の●と+が表示されます。この●を+の位置まで押し込むという形で実装が進みます。PokeInteractable の Rotation の値を変更し、ボタンを押したい向きに直します。
Visuals > ButtonVisual
5 . ボタンの押される部分のビジュアルのオブジェクトを、この子に配置します。今回は Cylinder にしました。ButtonVisual 自身の MeshRenderer はオフにしておきます。
再び PokeInteractable
ここまで出来たら、改めて PokeInteractable に戻り設定していきます。
6 . PokeInteractable の MaxDistance で●と+間の距離を変更できるので、Transform と MaxDistance の値を変更して位置を合わせます。●はボタンの押される部分の表面位置にします。

これでボタンを押すことが出来るようになりました。
最後に、ボタンを押した際に UnityEvent を発火できるようにします。
7 . PokeInteractable に InteractableUnityEventWrapper コンポーネントを追加します。一番上の Interactable View には、自分自身の PokeInteractable コンポーネントの参照を渡します。
これでおなじみの、ボタンイベントが実装できるようになりました。
Poke されるオブジェクトの作成【応用】
せっかくなので、もう少し Poke に関するコンポーネントを読み解いてみましょう。
デフォルトでは人差し指でしか Poke できない使用になっているので、他の指でボタンを押せるようにしたり、更に応用して複数の指で同時に複数のボタンを押したり出来るように改造してみたいと思います。
(複数の指で同時に複数のボタンを押すのは開発者としての実験的な試みであり、現時点のトラッキング精度では推奨はできません。)
「人差し指で Poke する」ことが設計されているのは、どこでしょうか。
OVRCameraRig を組み立てた際に出会った HandPokeInteractor から辿ってみます。

HandPokeInteractor の中身を確認すると、HandPokeInteractor の PokeInteractor コンポーネントの、Point Transform で「Pokeする位置(Transform)」を参照していることがわかります。
更にその参照先である HandIndexFingertip を確認すると、Hand Joint id として「手のどの箇所でPokeするか」が設定されていることがわかります。
この値を変更することで、他の指でボタンを押すことが出来るようになるようです。
指以外にも、手のひらで大きなボタンを押すなど色々な工夫ができそうですね!

PokeInteractor の Point Transform は、1つしか参照を渡せない設計になっています。
複数の指で複数のボタンを押す場合は次のような手順になります。
1 . HandPokeInteractor に PokeInteractor コンポーネントを複数アタッチ
2 . その参照先である HandIndexFingertip を複製
3 . 「2.」で複製した HandIndexFingertip の HandJointId を変更
4 . 「1.」で作った PokeInteractor の Point Transform に参照を渡す
UI 操作
ここまでで既に Poke する Player システムが仕上がっているので、FlatUnityCanvas または CurvedUnityCanvas プレハブを使うことで簡単に実装できます。これらは、Scroll View を Poke で操作できるように組み立てられたプレハブです。
特に詰まるポイントが無かったので、ここでは割愛させて頂きます。
おわりに
長くなってしまいましたが、これで Interaction SDK を扱うための基礎知識は揃ったのではないかと思います。
ノンコーディングでここまで開発できるとは、大変ありがたいですね。
(とは言え、いつか自分で設計し作れるようになりたいです…!)
引き続きハンドトラッキングの開発知識を深めて行きたいと思います。
今回はここまで!