はじめに
前回までは、顔のオブジェクトはUnityで作っていましたが、今回はBlenderで顔を作ってBlendShapeを適用してみました☺️
手はLandmarkを繋ぐように、Sphereを引き延ばして手の輪郭を作りました。
本当は手もモデルを使いたかったのですが、指の回転が難しく🥺
Python
前回から変更なし
Blender
顔のモデルを作成しました。
パーツ構成:
顔
目(左右)
瞳(左右)
眉(左右)
口と瞼は、顔のメッシュ変形で表現します。
怖いて😂
動作がわかる形なら何でも良いやーと、思いつきで作っていたらこうなりました。
どうやらeightにはデザインセンスがなかったようです🤔
手にはアーマチュアも仕込んでありますが、今回使うのは顔だけです。
シェイプキーはこれらを登録しました。
顔オブジェクトのシェイプキー
眉オブジェクトのシェイプキー
BlendShapeで反映されるのはメッシュの変形のみです。座標の移動や回転は反映されません。
そのため、たとえば瞬きであれば、瞼のメッシュを下方向へ引き伸ばすことで表現しています。
作成したら、FBX形式でエクスポートします。
Unity
もともとSceneにあった顔パーツは削除し、Blenderで作成したFBXをインポートして顔として使用します。
スクリプト
MediaPipe管理
前回から変更なし
UDP管理
前回から変更なし
顔パーツ管理
眉・瞼・口はBlendShapeを使用します。
using System.Collections.Generic;
using UnityEngine;
namespace PartsData_face_ns{
[System.Serializable]
public class PartsData_face
{
public string parts = "";
public float[] nose = new float[3];
public float[] top = new float[3];
public float[] bottom = new float[3];
public float[] right = new float[3];
public float[] left = new float[3];
public float[] blendshape = new float[52];
}
public class FaceManager : MonoBehaviour
{
public enum FaceBlendshapeName
{
Neutral = 0,
BrowDownLeft,
BrowDownRight,
BrowInnerUp,
BrowOuterUpLeft,
BrowOuterUpRight,
CheekPuff,
CheekSquintLeft,
CheekSquintRight,
EyeBlinkLeft,
EyeBlinkRight,
EyeLookDownLeft,
EyeLookDownRight,
EyeLookInLeft,
EyeLookInRight,
EyeLookOutLeft,
EyeLookOutRight,
EyeLookUpLeft,
EyeLookUpRight,
EyeSquintLeft,
EyeSquintRight,
EyeWideLeft,
EyeWideRight,
JawForward,
JawLeft,
JawOpen,
JawRight,
MouthClose,
MouthDimpleLeft,
MouthDimpleRight,
MouthFrownLeft,
MouthFrownRight,
MouthFunnel,
MouthLeft,
MouthLowerDownLeft,
MouthLowerDownRight,
MouthPressLeft,
MouthPressRight,
MouthPucker,
MouthRight,
MouthRollLower,
MouthRollUpper,
MouthShrugLower,
MouthShrugUpper,
MouthSmileLeft,
MouthSmileRight,
MouthStretchLeft,
MouthStretchRight,
MouthUpperUpLeft,
MouthUpperUpRight,
NoseSneerLeft,
NoseSneerRight,
FaceBlendshapeName_Max
}
public static Queue<string> rcvData_face;
public Queue<PartsData_face> moveData_face;
[SerializeField] GameObject RightBrow;
[SerializeField] GameObject LeftBrow;
[SerializeField] GameObject RightEye;
[SerializeField] GameObject LeftEye;
[SerializeField] GameObject RightPupil;
[SerializeField] GameObject LeftPupil;
[SerializeField] GameObject Nose;
private int hidecnt;
private Vector3 RightEye_angle_base;
private Vector3 LeftEye_angle_base;
private Vector3 Nose_pos_base;
private Vector3 Nose_angle_base;
private PartsData_face prev_moveData_face;
private int rcvdata_devide; //受信データを分割して滑らかにする
void Start()
{
hidecnt = 0;
rcvdata_devide = 2;
rcvData_face = new Queue<string>();
moveData_face = new Queue<PartsData_face>();
prev_moveData_face = null;
ChangeVisibility(false);
RightEye_angle_base = RightEye.transform.localEulerAngles;
LeftEye_angle_base = LeftEye.transform.localEulerAngles;
Nose_pos_base = Nose.transform.localPosition;
Nose_angle_base = Nose.transform.localEulerAngles;
}
void Update()
{
hidecnt++;
if((rcvData_face != null) && (rcvData_face.Count != 0))
{
string rcvdata = rcvData_face.Dequeue();
PartsData_face rcv_face = JsonUtility.FromJson<PartsData_face>(rcvdata);
//今回値を前回値の差を、今回値(目標)に向かって数回に分けて移動させる
if(prev_moveData_face != null)
{
PartsData_face div_face = new PartsData_face();
//前回値を開始地点とする
div_face.parts = prev_moveData_face.parts;
for(int idx=0; idx<3; idx++)
{
div_face.nose[idx] = prev_moveData_face.nose[idx];
div_face.top[idx] = prev_moveData_face.top[idx];
div_face.bottom[idx] = prev_moveData_face.bottom[idx];
div_face.right[idx] = prev_moveData_face.right[idx];
div_face.left[idx] = prev_moveData_face.left[idx];
}
for(int idx=(int)FaceBlendshapeName.Neutral; idx<(int)FaceBlendshapeName.FaceBlendshapeName_Max; idx++)
{
div_face.blendshape[idx] = prev_moveData_face.blendshape[idx];
}
//今回値と前回値の差分を分割し、キューへ格納する
for(int div=0; div<rcvdata_devide-1; div++)
{
for(int idx=0; idx<3; idx++)
{
div_face.nose[idx] += (rcv_face.nose[idx] - prev_moveData_face.nose[idx]) / rcvdata_devide;
div_face.top[idx] += (rcv_face.top[idx] - prev_moveData_face.top[idx]) / rcvdata_devide;
div_face.bottom[idx] += (rcv_face.bottom[idx] - prev_moveData_face.bottom[idx]) / rcvdata_devide;
div_face.right[idx] += (rcv_face.right[idx] - prev_moveData_face.right[idx]) / rcvdata_devide;
div_face.left[idx] += (rcv_face.left[idx] - prev_moveData_face.left[idx]) / rcvdata_devide;
}
for(int idx=(int)FaceBlendshapeName.Neutral; idx<(int)FaceBlendshapeName.FaceBlendshapeName_Max; idx++)
{
div_face.blendshape[idx] += (rcv_face.blendshape[idx] - prev_moveData_face.blendshape[idx]) / rcvdata_devide;
}
moveData_face.Enqueue(div_face);
}
}
else
{
//ここを実行するのは初回のみ
prev_moveData_face = new PartsData_face();
}
//分割数によっては最終値が中途半端になるため、最後は今回値ぴったりで終わらせる
moveData_face.Enqueue(rcv_face);
//今回値を前回値として保持
prev_moveData_face.parts = rcv_face.parts;
for(int idx=0; idx<3; idx++)
{
prev_moveData_face.nose[idx] = rcv_face.nose[idx];
prev_moveData_face.top[idx] = rcv_face.top[idx];
prev_moveData_face.bottom[idx] = rcv_face.bottom[idx];
prev_moveData_face.right[idx] = rcv_face.right[idx];
prev_moveData_face.left[idx] = rcv_face.left[idx];
}
for(int idx=(int)FaceBlendshapeName.Neutral; idx<(int)FaceBlendshapeName.FaceBlendshapeName_Max; idx++)
{
prev_moveData_face.blendshape[idx] = rcv_face.blendshape[idx];
}
}
if((moveData_face != null) && (moveData_face.Count != 0))
{
PartsData_face face = moveData_face.Dequeue();
hidecnt = 0;
//処理順は固定
NoseMove(face);
BrowMove_blendshape(face);
EyeBlink_blendshape(face);
EyeMove(face);
MouthMove_blendshape(face);
ChangeVisibility(true);
}
if(hidecnt > 100)
{
hidecnt = 0;
for(int i=(int)FaceBlendshapeName.Neutral; i<(int)FaceBlendshapeName.FaceBlendshapeName_Max; i++)
{
ChangeVisibility(false);
}
rcvData_face.Clear();
moveData_face.Clear();
}
}
private void OnApplicationQuit()
{
rcvData_face.Clear();
moveData_face.Clear();
}
private void OnDestroy()
{
rcvData_face.Clear();
moveData_face.Clear();
}
public static void SetRcvData(string rcvdata)
{
rcvData_face.Enqueue(rcvdata);
}
private void NoseMove(PartsData_face face)
{
Transform Nose_Transform = Nose.transform;
//鼻の位置を計算する
Vector3 Nose_pos = Nose_Transform.localPosition;
Nose_pos.x = Nose_pos_base.x + ((face.nose[0]*6f) * (-1f));
Nose_pos.y = Nose_pos_base.y + ((face.nose[1]*3f) * (-1f));
Nose_pos.z = Nose_pos_base.z + (face.nose[2]*100f);
Nose_Transform.localPosition = Nose_pos;
//顔の上下左右端の座標から、顔全体の傾きを計算する
Vector3 Nose_angle = Nose_Transform.localEulerAngles;
Nose_angle.x = Nose_angle_base.x + ((face.top[2] - face.bottom[2]) * 250f);
Nose_angle.y = Nose_angle_base.y + ((face.right[2] - face.left[2]) * 250f);
Nose_angle.z = Nose_angle_base.z + ((face.top[0] - face.bottom[0]) * 250f);
Nose_Transform.localEulerAngles = Nose_angle;
}
private void BrowMove_blendshape(PartsData_face face)
{
SkinnedMeshRenderer rightbrow_smr = RightBrow.GetComponent<SkinnedMeshRenderer>();
SkinnedMeshRenderer leftbrow_smr = LeftBrow.GetComponent<SkinnedMeshRenderer>();
float rightbrow = (face.blendshape[(int)FaceBlendshapeName.BrowInnerUp])*120;
float leftbrow = (face.blendshape[(int)FaceBlendshapeName.BrowInnerUp])*120;
if(rightbrow > 100.0f) rightbrow = 100.0f;
if(leftbrow > 100.0f) leftbrow = 100.0f;
rightbrow_smr.SetBlendShapeWeight(0, rightbrow);
leftbrow_smr.SetBlendShapeWeight(0, leftbrow);
}
private void EyeBlink_blendshape(PartsData_face face)
{
SkinnedMeshRenderer Nose_smr;
Nose_smr = Nose.GetComponent<SkinnedMeshRenderer>();
float rightblink = (face.blendshape[(int)FaceBlendshapeName.EyeBlinkRight])*120;
float leftblink = (face.blendshape[(int)FaceBlendshapeName.EyeBlinkLeft])*120;
if(rightblink > 100.0f) rightblink = 100.0f;
if(leftblink > 100.0f) leftblink = 100.0f;
Nose_smr.SetBlendShapeWeight(2, rightblink);
Nose_smr.SetBlendShapeWeight(3, leftblink);
}
private void EyeMove(PartsData_face face)
{
EyeMove_Right(face);
EyeMove_Left(face);
}
private void EyeMove_Right(PartsData_face face)
{
float eyemove_data_updown;
float eyemove_data_inout;
//上下方向に大きい値の方向へ動かす
if(face.blendshape[(int)FaceBlendshapeName.EyeLookDownRight] > face.blendshape[(int)FaceBlendshapeName.EyeLookUpRight])
{
eyemove_data_updown = face.blendshape[(int)FaceBlendshapeName.EyeLookDownRight] * (-1f);
}
else
{
eyemove_data_updown = face.blendshape[(int)FaceBlendshapeName.EyeLookUpRight];
}
//左右方向に大きい値の方向へ動かす
if(face.blendshape[(int)FaceBlendshapeName.EyeLookInRight] > face.blendshape[(int)FaceBlendshapeName.EyeLookOutRight])
{
eyemove_data_inout = face.blendshape[(int)FaceBlendshapeName.EyeLookInRight] * (-1f);
}
else
{
eyemove_data_inout = face.blendshape[(int)FaceBlendshapeName.EyeLookOutRight];
}
Transform RightEye_Transform = RightEye.transform;
Vector3 RightEye_angle = RightEye_Transform.localEulerAngles;
RightEye_angle.x = RightEye_angle_base.x + (eyemove_data_updown*25); //上下方向の回転はx軸を中心とする
RightEye_angle.y = RightEye_angle_base.y + ((eyemove_data_inout*25) * (-1)); //左右方向の回転はy軸を中心とする
RightEye_Transform.localEulerAngles = RightEye_angle;
}
private void EyeMove_Left(PartsData_face face)
{
float eyemove_data_updown;
float eyemove_data_inout;
//上下方向に大きい値の方向へ動かす
if(face.blendshape[(int)FaceBlendshapeName.EyeLookDownLeft] > face.blendshape[(int)FaceBlendshapeName.EyeLookUpLeft])
{
eyemove_data_updown = face.blendshape[(int)FaceBlendshapeName.EyeLookDownLeft] * (-1f);
}
else
{
eyemove_data_updown = face.blendshape[(int)FaceBlendshapeName.EyeLookUpLeft];
}
//左右方向に大きい値の方向へ動かす
if(face.blendshape[(int)FaceBlendshapeName.EyeLookInLeft] > face.blendshape[(int)FaceBlendshapeName.EyeLookOutLeft])
{
eyemove_data_inout = face.blendshape[(int)FaceBlendshapeName.EyeLookInLeft];
}
else
{
eyemove_data_inout = face.blendshape[(int)FaceBlendshapeName.EyeLookOutLeft] * (-1f);
}
Transform LeftEye_Transform = LeftEye.transform;
Vector3 LeftEye_angle = LeftEye_Transform.localEulerAngles;
LeftEye_angle.x = LeftEye_angle_base.x + (eyemove_data_updown*25); //上下方向の回転はx軸を中心とする
LeftEye_angle.y = LeftEye_angle_base.y + ((eyemove_data_inout*25) * (-1)); //左右方向の回転はy軸を中心とする
LeftEye_Transform.localEulerAngles = LeftEye_angle;
}
private void MouthMove_blendshape(PartsData_face face)
{
SkinnedMeshRenderer Nose_smr;
Nose_smr = Nose.GetComponent<SkinnedMeshRenderer>();
float jowopen = (face.blendshape[(int)FaceBlendshapeName.JawOpen])*120f;
float mouthstretch = (face.blendshape[(int)FaceBlendshapeName.MouthStretchRight])*10000f;
if(jowopen > 100.0f) jowopen = 100.0f;
if(mouthstretch > 100.0f) mouthstretch = 100.0f;
Nose_smr.SetBlendShapeWeight(0, jowopen);
Nose_smr.SetBlendShapeWeight(1, mouthstretch);
}
private void ChangeVisibility(bool vis)
{
RightBrow.GetComponent<Renderer>().enabled = vis;
LeftBrow.GetComponent<Renderer>().enabled = vis;
RightEye.GetComponent<Renderer>().enabled = vis;
LeftEye.GetComponent<Renderer>().enabled = vis;
RightPupil.GetComponent<Renderer>().enabled = vis;
LeftPupil.GetComponent<Renderer>().enabled = vis;
Nose.GetComponent<Renderer>().enabled = vis;
}
}
}
BlendShape使用箇所を見ていきます。
private void BrowMove_blendshape(PartsData_face face)
{
SkinnedMeshRenderer rightbrow_smr = RightBrow.GetComponent<SkinnedMeshRenderer>();
SkinnedMeshRenderer leftbrow_smr = LeftBrow.GetComponent<SkinnedMeshRenderer>();
float rightbrow = (face.blendshape[(int)FaceBlendshapeName.BrowInnerUp])*120;
float leftbrow = (face.blendshape[(int)FaceBlendshapeName.BrowInnerUp])*120;
if(rightbrow > 100.0f) rightbrow = 100.0f;
if(leftbrow > 100.0f) leftbrow = 100.0f;
rightbrow_smr.SetBlendShapeWeight(0, rightbrow);
leftbrow_smr.SetBlendShapeWeight(0, leftbrow);
}
眉の制御です。
SkinnedMeshRendererのSetBlendShapeWeight()で、BlendShapeを動かします。
第1引数は、眉に設定したBlendShapeに割り当てられたIDを指定します。
第2引数は値です。MediaPipeからの受信データにBlendShapeの値を含めているので、それを参照します。
値は0〜100の範囲なので、100でリミットしています。
private void EyeBlink_blendshape(PartsData_face face)
{
SkinnedMeshRenderer Nose_smr;
Nose_smr = Nose.GetComponent<SkinnedMeshRenderer>();
float rightblink = (face.blendshape[(int)FaceBlendshapeName.EyeBlinkRight])*120;
float leftblink = (face.blendshape[(int)FaceBlendshapeName.EyeBlinkLeft])*120;
if(rightblink > 100.0f) rightblink = 100.0f;
if(leftblink > 100.0f) leftblink = 100.0f;
Nose_smr.SetBlendShapeWeight(2, rightblink);
Nose_smr.SetBlendShapeWeight(3, leftblink);
}
瞬きの制御です。眉と同じですね。
private void MouthMove_blendshape(PartsData_face face)
{
SkinnedMeshRenderer Nose_smr;
Nose_smr = Nose.GetComponent<SkinnedMeshRenderer>();
float jowopen = (face.blendshape[(int)FaceBlendshapeName.JawOpen])*120f;
float mouthstretch = (face.blendshape[(int)FaceBlendshapeName.MouthStretchRight])*10000f;
if(jowopen > 100.0f) jowopen = 100.0f;
if(mouthstretch > 100.0f) mouthstretch = 100.0f;
Nose_smr.SetBlendShapeWeight(0, jowopen);
Nose_smr.SetBlendShapeWeight(1, mouthstretch);
}
口の制御です。
stretch(横方向)の変化量は非常に小さいようなので、係数を大きくしました。
右手パーツ管理
using System.Collections.Generic;
using UnityEngine;
namespace PartsData_righthand_ns{
[System.Serializable]
public class PartsData_righthand
{
public string parts = "";
public float[] x = new float[21];
public float[] y = new float[21];
public float[] z = new float[21];
}
public class RightHandManager : MonoBehaviour
{
public enum RightHandLandmarkName
{
Wrist = 0,
Thumb_Cmc,
Thumb_Mcp,
Thumb_Ip,
Thumb_Tip,
Index_Finger_Mcp,
Index_Finger_Pip,
Index_Finger_Dip,
Index_Finger_Tip,
Middle_Finger_Mcp,
Middle_Finger_Pip,
Middle_Finger_Dip,
Middle_Finger_Tip,
Ring_Finger_Mcp,
Ring_Finger_Pip,
Ring_Finger_Dip,
Ring_Finger_Tip,
Pinky_Finger_Mcp,
Pinky_Finger_Pip,
Pinky_Finger_Dip,
Pinky_Finger_Tip,
HandLandmarkName_Max
}
private int[,] righthaand_CylinderIndexList = new int[21,2]
{
{(int)RightHandLandmarkName.Thumb_Mcp, (int)RightHandLandmarkName.Thumb_Cmc},
{(int)RightHandLandmarkName.Thumb_Ip, (int)RightHandLandmarkName.Thumb_Mcp},
{(int)RightHandLandmarkName.Thumb_Tip, (int)RightHandLandmarkName.Thumb_Ip},
{(int)RightHandLandmarkName.Index_Finger_Pip, (int)RightHandLandmarkName.Index_Finger_Mcp},
{(int)RightHandLandmarkName.Index_Finger_Dip, (int)RightHandLandmarkName.Index_Finger_Pip},
{(int)RightHandLandmarkName.Index_Finger_Tip, (int)RightHandLandmarkName.Index_Finger_Dip},
{(int)RightHandLandmarkName.Middle_Finger_Pip, (int)RightHandLandmarkName.Middle_Finger_Mcp},
{(int)RightHandLandmarkName.Middle_Finger_Dip, (int)RightHandLandmarkName.Middle_Finger_Pip},
{(int)RightHandLandmarkName.Middle_Finger_Tip, (int)RightHandLandmarkName.Middle_Finger_Dip},
{(int)RightHandLandmarkName.Ring_Finger_Pip, (int)RightHandLandmarkName.Ring_Finger_Mcp},
{(int)RightHandLandmarkName.Ring_Finger_Dip, (int)RightHandLandmarkName.Ring_Finger_Pip},
{(int)RightHandLandmarkName.Ring_Finger_Tip, (int)RightHandLandmarkName.Ring_Finger_Dip},
{(int)RightHandLandmarkName.Pinky_Finger_Pip, (int)RightHandLandmarkName.Pinky_Finger_Mcp},
{(int)RightHandLandmarkName.Pinky_Finger_Dip, (int)RightHandLandmarkName.Pinky_Finger_Pip},
{(int)RightHandLandmarkName.Pinky_Finger_Tip, (int)RightHandLandmarkName.Pinky_Finger_Dip},
{(int)RightHandLandmarkName.Middle_Finger_Mcp, (int)RightHandLandmarkName.Index_Finger_Mcp},
{(int)RightHandLandmarkName.Ring_Finger_Mcp, (int)RightHandLandmarkName.Middle_Finger_Mcp},
{(int)RightHandLandmarkName.Pinky_Finger_Mcp, (int)RightHandLandmarkName.Ring_Finger_Mcp},
{(int)RightHandLandmarkName.Thumb_Cmc, (int)RightHandLandmarkName.Wrist},
{(int)RightHandLandmarkName.Index_Finger_Mcp, (int)RightHandLandmarkName.Wrist},
{(int)RightHandLandmarkName.Pinky_Finger_Mcp, (int)RightHandLandmarkName.Wrist}
};
public static Queue<string> rcvData_righthand;
public Queue<PartsData_righthand> moveData_righthand;
[SerializeField] GameObject righthand_Wrist;
[SerializeField] GameObject righthand_Thumb_Cmc;
[SerializeField] GameObject righthand_Thumb_Mcp;
[SerializeField] GameObject righthand_Thumb_Ip;
[SerializeField] GameObject righthand_Thumb_Tip;
[SerializeField] GameObject righthand_Index_Finger_Mcp;
[SerializeField] GameObject righthand_Index_Finger_Pip;
[SerializeField] GameObject righthand_Index_Finger_Dip;
[SerializeField] GameObject righthand_Index_Finger_Tip;
[SerializeField] GameObject righthand_Middle_Finger_Mcp;
[SerializeField] GameObject righthand_Middle_Finger_Pip;
[SerializeField] GameObject righthand_Middle_Finger_Dip;
[SerializeField] GameObject righthand_Middle_Finger_Tip;
[SerializeField] GameObject righthand_Ring_Finger_Mcp;
[SerializeField] GameObject righthand_Ring_Finger_Pip;
[SerializeField] GameObject righthand_Ring_Finger_Dip;
[SerializeField] GameObject righthand_Ring_Finger_Tip;
[SerializeField] GameObject righthand_Pinky_Finger_Mcp;
[SerializeField] GameObject righthand_Pinky_Finger_Pip;
[SerializeField] GameObject righthand_Pinky_Finger_Dip;
[SerializeField] GameObject righthand_Pinky_Finger_Tip;
private GameObject[] righthand_objs;
private GameObject righthand_Cylinder_00;
private GameObject righthand_Cylinder_01;
private GameObject righthand_Cylinder_02;
private GameObject righthand_Cylinder_03;
private GameObject righthand_Cylinder_04;
private GameObject righthand_Cylinder_05;
private GameObject righthand_Cylinder_06;
private GameObject righthand_Cylinder_07;
private GameObject righthand_Cylinder_08;
private GameObject righthand_Cylinder_09;
private GameObject righthand_Cylinder_10;
private GameObject righthand_Cylinder_11;
private GameObject righthand_Cylinder_12;
private GameObject righthand_Cylinder_13;
private GameObject righthand_Cylinder_14;
private GameObject righthand_Cylinder_15;
private GameObject righthand_Cylinder_16;
private GameObject righthand_Cylinder_17;
private GameObject righthand_Cylinder_18;
private GameObject righthand_Cylinder_19;
private GameObject righthand_Cylinder_20;
private GameObject[] righthand_Cylinder_objs;
private int hidecnt;
PartsData_righthand prev_moveData_righthand;
private int rcvdata_devide; //受信データを分割して滑らかにする
void Start()
{
hidecnt = 0;
rcvdata_devide = 2;
rcvData_righthand = new Queue<string>();
moveData_righthand = new Queue<PartsData_righthand>();
prev_moveData_righthand = null;
righthand_objs = new GameObject[(int)RightHandLandmarkName.HandLandmarkName_Max];
righthand_objs[(int)RightHandLandmarkName.Wrist] = righthand_Wrist;
righthand_objs[(int)RightHandLandmarkName.Thumb_Cmc] = righthand_Thumb_Cmc;
righthand_objs[(int)RightHandLandmarkName.Thumb_Mcp] = righthand_Thumb_Mcp;
righthand_objs[(int)RightHandLandmarkName.Thumb_Ip] = righthand_Thumb_Ip;
righthand_objs[(int)RightHandLandmarkName.Thumb_Tip] = righthand_Thumb_Tip;
righthand_objs[(int)RightHandLandmarkName.Index_Finger_Mcp] = righthand_Index_Finger_Mcp;
righthand_objs[(int)RightHandLandmarkName.Index_Finger_Pip] = righthand_Index_Finger_Pip;
righthand_objs[(int)RightHandLandmarkName.Index_Finger_Dip] = righthand_Index_Finger_Dip;
righthand_objs[(int)RightHandLandmarkName.Index_Finger_Tip] = righthand_Index_Finger_Tip;
righthand_objs[(int)RightHandLandmarkName.Middle_Finger_Mcp] = righthand_Middle_Finger_Mcp;
righthand_objs[(int)RightHandLandmarkName.Middle_Finger_Pip] = righthand_Middle_Finger_Pip;
righthand_objs[(int)RightHandLandmarkName.Middle_Finger_Dip] = righthand_Middle_Finger_Dip;
righthand_objs[(int)RightHandLandmarkName.Middle_Finger_Tip] = righthand_Middle_Finger_Tip;
righthand_objs[(int)RightHandLandmarkName.Ring_Finger_Mcp] = righthand_Ring_Finger_Mcp;
righthand_objs[(int)RightHandLandmarkName.Ring_Finger_Pip] = righthand_Ring_Finger_Pip;
righthand_objs[(int)RightHandLandmarkName.Ring_Finger_Dip] = righthand_Ring_Finger_Dip;
righthand_objs[(int)RightHandLandmarkName.Ring_Finger_Tip] = righthand_Ring_Finger_Tip;
righthand_objs[(int)RightHandLandmarkName.Pinky_Finger_Mcp] = righthand_Pinky_Finger_Mcp;
righthand_objs[(int)RightHandLandmarkName.Pinky_Finger_Pip] = righthand_Pinky_Finger_Pip;
righthand_objs[(int)RightHandLandmarkName.Pinky_Finger_Dip] = righthand_Pinky_Finger_Dip;
righthand_objs[(int)RightHandLandmarkName.Pinky_Finger_Tip] = righthand_Pinky_Finger_Tip;
righthand_Cylinder_objs = new GameObject[righthaand_CylinderIndexList.GetLength(0)];
righthand_Cylinder_objs[0] = righthand_Cylinder_00;
righthand_Cylinder_objs[1] = righthand_Cylinder_01;
righthand_Cylinder_objs[2] = righthand_Cylinder_02;
righthand_Cylinder_objs[3] = righthand_Cylinder_03;
righthand_Cylinder_objs[4] = righthand_Cylinder_04;
righthand_Cylinder_objs[5] = righthand_Cylinder_05;
righthand_Cylinder_objs[6] = righthand_Cylinder_06;
righthand_Cylinder_objs[7] = righthand_Cylinder_07;
righthand_Cylinder_objs[8] = righthand_Cylinder_08;
righthand_Cylinder_objs[9] = righthand_Cylinder_09;
righthand_Cylinder_objs[10] = righthand_Cylinder_10;
righthand_Cylinder_objs[11] = righthand_Cylinder_11;
righthand_Cylinder_objs[12] = righthand_Cylinder_12;
righthand_Cylinder_objs[13] = righthand_Cylinder_13;
righthand_Cylinder_objs[14] = righthand_Cylinder_14;
righthand_Cylinder_objs[15] = righthand_Cylinder_15;
righthand_Cylinder_objs[16] = righthand_Cylinder_16;
righthand_Cylinder_objs[17] = righthand_Cylinder_17;
righthand_Cylinder_objs[18] = righthand_Cylinder_18;
righthand_Cylinder_objs[19] = righthand_Cylinder_19;
righthand_Cylinder_objs[20] = righthand_Cylinder_20;
for(int idx=0; idx<righthaand_CylinderIndexList.GetLength(0); idx++)
{
righthand_Cylinder_objs[idx] = GameObject.CreatePrimitive(PrimitiveType.Sphere);
righthand_Cylinder_objs[idx].transform.localScale = new Vector3(0.1f, 0.1f, 0.1f);
}
ChangeVisibility(false);
}
void Update()
{
hidecnt++;
if((rcvData_righthand != null) && (rcvData_righthand.Count != 0))
{
string rcvdata = rcvData_righthand.Dequeue();
PartsData_righthand rcv_righthand = JsonUtility.FromJson<PartsData_righthand>(rcvdata);
//今回値を前回値の差を、今回値(目標)に向かって数回に分けて移動させる
if(prev_moveData_righthand != null)
{
PartsData_righthand div_righthand = new PartsData_righthand();
//前回値を開始地点とする
div_righthand.parts = prev_moveData_righthand.parts;
for(int idx=(int)RightHandLandmarkName.Wrist; idx<(int)RightHandLandmarkName.HandLandmarkName_Max; idx++)
{
div_righthand.x[idx] = prev_moveData_righthand.x[idx];
div_righthand.y[idx] = prev_moveData_righthand.y[idx];
div_righthand.z[idx] = prev_moveData_righthand.z[idx];
}
//今回値と前回値の差分を分割し、キューへ格納する
for(int div=0; div<rcvdata_devide-1; div++)
{
for(int idx=(int)RightHandLandmarkName.Wrist; idx<(int)RightHandLandmarkName.HandLandmarkName_Max; idx++)
{
div_righthand.x[idx] += (rcv_righthand.x[idx] - prev_moveData_righthand.x[idx]) / rcvdata_devide;
div_righthand.y[idx] += (rcv_righthand.y[idx] - prev_moveData_righthand.y[idx]) / rcvdata_devide;
div_righthand.z[idx] += (rcv_righthand.z[idx] - prev_moveData_righthand.z[idx]) / rcvdata_devide;
}
moveData_righthand.Enqueue(div_righthand);
}
}
else
{
//ここを実行するのは初回のみ
prev_moveData_righthand = new PartsData_righthand();
}
//分割数によっては最終値が中途半端になるため、最後は今回値ぴったりで終わらせる
moveData_righthand.Enqueue(rcv_righthand);
//今回値を前回値として保持
prev_moveData_righthand.parts = rcv_righthand.parts;
for(int idx=(int)RightHandLandmarkName.Wrist; idx<(int)RightHandLandmarkName.HandLandmarkName_Max; idx++)
{
prev_moveData_righthand.x[idx] = rcv_righthand.x[idx];
prev_moveData_righthand.y[idx] = rcv_righthand.y[idx];
prev_moveData_righthand.z[idx] = rcv_righthand.z[idx];
}
}
if((moveData_righthand != null) && (moveData_righthand.Count != 0))
{
PartsData_righthand right_hand = moveData_righthand.Dequeue();
hidecnt = 0;
//右手に連動する
for(int idx=(int)RightHandLandmarkName.Wrist; idx<(int)RightHandLandmarkName.HandLandmarkName_Max; idx++)
{
Transform myTransform;
myTransform = righthand_objs[idx].transform;
Vector3 pos = myTransform.position;
pos.x = right_hand.x[idx]*(-10f)+5f;
pos.y = right_hand.y[idx]*(-6f)+3f;
pos.z = right_hand.z[idx]*10f-3;
myTransform.position = pos;
}
LandmarkJoint(righthand_objs, righthand_Cylinder_objs);
ChangeVisibility(true);
}
if(hidecnt > 100)
{
hidecnt = 0;
ChangeVisibility(false);
rcvData_righthand.Clear();
moveData_righthand.Clear();
}
}
private void OnApplicationQuit()
{
rcvData_righthand.Clear();
moveData_righthand.Clear();
}
private void OnDestroy()
{
rcvData_righthand.Clear();
moveData_righthand.Clear();
}
public static void SetRcvData(string rcvdata)
{
rcvData_righthand.Enqueue(rcvdata);
}
private void LandmarkJoint(GameObject[] hand_objs, GameObject[] cylinder_objs)
{
for(int idx=0; idx<righthaand_CylinderIndexList.GetLength(0); idx++)
{
cylinder_objs[idx].transform.position = Vector3.Lerp(hand_objs[righthaand_CylinderIndexList[idx,0]].transform.position, hand_objs[righthaand_CylinderIndexList[idx,1]].transform.position, 0.5f);
cylinder_objs[idx].transform.LookAt(hand_objs[righthaand_CylinderIndexList[idx,1]].transform.position);
cylinder_objs[idx].transform.localScale = new Vector3(0.2f, 0.2f, (hand_objs[righthaand_CylinderIndexList[idx,0]].transform.position - hand_objs[righthaand_CylinderIndexList[idx,1]].transform.position).magnitude);
}
}
private void ChangeVisibility(bool vis)
{
for(int idx=(int)RightHandLandmarkName.Wrist; idx<(int)RightHandLandmarkName.HandLandmarkName_Max; idx++)
{
righthand_objs[idx].GetComponent<Renderer>().enabled = vis;
}
for(int idx=0; idx<righthaand_CylinderIndexList.GetLength(0); idx++)
{
righthand_Cylinder_objs[idx].GetComponent<Renderer>().enabled = vis;
}
}
}
}
変更点を見ていきます。
private int[,] righthaand_CylinderIndexList = new int[21,2]
{
{(int)RightHandLandmarkName.Thumb_Mcp, (int)RightHandLandmarkName.Thumb_Cmc},
{(int)RightHandLandmarkName.Thumb_Ip, (int)RightHandLandmarkName.Thumb_Mcp},
{(int)RightHandLandmarkName.Thumb_Tip, (int)RightHandLandmarkName.Thumb_Ip},
{(int)RightHandLandmarkName.Index_Finger_Pip, (int)RightHandLandmarkName.Index_Finger_Mcp},
{(int)RightHandLandmarkName.Index_Finger_Dip, (int)RightHandLandmarkName.Index_Finger_Pip},
{(int)RightHandLandmarkName.Index_Finger_Tip, (int)RightHandLandmarkName.Index_Finger_Dip},
{(int)RightHandLandmarkName.Middle_Finger_Pip, (int)RightHandLandmarkName.Middle_Finger_Mcp},
{(int)RightHandLandmarkName.Middle_Finger_Dip, (int)RightHandLandmarkName.Middle_Finger_Pip},
{(int)RightHandLandmarkName.Middle_Finger_Tip, (int)RightHandLandmarkName.Middle_Finger_Dip},
{(int)RightHandLandmarkName.Ring_Finger_Pip, (int)RightHandLandmarkName.Ring_Finger_Mcp},
{(int)RightHandLandmarkName.Ring_Finger_Dip, (int)RightHandLandmarkName.Ring_Finger_Pip},
{(int)RightHandLandmarkName.Ring_Finger_Tip, (int)RightHandLandmarkName.Ring_Finger_Dip},
{(int)RightHandLandmarkName.Pinky_Finger_Pip, (int)RightHandLandmarkName.Pinky_Finger_Mcp},
{(int)RightHandLandmarkName.Pinky_Finger_Dip, (int)RightHandLandmarkName.Pinky_Finger_Pip},
{(int)RightHandLandmarkName.Pinky_Finger_Tip, (int)RightHandLandmarkName.Pinky_Finger_Dip},
{(int)RightHandLandmarkName.Middle_Finger_Mcp, (int)RightHandLandmarkName.Index_Finger_Mcp},
{(int)RightHandLandmarkName.Ring_Finger_Mcp, (int)RightHandLandmarkName.Middle_Finger_Mcp},
{(int)RightHandLandmarkName.Pinky_Finger_Mcp, (int)RightHandLandmarkName.Ring_Finger_Mcp},
{(int)RightHandLandmarkName.Thumb_Cmc, (int)RightHandLandmarkName.Wrist},
{(int)RightHandLandmarkName.Index_Finger_Mcp, (int)RightHandLandmarkName.Wrist},
{(int)RightHandLandmarkName.Pinky_Finger_Mcp, (int)RightHandLandmarkName.Wrist}
};
どこのLandmarkどうしを接続するかを定義するindexリストです。
void Start()
{
・・・
for(int idx=0; idx<righthaand_CylinderIndexList.GetLength(0); idx++)
{
righthand_Cylinder_objs[idx] = GameObject.CreatePrimitive(PrimitiveType.Sphere);
righthand_Cylinder_objs[idx].transform.localScale = new Vector3(0.1f, 0.1f, 0.1f);
}
Landmarkどうしの接続に使用するオブジェクトは、ここで動的に生成しています。
オブジェクトをヒエラルキーに用意しておくか、動的に生成するか、どちらでもOKです。
private void LandmarkJoint(GameObject[] hand_objs, GameObject[] cylinder_objs)
{
for(int idx=0; idx<righthaand_CylinderIndexList.GetLength(0); idx++)
{
cylinder_objs[idx].transform.position = Vector3.Lerp(hand_objs[righthaand_CylinderIndexList[idx,0]].transform.position, hand_objs[righthaand_CylinderIndexList[idx,1]].transform.position, 0.5f);
cylinder_objs[idx].transform.LookAt(hand_objs[righthaand_CylinderIndexList[idx,1]].transform.position);
cylinder_objs[idx].transform.localScale = new Vector3(0.2f, 0.2f, (hand_objs[righthaand_CylinderIndexList[idx,0]].transform.position - hand_objs[righthaand_CylinderIndexList[idx,1]].transform.position).magnitude);
}
}
2点のLandmarkをオブジェクトで接続します。
2点の座標の中間点は、Vector3.Lerp()で求められます。
LookAtで、オブジェクトの正面(ローカルZ軸プラス方向)をLandmarkの方向に向けます。
localScaleでZ軸を可変します。長さは (座標1 – 座標2).magnitude で求められます。
左手パーツ管理
using System.Collections.Generic;
using UnityEngine;
namespace PartsData_lefthand_ns{
[System.Serializable]
public class PartsData_lefthand
{
public string parts = "";
public float[] x= new float[21];
public float[] y= new float[21];
public float[] z= new float[21];
}
public class LeftHandManager : MonoBehaviour
{
public enum LeftHandLandmarkName
{
Wrist = 0,
Thumb_Cmc,
Thumb_Mcp,
Thumb_Ip,
Thumb_Tip,
Index_Finger_Mcp,
Index_Finger_Pip,
Index_Finger_Dip,
Index_Finger_Tip,
Middle_Finger_Mcp,
Middle_Finger_Pip,
Middle_Finger_Dip,
Middle_Finger_Tip,
Ring_Finger_Mcp,
Ring_Finger_Pip,
Ring_Finger_Dip,
Ring_Finger_Tip,
Pinky_Finger_Mcp,
Pinky_Finger_Pip,
Pinky_Finger_Dip,
Pinky_Finger_Tip,
HandLandmarkName_Max
}
private int[,] lefthaand_CylinderIndexList = new int[21,2]
{
{(int)LeftHandLandmarkName.Thumb_Mcp, (int)LeftHandLandmarkName.Thumb_Cmc},
{(int)LeftHandLandmarkName.Thumb_Ip, (int)LeftHandLandmarkName.Thumb_Mcp},
{(int)LeftHandLandmarkName.Thumb_Tip, (int)LeftHandLandmarkName.Thumb_Ip},
{(int)LeftHandLandmarkName.Index_Finger_Pip, (int)LeftHandLandmarkName.Index_Finger_Mcp},
{(int)LeftHandLandmarkName.Index_Finger_Dip, (int)LeftHandLandmarkName.Index_Finger_Pip},
{(int)LeftHandLandmarkName.Index_Finger_Tip, (int)LeftHandLandmarkName.Index_Finger_Dip},
{(int)LeftHandLandmarkName.Middle_Finger_Pip, (int)LeftHandLandmarkName.Middle_Finger_Mcp},
{(int)LeftHandLandmarkName.Middle_Finger_Dip, (int)LeftHandLandmarkName.Middle_Finger_Pip},
{(int)LeftHandLandmarkName.Middle_Finger_Tip, (int)LeftHandLandmarkName.Middle_Finger_Dip},
{(int)LeftHandLandmarkName.Ring_Finger_Pip, (int)LeftHandLandmarkName.Ring_Finger_Mcp},
{(int)LeftHandLandmarkName.Ring_Finger_Dip, (int)LeftHandLandmarkName.Ring_Finger_Pip},
{(int)LeftHandLandmarkName.Ring_Finger_Tip, (int)LeftHandLandmarkName.Ring_Finger_Dip},
{(int)LeftHandLandmarkName.Pinky_Finger_Pip, (int)LeftHandLandmarkName.Pinky_Finger_Mcp},
{(int)LeftHandLandmarkName.Pinky_Finger_Dip, (int)LeftHandLandmarkName.Pinky_Finger_Pip},
{(int)LeftHandLandmarkName.Pinky_Finger_Tip, (int)LeftHandLandmarkName.Pinky_Finger_Dip},
{(int)LeftHandLandmarkName.Middle_Finger_Mcp, (int)LeftHandLandmarkName.Index_Finger_Mcp},
{(int)LeftHandLandmarkName.Ring_Finger_Mcp, (int)LeftHandLandmarkName.Middle_Finger_Mcp},
{(int)LeftHandLandmarkName.Pinky_Finger_Mcp, (int)LeftHandLandmarkName.Ring_Finger_Mcp},
{(int)LeftHandLandmarkName.Thumb_Cmc, (int)LeftHandLandmarkName.Wrist},
{(int)LeftHandLandmarkName.Index_Finger_Mcp, (int)LeftHandLandmarkName.Wrist},
{(int)LeftHandLandmarkName.Pinky_Finger_Mcp, (int)LeftHandLandmarkName.Wrist}
};
public static Queue<string> rcvData_lefthand;
public Queue<PartsData_lefthand> moveData_lefthand;
[SerializeField] GameObject lefthand_Wrist;
[SerializeField] GameObject lefthand_Thumb_Cmc;
[SerializeField] GameObject lefthand_Thumb_Mcp;
[SerializeField] GameObject lefthand_Thumb_Ip;
[SerializeField] GameObject lefthand_Thumb_Tip;
[SerializeField] GameObject lefthand_Index_Finger_Mcp;
[SerializeField] GameObject lefthand_Index_Finger_Pip;
[SerializeField] GameObject lefthand_Index_Finger_Dip;
[SerializeField] GameObject lefthand_Index_Finger_Tip;
[SerializeField] GameObject lefthand_Middle_Finger_Mcp;
[SerializeField] GameObject lefthand_Middle_Finger_Pip;
[SerializeField] GameObject lefthand_Middle_Finger_Dip;
[SerializeField] GameObject lefthand_Middle_Finger_Tip;
[SerializeField] GameObject lefthand_Ring_Finger_Mcp;
[SerializeField] GameObject lefthand_Ring_Finger_Pip;
[SerializeField] GameObject lefthand_Ring_Finger_Dip;
[SerializeField] GameObject lefthand_Ring_Finger_Tip;
[SerializeField] GameObject lefthand_Pinky_Finger_Mcp;
[SerializeField] GameObject lefthand_Pinky_Finger_Pip;
[SerializeField] GameObject lefthand_Pinky_Finger_Dip;
[SerializeField] GameObject lefthand_Pinky_Finger_Tip;
GameObject[] lefthand_objs;
private GameObject lefthand_Cylinder_00;
private GameObject lefthand_Cylinder_01;
private GameObject lefthand_Cylinder_02;
private GameObject lefthand_Cylinder_03;
private GameObject lefthand_Cylinder_04;
private GameObject lefthand_Cylinder_05;
private GameObject lefthand_Cylinder_06;
private GameObject lefthand_Cylinder_07;
private GameObject lefthand_Cylinder_08;
private GameObject lefthand_Cylinder_09;
private GameObject lefthand_Cylinder_10;
private GameObject lefthand_Cylinder_11;
private GameObject lefthand_Cylinder_12;
private GameObject lefthand_Cylinder_13;
private GameObject lefthand_Cylinder_14;
private GameObject lefthand_Cylinder_15;
private GameObject lefthand_Cylinder_16;
private GameObject lefthand_Cylinder_17;
private GameObject lefthand_Cylinder_18;
private GameObject lefthand_Cylinder_19;
private GameObject lefthand_Cylinder_20;
private GameObject[] lefthand_Cylinder_objs;
private int hidecnt;
PartsData_lefthand prev_moveData_lefthand;
private int rcvdata_devide; //受信データを分割して滑らかにする
void Start()
{
hidecnt = 0;
rcvdata_devide = 2;
rcvData_lefthand = new Queue<string>();
moveData_lefthand = new Queue<PartsData_lefthand>();
prev_moveData_lefthand = null;
lefthand_objs = new GameObject[(int)LeftHandLandmarkName.HandLandmarkName_Max];
lefthand_objs[(int)LeftHandLandmarkName.Wrist] = lefthand_Wrist;
lefthand_objs[(int)LeftHandLandmarkName.Thumb_Cmc] = lefthand_Thumb_Cmc;
lefthand_objs[(int)LeftHandLandmarkName.Thumb_Mcp] = lefthand_Thumb_Mcp;
lefthand_objs[(int)LeftHandLandmarkName.Thumb_Ip] = lefthand_Thumb_Ip;
lefthand_objs[(int)LeftHandLandmarkName.Thumb_Tip] = lefthand_Thumb_Tip;
lefthand_objs[(int)LeftHandLandmarkName.Index_Finger_Mcp] = lefthand_Index_Finger_Mcp;
lefthand_objs[(int)LeftHandLandmarkName.Index_Finger_Pip] = lefthand_Index_Finger_Pip;
lefthand_objs[(int)LeftHandLandmarkName.Index_Finger_Dip] = lefthand_Index_Finger_Dip;
lefthand_objs[(int)LeftHandLandmarkName.Index_Finger_Tip] = lefthand_Index_Finger_Tip;
lefthand_objs[(int)LeftHandLandmarkName.Middle_Finger_Mcp] = lefthand_Middle_Finger_Mcp;
lefthand_objs[(int)LeftHandLandmarkName.Middle_Finger_Pip] = lefthand_Middle_Finger_Pip;
lefthand_objs[(int)LeftHandLandmarkName.Middle_Finger_Dip] = lefthand_Middle_Finger_Dip;
lefthand_objs[(int)LeftHandLandmarkName.Middle_Finger_Tip] = lefthand_Middle_Finger_Tip;
lefthand_objs[(int)LeftHandLandmarkName.Ring_Finger_Mcp] = lefthand_Ring_Finger_Mcp;
lefthand_objs[(int)LeftHandLandmarkName.Ring_Finger_Pip] = lefthand_Ring_Finger_Pip;
lefthand_objs[(int)LeftHandLandmarkName.Ring_Finger_Dip] = lefthand_Ring_Finger_Dip;
lefthand_objs[(int)LeftHandLandmarkName.Ring_Finger_Tip] = lefthand_Ring_Finger_Tip;
lefthand_objs[(int)LeftHandLandmarkName.Pinky_Finger_Mcp] = lefthand_Pinky_Finger_Mcp;
lefthand_objs[(int)LeftHandLandmarkName.Pinky_Finger_Pip] = lefthand_Pinky_Finger_Pip;
lefthand_objs[(int)LeftHandLandmarkName.Pinky_Finger_Dip] = lefthand_Pinky_Finger_Dip;
lefthand_objs[(int)LeftHandLandmarkName.Pinky_Finger_Tip] = lefthand_Pinky_Finger_Tip;
lefthand_Cylinder_objs = new GameObject[lefthaand_CylinderIndexList.GetLength(0)];
lefthand_Cylinder_objs[0] = lefthand_Cylinder_00;
lefthand_Cylinder_objs[1] = lefthand_Cylinder_01;
lefthand_Cylinder_objs[2] = lefthand_Cylinder_02;
lefthand_Cylinder_objs[3] = lefthand_Cylinder_03;
lefthand_Cylinder_objs[4] = lefthand_Cylinder_04;
lefthand_Cylinder_objs[5] = lefthand_Cylinder_05;
lefthand_Cylinder_objs[6] = lefthand_Cylinder_06;
lefthand_Cylinder_objs[7] = lefthand_Cylinder_07;
lefthand_Cylinder_objs[8] = lefthand_Cylinder_08;
lefthand_Cylinder_objs[9] = lefthand_Cylinder_09;
lefthand_Cylinder_objs[10] = lefthand_Cylinder_10;
lefthand_Cylinder_objs[11] = lefthand_Cylinder_11;
lefthand_Cylinder_objs[12] = lefthand_Cylinder_12;
lefthand_Cylinder_objs[13] = lefthand_Cylinder_13;
lefthand_Cylinder_objs[14] = lefthand_Cylinder_14;
lefthand_Cylinder_objs[15] = lefthand_Cylinder_15;
lefthand_Cylinder_objs[16] = lefthand_Cylinder_16;
lefthand_Cylinder_objs[17] = lefthand_Cylinder_17;
lefthand_Cylinder_objs[18] = lefthand_Cylinder_18;
lefthand_Cylinder_objs[19] = lefthand_Cylinder_19;
lefthand_Cylinder_objs[20] = lefthand_Cylinder_20;
for(int idx=0; idx<lefthaand_CylinderIndexList.GetLength(0); idx++)
{
lefthand_Cylinder_objs[idx] = GameObject.CreatePrimitive(PrimitiveType.Sphere);
lefthand_Cylinder_objs[idx].transform.localScale = new Vector3(0.1f, 0.1f, 0.1f);
}
ChangeVisibility(false);
}
void Update()
{
hidecnt++;
if((rcvData_lefthand != null) && (rcvData_lefthand.Count != 0))
{
string rcvdata = rcvData_lefthand.Dequeue();
PartsData_lefthand rcv_lefthand = JsonUtility.FromJson<PartsData_lefthand>(rcvdata);
//今回値を前回値の差を、今回値(目標)に向かって数回に分けて移動させる
if(prev_moveData_lefthand != null)
{
PartsData_lefthand div_lefthand = new PartsData_lefthand();
//前回値を開始地点とする
div_lefthand.parts = prev_moveData_lefthand.parts;
for(int idx=(int)LeftHandLandmarkName.Wrist; idx<(int)LeftHandLandmarkName.HandLandmarkName_Max; idx++)
{
div_lefthand.x[idx] = prev_moveData_lefthand.x[idx];
div_lefthand.y[idx] = prev_moveData_lefthand.y[idx];
div_lefthand.z[idx] = prev_moveData_lefthand.z[idx];
}
//今回値と前回値の差分を分割し、キューへ格納する
for(int div=0; div<rcvdata_devide-1; div++)
{
for(int idx=(int)LeftHandLandmarkName.Wrist; idx<(int)LeftHandLandmarkName.HandLandmarkName_Max; idx++)
{
div_lefthand.x[idx] += (rcv_lefthand.x[idx] - prev_moveData_lefthand.x[idx]) / rcvdata_devide;
div_lefthand.y[idx] += (rcv_lefthand.y[idx] - prev_moveData_lefthand.y[idx]) / rcvdata_devide;
div_lefthand.z[idx] += (rcv_lefthand.z[idx] - prev_moveData_lefthand.z[idx]) / rcvdata_devide;
}
moveData_lefthand.Enqueue(div_lefthand);
}
}
else
{
//ここを実行するのは初回のみ
prev_moveData_lefthand = new PartsData_lefthand();
}
//分割数によっては最終値が中途半端になるため、最後は今回値ぴったりで終わらせる
moveData_lefthand.Enqueue(rcv_lefthand);
//今回値を前回値として保持
prev_moveData_lefthand.parts = rcv_lefthand.parts;
for(int idx=(int)LeftHandLandmarkName.Wrist; idx<(int)LeftHandLandmarkName.HandLandmarkName_Max; idx++)
{
prev_moveData_lefthand.x[idx] = rcv_lefthand.x[idx];
prev_moveData_lefthand.y[idx] = rcv_lefthand.y[idx];
prev_moveData_lefthand.z[idx] = rcv_lefthand.z[idx];
}
}
if((moveData_lefthand != null) && (moveData_lefthand.Count != 0))
{
PartsData_lefthand left_hand = moveData_lefthand.Dequeue();
hidecnt = 0;
//左手に連動する
for(int idx=(int)LeftHandLandmarkName.Wrist; idx<(int)LeftHandLandmarkName.HandLandmarkName_Max; idx++)
{
Transform myTransform;
myTransform = lefthand_objs[idx].transform;
Vector3 pos = myTransform.position;
pos.x = left_hand.x[idx]*(-10f)+5f;
pos.y = left_hand.y[idx]*(-6f)+3f;
pos.z = left_hand.z[idx]*10f-3;
myTransform.position = pos;
}
LandmarkJoint(lefthand_objs, lefthand_Cylinder_objs);
ChangeVisibility(true);
}
if(hidecnt > 100)
{
hidecnt = 0;
ChangeVisibility(false);
rcvData_lefthand.Clear();
moveData_lefthand.Clear();
}
}
private void OnApplicationQuit()
{
rcvData_lefthand.Clear();
moveData_lefthand.Clear();
}
private void OnDestroy()
{
rcvData_lefthand.Clear();
moveData_lefthand.Clear();
}
public static void SetRcvData(string rcvdata)
{
rcvData_lefthand.Enqueue(rcvdata);
}
private void LandmarkJoint(GameObject[] hand_objs, GameObject[] cylinder_objs)
{
for(int idx=0; idx<lefthaand_CylinderIndexList.GetLength(0); idx++)
{
cylinder_objs[idx].transform.position = Vector3.Lerp(hand_objs[lefthaand_CylinderIndexList[idx,0]].transform.position, hand_objs[lefthaand_CylinderIndexList[idx,1]].transform.position, 0.5f);
cylinder_objs[idx].transform.LookAt(hand_objs[lefthaand_CylinderIndexList[idx,1]].transform.position);
cylinder_objs[idx].transform.localScale = new Vector3(0.2f, 0.2f, (hand_objs[lefthaand_CylinderIndexList[idx,0]].transform.position - hand_objs[lefthaand_CylinderIndexList[idx,1]].transform.position).magnitude);
}
}
private void ChangeVisibility(bool vis)
{
for(int idx=(int)LeftHandLandmarkName.Wrist; idx<(int)LeftHandLandmarkName.HandLandmarkName_Max; idx++)
{
lefthand_objs[idx].GetComponent<Renderer>().enabled = vis;
}
for(int idx=0; idx<lefthaand_CylinderIndexList.GetLength(0); idx++)
{
lefthand_Cylinder_objs[idx].GetComponent<Renderer>().enabled = vis;
}
}
}
}
変更点は右手と同様です。
エディタ設定
BlendShapeが設定されているオブジェクトを選択すると、インスペクターにスライダーが表示されます。これを動かすことで、モデルがどのように動くかを確認できます。
値は0〜100の範囲になっています。
インスペクターのBlendShapeに並んでいる上から順に、0から始まる数字(ID)が設定されています。
これをスクリプトのSetBlendShapeWeight()の第1引数に指定します。
動作確認
眉・瞬き・口をBlendShapeで動かすことができました。
目と口はもう少し開くようにしても良いかも🤔
手も輪郭が付いたことで、少し人間に近づきましたね。
このアバターは味はあるかもしれませんが、使い続けるのはキツいので、もう少しちゃんと作り直したいです😭
BlenderとUnityの回転軸
ここ数日、Blenderのモデルを使う上で、回転軸の問題に詰まっていました。
Blenderの座標系は左手系
Unityは右手系
この違いは、Unityへインポートしたときに自動的に変換されるのですが、それが原因で意図しない向きになってしまうことがあります。
Unityにインポートしたときの回転を確認して、Blenderでそれを補正するようにモデルを回転させてエクスポートすると良いですね。
さいごに
アバターの顔を、MediaPipeのBlendShapeで動かすことができました。
手のモデルは、もう少し考えたら何とかなりそうな気がするので、頑張ってみます。
アバターの顔も何とかします😅
それでは、今回はここまで。
ありがとうございました😊
コメント