20190523のUnityに関する記事は6件です。

Unity でVRのテレポートを一番簡単な実装

概要

この記事は、VR上でよく目にする「テレポート」を実現するための手法を書いてあります。ハンドデバイスの先に放物線状に線が表示されて、ボタンを押すとその着地点にワープするというアレです。

順番としては

  1. VRのためのセットアップ
  2. VRTKインストール(Oculus Integrationも)
  3. ゲーム画面のセットアップ
  4. デバイス入力
  5. テレポートシステム

とやっていきます。

システム要件

  • Unity 2018.3.0f2
  • Oculus GO
  • Asset Storeで無料で取得できるアセット
    • VRTK (4.0.0)
    • Oculus Integration (1.36)

Android プラットフォームに切り替え

  1. Androidを選択
  2. Texture Compression をASTCに
  3. Switch Platform をクリック

    Build_Settings_と_Unity_2018_3_0f2_-_SampleScene_unity_-_MultiplayTest_-_PC__Mac___Linux_Standalone__Metal_.png

PlayerSettings の設定

Oculus GO用にPlayerSettingsをいくつか変更します。

XR Settings

  1. Virtual Reality Supported にチェック
  2. +ボタンをクリック
  3. Oculus を選択

    Unity_2018_3_0f2_-_SampleScene_unity_-_MultiplayTest_-_Android__Metal_.png

Other Settings

  1. Package Name を適当に自分のドメインを入れる
  2. Minimum API Level を「Android 4.4 'KitKat' (API level 19)」を選択
  3. Scripting Runtime Version を「.NET 4.x Equivalent」(デフォルトでなっているはず)

    Unity_2018_3_0f2_-_SampleScene_unity_-_MultiplayTest_-_Android__Metal_.png

VRTKとOculus Integrationのインストール

ハンドデバイスやテレポートシステムを簡易的に使うには「VRTK」が一番簡単で良いだろうということで、VRTKを使うことにします。VRTKは最新版のv4.0.0を使うことにします。ただし、最新版はGitHubにしかない(2019/5/22現在)ので、gitコマンドで持ってくる必要があります。

ターミナル(もしくはcmd)で プロジェクトの Assets/ 以下に行って、以下のコマンドを打ちます

git clone --recurse-submodules https://github.com/ExtendRealityLtd/VRTK.git
cd VRTK
git submodule init && git submodule update

Gitコマンドがない、というかたは

https://github.com/ExtendRealityLtd/VRTK

からダウンロードしてください。その場合は

https://github.com/ExtendRealityLtd/Zinnia.Unity/tree/91b8e16fc366cf6022440ed1efbe7ce4e636eae4

もダウンロードして

Assets/VRTK/Dependency/Zinnia.Unity/

に配置する必要があるので気をつけてください。

なお、現段階(2019/5/22現在)では以下のようなエラーが出てしまいます。

Assets/VRTK/Dependencies/Zinnia.Unity/Editor/Data/Collection/ObservableListEditor.cs(17,41): error CS0246: The type or namespace name 'InspectorEditor' could not be found (are you missing a using directive or an assembly reference?)

仕方ないので、手っ取り早く以下のファイルを消します

Assets/VRTK/Dependencies/Zinnia.Unity/Editor/Data/Collection/ObservableListEditor.cs

正常にインポートできれば、最後にウィンドウが出ます。「Add Input Mappings」を押します。
_VRTK__-_Manage_Input_Mappings.png

Oculus Integration は AssetStore からダウンロードしてインポートします。ただし全部必要ないので、「Platform」「VR」だけチェックが入るようにしましょう。

Import_Unity_Package.png

ゲーム画面のセットアップ

とりあえずステージとなるものとプレイヤーとなるものを作っています。
(ここでやっていることはこの通りではなくて良いです。ステージとなるCubeと、プレイヤーとなるオブジェクトを作っていれば何でも良いです)

  1. GameObject→3DObject→Cube で立方体を作って、Position(0,-1,0), Scale(20,1,20)。名前をFloorにする。
  2. GameObject→3DObject→Capsule でカプセルを作って、Position(0,0,0), Scale(1,0.5,1)。名前をHeadにする。
  3. GameObject→3DObject→Cube で立方体を作って、Position(0,0,0.2), Scale(0.7,0.2,0.7)。名前をHMDにして、Headにドラッグアンドドロップ(以下D&D)して子オブジェクトにする.
  4. GameObject→Create Empty で空オブジェクトを作って、Position(0,0,0)で名前をPlayer にする.
  5. Head を Player にD&Dして子オブジェクトにする.

こんな感じになるかと思います。(ちなみにこのプレイヤーは次回に使います。この時点では動きません)
Unity_2018_3_0f2_-_SampleScene_unity_-_MultiplayTest_-_Android__Metal_.png

VRTKのセットアップ

