MRが楽しい

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

HoloLens2でスクリプトからホログラムを合成したビデオ録画を行う

本日は HoloLens2 の小ネタ枠です。
HoloLens2でスクリプトからホログラムを合成したビデオ録画を行う手順を記事にします。
f:id:bluebirdofoz:20210514002640j:plain

VideoCapture

HoloLens でビデオを MP4 形式のファイルシステムに直接記録する API で公開しています。
VideoCapture を使用するには WebCam と Microphone の機能を有効にする必要があります。
docs.unity3d.com
docs.unity3d.com

サンプルシーン

動作確認のため、以下のサンプルシーンを作成しました。
ホログラムが同時に録画されていることを確認するため、Cube オブジェクトを配置しています。
f:id:bluebirdofoz:20210514002651j:plain

録画を行うための以下のスクリプトを作成しました。
StartVideoCaptureTest 関数で録画の開始、StopVideoCaptureTest 関数で録画を停止します。
・VideoCaptureTest.cs

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System.Linq;
using UnityEngine.Windows.WebCam;

public class VideoCaptureTest : MonoBehaviour
{
    /// <summary>
    /// ビデオキャプチャの参照
    /// </summary>
    VideoCapture m_VideoCapture = null;

    /// <summary>
    /// カメラパラメータの参照
    /// </summary>
    CameraParameters m_CameraParameters;

    /// <summary>
    /// 開始時処理
    /// </summary>
    void Start()
    {
        // 録画解像度の取得
        Resolution cameraResolution = VideoCapture.SupportedResolutions.OrderByDescending((res) => res.width * res.height).First();
        Debug.Log(cameraResolution);

        // 録画フレームレートの取得
        float cameraFramerate = VideoCapture.GetSupportedFrameRatesForResolution(cameraResolution).OrderByDescending((fps) => fps).First();
        Debug.Log(cameraFramerate);

        // ホログラムの合成フラグ
        bool HologramFlg = true;

        // VideoCapture インスタンスの作成
        VideoCapture.CreateAsync(HologramFlg, delegate (VideoCapture videoCapture)
        {
            if (videoCapture != null)
            {
                m_VideoCapture = videoCapture;
                Debug.Log("Created VideoCapture Instance!");

                // カメラパラメータの設定
                CameraParameters cameraParameters = new CameraParameters();
                // キャプチャされたホログラムの不透明度(0.0 : 透明 ~ 1.0 : 不透明)
                cameraParameters.hologramOpacity = 1.0f;
                // ビデオをキャプチャするフレームレート
                cameraParameters.frameRate = cameraFramerate;
                // 幅の解像度
                cameraParameters.cameraResolutionWidth = cameraResolution.width;
                // 高さの解像度
                cameraParameters.cameraResolutionHeight = cameraResolution.height;
                // 記録に使用されるピクセル形式
                cameraParameters.pixelFormat = CapturePixelFormat.BGRA32;

                m_CameraParameters = cameraParameters;
            }
            else
            {
                Debug.LogError("Failed to create VideoCapture Instance!");
            }
        });
    }

    /// <summary>
    /// ビデオ録画の開始
    /// </summary>
    public void StartVideoCaptureTest()
    {
        if (m_VideoCapture != null)
        {
            if (!m_VideoCapture.IsRecording)
            {
                m_VideoCapture.StartVideoModeAsync(m_CameraParameters,
                    VideoCapture.AudioState.ApplicationAndMicAudio,
                    OnStartedVideoCaptureMode);
            }
            else
            {
                Debug.LogError("Aready Recording!");
            }
        }
        else
        {
            Debug.LogError("Failed to create VideoCapture Instance!");
        }
    }

    /// <summary>
    /// ビデオ録画の停止
    /// </summary>
    public void StopVideoCaptureTest()
    {
        // ビデオモードを非同期的に停止する
        m_VideoCapture.StopRecordingAsync(OnStoppedVideoCaptureMode);
    }

