Aizu-Progressive xr Lab blog

会津大学のVR部であるA-PxLの部員が持ち回りで投稿していくブログです。部員がそれぞれVRに関する出来事やVRにちなんだことについて学んだことを書いていきます。

連絡はサークルTwitterのDMへお願いします。
「面白法人カヤックVR分室」としても活動しています。詳細はこちら

部室を3Dスキャンしてみた

こんにちは、ぶろっくのいずです。 タイトルの通り、許可を得てスマートシティAiCTにあるA-PxLの部室を数千枚の写真から3D復元してみました。



出来たのはこんな感じです。 f:id:aizu-vr:20200413212933p:plain



clusterというアプリ(PCとスマホ対応)で歩き回れます。



そして、A-PxLはなんとVRコミケ「バーチャルマーケット4」に出店予定!!

バーチャルマーケットでもA-PxLの部室が使われることになりました。

※A-PxLが出店しているこのブースは現在、数人の部員によるチームで活動しています。



今回、使ったのはフォトグラメトリという技術です。フォトグラメトリとは、被写体を様々な角度から撮影し、その写真を解析することで被写体を3次元的に復元する技術のことです。



フォトグラメトリのソフトの紹介↓


自分もフォトグラメトリをやりたい!という人には、まず、スマホのアプリ「Display.land」をおすすめします。 styly.cc 無料な上に扱いやすく、撮影を頑張ればモデルのクオリティもそれなりに高いです。



スマホじゃなくてパソコンでフォトグラメトリをやりたいという人には、まずSteamにある「3DF Zephyr」の体験版をおすすめします。 store.steampowered.com 3DF Zephyrの体験版は50枚まで写真を使えて、モデルをエクスポートすることができるため、フォトグラメトリを使ってみたい人にはおすすめです。3DF Zephyrは日本語にも対応してるので人によっては使いやすいですね。



「お金は出すから一番クオリティが高いフォトグラメトリのモデルが欲しいんだ!」という人には「Reality Capture」をおすすめします。 store.steampowered.com 業者が使うようなライセンスはめちゃくちゃ高くて、Steamの個人向けのライセンスでも高いです。それでも、フォトグラメトリソフトでReality Capture以上の品質を出せるソフトは今のところ見たことがありません。ちなみに今回の記事で紹介しているA-PxLの部室のモデルは、Reality Captureを使っています。


それぞれのフォトグラメトリソフトに得意不得意があるかと思いますが、おすすめしたいソフトはこんな感じです。

...終わり...

Unity上で動く履帯の作り方 in Blender 2.82

はじめに

こんにちは、部員の杉山です。今回は新入生向けコンテンツを作り始めた際にひっかかった所を記事にしていきます。

事の起こり

履帯を作るにあたり私はいつも通りArrayモディファイアとCurveモディファイアを併用していました。そしてリグを打ち、ポーズモードでアニメーションを作るぞ!と思ったとき。

f:id:aizu-vr:20200321115540p:plain

失敗例1-履帯が丸ごと動いている

履帯が丸ごと動いてしまうことに気づきました、これではいけませんね。

本題

この問題を解決するにあたり、Youtubeで検索したところこのような動画が!

youtu.be

ここからは動画を参考に作っていきます。
動画 0:35~ 履帯部分作成

キューブの中心にループカットを挿入して、下半分を削除、面を作成し埋めます。
こうすることで、キューブの中心座標を下面に合わせているんですね。
上面を移動ツールで下げ、スケールツールでⅹ軸方向の厚みを調整。(筆者はY軸方向に厚み調節しました)
この時、Ctrl キーを押しながらだと綺麗に動かせます。

f:id:aizu-vr:20200321190742p:plain



動画 1:23~ ボーンと関連付け

Amatureを作成し、CubeをBoneのTailに中心部分が重なるように移動させます。(キューブを動かす際は3Dカーソルを使うとうまく動かせます、Shift+Sのショートカットを上手く使いましょう)
移動出来たら、Shift キーを押しながらCube→Armatureの順で選択し、Ctrl+P、「自動のウェイト」を選択

f:id:aizu-vr:20200321190602p:plain

動画 1:50~ 複製

平行投影で作業するのがよいでしょう。先ほど作成したArmatureとCubeのセットを選択、Shift+D で複製し動画の通りに並べていきます。数はお好きに。

 f:id:aizu-vr:20200322183542p:plain

