MRが楽しい

MRやVRについて学習したことを書き残す

HoloLens特化のホロ恋子モデルを作成する その1(三面図の作成と身体の比率)

本日はホロ恋子モデル2の作成枠です。
昨年度の「自身が作成したキャラクタをhololensアプリに登場させる」の目標を達成しました。
次なる目標として「デジタルなモンスターと友達になるHoloLensアプリを作成する」を目標に新たに掲げます。

さしあたっては前回までホロ恋子モデルの作成を一旦完了として新規モデルの作成に着手します。
ただ作成キャラクタは同じくホロ恋子です。
今回は1体目の反省点を踏まえながら、3Dモデルの更なるローポリゴン化を試みます。

1体目のホロ恋子はリソース的にも作成工数的にもコストがまだまだ高過ぎました。
最低限の品質を保持したローポリゴン化を達成できればデジタルなモンスターの量産化も可能なはずです。
練習も兼ねて、再度フルスクラッチで作成します。

初めの改修点として、三面図の頭身とポーズを変更しました。
f:id:bluebirdofoz:20190113163612j:plain
(正確に言うと三面図ではなく二面図ではありますが)
※ 2019/01/13 その23での三面図更新に合わせて三面図を最新のものに差し替え。

前回のホロ恋子モデルの三面図は6頭身で作成していました。
f:id:bluebirdofoz:20181128094813j:plain

絵で見るとそれほど違和感はないのですが、VRやMRなどで立体視してみると、頭部がかなり大きく感じることが分かりました。
VRChatで他のモデルを見る限り、最低7頭身以上はあった方が違和感がありません。
今回はより高めに、モデル体型の理想形と言われる7.5頭身に調整しています。
f:id:bluebirdofoz:20181128094823j:plain

合わせて全体のバランスも調整しています。
体型を描く時に気を付けるべき、身体の比率を抜粋しておきます。

・キャラクタの中心は股下の位置
およそ7頭身の時、高さは股下を中心に1:1の比率になります。
頭身による頭部比率の変化は固定のため、7.5頭身の今回は脚の長さの比率が少し大きめになっています。
f:id:bluebirdofoz:20181128094922j:plain

・肩から股下の長さと肩から手頸までの長さは同じ
身体を表現する上で最も重要な比率です。この比率は頭身が変わっても基本変わりません。
Tスタンスの場合、肩が内側に寄ることを考慮する必要があります。
f:id:bluebirdofoz:20181128094945j:plain

・顔の幅と比べて肩幅はおよそ2倍
顔の幅(髪を含まない)と比較して肩幅はおよそ2倍の長さになります。
ただしこの比率はあくまで目安です。肩幅の広い狭いなどキャラクタを表現するにあたっての個性付けになります。
今回のホロ恋子は少し肩幅の狭いシルエットにしています。
f:id:bluebirdofoz:20181128095004j:plain

・顔の長さと首の長さの比率は3:1
首の長さ(付け根から付け根)は顔の長さの3分の1くらいにしておけば収まりが良いです。
あくまで目安ですが、首が長く感じたり、短く感じたりしたらこの比率に収めておきます。
f:id:bluebirdofoz:20181128095017j:plain

・上腕と下腕の比率は同じではない
上腕は下腕より僅かに長くなります。直感的に理解し辛い部分なので比率を意識します。
試しに腕を折り曲げて手を肩に寄せてみると上腕の方が長い事がよく分かります。
f:id:bluebirdofoz:20181128095157j:plain

ローポリゴンモデル作成にあたっては心優しき坊主さん(@KoKoRoBoUZu)のローポリゴン古文書を最大限参考にさせていただきます。
twitter.com

次回は Blender プロジェクトの初期設定です。
bluebirdofoz.hatenablog.com

HoloLens用のオリジナルモデルを作成する その42(ホロ恋子モデルをHoloLensアプリに組み込む)

本日はホロ恋子モデルの作成枠です。
ホロ恋子モデルを使って HoloLens アプリを作成してみます。

ホロ恋子モデルのダウンロード