まずはVRTK で基本となる「TrackedAlias」を設定します。これは様々なデバイスを利用できるようにVRTK側でまとめてくれているものです。これを経由することでいちいちOculusはこれでVIVEはこれでとか設定しなくても良くなるというものです。

Assets/VRTK/Prefabs/CameraRig/TrackedAlias/TrackedAlias.prefab

をシーンにD&Dします。
Unity_2018_3_0f2_-_SampleScene_unity_-_MultiplayTest_-_Android__Metal_.png

次にOculus のカメラ

Assets/Oculus/VR/Prefabs/OVRCameraRig.prefab

もシーンにD&Dします。
シーンに元々ある MainCamera は削除しておきましょう。
Unity_2018_3_0f2_-_SampleScene_unity_-_MultiplayTest_-_Android__Metal_.png

シーンのルートに空オブジェクト(Position(0,0,0)に)を作って「OVRCameraRigRoot」という名前にして、それに先程の「OVRCameraRig」オブジェクトをD&Dして子オブジェクトにします。

シーン上の「OVRCameraRig」を選択して、OVRManager の項目で以下の項目を変更します。

  • Tracking Origin Type → Floor Level
  • Reset Tracker On Load → チェック

Unity_2018_3_0f2_-_Game_unity_-_MultiplayTest_-_Android__Metal_.png

「OVRCameraRigRoot」に「Linked Alias Association Collection」をアタッチします。そして以下のように設定します。

  • OVRCameraRigRoot → PlayArea
  • OVRCameraRigRoot>OVRCameraRig → Headset
  • OVRCameraRigRoot>OVRCameraRig>CenterEyeAnchor → Headset Camera
  • OVRCameraRigRoot>OVRCameraRig>LeftHandAnchor → Left Controller
  • OVRCameraRigRoot>OVRCameraRig>RightHandAnchor → Right Controller

Unity_2018_3_0f2_-_SampleScene_unity_-_MultiplayTest_-_Android__Metal_.png

TrackedAliasを選択して、Inspector(右の詳細画面)の一番下にある Elements の Size を 1 にします。
Element 0 のところに先程作った「OVRCameraRigRoot」をD&Dします。
Unity_2018_3_0f2_-_SampleScene_unity_-_MultiplayTest_-_Android__Metal_.png

デバイス入力

VRTKでデバイス入力をするためにはまず、VRTKの基本となる部分を進めます。

Assets/VRTK/Prefabs/CameraRig/UnityXRCameraRig/InputMappings/UnityXR.OpenVR.RightController.prefab

をシーンにD&D
Unity_2018_3_0f2_-_SampleScene_unity_-_MultiplayTest_-_Android__Metal_.png

続けて

  • Assets/VRTK/Prefabs/Pointers/ObjectPointer.Curved.prefab

をシーンにD&D
Unity_2018_3_0f2_-_SampleScene_unity_-_MultiplayTest_-_Android__Metal_.png

PointerFacadeの各項目に下のようにアサインします。

  • TrackedAlias>Aliases>RightControllerAlias → Folow Source
  • UnityXR.OpenVR.RightController>Trackpad>Touch[17] → Activation Action
  • UnityXR.OpenVR.RightController>Trackpad>Press[9] → Selection Action

Unity_2018_3_0f2_-_SampleScene_unity_-_MultiplayTest_-_Android__Metal_.png

一旦このシーンをセーブしておいてください。ファイル名は"Game"とかにしておきましょう。
この段階でOculus GO にビルドしてみましょう。デバイスのタッチ部に触ると、棒から放物線が出るはずです。

テレポートシステム

Assets/VRTK/Prefabs/Locomotion/Teleporters/Teleporter.Instant.prefab をシーンにD&Dします。

Unity_2018_3_0f2_-_SampleScene_unity_-_MultiplayTest_-_Android__Metal_.png

シーンに配置した「Teleporter.Instant」を選択して、TeleportFacadeに以下のようにアサインします。

  • TrackedAlias>Aliases>PlayAreaAlias → Target
  • TrackedAlias>Aliases>HeadsetAlias → Offset
  • TrackedAlias>Aliases>SceneCameras → Camera Validity

Unity_2018_3_0f2_-_SampleScene_unity_-_MultiplayTest_-_Android__Metal_.png

次にパッドボタンを押した時にテレポートが発動できるようにします。
「ObjectPointer.Curved」を選択して、

  1. 一番下の方にある「Selected (Event)」の「+」ボタンをクリック
  2. 「Teleporter.Instant」を項目にD&D
  3. 「No Function」 を 「TeleporterFacade→Teleport」 に変更

Unity_2018_3_0f2_-_Game_unity_-_MultiplayTest_-_Android__Metal_.png

これで再度シーンをセーブしてOculus GOでビルドしてみて、ちゃんとテレポートシステムが作動しているか確認してください。

