始めに
半年前ぐらいにVive Pro Eyeを触りましてちょっとしか遊んでなかったので、今回は取得した情報をモデルに反映させてみました。
今回使用するモデルはこちらです。
Vive Pro Eyeについては過去記事を参考にして下さい。SDKについて簡単にまとめています。
Vive Pro Eyeから瞳の座標を取得する
Vive Pro Eyeから瞳の座標を取得するためには SRanipal_Eye_v2.GetPupilPosition
を使います。
position
には瞳の座標が -1 ~ 1 の範囲で代入されます。(xは右、yは上方向が1に値する)
using UnityEngine; using ViveSR.anipal.Eye; public class GetPupilPositionSample : MonoBehaviour { void Update() { var leftPupilPosition = GetPupilPosition(EyeIndex.LEFT); var rightPupilPosition = GetPupilPosition(EyeIndex.RIGHT); Debug.Log($"Left Pupil Position:{leftPupilPosition}"); Debug.Log($"right Pupil Position:{rightPupilPosition}"); } private Vector2 GetPupilPosition(EyeIndex eyeIndex) { SRanipal_Eye_v2.GetPupilPosition(eyeIndex, out var position); return position; } }
また、SRanipal_Eye_v2
を使用するにはUnityのHierarchyに SRanipal_Eye_Framework
コンポーネントを追加して、Enable Eye Version
を Version 2
に設定する必要があります。
モデルの瞳を動かす
モデルの瞳の構造とその問題
今回使用するモデルの瞳は眼球のように球体ではなく、瞳の絵が白目に張ってある状態でした。
なので、瞳のオブジェクトを回転させても瞳の絵が回転するだけになりました。
また、瞳のオブジェクトを平行移動させた場合は瞳の絵が目からはみ出てしまう問題がありました。
任意の中心点を元に瞳を回転させる
考えてみれば当たり前ですが、現実の人の瞳は眼球の中心点を元に回転しています。この考えに基づいて、モデルの瞳のオブジェクトも同様に中心点を決めて回転させます。
以下モデルの瞳のオブジェクト回転させるサンプルです。
MaplePupilRotateCheck
using UnityEngine;
public class MaplePupilRotateCheck : MonoBehaviour
{
[SerializeField] private Transform _leftEyePupil;
[SerializeField] private Transform _rightEyePupil;
[SerializeField] private Transform _leftEyeCenter;
[SerializeField] private Transform _rightEyeCenter;
[SerializeField] private float _angleX;
[SerializeField] private float _angleY;
private Vector3 _originLeftEyePupilPosition;
private Quaternion _originLeftEyePupilRotation;
private Vector3 _originRightEyePupilPosition;
private Quaternion _originRightEyePupilRotation;
private Vector3 _leftDistance;
private Vector3 _rightDistance;
void Start()
{
_originLeftEyePupilPosition = _leftEyePupil.localPosition;
_originLeftEyePupilRotation = _leftEyePupil.localRotation;
_originRightEyePupilPosition = _rightEyePupil.localPosition;
_originRightEyePupilRotation = _rightEyePupil.localRotation;
Reset();
}
void Update()
{
// 実行中に中心点を移動させたとき用のリセット処理
if (Input.GetKeyDown(KeyCode.Space))
{
Reset();
return;
}
var localRotation = Quaternion.AngleAxis(_angleX, Vector3.right) * Quaternion.AngleAxis(_angleY, Vector3.up);
_leftEyePupil.localRotation = localRotation;
_rightEyePupil.localRotation = localRotation;
_leftEyePupil.localPosition = localRotation * _leftDistance + _leftEyeCenter.localPosition;
_rightEyePupil.localPosition = localRotation * _rightDistance + _rightEyeCenter.localPosition;
}
private void Reset()
{
_angleX = 0;
_angleY = 0;
_leftEyePupil.localPosition = _originLeftEyePupilPosition;
_leftEyePupil.localRotation = _originLeftEyePupilRotation;
_rightEyePupil.localPosition = _originRightEyePupilPosition;
_rightEyePupil.localRotation = _originRightEyePupilRotation;
_leftDistance = _leftEyePupil.localPosition - _leftEyeCenter.localPosition;
_rightDistance = _rightEyePupil.localPosition - _rightEyeCenter.localPosition;
}
}
上記のサンプルの実行結果は次のようになります。上手く中心点を設定することで、瞳がはみ出ることなく動かせています。
解説
初期化時に中心点から瞳までのベクトルを求めます。
この値は回転させるときに使用します。
_leftDistance = _leftEyePupil.localPosition - _leftEyeCenter.localPosition; _rightDistance = _rightEyePupil.localPosition - _rightEyeCenter.localPosition;
Update
で指定されたx軸とy軸の回転から回転量 localRotation
を求めて瞳をローカル空間で回転させます。
var localRotation = Quaternion.AngleAxis(_angleX, Vector3.right) * Quaternion.AngleAxis(_angleY, Vector3.up); _leftEyePupil.localRotation = localRotation; _rightEyePupil.localRotation = localRotation;
次に瞳の座標を設定します。初期化時に求めた中心点から瞳までのベクトルに先程求めた回転量を掛け合わせて、中心点の座標に加算します。
こうすることで、中心点を元に瞳を回転させられます。
_leftEyePupil.localPosition = localRotation * _leftDistance + _leftEyeCenter.localPosition; _rightEyePupil.localPosition = localRotation * _rightDistance + _rightEyeCenter.localPosition;
※ Transform.RotateAroundを使わなかった理由
任意のオブジェクトを中心に回転させるメソッドとして Transform.RotateAround
があります。
Transform-RotateAround - Unity スクリプトリファレンス
こちらは回転量を指定して現在の座標から回転させるメソッドになります。(上記のプログラムでは元の座標から回転させる)
Vive Pro Eyeで取得した瞳の座標から Transform.RotateAround
を使用するには前フレームとの誤差を取得して回転量を求める等かなり面倒くさい方法をしなければならなかったため、使用しませんでした。
Vive Pro Eyeと組み合わせる
後はVive Pro Eyeと瞳を動かすプログラムを組み合わせます。
以下サンプルです。
MaplePupilRotate
using UnityEngine;
public class MaplePupilRotate : MonoBehaviour
{
[SerializeField] private Transform _leftPupil;
[SerializeField] private Transform _rightPupil;
[SerializeField] private Transform _leftEyeCenter;
[SerializeField] private Transform _rightEyeCenter;
[SerializeField] private float _maxAngleX;
[SerializeField] private float _maxAngleY;
private Vector3 _leftEyeCenterLocalPosition;
private Vector3 _rightEyeCenterLocalPosition;
private Vector3 _distanceFromLeftCenterToPupil;
private Vector3 _distanceFromRightCenterToPupil;
public void Awake()
{
_leftEyeCenterLocalPosition = _leftEyeCenter.localPosition;
_rightEyeCenterLocalPosition = _rightEyeCenter.localPosition;
_distanceFromLeftCenterToPupil = _leftPupil.localPosition - _leftEyeCenterLocalPosition;
_distanceFromRightCenterToPupil = _rightPupil.localPosition - _rightEyeCenterLocalPosition;
}
public void SetLeftPupilPosition(Vector2 pupilPosition)
{
var localRotation = CalculateEyeRotation(pupilPosition);
_leftPupil.localRotation = localRotation;
_leftPupil.localPosition = localRotation * _distanceFromLeftCenterToPupil + _leftEyeCenterLocalPosition;
}
public void SetRightPupilPosition(Vector2 pupilPosition)
{
var localRotation = CalculateEyeRotation(pupilPosition);
_rightPupil.localRotation = localRotation;
_rightPupil.localPosition = localRotation * _distanceFromRightCenterToPupil + _rightEyeCenterLocalPosition;
}
private Quaternion CalculateEyeRotation(Vector2 pupilPosition)
{
// MEMO: pupilPositionは瞳の座標、計算では瞳の回転角度なので x, y が逆になっている
var angleX = pupilPosition.y * _maxAngleX;
var angleY = pupilPosition.x * _maxAngleY;
return Quaternion.AngleAxis(angleX, Vector3.left) * Quaternion.AngleAxis(angleY, Vector3.up);
}
}
MaplePupilSample
using UnityEngine;
using ViveSR.anipal.Eye;
public class MaplePupilSample : MonoBehaviour
{
[SerializeField] private MaplePupilRotate _maplePupilRotate;
void Update()
{
var leftPupilPosition = GetPupilPosition(EyeIndex.LEFT);
var rightPupilPosition = GetPupilPosition(EyeIndex.RIGHT);
_maplePupilRotate.SetLeftPupilPosition(leftPupilPosition);
_maplePupilRotate.SetRightPupilPosition(rightPupilPosition);
}
private Vector2 GetPupilPosition(EyeIndex eyeIndex)
{
SRanipal_Eye_v2.GetPupilPosition(eyeIndex, out var position);
return position;
}
}
実行結果
上記のサンプル以外にまばたきするプログラムも動かしています。
また、シェイプキーでしいたけ目にしたり目を小さくしても問題なく動きました。
最後にウィンクをしてみました。(苦手なので許してください)瞳がぶれたりしないか心配でしたが、問題なさそうです。
最後に
アバターに瞬きや目線を加えるだけで、かなり生きてる感が増して楽しかったです。
今後はフェイシャルトラッカーにも挑戦してみる予定です。
また、丁度VRCでもOSCを使えばVive Pro Eyeやフェイシャルトラッカーの情報も送信できるみたいなので機会があれば触ってみようと思います。