ホロ恋子モデルのダウンロードページから HoloLenko_Ver1.1.zip をダウンロードします。
ファイルのダウンロードを行うにはニコニコ動画のアカウントが必要です。
3d.nicovideo.jp
f:id:bluebirdofoz:20181127031330j:plain

ダウンロードしたファイルを展開して開きます。
今回利用するのは HoloLenko_Ver1.1/UnityPackage_Ver1.1 配下の HoloLenKo_Ver1.1.unitypackage です。
f:id:bluebirdofoz:20181127031340j:plain

HoloLens向けプロジェクトの作成

HoloLens 向けのビルド設定を行ったプロジェクトを作成します。
MRTK を利用すれば数ステップで各種設定を行った Unity プロジェクトが作成可能です。
以下の記事を参考に、基本設定までを実施して Unity プロジェクトを作成します。
bluebirdofoz.hatenablog.com
f:id:bluebirdofoz:20181127031356j:plain

基本設定を行ったら、先ほどダウンロードした HoloLenKo_Ver1.1.unitypackage を実行します。
Import Unity Package ダイアログが表示されるので[Import]ボタンをクリックします。
f:id:bluebirdofoz:20181127031406j:plain

Assets 配下にインポートされた HoloLenKo/Prefabs フォルダを開きます。
HoloLenKo.prefab ファイルを Hierarchy にドラッグしてホロ恋子モデルをシーンに配置します。
f:id:bluebirdofoz:20181127031426j:plain

そのままだとホロ恋子モデルがカメラに重なった状態になっているので、表示位置を調整します。
HoloLenKo オブジェクトを選択し、Inspector ビューの Transform を以下の通り変更します。

Position X:0 Y:-1.6 Z:2
Rotation X:0 Y:180  Z:0
Scale    X:1 Y:1    Z:1

f:id:bluebirdofoz:20181127031441j:plain

シーンの作り込み

このままビルドしてもホロ恋子が目の前に表示される HoloLens 向けアプリが作成できます。
しかし、折角なのでモデルが様々なポーズを取る機能を追加します。
アセットとしてユニティちゃんの UnityChan_1_2_1.unitypackage を利用します。
以下の記事を参考にアセットをインポートしてください。
bluebirdofoz.hatenablog.com
f:id:bluebirdofoz:20181127031501j:plain

再び HoloLenKo オブジェクトをクリックして Inspector ビューを開きます。
この状態で Assets 配下にインポートされた UnityChan/Animators フォルダを開きます。
Animator コンポーネントの[Cotroller]項目に UnityChanActionCheck.controller を設定します。
f:id:bluebirdofoz:20181127031510j:plain

Controller を設定したら、これを制御するスクリプトを追加します。
[Add Component]を選択し、[Idle Changer]を検索します。
[Idle Changer (Script)]が見つかるので、これを追加します。
f:id:bluebirdofoz:20181127031519j:plain

[Idle Changer (Script)]コンポーネントが追加されます。
各項目の設定値を以下の通り、変更します。

Random(自動遷移設定):チェックあり
Threshold(判定閾値) :0
Interval(遷移待機秒):3

f:id:bluebirdofoz:20181127031527j:plain

プロジェクトのビルドとインストール

以上でシーンの作り込みは完了です。
以下の記事を参考に、作成したアプリケーションをビルドし、HoloLens にインストールします。
bluebirdofoz.hatenablog.com
bluebirdofoz.hatenablog.com

※ 2018/11/27現在、ユニティちゃんアセットを全て含んだ状態で UWP 向けビルドを行うと以下のエラーが発生します。

Assets\UnityChan\Scripts\AutoBlink.cs(8,23): error CS0234: The type or namespace name 'Policy' does not exist in the namespace 'System.Security' (are you missing an assembly reference?)

該当箇所の以下の2つのスクリプトについて今回は利用しないのでコメントアウトで回避して問題ありません。
・AutoBlink.cs 6行目から

using UnityEngine;
using System.Collections;
//using System.Security.Policy; // コメントアウト

・SpringManager.cs 66行目から