GO01.gif

次回は「UnityでマルチプレイVRを一番簡単に実現する方法」を解説していきます。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Unity でVRのテレポートを一番簡単に実現する方法

概要

この記事は、VR上でよく目にする「テレポート」を実現するための手法を書いてあります。ハンドデバイスの先に放物線状に線が表示されて、ボタンを押すとその着地点にワープするというアレです。

順番としては

  1. VRのためのセットアップ
  2. VRTKインストール(Oculus Integrationも)
  3. ゲーム画面のセットアップ
  4. デバイス入力
  5. テレポートシステム

とやっていきます。

システム要件

  • Unity 2018.3.0f2
  • Oculus GO
  • Asset Storeで無料で取得できるアセット
    • VRTK (4.0.0)
    • Oculus Integration (1.36)

Android プラットフォームに切り替え

  1. Androidを選択
  2. Texture Compression をASTCに
  3. Switch Platform をクリック

    Build_Settings_と_Unity_2018_3_0f2_-_SampleScene_unity_-_MultiplayTest_-_PC__Mac___Linux_Standalone__Metal_.png

PlayerSettings の設定

Oculus GO用にPlayerSettingsをいくつか変更します。

XR Settings

  1. Virtual Reality Supported にチェック
  2. +ボタンをクリック
  3. Oculus を選択

    Unity_2018_3_0f2_-_SampleScene_unity_-_MultiplayTest_-_Android__Metal_.png

Other Settings

  1. Package Name を適当に自分のドメインを入れる
  2. Minimum API Level を「Android 4.4 'KitKat' (API level 19)」を選択
  3. Scripting Runtime Version を「.NET 4.x Equivalent」(デフォルトでなっているはず)

    Unity_2018_3_0f2_-_SampleScene_unity_-_MultiplayTest_-_Android__Metal_.png

VRTKとOculus Integrationのインストール

ハンドデバイスやテレポートシステムを簡易的に使うには「VRTK」が一番簡単で良いだろうということで、VRTKを使うことにします。VRTKは最新版のv4.0.0を使うことにします。ただし、最新版はGitHubにしかない(2019/5/22現在)ので、gitコマンドで持ってくる必要があります。

ターミナル(もしくはcmd)で プロジェクトの Assets/ 以下に行って、以下のコマンドを打ちます

git clone --recurse-submodules https://github.com/ExtendRealityLtd/VRTK.git
cd VRTK
git submodule init && git submodule update

Gitコマンドがない、というかたは

https://github.com/ExtendRealityLtd/VRTK

からダウンロードしてください。その場合は

https://github.com/ExtendRealityLtd/Zinnia.Unity/tree/91b8e16fc366cf6022440ed1efbe7ce4e636eae4

もダウンロードして

Assets/VRTK/Dependency/Zinnia.Unity/

に配置する必要があるので気をつけてください。

なお、現段階(2019/5/22現在)では以下のようなエラーが出てしまいます。

Assets/VRTK/Dependencies/Zinnia.Unity/Editor/Data/Collection/ObservableListEditor.cs(17,41): error CS0246: The type or namespace name 'InspectorEditor' could not be found (are you missing a using directive or an assembly reference?)

仕方ないので、手っ取り早く以下のファイルを消します

Assets/VRTK/Dependencies/Zinnia.Unity/Editor/Data/Collection/ObservableListEditor.cs

正常にインポートできれば、最後にウィンドウが出ます。「Add Input Mappings」を押します。
_VRTK__-_Manage_Input_Mappings.png

Oculus Integration は AssetStore からダウンロードしてインポートします。ただし全部必要ないので、「Platform」「VR」だけチェックが入るようにしましょう。

Import_Unity_Package.png

ゲーム画面のセットアップ

とりあえずステージとなるものとプレイヤーとなるものを作っています。
(ここでやっていることはこの通りではなくて良いです。ステージとなるCubeと、プレイヤーとなるオブジェクトを作っていれば何でも良いです)

  1. GameObject→3DObject→Cube で立方体を作って、Position(0,-1,0), Scale(20,1,20)。名前をFloorにする。
  2. GameObject→3DObject→Capsule でカプセルを作って、Position(0,0,0), Scale(1,0.5,1)。名前をHeadにする。
  3. GameObject→3DObject→Cube で立方体を作って、Position(0,0,0.2), Scale(0.7,0.2,0.7)。名前をHMDにして、Headにドラッグアンドドロップ(以下D&D)して子オブジェクトにする.
  4. GameObject→Create Empty で空オブジェクトを作って、Position(0,0,0)で名前をPlayer にする.
  5. Head を Player にD&Dして子オブジェクトにする.

こんな感じになるかと思います。(ちなみにこのプレイヤーは次回に使います。この時点では動きません)
Unity_2018_3_0f2_-_SampleScene_unity_-_MultiplayTest_-_Android__Metal_.png