動画 5:10~ アニメーション作成

今しがた作ったArmatureをすべて選択してPoseModeにし、履帯の動きを作っていきましょう。ここも動画の通りにしていれば問題ありません。

動画 8:22~ 複製

履帯を複製します。ObjectModeに戻し、全選択した後、複製しましょう。

動画 8:45~ 後退アニメーション

先ほど作ったアニメーションに後退を追加します。画面を増やして、DopeSheetに変更しましょう。開始時のモーションをShift+Dで複製し等間隔に配置します。

動画 10:13~ ループアニメーション

ここまで作ったアニメーションを再生するとゆっくりと加速減速し、ループ感が出ません。画面をGraphEditorに切り替えます。グラフが緩やかな曲線を描いているのが見て取れると思います。Aキーで全選択した後に、Vキーを押し「ベクトル」の項目を選択しましょう。再生すると直線的な緩急のない動きになったと思います。

動画 11:10~ FBX書き出し

FBX形式で書き出しします。

f:id:aizu-vr:20200322194553p:plain

動画 11:23~ Unityに持っていく

ここからはUnityでの作業になります。出力したFBX形式のファイルをUnity上に持っていき、Scene上に配置します。Scene上に配置したモデルにAnimatorControllerを設定しておきましょう。

動画 11:40~ アニメーション

Unityに持ってきたモデルのPrefabを選択し、InspectorビューでAnimationの長さを調整します。前進部分のみを選択しLoopTimeにチェックを入れてください。分かりやすい様に名前を付けたらApplyを押しましょう。次に+マークを押し同じように後退のアニメーションも切り抜きます。

動画 13:40~ アニメーションコントロール

Animatorビューを開き、先程作った前進のアニメーションをドラッグ&ドロップしましょう。綺麗に前進できていますか?できていたら成功です。

f:id:aizu-vr:20200323133059p:plain

まとめ

動画で紹介されているのはここまででしたが、自分で調べて動画終わりのように挙動に合わせた履帯の動きも作れればいいなと思います。

2019年度活動報告

はじめに

こんにちは, 部長の木村です. 昨年に続いて, 2019年度の振り返りをしたいと思います.

普段の活動

2019年度の活動では, 「ゲーム部」と「新規開発部」という2つの部署に分かれて活動していました. 昨年度まではチームでのVRゲームの開発をメインに活動してきましたが, 個人での活動やゲームだけではなく新しいデバイスや技術を使った開発もしたとの声があり, このような体制での活動をする事に決定しました.

全体

前期の間は新入生向けにxR開発に必要な技術を学ぶためのワークショップを開きました.

また, 毎週第4金曜日には全体での定例会を開いてLT会などを行いました.

ゲーム部

ゲーム部では, 展示イベントに向けて全員で1つのVRゲームの制作を行いました. スケジュールは以下の通りです.

オープンキャンパスやデジゲー博で出展した作品です.

新入生歓迎会に向けての開発は現在も進行中です.

新規開発部

新規開発部では個人開発の他, 次のような活動を行いました.

  • xR関連の映像作品の試聴会
  • VR界隈のワード紹介
    • VR界隈でよく目にするワードについて調べ, 毎週交代でLTのような形式で発表し合いました.

また, 現在はバーチャルマーケット4への出展に向けての準備と新入生歓迎会に向けてのチーム開発が進行中です.

イベント

VRキャラバン活動 (5/18,19)

HTC Vive ProやゲーミングPC等の機材の運搬・電力供給をすべて電気自動車で行い, 野外でVRコンテンツの展示をしました.

詳しくはこちらの記事をご覧ください.

Oculus Quest体験会 (6/16)

Oculus Questが発売され, A-PxLでも多くの部員が購入したので大学の体育館を貸し切ってQuestの体験会を開きました.

新入生対抗ハッカソン (7/13,14)

新入生向けのワークショップが一通り終わり, 新入生同士のチームでのハッカソンを行いました. テーマは「既存のゲームを面白くハックしよう」ということで, Unity公式のサンプルプロジェクトや事前に用意したシンプルなゲームの改造をしてもらいました.

オープンキャンパス VR作品体験会 (8/11)

