Unity
Unityちゃん
WindowsMixedReality
WindowsMR
WinMR

WindowsMRでバーチャルYouTuberぽい事をしたい時[キャラクターになりきり編]

はじめに

・とりあえず動かないとやる気でない人
・手っ取り早く例が欲しい人
・解説より動く事を重視する人
・我初心者成
等々の方々向けです。

レベルの高い方々には退屈だったりえーって思われる内容を含ます。

解説を省くところが多いですが、それでも問題ない方はどうぞ読んでいって下さい。

更新履歴:
2018/01/25:語彙の修正と若干の加筆

今回の環境

Unity 2017.2.0f3
HoloToolkit-Unity-v1.Dev.2017.2.1.unitypackage
HoloToolkit-Unity-Examples-v1.Dev.2017.2.1.unitypackage
Final IK 1.6.1 (VRIK) $90
(↑これに関しては他のモノでも代用可能だが、大変便利なのでオススメです。)

UnityでのWindowsMRの開発に必要なモノ

MRTKを入れる。
↓ MicrosoftがGithubに出しています。
MixedRealityToolkit-Unity
↑ から2つのパッケージをダウンロードして、Unityにブッコミましょう。
HoloToolkit-Unity-v1.Dev.2017.2.1.unitypackage
HoloToolkit-Unity-Examples-v1.Dev.2017.2.1.unitypackage

パッケージの導入が完了したら、
ツールバーのMixed Reality Toolkitより、Configureから
Apply Mixed Reality Project Settings
Apply Mixed Reality Scene Settings
の2つを全部チェック入れてApplyしちゃいましょう。
(本来で有れば全てチェックする必要は無いですが説明を省く為全部チェックで)
そうしますと諸々、必要なモノが出てくると思います。

キ2526.PNG

とりあえずMR関連の導入はこれで完了です。

なりたい自分をインポートする!

今回はUnityちゃんにお願いしましょう!
(今回はUnityちゃんですが、他のモデルでも基本手順は変わりません。)
yunite.PNG

Unityちゃんを召喚しましたら、Unityちゃんを動かすためにFinal IKからVRIKをアタッチします。

VRIK.png

ではVRIKの中身を見てみましょう
Referenceskoko.PNG
まずは、Referencesを開いて見てみると、各部位が割り当てられている事が分かりますね!!!
(最悪、分からなくてもUnityちゃんだけでやるのであれば問題無いので気にせず行きましょう。)

では次です。
VRIKtate.PNG
Solverを開いて見ると、幾つか身体の部位が書かれていますね。

とりあえずLeft Armを見てみましょう!
今回見てほしいところはTargetという項目です!!!
VRIKは大変便利でこのTargetを追従して自然に人間のモデルを動かしてくれます。

なので、今回作るのはTargetとなるオブジェクトとそれを動かす為のスクリプトですね!!!

WindowsMRで身体の動きをトラッキングしてみよう

WindowsMRでは、頭と右手と左手がトラッキングの対象になります!

3つのTarget対象用のオブジェクトを作成します。

先程、Applyしたものをそのまま使う事も出来ますが、説明的に色々煩雑になってしまいますので、下記構成ぽい感じで作って下さい。

  • MR_Camera_Root
    • HeightOffset
      • MixedRealityCamera
      • MR_Controllers
        • R_HAND
        • L_HAND
        • T_HEAD

tetetetetetetete.PNG

各オブジェクトの説明
MR_Camera_Root 管理用の空オブジェクト
HeightOffset キャラクターの背丈に合わせる為のオブジェクト
MixedRealityCamera ApplyしたMixedRealityCameraParentの中に有りますのでそのままコピーして持ってきて下さい。MR用のカメラ
MR_Controllers Targetオブジェクトを管理するオブジェクト
R_HAND 右手!
L_HAND 左手!
T_HEAD 頭部!

MixedRealityCameraが2つあると色々動きがヤバイので、MixedRealityCameraParentの無効化を忘れずやっておきましょう!!!

まずは、各部の調整をしていきましょう。
(今回はUnityちゃんの例としての値になります。その他のモデルでは当たり前ですが大きく差が出ますのでご注意を)

HeightOffsetの位置を調整してUnityちゃんの視点を作りましょう
HeiOff.PNG
positionをY 1.4 の Z 0.1ですね(1.4だと若干高いかもしれないので1.37とかも有りです。調整してみて下さい)

MixedRealityCameraはClipping Planes Near を0.03位にしておきましょう

MRCAMERASER.PNG

カメラの都合上、顔面の裏側を見る事になりホラーな場合が有ります。
ホラー回避の為、顔だけ別のレイヤーを割り当てて、非表示設定にしましょう。

Unityちゃんの顔を選択すると顔だけ選択されますね!
レイヤーを適当に変更しましょう!

kaooooooooooooo.PNG

そして、MRをカメラ側で顔のレイヤーを非表示に設定します。