VRTKのセットアップ

まずはVRTK で基本となる「TrackedAlias」を設定します。これは様々なデバイスを利用できるようにVRTK側でまとめてくれているものです。これを経由することでいちいちOculusはこれでVIVEはこれでとか設定しなくても良くなるというものです。

Assets/VRTK/Prefabs/CameraRig/TrackedAlias/TrackedAlias.prefab

をシーンにD&Dします。
Unity_2018_3_0f2_-_SampleScene_unity_-_MultiplayTest_-_Android__Metal_.png

次にOculus のカメラ

Assets/Oculus/VR/Prefabs/OVRCameraRig.prefab

もシーンにD&Dします。
シーンに元々ある MainCamera は削除しておきましょう。
Unity_2018_3_0f2_-_SampleScene_unity_-_MultiplayTest_-_Android__Metal_.png

シーンのルートに空オブジェクト(Position(0,0,0)に)を作って「OVRCameraRigRoot」という名前にして、それに先程の「OVRCameraRig」オブジェクトをD&Dして子オブジェクトにします。

シーン上の「OVRCameraRig」を選択して、OVRManager の項目で以下の項目を変更します。

  • Tracking Origin Type → Floor Level
  • Reset Tracker On Load → チェック

Unity_2018_3_0f2_-_Game_unity_-_MultiplayTest_-_Android__Metal_.png

「OVRCameraRigRoot」に「Linked Alias Association Collection」をアタッチします。そして以下のように設定します。

  • OVRCameraRigRoot → PlayArea
  • OVRCameraRigRoot>OVRCameraRig → Headset
  • OVRCameraRigRoot>OVRCameraRig>CenterEyeAnchor → Headset Camera
  • OVRCameraRigRoot>OVRCameraRig>LeftHandAnchor → Left Controller
  • OVRCameraRigRoot>OVRCameraRig>RightHandAnchor → Right Controller

Unity_2018_3_0f2_-_SampleScene_unity_-_MultiplayTest_-_Android__Metal_.png

TrackedAliasを選択して、Inspector(右の詳細画面)の一番下にある Elements の Size を 1 にします。
Element 0 のところに先程作った「OVRCameraRigRoot」をD&Dします。
Unity_2018_3_0f2_-_SampleScene_unity_-_MultiplayTest_-_Android__Metal_.png

デバイス入力

VRTKでデバイス入力をするためにはまず、VRTKの基本となる部分を進めます。

Assets/VRTK/Prefabs/CameraRig/UnityXRCameraRig/InputMappings/UnityXR.OpenVR.RightController.prefab

をシーンにD&D
Unity_2018_3_0f2_-_SampleScene_unity_-_MultiplayTest_-_Android__Metal_.png

続けて

  • Assets/VRTK/Prefabs/Pointers/ObjectPointer.Curved.prefab

をシーンにD&D
Unity_2018_3_0f2_-_SampleScene_unity_-_MultiplayTest_-_Android__Metal_.png

PointerFacadeの各項目に下のようにアサインします。

  • TrackedAlias>Aliases>RightControllerAlias → Folow Source
  • UnityXR.OpenVR.RightController>Trackpad>Touch[17] → Activation Action
  • UnityXR.OpenVR.RightController>Trackpad>Press[9] → Selection Action

Unity_2018_3_0f2_-_SampleScene_unity_-_MultiplayTest_-_Android__Metal_.png

一旦このシーンをセーブしておいてください。ファイル名は"Game"とかにしておきましょう。
この段階でOculus GO にビルドしてみましょう。デバイスのタッチ部に触ると、棒から放物線が出るはずです。

テレポートシステム

Assets/VRTK/Prefabs/Locomotion/Teleporters/Teleporter.Instant.prefab をシーンにD&Dします。

Unity_2018_3_0f2_-_SampleScene_unity_-_MultiplayTest_-_Android__Metal_.png

シーンに配置した「Teleporter.Instant」を選択して、TeleportFacadeに以下のようにアサインします。

  • TrackedAlias>Aliases>PlayAreaAlias → Target
  • TrackedAlias>Aliases>HeadsetAlias → Offset
  • TrackedAlias>Aliases>SceneCameras → Camera Validity

Unity_2018_3_0f2_-_SampleScene_unity_-_MultiplayTest_-_Android__Metal_.png

次にパッドボタンを押した時にテレポートが発動できるようにします。
「ObjectPointer.Curved」を選択して、

  1. 一番下の方にある「Selected (Event)」の「+」ボタンをクリック
  2. 「Teleporter.Instant」を項目にD&D
  3. 「No Function」 を 「TeleporterFacade→Teleport」 に変更

Unity_2018_3_0f2_-_Game_unity_-_MultiplayTest_-_Android__Metal_.png