    /// <summary>
    /// ファイルシステムへのビデオ録画の記録を開始する
    /// </summary>
    /// <param name="result"></param>
    void OnStartedVideoCaptureMode(VideoCapture.VideoCaptureResult result)
    {
        Debug.Log("Started Video Capture Mode!");

        // タイムスタンプを元にファイル名を作成する
        string timeStamp = Time.time.ToString().Replace(".", "").Replace(":", "");
        string filename = string.Format("TestVideo_{0}.mp4", timeStamp);

        // ディレクトリの保存先を指定する
#if WINDOWS_UWP
        // HoloLens上での動作の場合、LocalAppData/AppName/LocalStateフォルダを参照する
        string filepath = System.IO.Path.Combine(Windows.Storage.ApplicationData.Current.LocalFolder.Path, filename);
#else
        // Unity上での動作の場合、Assets/StreamingAssetsフォルダを参照する
        string filepath = System.IO.Path.Combine(UnityEngine.Application.streamingAssetsPath, filename);
#endif
        filepath = filepath.Replace("/", @"\");

        // 書き出し先を指定してファイルシステムへのビデオ録画を非同期に開始する
        m_VideoCapture.StartRecordingAsync(filepath, OnStartedRecordingVideo);
    }

    /// <summary>
    /// 録画開始時のコールバック関数
    /// </summary>
    /// <param name="result"></param>
    void OnStartedRecordingVideo(VideoCapture.VideoCaptureResult result)
    {
        Debug.Log("Started Recording Video!");
    }

    /// <summary>
    /// ファイルシステムへのビデオ録画を停止する
    /// </summary>
    void OnStoppedVideoCaptureMode(VideoCapture.VideoCaptureResult result)
    {
        Debug.Log("Stopped Video Capture Mode!");
        // ファイルシステムへのビデオ録画を非同期に停止する
        m_VideoCapture.StopVideoModeAsync(OnStoppedRecordingVideo);
    }

    /// <summary>
    /// 録画終了時のコールバック関数
    /// </summary>
    void OnStoppedRecordingVideo(VideoCapture.VideoCaptureResult result)
    {
        Debug.Log("Stopped Recording Video!");
    }
}

f:id:bluebirdofoz:20210514002723j:plain

作成したスクリプトを適当なゲームオブジェクトにアタッチします。
f:id:bluebirdofoz:20210514002736j:plain

ボタンの配置

ボタンオブジェクトを利用するため、Unity プロジェクトに MRTK をインポートします。
MRTK のインポート手順は以下の記事などを参考にしてください
bluebirdofoz.hatenablog.com

シーンにボタンを配置し、それぞれのイベントを開始/停止関数に割り当てました。
f:id:bluebirdofoz:20210514002834j:plain
f:id:bluebirdofoz:20210514002757j:plain

Capabilitiesの設定

録画を行うため、カメラとマイクの権限許可を行います。
メニューから[Edit -> Project Settings..]を開き、[Player]タブの[Publishing Settings]を開きます。
[Capabilities]の項目で[WebCam]と[Microphone]にチェックを入れます。
f:id:bluebirdofoz:20210514002849j:plain

ホログラムの位置合わせ

デフォルト設定でカメラ映像とホログラムの合成を行うと、カメラ位置のズレにより、動画内でホログラムの位置ズレが発生します。
以下の設定を行うことで、ホログラムの位置ズレ修正することができます。
1. [Hierarchy]から[MixedRealityToolkit]オブジェクトを選択し、Inspector ビューを開きます。
2. MRTK のプロファイルから[Camera -> Camera Settings Providers -> Windows Mixed Reality Camera Settings]を開きます。
3. [Render from PV Camera]のチェックを入れます。
f:id:bluebirdofoz:20210514002900j:plain

HoloLens2での動作確認

アプリを HoloLens2 にインストールして動作を確認します。
f:id:bluebirdofoz:20210514002911j:plain

[Start]ボタンをクリックすると録画が開始されます。
f:id:bluebirdofoz:20210514002920j:plain

録画を停止する場合は[Stop]ボタンをクリックします。
f:id:bluebirdofoz:20210514002931j:plain

録画された結果はアプリケーションのローカルフォルダに保存されます。
DevicePortal のファイルエクスプローラからダウンロード可能です。
f:id:bluebirdofoz:20210514002942j:plain

ダウンロードした動画を確認すると、ホログラムが映った動画が撮影できています。
f:id:bluebirdofoz:20210514002954j:plain

既知の問題

スクリプトは Editor 上ではインスタンスを生成できず失敗します。
一度録画を開始した後、録画を停止して再録画を行った場合、2度目の録画ではホログラムが合成されない問題を確認しています。
原因を調査中です。

2021/05/14 16:45

スクリプトの停止処理の記述ミスが原因だったため、修正後のコードに差し替えました。
本コードは2度目の録画も正常に動作します。

UnityのScene画面に表示されるGizumoアイコンを小さくしたり非表示にする

本日は Unity の小ネタ枠です。
UnityのScene画面に表示されるGizumoアイコンを小さくしたり非表示にする方法を記事にします。
f:id:bluebirdofoz:20210513230946j:plain

Gizumo

Gizumo は Scene 内のゲームオブジェクトに関連付けられたグラフィックスです。
選択時にのみ描画されるものと、選択状態に関係なく描画されるものがあります。
docs.unity3d.com

Gizumoアイコンの非表示/調整

以下のように常に描画される Gizumo アイコンが邪魔で Scene 画面がよく見えない場合があります。
f:id:bluebirdofoz:20210513231011j:plain

対処として Scene 画面上部にある[Gizumos]ボタンをクリックすると、Gizumo に関する表示が全て非表示になります。
f:id:bluebirdofoz:20210513231158j:plain

またはプルダウンを開き、[3D Icons]のスライダーを調整することで Gizumo アイコンのサイズを変更できます。
f:id:bluebirdofoz:20210513231037j:plain

アイコンのサイズを小さく調整することで、Scene 画面が見やすくなります。
f:id:bluebirdofoz:20210513231046j:plain

Vector3.sqrMagnitudeを使ってより高速な距離比較を行う

本日は Unity の小ネタ枠です。
Vector3.sqrMagnitudeを使ってより高速な距離比較を行う検証を行います。

Vector3.sqrMagnitude

2点間のベクトルの 2 乗の長さを返します。
Vector3.Distance で2点間の距離を算出する場合、ルートの計算に時間がかかります。
このため、単純に距離の遠近を比較したい場合は sqrMagnitude を利用し、2乗値で比較するようにすると処理が高速に行えます。
docs.unity3d.com

検証シーンの作成

実際にサンプルシーンを作成して2つのコードの実行時間を比較してみます。
指定する2つのオブジェクトとメインカメラの距離を比較して近い方を検出する以下のスクリプトを作成しました。
・DistanceComparison.cs

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class DistanceComparison : MonoBehaviour
{
    [SerializeField, Tooltip("チェック対象A")]
    private Transform p_ObjectA;

    [SerializeField, Tooltip("チェック対象B")]
    private Transform p_ObjectB;

    // Start is called before the first frame update
    void Start()
    {
        Vector3DistanceCheck(p_ObjectA, p_ObjectB);

        Vector3SqrMagnitudeCheck(p_ObjectA, p_ObjectB);
    }

    void Vector3DistanceCheck(Transform a_ObjectA, Transform a_ObjectB)
    {
        // カメラ位置を取得
        Transform myPosition = Camera.main.transform;

        // Stopwatchの開始
        System.Diagnostics.Stopwatch stopwatch = new System.Diagnostics.Stopwatch();
        stopwatch.Start();

        // Vector3Distance で距離を取得
        float distanceA = Vector3.Distance(myPosition.position, a_ObjectA.position);

        // Vector3Distance で距離を取得
        float distanceB = Vector3.Distance(myPosition.position, a_ObjectB.position);

        // 経過時間
        Debug.Log("ElapsedMilliseconds : " + stopwatch.ElapsedMilliseconds + " ms");
        stopwatch.Stop();

        // 結果を表示
        Debug.Log("distanceA : " + distanceA + ", distanceB : " + distanceB + ".");
        Debug.Log("Near : " + ((distanceA < distanceB) ? "ObjectA" : "ObjectB") + ".");
    }
    void Vector3SqrMagnitudeCheck(Transform a_ObjectA, Transform a_ObjectB)
    {
        // カメラ位置を取得
        Transform myPosition = Camera.main.transform;

        // Stopwatchの開始
        System.Diagnostics.Stopwatch stopwatch = new System.Diagnostics.Stopwatch();
        stopwatch.Start();

        // Vector3Magnitude で距離の2乗値を取得
        float distanceA = Vector3.SqrMagnitude(myPosition.position - a_ObjectA.position);

        // Vector3Magnitude で距離の2乗値を取得
        float distanceB = Vector3.SqrMagnitude(myPosition.position - a_ObjectB.position);

        // 経過時間
        Debug.Log("ElapsedMilliseconds : " + stopwatch.ElapsedMilliseconds + " ms");
        stopwatch.Stop();

        // 結果を表示
        Debug.Log("distanceA : " + distanceA + ", distanceB : " + distanceB + ".");
        Debug.Log("Near : " + ((distanceA < distanceB) ? "ObjectA" : "ObjectB") + ".");
    }
}

f:id:bluebirdofoz:20210512040924j:plain

処理時間の計測には以下の記事の方法を利用しています。
bluebirdofoz.hatenablog.com

シーンにスクリプトを配置し、比較する2つのオブジェクトを指定しました。
f:id:bluebirdofoz:20210512041014j:plain

処理時間の検証

処理を実行してそれぞれの経過時間を表示してみました。
f:id:bluebirdofoz:20210512041024j:plain

結果としてはどちらもミリ秒未満の処理速度で実行され、1度比較する程度では優位な差は見られませんでした。
普段はあまり意識する必要はないかもしれませんが、ルートの算術演算が通常の算術より時間がかかるのは確かなため、非常に多くの距離計算を毎フレーム行う場合などは試してみるとよいかもしれません。

Unityプロジェクトを予め指定したプラットフォームで開く

本日は Unity の小ネタ枠です。
Unityプロジェクトを予め指定したプラットフォームで開く手順を記事にします。
f:id:bluebirdofoz:20210511212323j:plain

ユースケース

例えば GitHub に登録したプロジェクトを取得した場合、デフォルトの gitignoore では Build Settings が保存されていません。
f:id:bluebirdofoz:20210511212331j:plain

GitHub から取得したホロモンプロジェクトを Unity からそのまま開いてみます。
ホロモンプロジェクトは UWP プラットフォームで実装していました。
f:id:bluebirdofoz:20210511212340j:plain

しかし Build Settings の情報は引き継がれないため、プロジェクトを開くと Platform がデフォルトの設定に変更されています。
UWP の Platform に再び切り替えを行う必要があり、Platform の切り替え時間が2重にかかることになります。
f:id:bluebirdofoz:20210511212350j:plain

プロジェクト読み込み時のプラットフォーム指定

これを UnityHub でプロジェクトを開くときに「ターゲット」を指定することで、初めから指定のプラットフォームでプロジェクトを開くことができます。
「ターゲット」から「Universal Windows Platform」を指定します。
f:id:bluebirdofoz:20210511212402j:plain

この状態でプロジェクトを開きます。
f:id:bluebirdofoz:20210511212410j:plain

すると初めから UWP の Platform を指定した形でプロジェクトが開かれます。
開いた後の Platform の切り替え時間が不要になるため、覚えておくと便利です。
f:id:bluebirdofoz:20210511212420j:plain

HoloLens2でEnvironment.UserNameでのユーザ名の取得を試す

本日は HoloLens2 の小ネタ枠です。
HoloLens2 で Environment.UserName でのユーザ名の取得を試した結果を記事にします。

Environment.UserName

現在のスレッドに関連付けられているユーザー名を取得する API です。
通常 Windows PC 上で本 API を実行すると、現在実行中の PC のユーザ名が表示されます。
docs.microsoft.com

合わせて、Environment.UserDomainName の取得値も確認してみました。
こちらは実行中の PC のコンピュータ名が表示されます。
docs.microsoft.com

試験アプリの作成

Text コンポーネントに結果を表示する試験アプリを作成しました。
f:id:bluebirdofoz:20210510232950j:plain

以下の通り、スクリプトから API を叩いて取得した文字列を Text に反映します。
・ConfirmUsername.cs

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class ConfirmUsername : MonoBehaviour
{
    [SerializeField, Tooltip("表示コンポーネント")]
    private ShowText p_ShowText;

    // Start is called before the first frame update
    void Start()
    {
        List<string> messageTextList = new List<string>();

        // ユーザ名を追加
        messageTextList.Add(System.Environment.UserName);

        // ドメイン名を追加
        messageTextList.Add(System.Environment.UserDomainName);
        
        p_ShowText.SetText(messageTextList.ToArray());
    }
}

・ShowText.cs

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
using System;

public class ShowText : MonoBehaviour
{
    /// 表示テキスト
    /// </summary>
    private Text p_Text;