大学のオープンキャンパスで, 部員の制作した作品の体験会をやりました.

学祭

学祭でも制作物の展示・体験会を予定していたのですが, 今年度は台風の影響で学祭自体が中止となってしまいました...

デジゲー博

今年も抽選に当選し, 参加させて頂く事ができました!

その他

これまでに部で制作した作品をまとめたポートフォリオです。

https://www.foriio.com/a-pxl

UniTaskとUniRxで、待ちの処理を実装する(後編:UniRx)

前編では、非同期処理のライブラリとしてUniTaskを軽く解説しました
UniTaskとUniRxで、待ちの処理を実装する(前編:UniTask) - Aizu-Progressive xr Lab blog
今回はUniRxというものを解説します

UniRxに関しては文章で説明するより、コードを見せる方が早そうです

void Start() {
  var count = 0;
  this.UpdateAsObservable()
    .Subscribe(_ => {
      if (Input.GetKeyDown(KeyCode.Space)) {
        print(count++);
      }
    });
}

はい
このコードだけで、スペースを押すごとにその回数を出力できます
フィールドに値を保持する必要がありません
正確にはライブラリの目的は、イベントハンドリングであり、イベントという概念をうまく扱う為のものですが
学習コストが高いので詳しくは触れない事にします

UniTaskと組み合わせるととても便利な部分と、UI等の処理には使えるかなという部分を紹介します

UniRx

まずUniRxの導入について
UniRxはAssetStoreにあるのでそちらから
UniRx - Reactive Extensions for Unity - Asset Store
コードで使うときは次の物が必要です

using UniRx;
using UniRx.Triggers;

最初に見せたコードの事もそれなのですが
取り敢えず、好きなところでUpdate処理を書ける事を見せます

this.UpdateAsObservable()
  .Where(_ => Input.GetKeyDown(KeyCode.Space)) // フィルタ
  .Subscribe(_ => {
    print("スペースキーが押された");
  });

ここでのthisは、自分自身・・・書いているコンポーネントのクラスの事ですね
そこから、UpdateAsObservable()というものを取得できて、Subscribeという物に、ラムダ式でUpdate処理を書くことになります
Whereという物を使うと、条件がtrueのときのみSubscribeの処理を実行できます
ここのラムダ式では、引数は存在するのですが、
値が無いを意味するUnit型という物であるため、
使わないので_にしています

他にも

this.OnCollisionEnterAsObservable()
  .Subscribe(collision => {
    var renderer = collision.collider.GetComponent<Renderer>();
    if (renderer != null) {
      renderer.enabled = false;
    }
  });

これで、OnCollisionEnter時にぶつかったColliderにRendererがあれば、表示を消す処理

this.OnCollisionStayAsObservable();
this.OnCollisionExitAsObservable();
this.OnTriggerEnterAsObservable();
this.OnTriggerStayAsObservable();
this.OnTriggerExitAsObservable();

これらのように、UniRxによって、
Updateの他にも一通りの
「決まったメソッドを定義して書かなければならないもの」
を任意の位置で書くことができるようになります

UniTaskと組み合わせる

ではUniTaskと組み合わせたら何ができるのか
見ていきましょう
いきなりちょっと複雑な例になりますが
例えば、
ボタンを押しているかつゲージが0より大きい場合にとある動作をし続けるというのがあるとします
そして、ゲージが0になったら動作をやめて、ゲージが回復し始めます
ゲージはUIとして表示されなくとも
ゲームの走る動作なんかはそういうのがよくありますよね
なので走る動作として実装してみます

using System;
using System.Threading;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UniRx;
using UniRx.Triggers;
using UniRx.Async;
using UniRx.Async.Triggers;

public class Player : MonoBehaviour {
  public float walkSpeed;
  public float runSpeed;
  public float statminaRecoverySpeed;
  public float staminaDecreaseSpeed;
  public float staminaMax;
  public float stamina;
  [NonSerialized] public float currentSpeed;

