読者です 読者をやめる 読者になる 読者になる

MRが楽しい

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

本日はおいかけっこアプリの修正枠です。
今回はキャラクタを担当するViewを作り込みます。
bluebirdofoz.hatenablog.com

設計にキャラクタのコントロールやイベントを統括するマネージャ部を追加しました。
切り分けが細かくなりますが、後々、ユニティちゃんを自作のキャラクタへと、キャラロジック修正無しに差し替えたいという目論みがあります。
ここの作り込みが後々の時間短縮になる…はずです。

キャラクタを担当するViewは以下の記事の考えに基づいて設計しています。
自動でキャラクターを動かすロジックは全てViewの中で処理を閉じてしまいます。
yutakaseda3216.hatenablog.com

作りつつ、設計の見直し点があれば組み直しをしているため、本日はインタフェースを作った辺りで終了です。
役割をどう切るかが本当に難しい。

以下のように、ロジックやキャラクタをUnity上のInspectorから簡単にアタッチできるように実装中です。
f:id:bluebirdofoz:20170525030818j:plain
このルールで設計を行えば、各オブジェクト間の関連についても可読性があがることを期待しています。

公式チュートリアル「HOLOGRAMS 210 4章」を試してみる

本日も継続して、チュートリアルお試し枠です。
今回は4章 インジケータを試します。
azure-recipe.kc-cloud.jp


アプリをビルドすると、宇宙飛行士が視界外にあるとき、その方向を矢印で示してくれるようになります。
f:id:bluebirdofoz:20170524014816j:plain
参考記事ではキャプチャを撮ると消えてしまうとありましたが、自身の環境ではキャプチャできました。
f:id:bluebirdofoz:20170524014824j:plain
矢印の方向を見ると、宇宙飛行士がいます。宇宙飛行士を画面内に捉えると矢印は消えます。

hololensは視野が狭く、目標の仮想オブジェクトを見失うことが多々あります。
この機能は色々と活用できそうです。おいかけっこアプリにも搭載すると便利かもしれません。
矢印で示すオブジェクトを動的に差し替えるなど、応用も効きそうです。


では早速、コード確認です。

今回追加したスクリプトは以下のDirectionIndicatorクラスです。
まずは起動時処理と、アップデート関数を確認します。
・DirectionIndicator.cs

public void Awake()
{
    if (DirectionIndicatorObject == null)
    {
        return;
    }

    // Instantiate the direction indicator.
    DirectionIndicatorObject = InstantiateDirectionIndicator(DirectionIndicatorObject);
    directionIndicatorDefaultRotation = DirectionIndicatorObject.transform.rotation;

    directionIndicatorRenderer = DirectionIndicatorObject.GetComponent<MeshRenderer>();
}

public void Update()
{
    if (DirectionIndicatorObject == null)
    {
        return;
    }

    // Direction from the Main Camera to this script's parent gameObject.
    Vector3 camToObjectDirection = gameObject.transform.position - Camera.main.transform.position;
    camToObjectDirection.Normalize();

    // The cursor indicator should only be visible if the target is not visible.
    isDirectionIndicatorVisible = !IsTargetVisible();
    directionIndicatorRenderer.enabled = isDirectionIndicatorVisible;

    if (isDirectionIndicatorVisible)
    {
        Vector3 position;
        Quaternion rotation;
        GetDirectionIndicatorPositionAndRotation(
            camToObjectDirection,
            out position,
            out rotation);

        DirectionIndicatorObject.transform.position = position;
        DirectionIndicatorObject.transform.rotation = rotation;
    }
}

途中、DirectionIndicatorObject変数に設定したDirectionalIndicator.prefabが矢印オブジェクトです。
Awake関数では登録された矢印オブジェクトの向きとメッシュを取得しています。
InstantiateDirectionIndicator関数は矢印オブジェクトからクローンを作成し、以下の下準備を行い、返却しています。
・メッシュを設定する
・衝突判定を除去する
・カラーを設定する
・子オブジェクトとして登録する

次にUpdate関数の処理についてです。処理の流れとしては以下のようです。
1.ゲームオブジェクト(宇宙飛行士)とカメラの位置情報から方向と距離を割り出す。
2.ゲームオブジェクト(宇宙飛行士)が非表示の時、矢印オブジェクトを表示する。
3.表示の際は、方向と距離の情報から矢印オブジェクトの位置と向きを決定する。

確認してみると、とてもシンプルな処理です。