private void UpdateParameter (string fieldName, float baseValue, AnimationCurve curve)
{
  /* 全てコメントアウト
  var start = curve.keys [0].time;
  var end = curve.keys [curve.length - 1].time;
  //var step	= (end - start) / (springBones.Length - 1);

  var prop = springBones [0].GetType ().GetField (fieldName, System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.Public);

  for (int i = 0; i < springBones.Length; i++) {
    //Kobayashi
    if (!springBones [i].isUseEachBoneForceSettings) {
      var scale = curve.Evaluate (start + (end - start) * i / (springBones.Length - 1));
      prop.SetValue (springBones [i], baseValue * scale);
    }
  }
*/
}

HoloLensでの動作確認

HoloLens 上でアプリを起動すると、目の前でホロ恋子モデルが色々とポーズを取ってくれます。
f:id:bluebirdofoz:20181127031558g:plain

HoloLens用のオリジナルモデルを作成する まとめ(19~34)

本日はまとめ記事です。
「HoloLens用のオリジナルモデルを作成する」記事をまとめます。
今回はその19~その34(VRMファイル公開)までです。

その1~その18は以下の記事にまとめています。
bluebirdofoz.hatenablog.com

公式チュートリアル「MR and Azure 307」の記事をまとめる

本日はまとめ枠です。
公式チュートリアル「MR and Azure 307」を実施した記事についてまとめておきます。

Chapter 4 前半

bluebirdofoz.hatenablog.com

Chapter 4 後半

bluebirdofoz.hatenablog.com

Chapter 5 ~ 7

bluebirdofoz.hatenablog.com

Chapter 10 ~ 11

bluebirdofoz.hatenablog.com

公式チュートリアル「MR and Azure 307 10~11章」を試してみる

本日はチュートリアルの実施枠です。
Academyの「MR and Azure 307: Machine learning」の実施内容をまとめます。
docs.microsoft.com
前回記事の続きです。
bluebirdofoz.hatenablog.com

今回は「Chapter 10 ~ Chapter 11(最終章)」です。

Chapter 10:Building the UWP Solution

Unity セクションで必要なものは全て完成したので、ビルドを行います。
1.メニューから File -> Build Settings を選択します。
f:id:bluebirdofoz:20181124212209j:plain

2.「Unity C# Projects」をチェックし、「Build」を実行します。
f:id:bluebirdofoz:20181124212223j:plain

3-4.Unity のビルドが完了すると、File Explorer が開きます。
ビルドフォルダを開き、新しいプロジェクトソリューションを開きます。
f:id:bluebirdofoz:20181124212231j:plain

Chapter 11:Deploying your application

アプリケーションを HoloLens に展開します。
1-3.ビルドで出力された sln ファイルを開きます。
構成を「Release」「x86」「リモートコンピュータ(HoloLensのIPアドレス)」に変更します。
f:id:bluebirdofoz:20181124212243j:plain

4-5.メニューから デバッグ -> デバッグなしで実行 でアプリを HoloLens にインストールして実行します。
f:id:bluebirdofoz:20181124212252j:plain

6.Mixed Realityアプリケーションを実行すると、ベンチと起動した日時のテキストが表示されます。
起動と同時に Azure で設定したデータが現在の日付と時刻を元に取得されます。
データはアプリケーション内で逆シリアル化され、3つの上位結果がベンチ上の3つのモデルとして視覚的に提供されます。
f:id:bluebirdofoz:20181124212305j:plain

以上で HOLOGRAMS 307 は終了です。
なお、Machine Learning Studio は Standard プランで作成した場合、利用の有無に関わらず作成したシートごとに従量課金が発生します。
お試し後、継続利用しないのであればサービスを削除しておきましょう。

公式チュートリアル「MR and Azure 307 9章」を試してみる

本日はチュートリアルの実施枠です。
Academyの「MR and Azure 307: Machine learning」の実施内容をまとめます。
docs.microsoft.com
前回記事の続きです。
bluebirdofoz.hatenablog.com
今回は「Chapter 9」です。