これで再度シーンをセーブしてOculus GOでビルドしてみて、ちゃんとテレポートシステムが作動しているか確認してください。

GO01.gif

次回は「UnityでマルチプレイVRを一番簡単に実現する方法」を解説していきます。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Unity+Oculus GO メモ

準備

下記、URLの記事を参考にすればできるので省略、PlayerSetting周りまではこの記事で可能

AssetStoreからOculuc Integration を入れる所までやっておく
(バージョンは1.37)

●参照URL
https://framesynthesis.jp/tech/unity/oculusgo/

テストシーン作成

参考URL:http://kan-kikuchi.hatenablog.com/entry/Oculus_Integration_2

参照URLを元に作成していく、Ver1.37のPrefabには TrackedRemote がいないので焦った。

代わりを探してみたところ、OVRControllerPrefab に名前が変わっていたので、
こちらを使う。下図のような配置にしてOculusGoにビルドするとGo内でコントローラーが表示された。

使用するPrefabは
VR⇒Prefabs⇒OVRCameraRig
VR⇒Prefabs⇒OVRControllerPrefab

2019-05-23.png

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

[Unity] ComputeShaderでモブを動かす【その3:車に道路上を走らせる+パーティクル版】

経緯

コンピュートシェーダー(ComputeShader)を学ぶため、自動車を動かす交通シミュレーターもどきを作ってみようと思いました。個々の自動車がそれぞれ衝突を回避しつつ適切な経路で目的地に移動できるようになるのが目標です。
前回作成した車用ポリゴン前々回作った道路 の上で走らせてみました。
caronroad03.gif

また前回、ジオメトリシェーダーとコンピュートシェーダーがMacでは同時に使えないことが判明したので、車をパーティクルシステムで代用する方法も試してみました。
caronroad04.gif

◀【その2:車を生成する

ジオメトリシェーダー版

一旦Macのことは置いておいて、ジオメトリシェーダーで描画する車との組み合わせをやってみました。

C#側実装

そろそろ実装も複雑になってきたし、張り付けても見づらくなってきたので GitHub にリポジトリを作りました。実際のソースはこちらをご覧ください。
(初回からの分も入ってます)
https://github.com/ShinodaNaoki/learnComputeShader

CarRepository

ComputeBuffer を直接コントローラーから弄るのも煩雑なので、 CarRepository なるクラスを作って管理させることにしました。同時にこれは ComputeBuffer で使う struct をラップした Car オブジェクトとしてコントローラー側から扱えるようにしたものです。

紆余曲折ありましたが、車のstructについて、色やサイズなどの(一度車を生成したら)不変の静的情報と、速度や向きなどの動的情報に分割しました。
こうすることで、 ComputeBuffer.GetData() の負荷が幾分抑えられると考えています。

ICarStaticInfo.cs
public interface ICarStaticInfo
{
    /// <summary>
    /// サイズ
    /// </summary>
    Vector3 size { get; }

    /// <summary>
    /// 色
    /// </summary>
    Color color { get; }
}
ICarDynamicInfo.cs
public interface ICarDynamicInfo
{

    /// <summary>
    /// 座標
    /// </summary>
    Vector2 pos { get; set; }

    /// <summary>
    /// 向き(進行方向)
    /// </summary>
    Vector2 direction { get; set; }

    /// <summary>
    /// 速度
    /// </summary>
    float velocity { get; set; }
}

CarTemplate

車種ごとの色と巡航速度の雛形情報を保持するクラスです。CarRepositoryでは車種を指定するだけで、この雛形情報を元に ICarStaticInfo, ICarDynamicInfo を生成します。

RoadPlane

道路上を走らせるため、車の初期配置情報を持つ EntryPoint なるものを提供するようにしました。

RoadPlane02.cs
public class EntryPoint
{
    /// <summary>
    /// 座標
    /// </summary>
    public readonly Vector2 pos;
    public readonly Vector2 dir;

    public EntryPoint(Vector2 pos, Vector2 dir)
    {
        this.pos = pos;
        this.dir = dir;
    }
}

public class RoadPlane02 : MonoBehaviour
{
  // ..中略..

    private Vector2 ToWorldPos(Vector2 local)
    {
        // Planeのmeshサイズは10なので、なんで20なのかよくわからないけど、ぴったり合う
        var scale = 20f * transform.localScale.x / MAP_SIZE;
        // Plane が(0,0)に配置されてる前提だと、座標の起点は -MAP_SIZE/2 にある
        var half = MAP_SIZE / 2;
        return new Vector2((local.x - half) * scale, (local.y - half) * scale);
    }