因みにゲームオブジェクト(宇宙飛行士)が非表示かの判定関数は以下です。
・DirectionIndicator.cs

private bool IsTargetVisible()
{
    // This will return true if the target's mesh is within the Main Camera's view frustums.
    Vector3 targetViewportPosition = Camera.main.WorldToViewportPoint(gameObject.transform.position);
    return (targetViewportPosition.x > TitleSafeFactor && targetViewportPosition.x < 1 - TitleSafeFactor &&
        targetViewportPosition.y > TitleSafeFactor && targetViewportPosition.y < 1 - TitleSafeFactor &&
        targetViewportPosition.z > 0);
}

Camera.main.WorldToViewportPoint関数は、ワールド空間のpositionをビューポート空間に変換する関数です。
docs.unity3d.com


hololensのアプリではワールド空間とビューポート空間の関係は重要なので覚えておいて損のない関数です。
Z位置はワールド空間のままなので、X/Yと判定が異なっています。


矢印オブジェクトの位置と向きを決定する関数は以下です。
・DirectionIndicator.cs

private void GetDirectionIndicatorPositionAndRotation(
    Vector3 camToObjectDirection,
    out Vector3 position,
    out Quaternion rotation)
{
    // Find position:
    // Use this value to decrease the distance from the cursor center an object is rendered to keep it in view.
    float metersFromCursor = 0.3f;

    // Save the cursor transform position in a variable.
    Vector3 origin = Cursor.transform.position;

    // Project the camera to target direction onto the screen plane.
    Vector3 cursorIndicatorDirection = Vector3.ProjectOnPlane(camToObjectDirection, -1 * Camera.main.transform.forward);
    cursorIndicatorDirection.Normalize();

    // If the direction is 0, set the direction to the right.
    // This will only happen if the camera is facing directly away from the target.
    if (cursorIndicatorDirection == Vector3.zero)
    {
        cursorIndicatorDirection = Camera.main.transform.right;
    }

    // The final position is translated from the center of the screen along this direction vector.
    position = origin + cursorIndicatorDirection * metersFromCursor;

    // Find the rotation from the facing direction to the target object.
    rotation = Quaternion.LookRotation(
        Camera.main.transform.forward,
        cursorIndicatorDirection) * directionIndicatorDefaultRotation;
}

カーソルを中心にオブジェクトを設置していますが、注目すべき点はVector3.ProjectOnPlaneでしょうか。
こちらもワールド空間の方向値を平面上のベクトルへと座標変換するコードです。
docs.unity3d.com

公式チュートリアル「HOLOGRAMS 210 3章」を試してみる

本日はチュートリアルお試し枠です。
いつも通り、以下ブログの記事を参考に実施します。
azure-recipe.kc-cloud.jp


記事では3章と4章が一緒くたになっていますが、内容を学習して進めたいので一つずつ進めます。
今回は3章 ターゲティングです。

参考記事にある通りにチュートリアルを実施します。
アプリをビルドすると、カーソルの移動がスムーズになるよう補正されます。
f:id:bluebirdofoz:20170523015630j:plain
…のはずなのですが、実際には実感なし。。
色々設定値を変えてみましたが、今一つ体感できる違いはありませんでした。
公式ページの動画見ても手順は間違っておらず、その動画でも分からない程度の補正のようなのでこれで正解のようです。
www.hololensdev.jp

Unity上でデバッグできれば簡単に確認できるのですが、HOLOGRAMS 210はhololens特有の機能を利用しているようでUnity上では機能が無効化されており確認できず。

とりあえずコード確認に移ります。

GazeManagerの変更箇所は以下の通りです。(210からはコードに章毎のコメントがあって読みやすい)
gazeOrigin(視点位置)とCamera.main.transform.rotation(視点向き)を元に、視点位置を補正しています。
・GazeManager.cs

void Awake()
{
    /* TODO: DEVELOPER CODING EXERCISE 3.a */

    // 3.a: GetComponent GazeStabilizer and assign it to gazeStabilizer.
    gazeStabilizer = GetComponent<GazeStabilizer>();
}

private void Update()
{
(略)

    // 3.a: Using gazeStabilizer, call function UpdateHeadStability.
    // Pass in gazeOrigin and Camera's main transform rotation.
    gazeStabilizer.UpdateHeadStability(gazeOrigin, Camera.main.transform.rotation);

    // 3.a: Using gazeStabilizer, get the StableHeadPosition and
    // assign it to gazeOrigin.
    gazeOrigin = gazeStabilizer.StableHeadPosition;

    UpdateRaycast();
}