Chapter 9:Create the ProductPrediction class

次に作成するクラスは、ProductPredictionクラスです。
このクラスは以下の機能を担当します。
・現在の日付と時刻で Machine Learning Service インスタンスを照会する。
JSONレスポンスを使用可能なデータにデシリアライズします。
・データの解釈を行い、3つの推奨製品を取得する。

1.Script フォルダに移動します。
フォルダの中で右クリックし、Create -> C# Script をクリックします。
作成したスクリプトの名前を ProductPrediction に設定します。
f:id:bluebirdofoz:20181123124203j:plain

2.以下の通り、スクリプトを編集します。
・ProductPrediction.cs

// 名前空間の追加
using System;
using System.Collections.Generic;
using UnityEngine;
using System.Linq;
using Newtonsoft.Json;
using UnityEngine.Networking;
using System.Runtime.Serialization;
using System.Collections;

public class ProductPrediction : MonoBehaviour {
    /// <summary>
    /// This object represents the Prediction request
    /// It host the day of the year and hour of the day
    /// The product must be left blank when serialising
    /// このオブジェクトは、予測要求を表します
    /// それは1年のうちの日付と1時間おきの時間をホストします
    /// シリアル化するときは、製品を空白のままにしておく必要があります
    /// </summary>
    public class RootObject
    {
        public Inputs Inputs { get; set; }
    }

    public class Inputs
    {
        public Input1 input1 { get; set; }
    }

    public class Input1
    {
        public List<string> ColumnNames { get; set; }
        public List<List<string>> Values { get; set; }
    }

    /// <summary>
    /// This object containing the deserialised Prediction result
    /// It host the list of the products
    /// and the likelihood of them being sold at current date and time
    /// このオブジェクトは、デシリアライズされた予測結果を含みます
    /// 現在の日付と時刻に販売される可能性と製品のリストをホストします
    /// </summary>
    public class Prediction
    {
        public Results Results { get; set; }
    }

    public class Results
    {
        public Output1 output1;
    }

    public class Output1
    {
        public string type;
        public Value value;
    }

    public class Value
    {
        public List<string> ColumnNames { get; set; }
        public List<List<string>> Values { get; set; }
    }


    /// <summary>
    /// The 'Primary Key' from your Machine Learning Portal
    /// Machine Learning ポータルの「API Key」を設定する
    /// </summary>
    private string authKey = "-- Insert your service authentication key here --";

    /// <summary>
    /// The 'Request-Response' Service Endpoint from your Machine Learning Portal
    /// Machine Learning ポータルの「リクエストURI(POST)」を設定する
    /// </summary>
    private string serviceEndpoint = "-- Insert your service endpoint here --";

    /// <summary>
    /// The Hour as set in Windows
    /// Windowsで設定された時間
    /// </summary>
    private string thisHour;

    /// <summary>
    /// The Day, as set in Windows
    /// Windowsで設定された日付(日)
    /// </summary>
    private string thisDay;

    /// <summary>
    /// The Month, as set in Windows
    /// Windowsで設定された日付(月)
    /// </summary>
    private string thisMonth;

    /// <summary>
    /// The Numeric Day from current Date Conversion
    /// 現在の日付を変換した年内の経過日数
    /// </summary>
    private string dayOfTheYear;

    /// <summary>
    /// Dictionary for holding the first (or default) provided prediction 
    /// from the Machine Learning Experiment
    /// 機械学習実験からの最初の(またはデフォルトの)予測を保持するための Dictionary クラス
    /// </summary>    
    private Dictionary<string, string> predictionDictionary;

    /// <summary>
    /// List for holding product prediction with name and scores
    /// 製品の予測を名前とスコアで保持するためのリスト
    /// </summary>
    private List<KeyValuePair<string, double>> keyValueList;