    private void InitializeEntryPoints(Road02[] roads)
    {
        entryPoints = new List<EntryPoint>();
        foreach(Road02 road in roads)
        {
            var dir = (road.pos2 - road.pos1).normalized;
            var cross = new Vector2(-dir.y, dir.x); // dirと直交するベクトル
            var offset = cross * LANE_WIDTH / 2;
            var step = cross * LANE_WIDTH;
            // 上りレーン
            for(int i = 0; i< road.lanes.x; i++)
            {
                entryPoints.Add(new EntryPoint(ToWorldPos(road.pos1 + offset), dir));
                offset += step;
            }
            cross *= -1;
            dir *= -1;
            offset = cross * LANE_WIDTH / 2;
            step = cross * LANE_WIDTH;
            // 下りレーン
            for (int i = 0; i < road.lanes.y; i++)
            {
                entryPoints.Add(new EntryPoint(ToWorldPos(road.pos2 + offset), dir));
                offset += step;
            }
        }
    }

今回も、道路は直線オンリーなので、一度 EntryPoint に車を置いたら後は道をはみ出る心配もなく直進させるだけです。

CarsController

ややこしいところは CarRepository に委譲したので、比較的すっきりしてます。

CarsContorller02
public class CarsController02 : MonoBehaviour
{
  // ..中略..
    CarRepository<Car02s,Car02> factory;

    void OnDisable()
    {
        // コンピュートバッファは明示的に破棄しないと怒られます
        factory.ReleaseBuffers();
    }

    void Start()
    {
        material = new Material(carShader);
        InitializeComputeBuffer();        
    }

    void Update()
    {
        carComputeShader.SetBuffer(0, "CarsStatic", factory.StaticInfoBuffer);
        carComputeShader.SetBuffer(0, "CarsDynamic", factory.DynamicInfoBuffer);
        carComputeShader.SetFloat("DeltaTime", Time.deltaTime);
        carComputeShader.Dispatch(0, factory.Length / 8 + 1, 1, 1);
    }

    void InitializeComputeBuffer()
    {
        factory = new CarRepository<Car02s,Car02>(MAX_CARS, CarTemplate02.dictionary);
        factory.AssignBuffers();

        RoadPlane02 roadPlane = GetComponent<RoadPlane02>();
        var entries = roadPlane.EntryPoints;

        // 配列に初期値を代入する
        for (int i = 0; i < MAX_CARS; i++)
        {
            var entry = entries[ Random.Range(0,entries.Count) ];
            factory.CreateRandomType(entry.pos, entry.dir);
        }

        factory.ApplyData();
    }