  void Start() {
    Run(this.GetCancellationTokenOnDestroy()).Forget();

    // 初期化
    stamina = staminaMax;
    currentSpeed = walkSpeed;
    // 移動処理の登録(前後のみ)
    this.UpdateAsObservable() // UniRx
      .Subscribe(_ => {
        Vector3 moveDir = Vector3.zero;
        if (Input.GetKey(KeyCode.W)) {
          moveDir += Vector3.forward;
        }
        if (Input.GetKey(KeyCode.D)) {
          moveDir += Vector3.back;
        }
        transform.position += moveDir * currentSpeed * Time.deltaTime;
        // スタミナの回復は常にしている
        stamina += statminaRecoverySpeed * Time.deltaTime;
        if (stamina > staminaMax) stamina = staminaMax;
      });
  }

  async UniTask Run(CancellationToken token) {
    // 走り始めの条件判定
    await UniTask.WaitUntil(() => {
        var staminaRate = stamina / staminaMax;
        var b1 = Input.GetKeyDown(KeyCode.LeftShift);
        var b2 = staminaRate > 0.3f;
        return b1 && b2;
      }, cancellationToken: token);

    // 走り始める
    currentSpeed = runSpeed;
    var disposable =  // スタミナ減少処理
      this.UpdateAsObservable() // UniRx
      .Where(_ => Input.GetKey(KeyCode.W))
      .Subscribe(_ => stamina -= staminaDecreaseSpeed * Time.deltaTime);
    // SubscribeしたのをCancellationTokenでCancelしたときに止めたいならこう書く
    token.Register(() => disposable.Dispose());

    // 止まる条件判定
    await UniTask.WaitUntil(() => {
        var b1 = Input.GetKeyUp(KeyCode.LeftShift) || Input.GetKeyUp(KeyCode.W);
        var b2 = stamina < 0f;
        return b1 || b2;
      }, cancellationToken: token);

    // 止まる
    disposable.Dispose(); // スタミナ減少処理の停止
    currentSpeed = walkSpeed;
    if (stamina < 0f) stamina = 0f;

    // 繰り返す
    Run(token).Forget();
  }
}

このコードをGameObjectにアタッチして、
f:id:aizu-vr:20200316033623p:plain
このような感じで値を設定すると、
WキーとShiftキーを押すと、移動速度が上がり、
スタミナが無くなるか、WキーやShiftキーが離されれば移動速度が下がるところが確認できます
Staminaも正しく上がったり下がったりするのが確認できます

UniRxを使ったところは2か所ありますが、
二つ目のところでdisposableという物を取得しています。
このdisposableDisposeすることで、登録した処理を止めることが出来ます

このように、UniTaskとUniRxを組み合わせることで、
async/awaitの非同期的な処理によって、Update動作を始めたり止めたりできるので
複雑そうな処理を割と簡単に書く事ができました

UIの更新

UniRxにある、ReactivePropertyというものを使うと
主にUI等の更新処理を書けます
テキストの更新は毎フレーム行うと、メモリの使い方が悪くなりますから

public FloatReactiveProperty hp;
public Text text;
void Start() {
  this.UpdateAsObservable()
    .Subscribe(_ => hp.Value += Time.deltaTime); // hpを増やすとする
  // hpの更新処理。hpの値が変更されたら処理が実行される
  // AddToを付けないとメモリに悪いので一応付けた方が良い
  hp.Subscribe(v => text.text = v.ToString()).AddTo(this);
}

UniRxについてとアニメーション実装してみた

ここからちょっと難易度が上がります
需要があるかはわからないですがUniRxとUniTaskを用いて、ちょっとスクリプトのみでアニメーションを実装してみます
f:id:aizu-vr:20200316061114g:plain

まず先に、
UniRxの型について解説します
UniRxではIObservableという型が重要です
this.UpdateAsObservable()の型や
this.OnTriggerEnterAsObservable()の型は
それぞれ、
IObservable<Unit>
IObservable<Collision>であり、
これらの型はWhereSubscribeを使うことができます
WhereSubscribe時のラムダ式の引数の型は
IObservable<T>Tの部分の型です

UniRxを使うと、次のような処理も楽に書けます
コルーチンが前提知識になるので知らない人は調べて欲しいかなってところですが
分からないなら飛ばしても構いません
好きなところから、指定した時間毎フレーム経過時間と指定した時間の割合を取って何か処理をしたい場合