    /// <summary>
    /// Called on initialization
    /// 初期化処理
    /// </summary>
    void Start()
    {
        // Call to get the current date and time as set in Windows
        // Windowsで設定されている現在の日付と時刻を取得する
        GetTodayDateAndTime();

        // Call to set the HOUR in the UI
        // UIで時間を設定するための呼び出し
        ShelfKeeper.instance.SetTime(thisHour);

        // Call to set the DATE in the UI
        // UIで日付を設定するための呼び出し
        ShelfKeeper.instance.SetDate(thisDay, thisMonth);

        // Run the method to Get Predication from Azure Machine Learning
        // Azure Machine LearningからPredicationを取得するメソッドの実行
        StartCoroutine(GetPrediction(thisHour, dayOfTheYear));
    }

    /// <summary>
    /// Get current date and hour
    /// 現在の日付と時間を取得する
    /// </summary>
    private void GetTodayDateAndTime()
    {
        // Get today date and time
        // 今日の日時を取得する
        DateTime todayDate = DateTime.Now;

        // Extrapolate the HOUR
        // 時刻の部分を取得する
        thisHour = todayDate.Hour.ToString();

        // Extrapolate the DATE
        // 日付の部分を取得する
        thisDay = todayDate.Day.ToString();
        thisMonth = todayDate.ToString("MMM");

        // Extrapolate the day of the year
        // 年の部分を取得する
        dayOfTheYear = todayDate.DayOfYear.ToString();
    }

    /// <summary>
    /// 現在の日時をMachine Learningエンドポイントに通知し、JSON形式の応答を受け取る
    /// </summary>
    /// <param name="timeOfDay"></param>
    /// <param name="dayOfYear"></param>
    /// <returns></returns>
    private IEnumerator GetPrediction(string timeOfDay, string dayOfYear)
    {
        // Populate the request object 
        // Using current day of the year and hour of the day
        // リクエストオブジェクトに値を設定する
        // 現在の日付と時間を使用する
        RootObject ro = new RootObject
        {
            Inputs = new Inputs
            {
                input1 = new Input1
                {
                    ColumnNames = new List<string>
                    {
                        "day",
                        "hour",
                    "product"
                    },
                    Values = new List<List<string>>()
                }
            }
        };

        List<string> l = new List<string>
        {
            dayOfYear,
            timeOfDay,
            ""
        };

        ro.Inputs.input1.Values.Add(l);

        Debug.LogFormat("Score request built");

        // Serialise the request
        // リクエストをシリアライズする
        string json = JsonConvert.SerializeObject(ro);

        using (UnityWebRequest www = UnityWebRequest.Post(serviceEndpoint, "POST"))
        {
            byte[] jsonToSend = new System.Text.UTF8Encoding().GetBytes(json);
            www.uploadHandler = new UploadHandlerRaw(jsonToSend);

            www.downloadHandler = new DownloadHandlerBuffer();
            www.SetRequestHeader("Authorization", "Bearer " + authKey);
            www.SetRequestHeader("Content-Type", "application/json");
            www.SetRequestHeader("Accept", "application/json");

            yield return www.SendWebRequest();
            string response = www.downloadHandler.text;

            // Deserialize the response
            // レスポンスをデシリアライズする
            DataContractSerializer serializer;
            serializer = new DataContractSerializer(typeof(string));
            DeserialiseJsonResponse(response);
        }
    }