    // Start is called before the first frame update
    void Awake()
    {
        p_Text = this.GetComponent<Text>();
    }

    /// <summary>
    /// テキストの設定
    /// </summary>
    /// <param name="a_messageTextList"></param>
    public void SetText(string[] a_messageTextList)
    {
        p_Text.text = "";
        foreach (string messageText in a_messageTextList)
        {
            p_Text.text += messageText + Environment.NewLine;
        }
    }
}

HoloLens2上での動作結果

HoloLens2上での動作させた結果、ユーザ名は Unknown が返り、コンピュータ名は取得できました。
f:id:bluebirdofoz:20210510233003j:plain

GitHubで2段階認証設定後にTortoiseGitからレポジトリ操作を行う

本日は GitHub の小ネタ枠です。
GitHubで2段階認証設定後にTortoiseGitからレポジトリ操作を行う手順についてです。

2段階認証設定後のエラー

GitHubで2重認証を設定した後に通常の手順で TortoiseGit から通常の手順で push や pull を行うと、エラーが発生するようになります。

Invalid username or password.

f:id:bluebirdofoz:20210509105249j:plain

2段階認証設定後に TortoiseGit からリポジトリ操作を行う場合はトークンを作成します。

トークンの作成と利用

最初に GitHub にログインしてトークンを作成します。
右上のメニューから[Settings]を選択します。
f:id:bluebirdofoz:20210509105306j:plain

[Developer settings]を選択します。
f:id:bluebirdofoz:20210509105320j:plain

[Personal access tokens]を選択します。
f:id:bluebirdofoz:20210509105333j:plain

[Generate new token]のボタンをクリックします。
f:id:bluebirdofoz:20210509105345j:plain

トークンを使って実行可能な権限の設定画面が開きます。
今回は pull や push などのレポジトリ操作を実行するための[repo]権限にのみチェックを入れました。
f:id:bluebirdofoz:20210509105356j:plain

[Generate token]ボタンをクリックしてトークンを作成します。
f:id:bluebirdofoz:20210509105408j:plain

これでトークンのキーが作成されます。
作成時の一回のみしか表示されないので、しっかりとメモを取っておきます。
f:id:bluebirdofoz:20210509105418j:plain

後は TortoiseGit 利用時のパスワードに、トークンのキーを入力するだけです。
これで TortoiseGit からリポジトリ操作を行うことができました。
f:id:bluebirdofoz:20210509105430j:plain

node.jsのインストール手順

本日は環境構築枠です。
Windows 環境における node.js のインストール手順を記録します。
f:id:bluebirdofoz:20210508232454j:plain

node.js

node.js はスケーラブルなネットワークアプリケーションを構築するために設計された非同期型のイベント駆動の JavaScript 環境です。
nodejs.org

今回は JavaScript 系のパッケージを管理する NPM(Node Package Manager) を利用するため、インストールを行いました。

node.jsのインストール

node.js のインストーラを以下のページから取得します。
今回は msiインストーラを利用しました。
nodejs.org
f:id:bluebirdofoz:20210508232526j:plain

ダウンロードした node-vXX.XX.X-xXX.msi を実行します。
f:id:bluebirdofoz:20210508232538j:plain

セットアップ画面が開くので「Next」をクリックします。
f:id:bluebirdofoz:20210508232551j:plain

最初に使用許諾画面が表示されるので、「I accept ...」をチェックして「Next」をクリックします。
f:id:bluebirdofoz:20210508232603j:plain

インストール先の指定画面が表示されます。
デフォルトまたは任意のインストール先を設定して「Next」をクリックします。
f:id:bluebirdofoz:20210508232614j:plain

セットアップ内容のカスタム画面が表示されます。
デフォルト設定で問題ありません。「Next」をクリックします。
f:id:bluebirdofoz:20210508232626j:plain

npm のインストールの際に C/C++ からコンパイルするモジュールが必要になります。
[Automatically ...]にチェックを入れておくと、必要なモジュールが自動でインストールされます。
f:id:bluebirdofoz:20210508232636j:plain

最後に、インストール実行確認画面が表示されます。
「Install」をクリックしてインストールを実行します。
f:id:bluebirdofoz:20210508232648j:plain

以上で node.js のインストールは完了です。
f:id:bluebirdofoz:20210508232703j:plain

PowerShell で node --version また npm --version を実行するとバージョンが表示されます。
f:id:bluebirdofoz:20210508232718j:plain
f:id:bluebirdofoz:20210508232729j:plain