gazeStabilizer.UpdateHeadStability関数が何をやっているかを確認します。

・gazeStabilizer.cs

/// <summary>
/// Updates the StableHeadPosition and StableHeadRotation based on GazeSample values.
/// Call this method with RaycastHit parameters to get stable values.
/// </summary>
/// <param name="position">Position value from a RaycastHit point.</param>
/// <param name="rotation">Rotation value from a RaycastHit rotation.</param>
public void UpdateHeadStability(Vector3 position, Quaternion rotation)
{
    gazePosition = position;
    gazeDirection = rotation * Vector3.forward;

    AddGazeSample(gazePosition, gazeDirection);

    UpdateInstability(out gazePositionInstability, out gazeDirectionInstability);

    // If we don't have a gravity point, just use the gaze position.
    if (!gravityPointExists)
    {
        gravityWellPosition = gazePosition;
        gravityWellDirection = gazeDirection;
        gravityPointExists = true;
    }

    UpdateGravityWellPositionDirection();
}

UpdateInstability関数、UpdateGravityWellPositionDirection関数は数学的なコードが何行も続いているので省略。
過去の視点位置、向きの情報を一定数保持して計算式を元に補正を行っています。
本gazeStabilizer.csはHoloToolKitでも提供されており、そちらでも利用可能なようです。

Blenderで3Dモデルを作成する(かんたん講座編その5)

本日は3Dモデリング練習の続きです。
髪のモデリングとマテリアルの設定、テクスチャの作成を行いました。
f:id:bluebirdofoz:20170522022609j:plain

以下はUV展開途中の図。
f:id:bluebirdofoz:20170522022729j:plain
詰め込み過ぎで、後々色塗りの際に困りました。メッシュ間はもう少し余裕を持っても良さそう。

色塗りを行い、テクスチャを読み込めば完成です。
f:id:bluebirdofoz:20170522022803j:plain
自作のキャプチャが見分けられるよう服は緑色にしています。


かんたん講座のサンプルテクスチャの瞳ですが、色塗りにかなり気合入ってます。
・かんたんBlender講座
 http://krlab.info.kochi-tech.ac.jp/kurihara/lecture/cg/BlenderWeb_Hayashi/html/materialAndTexture.html

テクスチャの完成度は3Dモデルの見た目の完成度に直結するので、当然でしょうか。
綺麗なテクスチャの作成は絵描きソフトの知識が必要になってくるので、ここでも学ぶことは多いです。

幸い自分はSAIを使ったことがあったので、本日中にそれなりの形を作れました。
f:id:bluebirdofoz:20170522022924j:plain
とはいえ、マウス描きだとやはり限界が…3Dモデリングにはペンタブも必要なようです。

Unityに設計おけるクラス設計を考える その4(GameUI)

本日はおいかけっこアプリの修正枠です。
f:id:bluebirdofoz:20170520230002j:plain

設計について、ハンドジェスチャーと視線ジェスチャーを統合しました。
現状、そこまで細かく分ける必要はないので。
bluebirdofoz.hatenablog.com


本日は以下を参考にGameUIを担当するViewの実装を実施中です。
developers.cyberagent.co.jp

Interfaceを利用し、UI操作の判定方法やキー入力を差し替え可能にしました。
インタフェースを利用しているオブジェクトをUnityの画面操作から差し替えるだけで切り替え可能となります。
・GameUI.cs

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

namespace CatchAndRun.GameUI
{
    public class GameUI : MonoBehaviour
    {
        // 利用キーボード操作クラス
        public GameObject targetKeyboard;
        private IGameUI p_Keybord;

        // 利用音声認識操作クラス
        public GameObject targetSpeech;
        private IGameUI p_Speech;

        // 利用ジェスチャー操作クラス
        public GameObject targetGesture;
        private IGameUI p_Gesture;

        // スティック入力が行われた時のイベントリスナー
        public Action<float, float> OnStickInputClickedListener;

        // ゲーム開始操作が行われた時のイベントリスナー
        public Action OnGameStartClickedListener;

        // ゲーム終了操作が行われた時のイベントリスナー
        public Action OnGameEndClickedListener;

        // モード選択操作が行われた時のイベントリスナー
        public Action<GameMode> OnModeSelectClickedListener;

