Oculus Questのハンドトラッキングでボーンを視覚的に表示する
Oculus Questのハンドトラッキングに割り当てられているボーンを視覚的に表示してみましょう。デバッグやテストをする際に少しわかりやすくなるかも...?
割り当てられているボーンIDを確認する
OVRSkeletonにボーン関連の情報が集約されており、以下のプロパティからIList型でボーンの情報にアクセスできるようです。
skeleteon = GetComponent<OVRSkeleton>(); skeleteon.Bones ← IList型で取得できる
型はOVRBoneで自身のID、親のインデックス、Transform型を持つ以下のようなクラスとして宣言されています。このクラスを使用すればIDや親子関係も取得できそうです。
public class OVRBone { public OVRSkeleton.BoneId Id { get; private set; } public short ParentBoneIndex { get; private set; } public Transform Transform { get; private set; } public OVRBone(OVRSkeleton.BoneId id, short parentBoneIndex, Transform trans) { Id = id; ParentBoneIndex = parentBoneIndex; Transform = trans; } }
で、ボーンIDは以下のように宣言されており、
public enum BoneId { Invalid = -1, Hand_Start = 0, Hand_WristRoot = Hand_Start + 0, // root frame of the hand, where the wrist is located Hand_ForearmStub = Hand_Start + 1, // frame for user's forearm Hand_Thumb0 = Hand_Start + 2, // thumb trapezium bone Hand_Thumb1 = Hand_Start + 3, // thumb metacarpal bone Hand_Thumb2 = Hand_Start + 4, // thumb proximal phalange bone Hand_Thumb3 = Hand_Start + 5, // thumb distal phalange bone Hand_Index1 = Hand_Start + 6, // index proximal phalange bone Hand_Index2 = Hand_Start + 7, // index intermediate phalange bone Hand_Index3 = Hand_Start + 8, // index distal phalange bone Hand_Middle1 = Hand_Start + 9, // middle proximal phalange bone Hand_Middle2 = Hand_Start + 10, // middle intermediate phalange bone Hand_Middle3 = Hand_Start + 11, // middle distal phalange bone Hand_Ring1 = Hand_Start + 12, // ring proximal phalange bone Hand_Ring2 = Hand_Start + 13, // ring intermediate phalange bone Hand_Ring3 = Hand_Start + 14, // ring distal phalange bone Hand_Pinky0 = Hand_Start + 15, // pinky metacarpal bone Hand_Pinky1 = Hand_Start + 16, // pinky proximal phalange bone Hand_Pinky2 = Hand_Start + 17, // pinky intermediate phalange bone Hand_Pinky3 = Hand_Start + 18, // pinky distal phalange bone Hand_MaxSkinnable = Hand_Start + 19, // Bone tips are position only. They are not used for skinning but are useful for hit-testing. // NOTE: Hand_ThumbTip == Hand_MaxSkinnable since the extended tips need to be contiguous Hand_ThumbTip = Hand_Start + Hand_MaxSkinnable + 0, // tip of the thumb Hand_IndexTip = Hand_Start + Hand_MaxSkinnable + 1, // tip of the index finger Hand_MiddleTip = Hand_Start + Hand_MaxSkinnable + 2, // tip of the middle finger Hand_RingTip = Hand_Start + Hand_MaxSkinnable + 3, // tip of the ring finger Hand_PinkyTip = Hand_Start + Hand_MaxSkinnable + 4, // tip of the pinky Hand_End = Hand_Start + Hand_MaxSkinnable + 5, // add new bones here Max = Hand_End + 0, }
個々のボーンのIDは以下の図のように割り当てられています。
ボーンの表示 & 指ごとに色分けしてみる
ボーンを視覚的に表示し指単位で色分けするスクリプトを作ってみました。以下のスクリプトをOVRHandPrefabにアタッチすれば、確認できます。実行時はOVR Mesh Rendererを無効にしておきましょう。
using System.Collections; using System.Collections.Generic; using UnityEngine; using System.Linq; public class BoneInfoSample : MonoBehaviour { OVRHand _hand; OVRSkeleton _skeleton; List<GameObject> _spheres = new List<GameObject>(); void Start() { _hand = GetComponent<OVRHand>(); _skeleton = GetComponent<OVRSkeleton>(); var boneColor = new Dictionary<string, Color>() { { "Start",Color.black}, // スタート位置 { "Thumb",Color.red}, // 親指 { "Index",Color.green}, // 人差し指 { "Middle",Color.blue}, // 中指 { "Ring", Color.cyan}, // 薬指 { "Pinky",Color.magenta}, // 小指 { "Forearm",Color.yellow}, // 前腕部 }; foreach (var bone in _skeleton.Bones) { // Sphereを生成しボーンに割り当てる var sphere = GameObject.CreatePrimitive(PrimitiveType.Sphere); sphere.transform.position = bone.Transform.position; sphere.transform.localScale = new Vector3(0.01f, 0.01f, 0.01f); sphere.transform.parent = bone.Transform; // 指単位で色を変える var color = boneColor.FirstOrDefault(x => bone.Id.ToString().Contains(x.Key)); sphere.GetComponent<Renderer>().material.color = color.Value; _spheres.Add(sphere); } } void Update() { // トラックが外れたらSphereを消す foreach (var sphere in _spheres) { sphere.SetActive(_hand.IsTracked); } } }
できました!
OVRSkeletonには「Capsules」が定義されていてここに衝突判定用のCapsule Colliderが入っている予感。次回はこの辺りを見ていこうと思います。
Oculus Questのハンドトラッキングでポインティング方向を取得する
ハンドトラッキング時、手がどの方向を向いているかを取得する方法についての紹介。
独自に座標や方向を設定しても良さそうだけど、公式が用意している機能を使って実装。公式なので向きや座標の共通化が図れるかなーと思うので自作アプリではなるべくこの方法を使っていこうと思います。
Pointer Poseの取得
以下の実装で手が向いている方向諸々をTransform型で取得することができます。
var hand = GetComponent<OVRHand>(); hand.PointerPose ←これでOK
Transformなのでpositionや、forwardが使えるので簡単なポインティング機能はこれで実装できそうです。また、PointPoseが取得できているかどうかは以下のbool型で取得できます。
hand.IsPointerPoseValid
これがfalseの場合、レイやポインティングを描画しないとすれば良さそうです。
簡単なサンプル
まずはレイを画面に表示するプログラムを作りましょう。初期化処理の手順に従ってOVRCameraRig階下にOVRHandPrefabを追加します。
以下のスクリプトを作成しOVRHandPrefabにアタッチします。
using System.Collections; using System.Collections.Generic; using UnityEngine; public class PointerPoseSample : MonoBehaviour { [SerializeField] float _rayDistance = 100; OVRHand _hand; LineRenderer _lineRenderer; // Start is called before the first frame update void Start() { _hand = GetComponent<OVRHand>(); _lineRenderer = GetComponent<LineRenderer>(); } // Update is called once per frame void Update() { // レイをLineRendererで描画 var positions = new Vector3[]{ _hand.PointerPose.position, _hand.PointerPose.position + _hand.PointerPose.forward * _rayDistance }; _lineRenderer.SetPositions(positions); // PointerPoseが有効な時のみLineRendererを表示 _lineRenderer.enabled = _hand.IsPointerPoseValid; } }
LineRendererもOVRHandPrefabにそれぞれ追加します。ラインのサイズは適当の小さめに調整
実行すると以下の動画のようになります。
ホームで表示されている手と同じ位置 & 方向にレイが伸びているのが確認できました。
ピンチを検出してオブジェクトを手元に引っ張ってくる
レイにオブジェクトが衝突している状態でピンチを検出したらオブジェクトを手元に引っ張ってくるサンプルを作ってみます。
ピンチ検出に関してはこちら参照。
先ほどのスクリプトにRaycastを追加します。
using System.Collections; using System.Collections.Generic; using UnityEngine; public class PointerPoseSample : MonoBehaviour { [SerializeField] float _rayDistance = 100; [SerializeField] float _speed = 10; OVRHand _hand; LineRenderer _lineRenderer; //[SerializeField]GameObject _cube; // Start is called before the first frame update void Start() { _hand = GetComponent<OVRHand>(); _lineRenderer = GetComponent<LineRenderer>(); } // Update is called once per frame void Update() { // レイをLineRendererで描画 var positions = new Vector3[]{ _hand.PointerPose.position, _hand.PointerPose.position + _hand.PointerPose.forward * _rayDistance }; _lineRenderer.SetPositions(positions); // PointerPoseが有効な時のみLineRendererを表示 _lineRenderer.enabled = _hand.IsPointerPoseValid; // ピンチされているか判定 if (_hand.GetFingerPinchStrength(OVRHand.HandFinger.Index) >= 1f) { // レイの衝突判定 var ray = new Ray(_hand.PointerPose.position, _hand.PointerPose.forward * _rayDistance); if (Physics.Raycast(ray, out var hitinfo, _rayDistance)){ var hitObject = hitinfo.collider.gameObject; var distance = _hand.PointerPose.position - hitObject.transform.position; var velocity = distance.normalized; hitObject.GetComponent<Rigidbody>().AddForce(velocity * _speed); } } } }
あとはRigidBodyを持ったオブジェクトを適当にフィールドに置けば完成です。
実際に動作させた動画がこちらになります。
もうちょっと全体に手を加えればテレキネシス的なものが作れそうです
Oculus Questのハンドトラッキングでピンチ判定を行う
この形を自作アプリ内で取得する方法。
ピンチ判定の方法
OVRHandに以下のメソッドが定義されているのでそれを使用する
メソッド名 | 戻り値 |
---|---|
GetFingerIsPinching | 指がピンチ状態かをTrue / Falseで取得 |
GetFingerPinchStrength | ピンチ強度を0~1fで取得 |
GetFingerConfidence | 指のポーズの信頼度をLow / Highで取得 |
引数にはそれぞれHandFinger型を指定する。
値 | 指 |
---|---|
HandFinger.Thumb | 親指 |
HandFinger.Index | 人差し指 |
HandFinger.Middle | 中指 |
HandFinger.Ring | 薬指 |
HandFinger.Pinky | 小指 |
因みに公式のサンプルではこんな感じで使用例がかかれていた
var hand = GetComponent<OVRHand>(); bool isIndexFingerPinching = hand.GetFingerIsPinching(HandFinger.Index); float ringFingerPinchStrength = hand.GetFingerPinchStrength(HandFinger.Ring);
画面上でピンチ判定を視覚化したいので以下のようなスクリプトを作成し親指~小指までの値を表示してみる。
using System.Collections; using System.Collections.Generic; using UnityEngine; using UnityEngine.UI; public class HandInfo : MonoBehaviour { [SerializeField]OVRHand _hand; Text _info; // Start is called before the first frame update void Start() { _info = GetComponent<Text>(); } // Update is called once per frame void Update() { _info.text = ""; _info.text += $"IsTracked : {_hand.IsTracked}\n"; _info.text += $"HandConfidence : {_hand.HandConfidence}\n\n"; _info.text += $"ThumbFingerPinch : {_hand.GetFingerIsPinching(OVRHand.HandFinger.Thumb)}\n"; _info.text += $"ThumbFingerPinchStrength : {_hand.GetFingerPinchStrength(OVRHand.HandFinger.Thumb)}\n"; _info.text += $"IndexFingerPinch : {_hand.GetFingerIsPinching(OVRHand.HandFinger.Index)}\n"; _info.text += $"IndexFingerPinchStrength : {_hand.GetFingerPinchStrength(OVRHand.HandFinger.Index)}\n"; _info.text += $"MiddleFingerPinch : {_hand.GetFingerIsPinching(OVRHand.HandFinger.Middle)}\n"; _info.text += $"MiddleFingerPinchStrength : {_hand.GetFingerPinchStrength(OVRHand.HandFinger.Middle)}\n"; _info.text += $"RingFingerPinch : {_hand.GetFingerIsPinching(OVRHand.HandFinger.Ring)}\n"; _info.text += $"RingFingerPinchStrength : {_hand.GetFingerPinchStrength(OVRHand.HandFinger.Ring)}\n"; _info.text += $"PinkyFingerPinch : {_hand.GetFingerIsPinching(OVRHand.HandFinger.Pinky)}\n"; _info.text += $"PinkyFingerPinchStrength : {_hand.GetFingerPinchStrength(OVRHand.HandFinger.Pinky)}\n"; } }
使ってみた個人的な所感だと「親指 + 人差し指」は無理なく判定できるけど、他の指だと結構意識してやらないと微妙な感じ...。ちょっとお遊びでGetFingerPinchStrengthから取得できる値をCubeの拡大率に反映してみた。動作しているコードは以下。
using System.Collections; using System.Collections.Generic; using UnityEngine; using UnityEngine.UI; public class HandInfo : MonoBehaviour { [SerializeField]OVRHand _hand; [SerializeField] GameObject _cube; Text _info; Vector3 _orignscale; // Start is called before the first frame update void Start() { _info = GetComponent<Text>(); _orignscale = _cube.transform.localScale; } // Update is called once per frame void Update() { _info.text = ""; _info.text += $"IsTracked : {_hand.IsTracked}\n"; _info.text += $"HandConfidence : {_hand.HandConfidence}\n\n"; _info.text += $"IndexFingerPinch : {_hand.GetFingerIsPinching(OVRHand.HandFinger.Index)}\n"; _info.text += $"IndexFingerPinchStrength : {_hand.GetFingerPinchStrength(OVRHand.HandFinger.Index)}\n"; var scale = Mathf.Abs(_hand.GetFingerPinchStrength(OVRHand.HandFinger.Index) - 1); _cube.transform.localScale = _orignscale * scale; } }
なるほど。なんか面白い事できそうな気がするかも??
Iwate Developers Meetupに参加した話
Iwate Developers Meetupとは?
おかもんさん、たくちゃんさん、chii.mさんが中心となって主催している岩手県内のIT関連の勉強会です。
グループ発足のきっかけは
- エンジニア人口が少ないと言われている地方で、技術の質を磨き合える仲間を見つける
- 地元を応援し、ITで岩手県を盛り上げる
というもので僕自身も同じ思いを持っていたので活動内容非常に共感し今回LT枠で参加を申し込みました。
当日のLTについて
LTの内容はIT関連なら特に定めないということだったのでWeb、セキュリティ、デバッガ、サイト運営と多岐に渡り普通では得られない知見も数多く得られました。当日のLTを簡単に列挙していきます(ガバガバですいません)
iwddのサイトをなんとかしたい件(@suzuryo)
- Webを軸に、関連数話題を扱う岩手県のIT勉強会
- 合計158回開催で13年続いている(!!)
- サイトはこちら→https://www.iwdd.net/
岩手県でこんなに続いていた勉強会があったとは...!話題も一部掲載されていましたその時代時代にあったテーマで開催しているようでした。
動画配信の話(@daidai)
- 動画配信技術に関する話
- WebRTCは聞いた事がある程度だったので用語含め勉強になった
- みんな!動画の沼に引きずりこむ
動画配信技術に関して普段は意識した事がなかったのでなかなか勉強になった。WebRTCちょっとかじろうと思った。
ハニポの話(@fatsheep)
ハニーポットも初めて聞く内容。(どんだけ俺は知見がないのか...)不正アクセスを受ける事を前提としたシステムがあるというのもこれまた発見。確かにこれ観測するのは面白そう。
Spotify Web APIについて(@tkpea)
Spotifyからこのようなデータが取れるのは良い発見。このAPIを使って曲のテイストにあったヴィジュアライザとか作れないかなーと思ったり。
自宅の開発環境のご紹介(oga_baku)
僕の自宅も似たような事をやっていたので非常に親近感が湧きました。お家ハック的なのって結構楽しいんですよねー。
ゲームだけじゃないUnity(俺)
- Unite Tokyo 2019に行って感じた事の感想
- Unity初めてみよう!っていう勧誘(?)
- スタートしたIwate.Unityの紹介
岩手県内のUnityユーザーを少しでも増やしたいと思い、Unityの紹介をメインとしたLTを行いました。これがきっかけで一人でも初めて貰えればー!
デバッガから低レイヤを学ぶそして自作デバッガ(Strader)の紹介(@NaokiYoshida)
- 自作デバッガ(!)の紹介
- デバッガとして必要な機能や低レイヤのお話
- 制作したのは若干10代の学生!
いやー凄いですねー....。デバッガを自作するって考え自体が僕にはなかったですし何よりそれを作ってアウトプットする姿勢が素晴らしいです。応援しますよ!
iOSのAirDropで遊んで見ました(@KumagaiY)
- 名刺管理アプリケーションの紹介
- 名刺の情報を読みアウトプットするアプリケーション
- アプリはFilemaker Proで作成されているみたい
名刺一枚で全ての情報にアプリからアクセスできるっていう機能紹介!要望ですが顔写真かアバターとかと同期できれば...!(名刺だけで顔忘れることあるので...
ETL現場のスクリプト構成(@yousken900)
- ETLは抽出、変換、格納の頭文字
- 散らばったデータを整理し集約する?
- ヤバイ、マジで初めて聞くワードだ
ETLというワード自体が初耳だったので手元のPCで調べながら聞いていました。イメージ的に社内に散らばったデータを成型しなおし、一か所に集約する?内容が面白そうなだけに知識不足が悔やまれる...
ビッグデータとセキュリティとログ解析と(@7GHz)
- ビッグデータ = ゴミ漁り(妙に納得した!)
- Splunkを使用することでログの傾向等の分析ができるみたい?
- なんとこれらの解析を仕事ではなく趣味でやっている
こちらの記事「Splunk Stream を使わずにパケット解析したい」も参照。ビッグデータの説明に納得。これらも普段触ることのない領域だったので新鮮な気持ちで聞けました
イベントを振り返って
どの地域にも「一定の熱いヤツ」が必ずいてそれらが繋がり自走できる環境が作れれば物事は勝手に進む...と楽観的に考えていたのですが、今回のイベントでその第一歩が作れたのでは!?と一参加者ながら感じています。
岩手というと決してITに明るくない土地ですが、実は多くの勉強会が立ち上がってます。(勉強会は大将さんのブログにまとまってますのでそちらご確認いただければ)
良い流れが来ていますし僕の周りでも「岩手熱い」というワードが少し聞こえるようになってきました。思うに地方を盛り上げるのを他人任せ(企業誘致やら行政まかせ)にするんじゃなく、ひとりひとりが当事者意識を持ち無理の無い範囲でその価値を高める行動をしていくのが正しい地方創生なんじゃないかなーっていうポエムっぽいことを書いてみたり。
運営の皆様、本当にお疲れ様でした!素敵なイベントをありがとうございます!!
余談:ちなみに二次会でLTが始まりました
二次会でLT始まるってww
— さわぐち@Iwate.Unity.Update() (@amidaSawaguchi) 2019年12月28日
#iwadev pic.twitter.com/A6oFsqe5jL
Oculus Questのハンドトラッキングを雑にまとめる
ハンドトラッキングがSDKで使用できるようになったので自分用メモということで簡単にまとめていこう。7割程度、自分用メモ
まずどこを見ればよいのか?
とりあえずこのページを読んでおけば一通りの設定はできそう developer.oculus.com
最短の設定
AndroidにSwitch Platform、Oculus Integrationをインポート済み前提
- OVRCameraRigをHierarchyに入れる
- OVR Managerの Hand Tracking Supportを「Controllers and Hands」か「Hands Only」に変更する
- LeftHandAnchor、RightHandAnchor直下にOVRHandPrefabを追加
- RightHandに設定したOVR Hand、OVR Skelton、OVR Meshをそれぞれ「Hand Right」に設定する
- ビルド
OVRHandPrefabの構成
OVR Skeleton
ボーン情報やジェスチャの検出、衝突判定まわりが含まれている。
- Skelton Type:右手か左手か
- Update Root Pose:OVR Camera Rigの下層に配置する場合チェックを付けない。OVRHandPrefab単体で使用する場合チェックをつける。
- Update Root Scale:トラッキングしている手のサイズを反映するかどうか(あんまり差がわからなかった)
- Enable Physics Capsules:物理挙動、衝突を有効にする
OVR Mesh
描画に使用するメッシュの情報。中身を見るとOVRPluginからメッシュを引っ張ってきてるみたい。
OVR Mesh Renderer
OVR SkeletonとOVR Meshから返されたデータを組み合わせて、手のアニメーション化された3Dモデルを生成します。(原文翻訳まま)
Inspectorに出ている値、特に設定しない場合同一オブジェクトからGetComponentして取得している。
if (_ovrMesh == null) { _ovrMesh = GetComponent<OVRMesh>(); } if (_ovrSkeleton == null) { _ovrSkeleton = GetComponent<OVRSkeleton>(); }
OVR Skeleton Renderer
ボーンを描画する。使用する際にはOVR Mesh Rendererのチェックを外しておいた方が良くみえる。下図は左がOVR Skeleton Rendererを有効にした状態。
ボーンID
OVRSkelton.cs内でBoneIDが以下のように定義されている。
public enum BoneId { Invalid = OVRPlugin.BoneId.Invalid, Hand_Start = OVRPlugin.BoneId.Hand_Start, Hand_WristRoot = OVRPlugin.BoneId.Hand_WristRoot, // root frame of the hand, where the wrist is located Hand_ForearmStub = OVRPlugin.BoneId.Hand_ForearmStub, // frame for user's forearm Hand_Thumb0 = OVRPlugin.BoneId.Hand_Thumb0, // thumb trapezium bone Hand_Thumb1 = OVRPlugin.BoneId.Hand_Thumb1, // thumb metacarpal bone Hand_Thumb2 = OVRPlugin.BoneId.Hand_Thumb2, // thumb proximal phalange bone Hand_Thumb3 = OVRPlugin.BoneId.Hand_Thumb3, // thumb distal phalange bone Hand_Index1 = OVRPlugin.BoneId.Hand_Index1, // index proximal phalange bone Hand_Index2 = OVRPlugin.BoneId.Hand_Index2, // index intermediate phalange bone Hand_Index3 = OVRPlugin.BoneId.Hand_Index3, // index distal phalange bone Hand_Middle1 = OVRPlugin.BoneId.Hand_Middle1, // middle proximal phalange bone Hand_Middle2 = OVRPlugin.BoneId.Hand_Middle2, // middle intermediate phalange bone Hand_Middle3 = OVRPlugin.BoneId.Hand_Middle3, // middle distal phalange bone Hand_Ring1 = OVRPlugin.BoneId.Hand_Ring1, // ring proximal phalange bone Hand_Ring2 = OVRPlugin.BoneId.Hand_Ring2, // ring intermediate phalange bone Hand_Ring3 = OVRPlugin.BoneId.Hand_Ring3, // ring distal phalange bone Hand_Pinky0 = OVRPlugin.BoneId.Hand_Pinky0, // pinky metacarpal bone Hand_Pinky1 = OVRPlugin.BoneId.Hand_Pinky1, // pinky proximal phalange bone Hand_Pinky2 = OVRPlugin.BoneId.Hand_Pinky2, // pinky intermediate phalange bone Hand_Pinky3 = OVRPlugin.BoneId.Hand_Pinky3, // pinky distal phalange bone Hand_MaxSkinnable = OVRPlugin.BoneId.Hand_MaxSkinnable, // Bone tips are position only. They are not used for skinning but are useful for hit-testing. // NOTE: Hand_ThumbTip == Hand_MaxSkinnable since the extended tips need to be contiguous Hand_ThumbTip = OVRPlugin.BoneId.Hand_ThumbTip, // tip of the thumb Hand_IndexTip = OVRPlugin.BoneId.Hand_IndexTip, // tip of the index finger Hand_MiddleTip = OVRPlugin.BoneId.Hand_MiddleTip, // tip of the middle finger Hand_RingTip = OVRPlugin.BoneId.Hand_RingTip, // tip of the ring finger Hand_PinkyTip = OVRPlugin.BoneId.Hand_PinkyTip, // tip of the pinky Hand_End = OVRPlugin.BoneId.Hand_End, // add new bones here Max = OVRPlugin.BoneId.Max }
OVRSkelton.csではボーンに関するメソッドが用意されている。試しに以下のようなスクリプトを作ってGetCurrentStartBoneID、_skeleton.GetCurrentEndBoneId、GetCurrentNumBones、GetCurrentNumSkinnableBonesを表示してみる。
using UnityEngine; using UnityEngine.UI; public class SkeletonInfo : MonoBehaviour { [SerializeField]OVRSkeleton _skeleton; Text _info; // Start is called before the first frame update void Start() { _info = GetComponent<Text>(); _info.text += $"StartBoneId : {_skeleton.GetCurrentStartBoneId()}\n"; _info.text += $"EndBoneId : {_skeleton.GetCurrentEndBoneId()}\n"; _info.text += $"CurrentNumBones : {_skeleton.GetCurrentNumBones()}\n"; _info.text += $"CurrentNumSkinnableBones : {_skeleton.GetCurrentNumSkinnableBones()}\n"; } }
GetCurrentStartBoneID、_skeleton.GetCurrentEndBoneIdはどちらもBone IDのHand_StartとHand_Endが取得できるよう。GetCurrentNumBonesではスケルトン内のボーン数が、GetCurrentNumSkinnableBonesでは指先の~Tipを除外した数が取得できる
手がトラッキングできてるかどうか
OVRHandのIsTrackedで手がトラッキングできているかをBool値で取得できる。HandConfidenceはトラッキングされた手の信頼度をTrackingConfidence型で取得できる。値はLowかHighのどちらか。 以下のようなスクリプトを作成し、動かしながら挙動を確認してみる
using UnityEngine; using UnityEngine.UI; public class HandInfo : MonoBehaviour { [SerializeField]OVRHand _hand; Text _info; // Start is called before the first frame update void Start() { _info = GetComponent<Text>(); } // Update is called once per frame void Update() { _info.text = ""; _info.text += $"IsTracked : {_hand.IsTracked}\n"; _info.text += $"HandConfidence : {_hand.HandConfidence}"; } }
両手が近くても描画したい!
どうやら「IsTrackedがtrueでもTrackingConfidenceがfalseだと手が描画されない」っていうのがデフォルトっぽい。これを修正したい場合、OVRHand.csの中でHandConfidenceを判定している部分を書き換えるとIsTrackedのみでも手が描画されるようになった。
OVRMeshRenderer.MeshRendererData OVRMeshRenderer.IOVRMeshRendererDataProvider.GetMeshRendererData() { var data = new OVRMeshRenderer.MeshRendererData(); data.IsDataValid = _isInitialized; if (_isInitialized) { // ↓ここの判定を書き換える data.IsDataHighConfidence = IsTracked;// && HandConfidence == TrackingConfidence.High; } return data; }
とりあえずここまで。
Oculus Questで俺のギターをコントローラーにする
この記事はUnity #3 Advent Calendar 2019の25日目の記事です。
僕が書くのは完全に趣味と自己満の記事です。
あぁーVRでもギターが弾きてぇよー
僕はギターが大好きだ。中一でベンチャーズ(パイプラインとか....)をコピーし、今でも歪ませた音ばかりを出している。30後半になったけども必死にスウィープ奏法を練習している。そんな大好きなギターをなんとかVR上でコントローラとして使えないかなーと思ったのがこの記事のきっかけです。
ということで手元にあるギターをOculus Questのコントローラーとして使用できないかを考えてみよー
これだ!多分これでいけるんじゃあないかと思うのでやってみる。おそらくコーディング量はほぼ0です。
用意するもの
Keijiroさんが公開している「Lasp」と「OscJack」を使用します。Unityは2019.3.0fを使用。
他必要なものはどのご家庭にもある
- ギター
- オーディオインターフェース
となります。さらにOculus Questがケーブルレスなのを考慮し
- ワイヤレスシステム(あれば)
- ワイヤレスヘッドフォン(あれば)
この辺まで揃えられればなんか良さそう
まぁぶっちゃけオーディオインターフェース経由ならギターである必要はないのです。もっといえば、PCにマイクが内臓できてればとりあえず入力は取れます。
Oculus Questのipアドレスを調べる
Oscを使用するためOculus QuestとPCをUSBで繋ぎ以下のコマンドをcmdに入力してipアドレスを調べます。(事前にadbがインストールされている前提)
adb shell ip addr show wlan0
「inet xxx.xxx.xxx.xxx brd ~」とipアドレスが表示されるので覚えておきましょう。 このipアドレスを送信アプリに埋め込みます。
Lasp → OSC送信用のプロジェクトを作成する
変換とOSC送信するPCアプリと、受信側のOculus Questアプリの2つを作ります。
まずは送信側のプロジェクトを作りそれぞれ「Lasp」と「OscJack」をインポートしておきます。
Laspを使用してオーディオ入力を受け取る
低・中・高の音域を取り出したいのでSphereをヒエラルキーに3つ追加しそれぞれ「AudioLevelTracker」をアタッチして「Filter Type」を「Low Pass」「Band Pass」「High Pass」に設定しておきます。
とりあえず左から「Low」「Band」「High」。きちんとオーディオ入力が取れているか確認するため簡単なスクリプトを作りアタッチします。
using System.Collections; using System.Collections.Generic; using UnityEngine; public class NormalizedLevelView : MonoBehaviour { public float Level { get; set; } void Update() { transform.localScale = new Vector3(Level, Level, Level); } }
アタッチしたら「AudioLevelTracker」の「Normalized Level Event」にプロパティの「Level」を設定します。
この段階で実行しオーディオ入力が取れているか確認しましょう。
※注意:Laspには「デフォルトデバイスを使用する」と「モノラル入力」の制限があるのでデバイス側で少し設定いじらないと入力取れないケースもあるかもしれません。その場合はサウンド設定を見直すといいかと。
OscJackを使用し入力結果を送信する
先ほど作成した3つのSphereに「OscPropertySender」もアタッチし、とりあえず以下の値を設定します
- IP Address:調べたOculus Questのipアドレス
- OSC Address:それぞれ「/low」「/band」「/high」
- Data Source:自身のオブジェクト
- Component:AudioLevelTracker
- Property:normalizedLevel
ひとまずこのプロジェクトで送信できているか確認するため、適当なGameObjectを置き「OscEventReceiver」をアタッチ。Window→OSC Monitorを開き確認します。
とりあえず送れてるっぽい。
Oculus Quest向け受信用のプロジェクトを作成する
Oculus Quest用のアプリの作り方は先人の方々がまとめてくださっているので「Oculus Quest Unity ビルド」でググってもらえれば色々ヒットします。
ひとまず先ほど作成した送信アプリから値を受け取り、Oculus Quest上でもSphereが動作するアプリを作ってみましょう。
新しく作成したプロジェクトに「OscJack」と先ほど作成した「NormalizedLevelView.cs」を追加します。
で、先ほどと全く同様にSphereで「Low」「Band」「High」を作っておき....(↓の画像は使いまわし)
それぞれに「NormalizedLevelView」と「OscEventReceiver」をアタッチします。
イベントの取得先をNormalizedLevelViewのLevelに設定するのを忘れないように。
これでビルドを行いOculus Questで実行します。
結果
やりたかったことはだいたいできた!!
ギターの音自体はオーディオインターフェースから出力。入力はPCを通してOsc送信→Oculus Questで受信→ビューに反映って感じで。遅延どうかなーと思ったけど自宅環境でやる分には個人的に気にならなかったです。そしてやっぱりkeijiroさんは神でした。
まとめ
今回の内容で何ができそうか考えてみると
- リアル楽器をコントローラーとして使ったインタラクティブな遊び
- VFXGraphとかを使用して音のビジュアライズ化(QuestではVFXGraphうまく表示されないけど..)
- Hapbeatとかと組み合わせるのも良い?
- タブ譜とかVR内で見ながら弾けたら良いかも
まぁ実際やってて思ったのは機材の準備が面倒。あと手元がよく見えない。自宅以外の環境でちゃんとできるのか?とか
まぁ....というわけで、Unityも楽器も....楽しんだもん勝ちだぜ!!
(おしまい)
Oculus QuestでUniversal Render Pipelineを使用してみる
Oculus QuestでURPを使うまでの簡単な軌跡です。Oculus Questのビルド手順はこりんさん(@korinVR)が完璧にまとめてくれているのでそちらを参照して頂ければと思います。
Universal Render Pipelineについて
詳しくは「凹みさん」が素晴らしくまとめてくれているのでこちらをご覧頂ければ。 tips.hecomi.com
どうやらこれまでのパイプラインはレガシーになりそうなので今のうちに色々調べておいた方が良さそう...というわけでOculus QuestをURPで使用してみたいと思います。
環境に関しては「Unity 2019.3.0f1」を使用してAndroidのビルド環境が既に構築済み前提で進めます。
プロジェクトの作成
テンプレートで「Universal RP」が用意されているのですが余計なアセットも同梱されてしまうので、「3D」で作ります。
Universal Render Pipeline関連のパッケージを追加
Package Managerを開き、以下のパッケージをがしがしと入れていきます。
- Core RP Library
- Shader Graph
- Universal RP
プロジェクトの設定
Switch PlatformでAndroidを選択します。Texture Compressionは「ASTC」を指定しておくと良いです。
次にProject Settings→Player→Other Settingsを開き、「Color Space」を「Linear」に変更します。Oculusの「Build and Upload Android Applications」のページを見るとわかるのですが「ガンマじゃなくてリニア使え、あとあんまポストプロセス使うなよ」的なことが書いてあるので「あっ、はい」と声に出しておきましょう。
そのすぐに下にある「Graphics APIs」から「Vulkan」を削除して、Minimum API Levelを6.0に変更します。
最後にProject Settings→XR Settingを開き下図のように設定します。
Oculus Integrationのインポート
Asset StoreからOculus Integrationをインポートしておきます。インポート結構時間がかかるのでお湯を沸かしてコーヒーでも飲んでゆっくりしましょう。もし奥さんがいるなら話しかけてみてはどうでしょうか(テクスチャの圧縮形式を変更しているのが原因っぽい...?)
完了するとUnity再起動促されるので再起動しておく。今再起動したらこうなった。
負けない。
ビルド時間短縮のためOculus Integrationから使わないアセットは予め削除しておくと良いです。因みに僕は以下を消しています。
- Avatar
- SampleFramework
Universal Render Pipelineの設定を行う
Assets→Create→Rendering→Universal Render Pipeline→Pipeline Assetsを選択します。名前は適当で良いですがここではOculusQuestURPとします。
再度Project Settingsを開き、GraphicsのScriptable Render Pipeline Settingsの欄に先ほど作成したURPのアセットを放り込みます。
適当にビューを作る
Main Cameraを削除しOVRCameraRigをヒエラルキーに追加し以下の設定を行います
- TargetDevice : Quest
- Tracking Origin Type : Floor Level
とりあえず床になるようにPlaneを設定します
ビルドしてみる
段階でビルドしてみる。URPを使用している関係か初回は長いので少し我慢。
これで正常に表示されれば完了です。
まとめ
URP適用自体は非常に簡単。確認したところポストプロセス = Global Volumeも一部使用できた(パフォーマンスは勿論下がるが....)。Shader Graphも勿論使用できる。
気になった & 気づいた点
- Multi Passにすると右目側が描画されない
- VFXGraphを使用すると描画がおかしくなる
何か作るわけではないのですがUnity2019.3 + Universal Render Pipelineで一応Oculus Quest動いた。Shader Graphも使える。「一応」ってのはVFXGraphはなんか謎の上昇気流に吸い上げられた挙動になったのでダメだw pic.twitter.com/WXqxw0xD0d
— さわぐち@Iwate.Unity.Update() (@amidaSawaguchi) 2019年10月1日