MRkamera.png

Near Clipの調整を行います。

大体0.15とかで良いと思います。
この値はどれだけの近さまで描画するかという値になります。
コレもホラー回避用です。
ner.PNG

各Targetオブジェクトを入れ行きましょう。
tttttttttt.PNG

ビルドをキャプチャ画像な感じにしておいて下さい
buildset.PNG

PlayersettingsにMRがある事を確認しておいて下さい
pppppset.PNG

これで調整はとりあえずおkです。

トラッキングするスクリプト

これで各部の調整が終わり、いよいよ各Targetオブジェクトを動かすスクリプトの出番です!!!

ここまで長かったと思いますが、これでUnityちゃんになれるね。
やったねたえちゃん。

下記のスクリプトを面倒で有れば読まずにコピペで、面倒で無ければ読んでからコピペしてスクリプトを作りましょう。
(このスクリプトに非効率的な書き方が含まれます。見るに堪えない方々は書き直してからお使い下さい。)
コメントが足りないとか不親切だとかそんなモンはどっかにやってとりあえず動かしてみましょう!!!

MR_CONT.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.XR;
using UnityEngine.XR.WSA.Input;

namespace HoloToolkit.Unity
{

public class MR_CONT : MonoBehaviour {

    public GameObject CRO_OBJ_R;//コントロール対象オブジェクト 右手
    public GameObject CRO_OBJ_L;//コントロール対象オブジェクト 左手


    public GameObject CRO_OBJ_Head;//コントロール対象オブジェクト 頭

    public GameObject CRO_OBJ_Camera;//コントロール値取得元、対象オブジェクト(まぁカメラ

    //RとLの座標値と方向
    public Vector3 koko_R;
    public Quaternion pritelig_R;
    public Vector3 koko_L;
    public Quaternion pritelig_L;

    public float H_offset_R_x;
    public float H_offset_R_y;
    public float H_offset_R_z;

    public float H_offset_L_x;
    public float H_offset_L_y;
    public float H_offset_L_z;

    public float Offset_Head_x;
    public float Offset_Head_y;
    public float Offset_Head_z;

    private class ControllerState //トラッキングの為のプライベートクラス
    {
        public InteractionSourceHandedness Handedness;
        public Vector3 PointerPosition;
        public Quaternion PointerRotation;
        public Vector3 GripPosition;
        public Quaternion GripRotation;
    }

    private Dictionary<uint, ControllerState> controllers;

    private void Awake()
    {
    controllers = new Dictionary<uint, ControllerState>();//キーと値のリストを生成する。名前はControllerState

    //イベント管理 スタート アップデート トラッキングロスト
    InteractionManager.InteractionSourceDetected += InteractionManager_InteractionSourceDetected;
    InteractionManager.InteractionSourceLost += InteractionManager_InteractionSourceLost;
    InteractionManager.InteractionSourceUpdated += InteractionManager_InteractionSourceUpdated;
    }


     void Start()
    {
        //トラッキング方式の変更
         // デフォルトはTrackingSpaceType.RoomScale
         //今回の変更はその場でトラッキングするという方式に変更
        UnityEngine.XR.XRDevice.SetTrackingSpaceType(TrackingSpaceType.Stationary);
    }

    void Update()
    {
        //頭部は常にトラッキングしている為、updateで取得する。
        CRO_OBJ_Head.transform.localPosition = CRO_OBJ_Camera.transform.localPosition;
        CRO_OBJ_Head.transform.localRotation = CRO_OBJ_Camera.transform.localRotation;
        CRO_OBJ_Head.transform.localRotation = CRO_OBJ_Head.transform.localRotation * Quaternion.Euler(Offset_Head_x, Offset_Head_y, Offset_Head_z);
    }


    private void InteractionManager_InteractionSourceDetected(InteractionSourceDetectedEventArgs obj)//トラッキングスタート
    {
        if (obj.state.source.kind == InteractionSourceKind.Controller && !controllers.ContainsKey(obj.state.source.id))
        {
            controllers.Add(obj.state.source.id, new ControllerState { Handedness = obj.state.source.handedness });
        }
    }

    private void InteractionManager_InteractionSourceLost(InteractionSourceLostEventArgs obj)//トラッキングロスト
    {
        controllers.Remove(obj.state.source.id);
    }