    void OnRenderObject()
    {
        // 車データバッファをマテリアルに設定
        material.SetBuffer("CarsStatic", factory.StaticInfoBuffer);
        material.SetBuffer("CarsDynamic", factory.DynamicInfoBuffer);
        // レンダリングを開始
        material.SetPass(0);    
        // オブジェクトをレンダリング
        Graphics.DrawProcedural(MeshTopology.Points, factory.ActiveCars);
    }
}

シェーダー実装

シェーダーは道路用は前々回のまま、車用もおおむねそのままです。
ただ、structを静的情報と動的情報に分割したので、その対応が入ってます。
詳しくは GitHub 上でご確認ください。

DrivingComputeShader02.compute

Cars02.shader

結果1 (road03.scene)

冒頭にも貼りましたが、こんな感じです。
車はランダムに車種を変えてますが、車種ごとに色と速度が決まっているので整列してるように見えますね。(実際は同じ車種同士、同じ位置に数千台重なっています!)

caronroad03.gif

パーティクル版

次に、車をパーティクルシステムで代用する方法を試してみました。
と言っても凝ったのは面倒なの矢印のテクスチャをQuadで表示するだけにします。
ps_renderer.jpgps_config.jpg

Emissionはコードで制御するので、設定値は0だけどチェックは入れておかないとダメみたい。

C#側実装

CarController

最初はスクリプト(C#)側でパーティクル設定するようにしてみました。

CarsController03.cs
    private void cpu_UpdateParticles()
    {
        var cars = factory.GetCars();
        var emitter = particlesSystem.emission;
        emitter.rateOverTime = cars.Length;
        int numParticlesAlive = particlesSystem.GetParticles(particles, cars.Length);
        int i = 0;
        foreach (var car in cars)
        {
            var par = particles[i];
            var pos = car.Dynamic.pos;
            par.position = new Vector3(pos.x, 0.1f, pos.y) * 0.5f;
            par.startSize3D = car.Static.size;
            par.startColor = car.Static.color;
            var dir = car.Dynamic.direction;
            par.rotation3D = new Vector3(1, 1 - dir.y, dir.x) * 90;
            particles[i] = par;
            i++;
        }
        while(i < numParticlesAlive)
        {
            particles[i++].startLifetime = 0;
        }
        particlesSystem.SetParticles(particles, cars.Length);
    }

結果2 (road04.scene)

caronroad04.gif
これでも動きますが、パフォーマンスが気になったので調べてみました。

パフォーマンス改良

パフォーマンス比較1

一万台の車を動かした状態です。
【ジオメトリシェーダー版】
prof03.jpg
【パーティクルCPU設定版】
prof04.jpg

あからさまですね。毎フレーム10000回ループしてるんだから、当然ですか。
GPUの負荷増加はパーティクルとジオメトリシェーダーの違いだと思います。面倒くさがって透明ありテクスチャを使ってますが、矢印程度ならmeshにしたほうが幾分マシになったかもしれませんね。

なお、ComputeBuffer.GetData() で GPU側で得た結果を取得するだけなら、比較的高速(下記の図で 1.03ms と 0.60ms)なので毎フレームやっても問題はなさそうでした。
prof04-2.jpg
これは静的情報と動的情報に分ける前の状態なので、最終的にはさらにこれの約半分になってます。

CarController改良

ParticleSystem.Particle も struct なので、そのまま ComputeBuffer に使えるんじゃないか?
そうしたらGPU側でパーティクルを設定できて、パフォーマンス大幅向上できるんじゃないか?
と考えました。

実際やってる人がいました!
https://github.com/sugi-cho/Unity-ParticleSystem-GPUUpdate

ただ、Unityバージョンの違いのせいか、この人のcgincでは上手く動かなかったので、即席で実際の構造を出力するコードを作って調べてみました。

structParticle.jpg
こんな感じで出力されました。どうやら最後のm_Flagsが足りなかった模様。
早速これを使って試してみました。

CarsController03.cs
    private void gpu_UpdateParticles()
    {
        var cars = factory.GetCars();
        var emitter = particlesSystem.emission;
        emitter.rateOverTime = cars.Length;
        int numParticlesAlive = cars.Length;

        particlesSystem.GetParticles(particles, cars.Length);
        particleBuffer.GetData(particles, 0, 0, numParticlesAlive);

        particlesSystem.SetParticles(particles, numParticlesAlive);
    }

パーティクル対応シェーダー

DrivingComputeShader03.compute
// 出力先のパーティクルバッファ
RWStructuredBuffer<Particle> Particles;

// Particle構造体へコピー
inline void PrepareParticle(in CarS carS, in CarD carD, inout Particle p)
{
    p.m_Position = half3(carD.pos.x, 0.1, carD.pos.y) * 0.5;
    p.m_StartSize = carS.size;
    p.m_StartColor = Particle_ColorToUint(carS.col);    
    const float radian90 = 3.14159 / 2;
    p.m_Rotation = half3(1, 1 - carD.dir.y, carD.dir.x) * radian90; // 角度単位がC#と違う
    p.m_StartLifetime = 1;
    p.m_Lifetime = 1;
}

[numthreads(8,1,1)]
void CSMain (uint3 id : SV_DispatchThreadID)
{
    CarD carD = CarsDynamic[id.x];
    // それぞれの位置情報に移動ベクトルを加算 (0.28はkm/hをm/sに変換する係数)
    carD.pos += carD.dir * carD.velocity * DeltaTime * 0.28;
    CarsDynamic[id.x] = carD;
    PrepareParticle(CarsStatic[id.x], carD, Particles[id.x]);
}

ところで、シェーダーの関数はCみたいに利用より先に定義記述しないとダメみたい?

パフォーマンス比較2

これも一万台の車を動かした状態です。

【パーティクルGPU設定版】
prof04-b.jpg

見ての通り、かなり効果がありました!
縦軸のスケールが約半分くらいになってるので、一見した以上に高速化されてます。

【パーティクルCPU設定版】
prof04.jpg

流石にジオメトリシェーダー版ほどではないですが、近いレベルになったかと思います。

【ジオメトリシェーダー版】
prof03.jpg

これ実装した後で、パーティクルにもGPUインスタンシング機能があるらしいことを知ったのですが、既に本来の目標からだいぶ横道に逸れているので、これ以上の深入りはしないでおきます。?

気づいたこと・まとめ

(※まだ大目標は完了じゃないですが)

複数のComputeBufferを使ったり、GPUで計算した結果をCPUで使ったり、別のシェーダーに渡したりする方法がわかった。当たり前だが、SetDataした後の配列を保持しておいてもGetDataしない限り、GPU側の変更は反映されない。

Macでは一部機能に制限があることを知った。

ComputeBuffer でやりとりする構造体はC#:HLSL間で矛盾があってもチェックされない。例えば float3 が float4 になってたりすると、一見訳のわからないことが起きたりする。Particleの構造体のメンバー数が間違ってた時もおかしな挙動になりました。

ComputeBuffer は確保した数だけ埋める必要がある。あとでセット数を減らすと、以前のデータがGPU上に残ってる(少なくともUnityの再生・停止ぐらいでは消えない)模様。ちゃんとReleaseするのはもちろん、Bufferサイズを適切に管理する必要がある。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Unityからブラウザを起動

// Unityからブラウザを起動
Application.OpenURL(url);

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

HoloLensアプリ開発手順 備忘録 (ボタン機能(Compound Button、Interaction Receiverなど))

はじめに

すでに沢山の方が開発手順の記事を記載してますが、個人の知識整理として投稿します。
HoloLens用アプリにおけるボタン追加、ボタン押下による処理について記載しています。
※2019/05/22時点の記事になります。

1.シーンにHolographic Buttonを追加する
2.InteractionReceiverを継承したスクリプトをアタッチしたオブジェクトを追加する
3.イベント処理を追加する

環境

Unity:2017.4.27f1 Personal(64bit)
MRTK:HoloToolkit-Unity-2017.4.3.0-Refresh.unitypackage

シーンにHolographic Buttonを追加する

ProjectウインドウのHoloToolkit内のHolographicButtonをHierarchyウインドウに追加する。
※HolographicButtonは HoloToolkit/UX/Prefab/Buttons/の配下にある。

HolographicButtonにはCompound Buttonというスクリプトがアタッチしている。このCompound Buttonがボタンそのものを表すスクリプトで、このスクリプトによってボタンの状態がイベントとして送信される仕組みとのこと。Cubeなどのオブジェクトにこのスクリプトをアタッチすることで、色々なオブジェクトを「ボタン」として扱うことができる。
※Compound Buttonスクリプトは HoloToolkit/UX/Scripts/Buttons/の配下にある。

compound.png

InteractionReceiverを使用してボタン押下イベントを処理する

HolographicButtonから送信されるイベントを処理するには、InteractionReceiverを継承したスクリプトをアタッチしたオブジェクトを作成する。

1.Hierarchyウインドウに空のGameObject(※)を追加する。
2.追加した空のGameObjectを選択し、Inspectorウインドウで「Add Component」で、新しいスクリプトを追加する。
3.追加したスクリプトを編集する。

using UnityEngine;
using HoloToolkit.Unity.Receivers;

public class Receiver : InteractionReceiver {   // InteractionReceiverを継承させる
}

4.InteractionReceiverを追加することで、UnityのInspectorウインドウのスクリプトに「Interactables」という項目が追加される。
Interactables.png
ここにはInteractionReceiverで受け取るイベントの送信元GameObjectを設定する。デフォルトではSize(送信元GameObjectの個数) : 0 となっている。この値を変更することでElement 0 ~ n が追加で表示されるようになる。このElementにHierarchyウインドウから送信元GameObjectをドラッグアンドドロップすることで設定する。

5.イベント発生時の処理を追加する。イベント発生時の処理は以下の様にinteractionReceiverで追加される各種メソッドをオーバーライドすることで実装する。

public class Receiver : InteractionReceiver {

    // メソッドをオーバーライドして処理を追加する
    protected override void InputClicked(GameObject obj, InputEventData eventData)
    {
        base.InputClicked(obj, eventData);

        // どのオブジェクトからのイベントかはobj.nameで判断できる
        switch (obj.name)
         ...
    }
}

今回はボタンのクリックイベントを使用したいので、InputClicked()メソッドをオーバーライドする(他にもFocusEnter()など様々なイベントが用意されている)。どのオブジェクトからのイベントかは引数objのフィールドobj.nameを参照することで判別できる。

使用例

今回はボタンを押下すると、キューブの色と、3Dテキストの文字が変わるサンプルを作成して動作を確認。

scene.png
Receiver.png

※キューブと3Dテキストを操作するためにプロパティとしてCube、DisplayMessageを持たせています。

using UnityEngine;
using HoloToolkit.Unity.Receivers;
using HoloToolkit.Unity.InputModule;

public class Receiver : InteractionReceiver {

    public GameObject Cube;
    private Renderer cubeRenderer;
    public GameObject DisplayMessage;
    private TextMesh dispMsgText;

    private bool flg = false;

    // Use this for initialization
    void Start()
    {
        cubeRenderer = Cube.GetComponent<Renderer>();
        dispMsgText = DisplayMessage.GetComponentInChildren<TextMesh>();
    }

    protected override void InputClicked(GameObject obj, InputClickedEventData eventData)
    {
        base.InputClicked(obj, eventData);

        switch (obj.name)
        {
            case "ButtonColorChange":

                // フラグ(true/false)の値でキューブの色、3Dテキストを赤・緑で切り替える
                if (flg)
                {
                    cubeRenderer.material.color = Color.green;
                    dispMsgText.text = "Green!";

                    flg = false;
                }
                else
                {
                    cubeRenderer.material.color = Color.red;
                    dispMsgText.text = "Red!";
                    flg = true;
                }

                break;

            default:
                break;
        }
    }
}

実行結果
ボタン押下でキューブの色と、3Dテキストが交互に切り替わった。

red.png
green.png

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む