目的
実際にOculusQuest上で動かした様子
Unity Editorで実行した様子
以前の記事で作成した方法ではUnity Editor環境ではかなりうまくいくのですが、実際にデバッグするといろいろとバグが発生することがあるので別の方法を探しました。
目次
- BlenderでOculus HandをVRMモデルにくっつける
- UnityでOculusCustomHandの指の動きをVRMの指の動きへミラーリングする
1. BlenderでOculus HandをVRMモデルにくっつける
1.1. OculusHandをVRMのモデルに合わせる
1.2. OculusHandのメッシュの編集
1.3. OcllusHandのUVの編集
1.4 OculusHandとVRMモデルの結合
1.5 VRMモデルのウェイトの変更
を行っていきます
1.1 OculusHandをVRMのモデルに合わせる
まず、OculusHandのfbxが欲しいので、Unityから
Assets > Oculus > VR > Meshes > HandTrackingにあるOculusHandをCtrl+Dで複製し、Exploerに複製したやつをDrag&Dropします。
次にimport したOculusHandのfbxについて、Armaturenの位置を調整して、写真のように配置します。
コツとしては手首の太さと位置が可能な限りVRMに一致するようにします。
1.2 OculusHandのメッシュの編集
次にOculusHandのメッシュについてVRM風に編集をします
注意点としては
- 指の太さを変えて調整をすること
- 指の股を深くして、指を長くすること
- 絶対に指の長さを指の先から長くしないこと
- 可能な限り左右対称に編集すること
以上になります。
Before
After
ちなみに、指の太さを変えるときはプロポーショナル編集の球状を利用し、股を下げるときはスムーズを利用しました。
あとは、手首を可能な限り自然に位置合わせをしてください。
1.3. OcllusHandのUVの編集
OculusHandのマテリアルタブから N00_000_00_Body_00_SKIN を選択します。
UV展開を写真のようにすると手首と同様の色が付きます
1.4.OculusHandとVRMモデルの結合
次に, OculusHandのarmatureとVRMのアーマチュアを結合します
- OculusHandのArmatureについて、ctrl+AですべてのTransformを適用します
- OculusHandのArmatureを選択後に、VRMのArmatureを選択し、Ctrl+Jで結合します。
- 編集モードでインスペクターから、b_l_wrist, b_r_wristのそれぞれについて、ペアレントをJ_Bip_L_LowerArm, J_Bip_R_LowerArmとして設定します。
- OculusHandのメッシュについて、VRMのBodyのメッシュにCtrl+Jで結合します
1.5 VRMモデルのウェイトの変更
最後にb_l_wrist, b_r_wristなどの新しく追加したボーンについて、ウェイトペイントでウェイトを編集し完成です。
2. UnityでOculusCustomHandの指の動きをVRMの指の動きへミラーリングする
2.1 オブジェクトのセットアップ
2.2 スクリプトの作成
2.3 スクリプトの設定
2.1 オブジェクトのセットアップ
まず、1で作成したVRMモデルをfbx形式で出力します
UnityにDrag&Dropしたあと、RigをHumanoidに変更します
また、マテリアルが消えているのですが、これは編集前のVRMモデルをimportすると、そのテクスチャ付きのマテリアルが選択できるようになるのでそれで、fbxのマテリアルについても設定します。
また、Tracerという空のオブジェクトを作成し、下にLeftHandTracerとRightHandTracerという空の子オブジェクトを作成します。
あとはOVRCameraRigを追加し、LeftHandControllerAnchor, RightHandControllerAnchorの下に、それぞれOVRCustomHandPrefab_L, OVRCustomHandPrefab_Rを設定します。
2.2 スクリプトの作成
以下の5つのスクリプトを作ってください
using System.Collections;
using System.Collections.Generic;
using System;
using UnityEngine;
public class HandType : MonoBehaviour
{
public enum Hand
{
None = -1,
LeftHand = 0,
Righthand = 1
}
}
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class GetOculusBones : MonoBehaviour
{
[SerializeField] Transform TargetRootBone;
private List<Transform> customBones = new List<Transform>(new Transform[(int)OVRSkeleton.BoneId.Hand_MaxSkinnable]);
// Start is called before the first frame update
private static readonly string[] _fbxHandSidePrefix = { "l_", "r_" };
private static readonly string _fbxHandBonePrefix = "b_";
private static readonly string[] _fbxHandBoneNames =
{
"wrist",
"forearm_stub",
"thumb0",
"thumb1",
"thumb2",
"thumb3",
"index1",
"index2",
"index3",
"middle1",
"middle2",
"middle3",
"ring1",
"ring2",
"ring3",
"pinky0",
"pinky1",
"pinky2",
"pinky3"
};
private static readonly string[] _fbxHandFingerNames =
{
"thumb",
"index",
"middle",
"ring",
"pinky"
};
public List<Transform> CustomBones { get { return customBones; } }
public void TryAutoMapBonesByName(HandType.Hand handType)
{
OVRSkeleton.BoneId start = OVRSkeleton.BoneId.Hand_Start;
OVRSkeleton.BoneId end = OVRSkeleton.BoneId.Hand_MaxSkinnable;
Debug.Log("start " + start + " end " + end);
if (start != OVRSkeleton.BoneId.Invalid && end != OVRSkeleton.BoneId.Invalid)
{
for (int bi = (int)start; bi < (int)end; ++bi)
{
string fbxBoneName = FbxBoneNameFromBoneId(handType, (OVRSkeleton.BoneId)bi);
Transform t = FindChildRecursive(TargetRootBone, fbxBoneName);
Debug.Log("name : " + fbxBoneName + " transform : " + t);
if (t != null)
{
customBones[(int)bi] = t;
}
}
}
}
private static string FbxBoneNameFromBoneId(HandType.Hand handType, OVRSkeleton.BoneId bi)
{
{
// if (bi >= OVRSkeleton.BoneId.Hand_ThumbTip && bi <= OVRSkeleton.BoneId.Hand_PinkyTip)
// {
// return _fbxHandSidePrefix[(int)handType-1] + _fbxHandFingerNames[(int)bi - (int)OVRSkeleton.BoneId.Hand_ThumbTip] + "_finger_tip_marker";
// }
// else
// {
return _fbxHandBonePrefix + _fbxHandSidePrefix[(int)handType] + _fbxHandBoneNames[(int)bi];
// }
}
}
public Transform FindChildRecursive(Transform parent, string boneName)
{
foreach (Transform child in parent)
{
if (child.name.Contains(boneName))
return child;
var result = FindChildRecursive(child, boneName);
if (result != null)
return result;
}
return null;
}
}
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class AvatarHand : GetOculusBones
{
}
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Agent : GetOculusBones
{
}
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class OculusHandCotroller : MonoBehaviour
{
[SerializeField]
private HandType.Hand HandType;
private Transform[] AgentBones;
private Transform[] AvatarBones;
// Start is called before the first frame update
public void Start()
{
var agentHand = gameObject.GetComponent<AgentHand>();
agentHand.TryAutoMapBonesByName(HandType);
AgentBones = agentHand.CustomBones.ToArray();
var avatarHand = gameObject.GetComponent<AvatarHand>();
avatarHand.TryAutoMapBonesByName(HandType);
AvatarBones = avatarHand.CustomBones.ToArray();
if (AgentBones.Length == AvatarBones.Length){
Debug.Log("Length is equal with Agent hands and Avatar hands ->" + AgentBones.Length + " and " + AvatarBones.Length);
}
}
// Update is called once per frame
public void LateUpdate()
{
for(int id = 1; id < AvatarBones.Length; id++){
AgentBones[id].localRotation = AvatarBones[id].localRotation;
}
}
}
軽く解説をすると、
HandType.csが左右の手のEnum方でこれで左右の手の判別をしています。
GetOculusBones.csが手首から、指までの指の変形に関するすべてのボーンのTransformを返すスクリプトになっています。
OculusHandController.csではエージェントの手とアバターの手の両方の手について別々にTransformを取得し、Update時には、アバターの手首から先のlocalRotationをエージェントの対応するTransformのlocalRotationに反映しています。
2.3 スクリプトの設定
まず、LeftHandTracerに以下のように設定をしてください。
注意点として、AgentHandにはJ_Bip_L_Handではなく、J_Bip_L_LowerArmを設定してください