        private void InteractionManager_InteractionSourceUpdated(InteractionSourceUpdatedEventArgs obj)//トラッキングアップデート
        {
            ControllerState controllerState;
            if (controllers.TryGetValue(obj.state.source.id, out controllerState))
            {
                obj.state.sourcePose.TryGetPosition(out controllerState.PointerPosition, InteractionSourceNode.Pointer);
                obj.state.sourcePose.TryGetRotation(out controllerState.PointerRotation, InteractionSourceNode.Pointer);
                obj.state.sourcePose.TryGetPosition(out controllerState.GripPosition, InteractionSourceNode.Grip);
                obj.state.sourcePose.TryGetRotation(out controllerState.GripRotation, InteractionSourceNode.Grip);

                if (controllerState.Handedness.Equals(InteractionSourceHandedness.Right))
                {
                    //座標系↓
                    koko_R = controllerState.GripPosition;
                    pritelig_R = controllerState.GripRotation;

                    CRO_OBJ_R.transform.position = koko_R;

                    CRO_OBJ_R.transform.position += new Vector3(CRO_OBJ_Camera.transform.position.x , CRO_OBJ_Camera.transform.position.y , CRO_OBJ_Camera.transform.position.z );
                    CRO_OBJ_R.transform.localRotation  = new Quaternion(pritelig_R.x,pritelig_R.y, pritelig_R.z, pritelig_R.w);

                    CRO_OBJ_R.transform.localRotation = CRO_OBJ_R.transform.localRotation * Quaternion.Euler(H_offset_R_x, H_offset_R_y, H_offset_R_z);

                }
                else if (controllerState.Handedness.Equals(InteractionSourceHandedness.Left))
                {

                    //座標系↓
                    koko_L = controllerState.GripPosition;
                    pritelig_L = controllerState.GripRotation;

                    CRO_OBJ_L.transform.position = koko_L;

                    CRO_OBJ_L.transform.position += new Vector3(CRO_OBJ_Camera.transform.position.x , CRO_OBJ_Camera.transform.position.y , CRO_OBJ_Camera.transform.position.z );
                    CRO_OBJ_L.transform.localRotation  = new Quaternion(pritelig_L.x,pritelig_L.y, pritelig_L.z, pritelig_L.w);

                    CRO_OBJ_L.transform.localRotation = CRO_OBJ_L.transform.localRotation * Quaternion.Euler(H_offset_L_x, H_offset_L_y, H_offset_L_z);

                }

            }
        }

}

}


作成したスクリプトをMR_Controllersにぶち込みましょう。
MR_CON_S.PNG

CRO_OBJ_の各人にいれるオブジェクトは見れば分かりますね。分かってください。

はい、では、入れ終わりましたらMRデバイスを接続してコントローラーを青歯でつなげましょう。

さぁテスト動作です!!!
・・・

・・・

・・・

・・・

・・・・・・・・・・・・・・・・・・・・・・・・・・・

あー凄い事になってますねー
人体が向いてはいけない方向をしていますね・・・

225625625.PNG
モデルの動作などを確認する時は、ヘッドセットを外してテストするのも大切です。
そしてVRでは主観視点の確認も大切です。
両方でどうなっているのかよく確認しながら、作業する様にしましょう。

ではここでスクリプトの説明をしていきましょう。

上記のスクリプトは基本的に、
・ヘッドセット
・右手コントローラー
・左手コントローラー
の座標と回転を取得しています。

そして取得した値を設定した各オブジェクトに与えています。

しかしながら、モデル毎に0の値となる位置や回転は違います。
違うモノに同じ値を入力したところで、結果は違ってしまいます。
(語彙力不足でいい感じの説明出来ないので、詳しく知りたい人はググりましょう。)

なので、回転に調整を入れて自然な方向を向くように調整しましょう。
先程のスクリプトの中にoffsetという値が入力出来るところが有りますね?
そこに値を入力しましょう!

Offset_Head_y
Offset_Head_z
に-90を入力しましょう。

わー普通な人間に見える様になってきましたよ!!!
offsetatai.PNG

しかし、身体の向きが変なのが治ると手首がアレな感じしてますねぇ

今度はH_offsetの値を調整しましょう!
参考値として、offsetの値を置いときますが、雑な値ですので基本はご自身でコレだ!って値を見つけて下さい。ヘッドセットを被りながら調整するのも大切な事だと思います!

H_offset_R_x 30
H_offset_R_y 90
H_offset_R_z 30
H_offset_L_x 0
H_offset_L_y 90
H_offset_L_z 0

第一部完!!!

とりあえずはこれで大まかには完了ですね。
細かい調整などは有りますが、キャラクターなりきりとしてはコレで終わりです。

VR空間でキャラクターどうなっているか確認したいとかの人は鏡を作成してそれを見て下さい。
Unity 鏡 ってググれば出てきますから。

書き忘れ等が有りそうで不安ですが、見つけ次第修正加筆していきますので、コメントとかでお知らせ下さい。

ライセンス表記って初めてなんだけどコレでいいのかな・・・
imageLicenseLogo.png

宣伝とか

https://www.youtube.com/channel/UCobRI1Pcw5P7di7vwf_1OWA?view_as=subscriber
でWindowsMRを使用した動画を投稿し始めてみました。
動画内で使用している技術をこちらにアウトプット出来ればと思ってちまちま書いて行きますのでよろしければ生暖かい目で見てってくだされ。