        // オブジェクト選択操作が行われた時のイベントリスナー
        public Action<GameObject> OnObjectSelectClickedListener;

        // ゲームアクション操作が行われた時のイベントリスナー
        public Action<GameAction> OnGameActionClickedListener;

        // 設定変更操作が行われた時のイベントリスナー
        public Action<SettingChange> OnSettingChangeClickedListener;
(略)

GameUIはアタッチされている全ての各種操作クラスと双方向にやりとりを行います。
・Keyboard.cs

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

namespace CatchAndRun.GameUI
{
    public class Keyboard : MonoBehaviour, IGameUI
    {
        // GameUIアクセス
        private GameUI p_GameUI;

        // Use this for initialization
        public void init(GameUI gameUi)
        {
            p_GameUI = gameUi;
        }
(略)

・Speech.cs

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

namespace CatchAndRun.GameUI
{
    public class Speech : MonoBehaviour, IGameUI
    {
        // GameUIアクセス
        private GameUI p_GameUI;

        // Use this for initialization
        public void init(GameUI gameUi)
        {
            p_GameUI = gameUi;
        }
(略)

・Gesture.cs

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

namespace CatchAndRun.GameUI
{
    public class Gesture : MonoBehaviour, IGameUI
    {
        // GameUIアクセス
        private GameUI p_GameUI;

        // Use this for initialization
        public void init(GameUI gameUi)
        {
            p_GameUI = gameUi;
        }
(略)

今回、Keyboard入力についてリファクタリングする際、一点悩んだことがありました。
入力判定でUpdate関数を利用すべきか、FixedUpdate関数を利用すべきかということです。

これまでキーボード入力を受け付けていたXboxOneController.csではFixedUpdate関数が利用されていました。
Rigidbodyを扱う場合は実行間隔が変わらないFixedUpdate関数を使えとのことです。
docs.unity3d.com
unity3d.com


しかし今回の場合、キーボード入力はその先の処理で行われることを知りません。
設計の理屈で言えばGameUI側はUpdate関数を用いて、Rigidbodyを扱う場所で何らかの考慮を行うべきでしょう。
という訳で現在はUpdate関数を用いています。
何か剛体に関する問題が発生した場合、ここを疑う必要があるため、備忘録として残しておきます。

Blenderで3Dモデルを作成する(かんたん講座編その4)

本日は服と靴のモデリングを行いました。
f:id:bluebirdofoz:20170519002914j:plain
後、頭頂部の丸みが歪なのが気になったのでその2、その3で学んだ知識をもとに修正しています。

今回は服の作成にシュリンクラップ機能というのを用いました。
素体を元に厚みを持った服が作成できるため、非常に効率的です。
・【Blenderシュリンクラップの使い方【覚書】
 http://madeinpc.blog50.fc2.com/blog-entry-886.html
・ブレンダー2.48の新機能シュリンクラップで包んでみよう
 http://pastel.slmame.com/e443938.html

服だけでなく、色々活用できそうな機能です。
現実の携帯端末を3Dスキャンでモデルを取り込み、この機能を用いてプラスチックカバーの3Dモデルを作成したり。
3Dプリンタまであれば自作のカスタムカバーを詳細な採寸無しで作れてしまいそう。

因みにblenderの3Dオブジェクトの加工機能ですが、見えるだけでまだまだこれだけあります。
f:id:bluebirdofoz:20170519003538j:plain
作成に辺り、知っていると便利な機能が色々出てくるので、一度全部調べておきます。

Blenderで3Dモデルを作成する(かんたん講座編その3)

本日は3Dモデリングの続きです。
今回は手と腕のモデリングを行いました。
f:id:bluebirdofoz:20170518013944j:plain

特に手のモデリングが大変です。自然な感じにポリゴンを繋げることができず、四苦八苦しました。
f:id:bluebirdofoz:20170518013951j:plain
人体の造形についてはある程度の正解があるはずなので、この辺りの習得は参考書を買った方が速そう。
今回はモデリングの練習なので自己流です。


細かなポリゴンを作るにあたって以下のサイトを参考にしました。
www.cg-ya.net

更に途中で面が反転しているという失敗を見事にやらかしたので面の確認方法をメモしておきます。
blog.sunday-model.net


最後に頭と体を結合させて、体の素体が完成です。
f:id:bluebirdofoz:20170518014042j:plain
少しずつblenderの使い方に慣れ始めている感覚はあるので今後も継続していきます。