    /// <summary>
    /// Deserialize the response received from the Machine Learning portal
    /// Machine Learning ポータルから受け取ったレスポンスをデシリアライズする
    /// </summary>
    public void DeserialiseJsonResponse(string jsonResponse)
    {
        // Deserialize JSON
        // JSON をデシリアライズする
        Prediction prediction = JsonConvert.DeserializeObject<Prediction>(jsonResponse);
        predictionDictionary = new Dictionary<string, string>();

        for (int i = 0; i < prediction.Results.output1.value.ColumnNames.Count; i++)
        {
            if (prediction.Results.output1.value.Values[0][i] != null)
            {
                predictionDictionary.Add(prediction.Results.output1.value.ColumnNames[i], prediction.Results.output1.value.Values[0][i]);
            }
        }

        keyValueList = new List<KeyValuePair<string, double>>();

        // Strip all non-results, by adding only items of interest to the scoreList
        // scoreList に可能性のある項目だけを追加する
        for (int i = 0; i < predictionDictionary.Count; i++)
        {
            KeyValuePair<string, string> pair = predictionDictionary.ElementAt(i);
            if (pair.Key.StartsWith("Scored Probabilities"))
            {
                // Parse string as double then simplify the string key so to only have the item name
                // 文字列を二重として解析し、文字列キーを単純化してアイテム名のみを持つようにする
                double scorefloat = 0f;
                double.TryParse(pair.Value, out scorefloat);
                string simplifiedName =
                    pair.Key.Replace("\"", "").Replace("Scored Probabilities for Class", "").Trim();
                keyValueList.Add(new KeyValuePair<string, double>(simplifiedName, scorefloat));
            }
        }

        // Sort Predictions (results will be lowest to highest)
        keyValueList.Sort((x, y) => y.Value.CompareTo(x.Value));

        // Spawn the top three items, from the keyValueList, which we have sorted
        for (int i = 0; i < 3; i++)
        {
            ShelfKeeper.instance.SpawnProduct(keyValueList[i].Key, i);
        }

        // Clear lists in case of reuse
        keyValueList.Clear();
        predictionDictionary.Clear();
    }
}

f:id:bluebirdofoz:20181123124229j:plain

authKey を、第4章で取得した API Key で置き換えます。
serviceEndpoint を、第4章で取得した リクエスURI(POST) のURLで置き換えます。
f:id:bluebirdofoz:20181123124240j:plain

3.Visual Studio で変更を保存して Unity に戻ります。
f:id:bluebirdofoz:20181123124255j:plain

4.Hierarchy の MixedRealityCameraParent を開きます。
直下の MixedRealityCamera を選択し、ProductPrediction をアタッチします。
f:id:bluebirdofoz:20181123124306j:plain

Chapter 9 はここまでです。
次回は Chapter 10 を実施します。
bluebirdofoz.hatenablog.com

HoloLens RS5(October 2018 Update)の近距離共有を利用する

本日は HoloLens と Windows 10 の調査枠です。
RS5から近距離共有が利用可能になったので試してみます。
docs.microsoft.com

受信PC側での近距離共通の有効化

近距離共有を利用するにはコンテンツを受信するPC側の設定が必要になります。
PC側のタスクバー右端にある[アクションセンター]のアイコンをクリックします。
f:id:bluebirdofoz:20181122001610j:plain

※ [アクションセンター]のアイコンが表示されていない場合
  設定 -> 個人用設定 -> タスクバー にある[システムアイコンのオン/オフの切り替え]を開きます。
  [アクションセンター]の表示が ON になっていることを確認してください。
f:id:bluebirdofoz:20181122001618j:plain

アクションセンターを開いたら[近距離共有]の機能を ON に変更します。
f:id:bluebirdofoz:20181122001628j:plain
これでPC側の設定は完了です。

HoloLens側での近距離共有の実行と受信

試しに HoloLens からキャプチャ画像の共有を行ってみます。
HoloLens でキャプチャを実行します。すると、以下のようなキャプチャ画像の確認ウィンドウが表示されます。
f:id:bluebirdofoz:20181122001638j:plain

ウィンドウ上部の真ん中にある[共有]ボタンをタップします。
f:id:bluebirdofoz:20181122001646j:plain

Share ダイアログが表示されます。
先ほど近距離共有の設定を行ったPCが一覧に表示されるので、これをタップします。
f:id:bluebirdofoz:20181122001655j:plain

PC側でファイルの受信通知が届きます。
[保存]ボタンをクリックしてファイルを受信します。
f:id:bluebirdofoz:20181122001703j:plain

受信したファイルは[ダウンロード]ディレクトリに保存されます。
f:id:bluebirdofoz:20181122001715j:plain

その他

Microsoft EdgeのURLなども共有できます。アプリを開いて[共有]ボタンをクリックします。
f:id:bluebirdofoz:20181122001727j:plain

PC側で受信すると、同じWeb画面が開きます。
f:id:bluebirdofoz:20181122001736j:plain