void Start() {
  // コルーチンに渡したobserverから流された値が流れてくる
  // oはobserver
  Observable.FromCorountine<float>(o => Timer(3f, o))
    .Subscribe(t => print(t)).AddTo(this);
}
IEnumerator Timer(float actTime, IObserver<float> observer) {
  var sTime = Time.time; // 最初の時間
  while(true) {
    var rate = (Time.time - sTime) / actTime; // 経過時間 / 指定した時間
    if (rate > 1f) break; // 割合が1より大きいなら終了
    observer.OnNext(rate); // 割合を送る
    yield return null;
  }
  observer.OnNext(1f); // 最後に1だけ送る
  observer.OnCompleted(); // Observableを終了させる
}

これで、3秒間0から1までの値を出力してくれます

ここでは、コルーチンに渡されたobserverに
OnNextで経過時間の割合を流します

FromCorountineの型はIObservableで、
IObserverに流した値が流れてくれ、
Subscribe時に流した値を使うことが出来ます

ここでは、経過時間の割合が出力されますね
OnCompletedすると、Observable側で値が流れるのが終了されたというのを検知できます

AddToしているのは、コンポーネントがDestroyしたときに処理をキャンセルする為です

UniTaskと組み合わせて、
アニメーションさせてみます

using System;
using System.Threading;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UniRx;
using UniRx.Triggers;
using UniRx.Async;
using UniRx.Async.Triggers;

public class SphereAnimation : MonoBehaviour {
  void Start() {
    PlayAnimation(this.GetCancellationTokenOnDestroy()).Forget();
  }

  async UniTask PlayAnimation(CancellationToken token) {
    await UniTask.WaitUntil(() => Input.GetKeyDown(KeyCode.Space)
        , cancellationToken: token);
    await Animation(token);
    PlayAnimation(token).Forget();
  }

  async UniTask Animation(CancellationToken token) {
    IObservable<float> timer;
    IDisposable disposable;

    // 弾む、1回目
    timer = Observable.FromCoroutine<float>(o => Timer(0.5f, o));
    disposable = timer.Subscribe(t => 
        transform.position = Vector3.up * (-(8f*t*t) + (8f*t))); // 放物線
    token.Register(() => disposable.Dispose());
    await timer.ToUniTask(token);

    // 弾む、2回目
    // 同じObservableの場合、設定しなおさなくていい(例外あり)
    disposable = timer.Subscribe(t => 
        transform.position = Vector3.up * (-(8f*t*t) + (8f*t)));
    token.Register(() => disposable.Dispose());
    await timer.ToUniTask(token);

    // 1回転
    disposable = timer.Subscribe(t => 
        transform.position = new Vector3(
          Mathf.Sin(Mathf.PI * 2f * t),
          0f,
          Mathf.Cos(Mathf.PI * 2f * t)
        ));
    token.Register(() => disposable.Dispose());
    await timer.ToUniTask(token);

    // 早く弾む
    disposable = timer.Subscribe(t => 
        transform.position = Vector3.up * (-Mathf.Sin(Mathf.PI * t * 4f) + 1f));
    token.Register(() => disposable.Dispose());
    await timer.ToUniTask(token);
  }

  IEnumerator Timer(float actTime, IObserver<float> observer) {
    var sTime = Time.time;
    while(true) {
      var rate = (Time.time - sTime) / actTime;
      if (rate > 1f) break;
      observer.OnNext(rate);
      yield return null;
    }
    observer.OnNext(1f);
    observer.OnCompleted();
  }
}

このコードを球にアタッチして
シーンを再生してSpaceキーを押すと
次のアニメーションが動きます
f:id:aizu-vr:20200316061114g:plain

最後の例はずいぶん難しいかもしれませんが、
UniTaskとUniRxを組み合わせると結構良い感じにコードが書ける事がわかったと思います

最後に

UniRxは基本的にはイベントハンドリングというのが目的で扱う物です
一応、UniRxはいろんな書き方ができてしまい、
UniRxで非同期処理もできなくはないのですが、
UniRxは非同期処理の為に使ってはいけません
(↑これは僕の経験談でもある)
"待ち"が入る非同期処理はUniTask
動作を登録したいときはUniRx
のような使い分けが大事です

それを分かったうえで、UniRxを学習するのも悪くは無いでしょう
とりさんの記事とかで、しっかり学べます
UniRx入門 その1 - Qiita

会津大学VR部の部員が持ち回りで投稿していくブログです。特にテーマに縛りを設けずに書いていきます!