原神(Genshin Impact)をDualSenceでプレイする
概要
原神はDualSenceを無線(Bluetooth)接続した場合に限りDualSenceを使用してプレイできると書かれた記事が見受けられます。 しかし、PC環境によって原神がコントローラーを認識したり、しなかったりといった状況がある様です。 認識されない場合の対処方法にはいくつかの方法がありますが、本記事ではSteamのBig Pictureモードを使用する方法を説明します。
尚、この記事の内容は動作を保証するものではありません。試す場合には自己責任でお願いします。 また、本記事の方法でも動作しない場合、当方では対応しかねます。
(2021-10-06追記:Ver.2.2でDualSenceに正式対応するとのことなので、こちらの方法は不要かもしれません。)
目次
確認環境
- Windows 10 Pro (21H1)
- Steam ビルド Sep 16 2021
- 原神(Genshin Impact) 2.1.0
参考情報
- Dualsense (DS5) for PC Help - Genshin Impact
- 【DualSense】PC版「原神」でPS5コントローラー「デュアルセンス」を使用するには - ゲーマー投資家の独り言
- PC版原神をDualSenseで操作する!(操作できない原因解決済み) | とある旅人の原神ブログ
解説
事前にBluetoothのペアリングは済ませてある前提で話を進めます。ペアリングが済んでいない場合には、他の記事を検索すると説明している記事が見つかると思います。以下の画像の様に、「Wireless Controller」が「ペアリング済み」になっている必要があります。
Steamを管理者として実行する
Steamを起動する際には「管理者として実行」を選んで起動する必要があります。
ライブラリに原神を登録する
ライブラリメニューから、「非Steamゲームを追加」を選択して Genshin Impact を追加します。
「参照」ボタンを押下して、exeファイルを選択するウィンドウを開きます。
C:\Program Files\Genshin Impact\Genshin Impact Game
フォルダにある GenshinImpact.exe
を選択します。
そして、「選択したプログラムを追加」ボタンを押下します。
以上で原神アプリがライブラリに追加されます。
Big Picture モードに切替え、コントローラを設定する
ゲームを起動する際には Big Picture モードから起動します。
まずは、Big Picture モードに切り替えます。
歯車アイコンから設定メニューを開き、コントローラー設定を開きます。
「PlayStation 設定サポート」を有効にします。 ついでに「Big Picture 終了時に電源を切る」を有効にすると、Big Picture を終了した際にコントローラーの電源が切れます。
以上で設定は完了です。
ゲームを起動する
ゲームを起動する際には、Big Picture モードのライブラリメニューから GENSHINIMPACT を選択します。
ゲームが開始されるとコントローラーでプレイできる思います。
(管理者として実行されていれば Big Picture モードでなくてもプレイできるみたいです。)
Pythonのプログラムでピボットテーブルを使う
概要
PythonからExcelのピボットテーブルを使いたいが、難しいプログラムを記述したくない場合の方法です。 ピボットテーブルの設定はExcelで行い、Pythonのプログラムではピボットテーブルの値の範囲のみを変更します。
目次
確認環境
- Python 3.8
- OpenPyXL 3.0.5
参考情報
- 疑似個人情報データ生成サービス
- ダミーデータ生成
- Pivot Tables — openpyxl 3.0.7 documentation
- OpenPyXL公式サイト Pivot Tables のページ
解説
テンプレートとなるExcelファイルを作成する
まずピボットテーブルを含んだExcelファイルを用意します。 今回は疑似個人情報データ生成サービスを使って生成したExcelファイルに対してピボットテーブルを作成します。 ピボットテーブルの作り方は検索して調べてください。
作成したExcelファイルは以下の通りになっているとします。
- ピボットテーブルは pivot シートに含まれている
- データは dummy_data シートに含まれている
- Excelファイルの名前はPivotTemplate.xlsx
Pythonでピボットテーブルの値の範囲を設定する
Pythonのプログラムで以下の2つを合わせます。
- PivotTemplate.xlsxに含まれているピボットテーブル
- 別途作成したデータシート
合わせるためのプログラムは以下のとおりです。
import openpyxl # データシートを作成する new_wb = openpyxl.Workbook() ws = new_wb.create_sheet('data') # ... wsにデータを追加する ... def add_pivot(cache_id, data_sheet, destination_book): template_wb = openpyxl.load_workbook('PivotTemplate.xlsx') pivot_sheet = template_wb['pivot'] # ピボットテーブルが含まれたシート pivot = pivot_sheet._pivots[0] pivot.name = data_sheet.title + '_pivot' # ピボットテーブルに名前を付ける pivot.cacheId = cache_id # IDが重複するとエラーになる pivot.cache.refreshOnLoad = True # 読み込み時にピボットテーブルを更新 source = pivot.cache.cacheSource.worksheetSource print(source) print(data_sheet.dimensions) print(data_sheet.title) source.ref = data_sheet.dimensions source.sheet = data_sheet.title pivot_sheet = destination_book.create_sheet(pivot.name) pivot_sheet.add_pivot(pivot) template_wb.close() add_pivot(1000, ws, new_wb) new_wb.save('PivotSample.xlsx')
まず、はじめにdataという名前のシートをnew_bookに追加して、
このdataシート(ws
)にデータを用意しています。
次に、add_pivot関数でピボットテーブルを含むシートを追加しています。
cache_idはピボットテーブルのIDです。
他のピボットテーブルと重複しない値にします。
ここでは重複しないであろう値として適当に1000にしています。
data_sheetはデータを含むシートです。
ここでは最初の段階で作成したdataシート(ws
)を指定しています。
destination_bookはピボットテーブルを含むシートを追加する先のブックです。
ここではnew_wb
を指定しています。
add_pivot関数で行っている内容を説明します。
まず、テンプレートとなるPivotTemplate.xlsxファイルを開き、ピボットテーブルが含まれるシートからピボットテーブルの情報を取得します。
そして、このピボットテーブルの情報(pivot
)を変更しています。
name
は設定しなくて構いませんが、ここではシート名に基づいて名付けています。
cacheId
は他のピボットテーブルと重複しない値を設定する必要があります。
refreshOnLoad
をTrue
に設定しておくとファイルを開いたときにピボットテーブルが更新されます。
次に、ピボットテーブルが参照するデータの範囲を変更します。
ref
にデータの範囲、sheet
にシート名を設定することで行えます。
data_sheet
のデータ範囲(data_sheet.dimensions
)とシート名(data_sheet.title
)を設定しています。
最後に、新規シートにピボットテーブルを追加します。
以上を行ってファイルを保存すれば、ピボットテーブルを含んだファイルが作成されます。
Unity Android Plugin の手間を軽減する管理方法(改)
概要
Unity Android Plugin の解説記事では JAR, AAR ファイルを手動コピーする例が多く見受けられます。 手動コピーでは依存ライブラリのバージョンを管理しづらくなるため、出来る限り手動コピーは避けたいです。 この記事では Gradle の Local リポジトリを経由して Android Plugin を Unity に公開する方法を説明します。
目次
確認環境
- macOS 10.15.7
- Android
- Android Studio 4.1.2
- minSdkVersion 19
- buildToolsVersion 30.0.3
- Gradle 6.5
- Unity 2019.4.18f1
- Rider 2020.3.2
参考情報
- Unity Androidプラグインの手間を軽減する管理方法. Android ライブラリを… | by Mitsuhiro Koga | Kadinche Engineering | Medium
- Maven Publish プラグインを使用する | Android デベロッパー | Android Developers
- 【成果発表会】「”ゲーム+何か”が求められる時代に挑戦」Unityネイティブプラグインで困った話(Android編) | ⬢ Appirits spirits
- 実行中の Activity を取得する
解説
Android Plugin (AAR) をビルドする
Android Library モジュールを作成し、モジュールの build.gradle を以下の様に記述します。 この例ではモジュール名を sample にしています。
plugins { id 'com.android.library' id 'kotlin-android' id 'maven-publish' } ext { groupId = "com.example" versionName = "1.0" unityVersion = "2019.4.18f1" unityJar = "/Applications/Unity/Hub/Editor/$unityVersion/PlaybackEngines/AndroidPlayer/Variations/mono/Release/Classes/classes.jar" } group = groupId version = versionName android { compileSdkVersion 30 buildToolsVersion "30.0.3" defaultConfig { minSdkVersion 19 targetSdkVersion 30 versionCode 1 versionName versionName testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" consumerProguardFiles "consumer-rules.pro" } buildTypes { release { minifyEnabled false proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' } } compileOptions { sourceCompatibility JavaVersion.VERSION_1_8 targetCompatibility JavaVersion.VERSION_1_8 } kotlinOptions { jvmTarget = '1.8' } } dependencies { compileOnly files(unityJar) implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version" implementation 'androidx.core:core-ktx:1.3.2' implementation 'androidx.appcompat:appcompat:1.2.0' testImplementation 'junit:junit:4.13.2' androidTestImplementation 'androidx.test.ext:junit:1.1.2' androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0' } // see: https://developer.android.com/studio/build/maven-publish-plugin?hl=ja afterEvaluate { publishing { publications { release(MavenPublication) { from components.release } debug(MavenPublication) { from components.debug artifactId = "${project.name}-debug" } } } }
ポイントは以下の2点です。
- Maven Publish Plugin を使用する
- Unity の classes.jar をコピーせずに直接指定する
1つめのポイントとして、
Maven Publish Plugin を使用するために plugins
に id 'maven-publish'
を追加しています。
また、afterEvaluate
内に publishing
の設定を記述しています。
この例の設定では、ビルドバリアント(debug, release)毎に AAR ファイルを生成します。
具体的には、sample-1.0.aar (release) ファイルと sample-debug-1.0.aar (debug) ファイルが生成されます。
2つめのポイントとして、
compileOnly files(unityJar)
によって Unity の classes.jar を参照しています。
Windows の場合は unityJar のパスを適切なパスに変更してください。
Unity のバージョンを変える場合には unityVersion を変更します。
implementation ではなく compileOnly にしている理由は、
Unity から AAR ファイルを参照する際に Unity の classes.jar が重複して参照されることを防ぐためです。
implementation にすると Unity でビルドする際にエラーになります。
ビルド設定は以上です。
ビルドを実行する前に、今回は以下のサンプルプログラムを作成します。
package com.example.unity.sample import com.unity3d.player.UnityPlayer class KotlinObject { fun getActivityName(): String = UnityPlayer.currentActivity.localClassName }
Android Plugin をビルドし、Local リポジトリに保存する
以下のコマンドを Terminal で実行するとビルドが行われ、その結果が Local リポジトリに保存されます。
project % ./gradlew publishToMavenLocal
Gradle のデフォルトでは、Local リポジトリは ~/.m2/repository
になっています。
この repository ディレクトリに以下のファイルといくつかの関連ファイルが作成されます。
- com/example/sample/1.0/sample-1.0.aar
- com/example/sample-debug/1.0/sample-debug-1.0.aar
以上で Android Plugin の作成と公開は終了です。
アプリに Android Plugin への依存を追加する
Unity で Project Settings > Player > Android > Publishing Settings > Build と辿っていくと以下の設定が表示されます。
画像の様に以下の2箇所にチェックを付けます。
- Custom Main Gradle Template
- Custom Base Gradle Template
チェックを付けることで Assets/Plugins/Android ディレクトリに以下の2つのファイルが作成されるはずです。 (作成されない場合は Build を実行してみると作成されると思います。)
- mainTemplate.gradle
- baseProjectTemplate.gradle
この2つのファイルを編集します。
// GENERATED BY UNITY. REMOVE THIS COMMENT TO PREVENT OVERWRITING WHEN EXPORTING AGAIN apply plugin: 'com.android.library' **APPLY_PLUGINS** dependencies { implementation fileTree(dir: 'libs', include: ['*.jar']) implementation 'com.example:sample:1.0' **DEPS**} // ... 以下省略
// ... 以上省略 repositories {**ARTIFACTORYREPOSITORY** google() jcenter() mavenLocal() flatDir { dirs "${project(':unityLibrary').projectDir}/libs" } } } task clean(type: Delete) { delete rootProject.buildDir }
mainTemplate.gradle には implementation 'com.example:sample:1.0'
を追加しています。
これは groupId:artifactId:version
の形式になっており、groupId と version は Android Plugin の build.gradle で指定した値になっています。
artifactId はモジュール名が使われており、今回は sample (release) か sample-debug (debug) です。
baseProjectTemplate.gradle には mavenLocal()
を repositories
に追加しています。
ファイル内に repositories が2箇所あるので注意して下さい。
buildscript 内の repositories はビルドスクリプト(Gradle)が使用するライブラリのリポジトリを指定しています。
buildscript ではない方の repositories は Android アプリをビルドする際に使用するライブラリのリポジトリ指定です。
ここに Local リポジトリを参照する指定 mavenLocal()
を追加します。
アプリをビルドする
今回は以下の Script を作成して動作確認をしました。
using UnityEngine; using UnityEngine.UI; public class AndroidExecutor : MonoBehaviour { [SerializeField] private Text result; void Start() { using (var o = new AndroidJavaObject("com.example.unity.sample.KotlinObject")) { result.text = o.Call<string>("getActivityName"); } } }
Android デバイスを PC に接続するか Android Emulator を起動した上で、 Build Settings で Android Platform に Switch して、 Run Device にデバイスか Emulator を指定します。 File メニューの Build And Run を選択すると APK ファイルのファイル名を訊ねられないので、 Build Settings の Build And Run は使わずに閉じて File メニューから実行しています。
実行するとText に以下の様に表示されます。
Sharing
概要
NRSDK (Nreal Light の Software Developer Kit) の Observer View を理解するために、 サンプルとして提供されている Sharing (Scene) のソースコードを解読してみます。 個人的な読解結果であるため誤りが含まれている可能性があることをご了承ください。
目次
確認環境
- NRSDK 1.2.1
解説
Sharing (Scene) の Hierarchy とそこで使われている Scripts を網羅して確認しました。
Sharing 機能は NetWorkSession (Prefab) を Hierarchy に追加して使用します。 NetWorkSession (Prefab) には SharingManager (Component) が設定されており、SharingManager のプロパティでは Player と ObjectPool を設定します。 Player, ObjectPool のいずれにも NetworkBehaviour (Component) を設定した Prefab を設定します。
ObjectPool には共有データを定義した Prefab を登録します。 共有データを NetObject と呼ぶとすると、他ユーザーから受け取る NetObject と自分から送信する NetObject を登録します。 Player には自分が生成する NetObject を定義した Prefab を登録します。
ObjectPool には複数の Prefab が登録されます。 登録する際には NetworkBehaviour を継承したクラスを作成し、作成したクラスを Component にもつ Prefab を作成します。 ObjectPool は NetworkBehaviour を継承したクラスのクラス名を Key、Prefab を Value とする辞書を保持します。 Key は NetObjectInfo.Key として使われます。
Player には単一の Prefab が登録されます。 ObjectPool に登録した Prefab のいずれか 1 つを登録します。 ObjectPool に複数の Prefab を登録するケースは理解できませんでした。 SharingManager は NetWorkSession のシングルトンインスタンスを使用しているため、SharingManager を複数使用してはいけないのではないかと思います。 そのため、Player に登録される Prefab は 1 つだけです。
NetworkBehaviour を継承したクラスの例として TestNetBehaviour が用意されています。 TestNetBehaviour では、共有データを管理します。 共有データはシリアライズして送受信します。 SynObject を継承させたクラスを独自に定義すれば任意のデータを共有できます。 標準では以下の SynObject を継承したクラスが用意されています。
- SynTransform
- SynInt
- SynVector2
- SynVector3
- SynQuaternion
また、NetworkBehaviour.IsOwner は自身のセッションで作成した NetObject である場合には true になります。 つまり、Player に指定して生成されたインスタンスでは IsOwner が true になります。
Hierarchy
Sharing
- Main Camera
- [C] Camera
- [C] Audio Listener
- Directional Light
- [C] Light
- NetWorkSession (Prefab)
- Main Camera
NetWorkSession (Prefab) (in Scripts/Sharing/Prefabs)
- [C] SharingManager
Player (Prefab) (in Demos/Sharing/Prefabs)
- [C] Mesh Renderer
- [C] TestNetBehaviour
Scripts
SharingManager (in Scripts/Sharing/Scripts)
- MonoBehaviour を継承している
- Inspector の設定項目
- Player : NetworkBehaviour (Demos/Sharing/Prefabs/Player)
- Object Pool : NetWorkObjectPool (Scripts/Sharing/Prefabs/NetWorkObjectPool)
- OnEnable
- Start
- Wraper.Initialize
- ObjectPool.Init
- NetObjectManager.Init
- NetWorkSession.SearchLocalServer
- LocalServerSearcher.Search
- IP 255.255.255.255 port 1989 UDP に RequestForServerIP を送信する
- IP:port を受信する
- LocalServerSearcher.m_LocalServer に IPEndPoint(IP, port) を設定する
- LocalServerSearcher.Search
- NetWorkSession.OnGetServerIP
- EasyClient.ConnectAsync(EndPoint)
- NetWorkSession.OnClientConnected
- MsgGroup.LoginEvent.Request(GUID)
- SharingManager.OnNetConnected
- OnNetConnected
- Player が設定されている場合は CreateNetObjRequest(NetworkBehaviour) を呼び出す
- CreateNetObjRequest
- MsgGroup.CreateNetObjectEvent.Request(NetworkBehaviour.GetType().Name)
- OnCreateNetObjectResp
- NetObjectManager.Create
- NetWorkObjectPool から NetObjectInfo.Key で NetworkBehaviour を検索する
- NetworkBehaviour が見つかったら NetworkBehaviour を Instantiate して、Initialize を呼び出す
- NetObjectInfo.Owner が NetWorkSession.GUID と一致したら、NetworkBehaviour.IsOwner を true にする
- Instantiate で生成された NetworkBehaviour の GameObject の name を NetObjectInfo.Key に設定する
- SynContextRequest
- MsgGroup.SynContextEvent.Request();
- NetObjectManager.Create
- SynContextRequest
- MsgGroup.SynContextEvent.Request()
- OnSynContextResp
- SynDataRequest
- MsgGroup.SynDataEvent.Request(bytes, Data.RequestType.Others)
- OnSynDataResp
- NetMsgType が SynValue なら NetworkBehaviour.DeserializeData
- NetMsgType が Commond なら NetworkBehaviour.ReplyCommond
MsgGroup
- SynContextEvent
- CreateRoomEvent
- CreateNetObjectEvent
- DestroyNetObjectEvent
- GetRoomStateEvent
- JoinRoomEvent
- LeaveRoomEvent
- LoginEvent
- DownLoadCommondListEvent
- UpLoadCommondListEvent
- SynDataEvent
TestNetBehaviour (in Demos/Sharing/Scripts)
- NetworkBehaviour を継承している
- Inspector の設定項目
- SynTransform
- SynInt
- SynVector2
- SynVector3
- SynQuaternion
- Update
- IsOwner が true ならば SynObject を更新する
- Space キー押下時、MsgGroup.SynDataEvent.Request(bytes, Data.RequestType.Everyone))
- bytes は NetMsgType.Commond, "Hello" をシリアライズ
NetworkBehaviour (in Scripts/Sharing/Scripts/RunTime)
- Updater コルーチン
- MsgGroup.SynDataEvent.Request(bytes, Data.RequestType.Everyone))
- bytes は NetMsgType.SynValue, List
をシリアライズ
- bytes は NetMsgType.SynValue, List
- MsgGroup.SynDataEvent.Request(bytes, Data.RequestType.Everyone))
- Updater コルーチン
NetWorkObjectPool (in Scripts/Sharing/Prefabs)
- ScriptableObject を継承している
- Inspector の設定項目
- NetObjects : List
- NetObjects : List
- NetworkBehaviour (のサブクラス) の Type Name をKey、NetworkBehaviour (のサブクラス) を Component に持つ GameObject を Value とした Dictionary を持つ
NRPhotoCapture, NRVideoCapture を読み解いてみる
概要
RGBCamera-Capture と RGBCamera-Record を理解すると NRPhotoCapture クラスと NRVideoCapture クラスが主要クラスだということが分かります。 NRPhotoCapture, NRVideoCapture の両クラスの動作を理解することで RGBCamera への理解が深まると考え、これらのソースコードを解読してみます。 個人的な読解結果であるため誤りが含まれている可能性があることをご了承ください。
目次
確認環境
- NRSDK 1.2.1
解説
拾い読み
例のごとく、NRPhotoCapture を拾い読みすると、以下が要点になるのかなと思います。
- NRPhotoCapture
- CreateAsync
- StartPhotoModeAsync
- FrameCaptureContext.StartCaptureMode(CameraParameters)
- CameraParameters の camMode は PhotoMode に設定される
- CaptureBehaviourBase インスタンスを生成する
- camMode が PhotoMode の場合は NRCaptureBehaviour (Record/Prefabs/NRCaptureBehaviour)
- IEncoder インスタンスを生成する
- camMode が PhotoMode の場合は ImageEncoder
- Config(CameraParameters) を行う
- FrameBlender インスタンスを生成する
- CaptureBehaviourBase.CaptureCamera, IEncoder, CameraParameters で初期化する
- FrameProvider.OnUpdate に CaptureBehaviourBase.OnFrame を登録する
- FrameCaptureContext.StartCapture()
- IEncoder.Start()
- FrameProvider.Play()
- FrameCaptureContext.StartCaptureMode(CameraParameters)
- TakePhotoAsync
- NRCaptureBehaviour.DoAsyn
- CaptureTask インスタンスを生成して、CameraParameters を設定する
- SystemInfo.supportsAsyncGPUReadback が true の場合、ImageEncoder.Commit(CaptureTask)
- 非同期実行で画像データが取得されて CaptureTask.OnReceive が呼ばれる
- false の場合、ImageEncoder.Encode(CaptureTask)
- RenderTexture を使用して Texture2D から画像データが取得されて CaptureTask.OnReceive が呼ばれる
- OnReceive では PhotoCaptureFrame インスタンスが生成される
- NRCaptureBehaviour.DoAsyn
- StopPhotoModeAsync
- FrameCaptureContext.StopCaptureMode()
- FrameCaptureContext.Release() を呼び出す
- FrameCaptureContext.StopCaptureMode()
- Dispose
- FrameCaptureContext.Release()
- FrameProvider.OnUpdate から CaptureBehaviourBase.OnFrame を削除する
- FrameProvider.Release()
- FrameBlender.Dispose()
- IEncoder.Release()
- Destroy(CaptureBehaviourBase)
- FrameCaptureContext を破棄する
- FrameCaptureContext.Release()
- NRCaptureBehaviour
- ImageEncoder の Commit, Encode により画像データを取得する
- OnFrame では
- NRFrame.GetHeadPoseByTime に成功した場合に RGBCameraRig の localPosition, localRotation を更新する
- FrameBlender.OnFrame を呼び出す
- ImageEncoder
- 画像データを取得する
- FrameBlender
- OnFrame で BlendMode (RGBOnly/VirtualOnly/Blend/WidescreenBlend) に応じて Blit を行い画像データを作る
- 作った画像データは ImageEncoder に渡される
- OnFrame で BlendMode (RGBOnly/VirtualOnly/Blend/WidescreenBlend) に応じて Blit を行い画像データを作る
- RGBCameraFrameProvider
- NRRGBCamTexture を使う
- Play, Pause, Stop で NRRGBCamTexture の Play, Pause, Stop を呼び出す
- NRRGBCamTexture.OnUpdate に AbstractFrameProvider.OnUpdate を登録する
- NRRGBCamTexture を使う
- EditorFrameProvider
- AbstractFrameProvider.OnUpdate に毎フレーム RGBTextureFrame を渡す
- RGBTextureFrame.texture に Texture2D (Record/Textures/captureDefault) を設定する
- AbstractFrameProvider.OnUpdate に毎フレーム RGBTextureFrame を渡す
同様に、NRVideoCapture を拾い読みすると、以下が要点になるのかなと思います。
- NRVideoCapture
- CreateAsync
- NRVideoCapture インスタンスを生成する
- 他は NRPhotoCapture と同様
- StartVideoModeAsync
- FrameCaptureContext.StartCaptureMode(CameraParameters)
- CameraParameters の camMode は VideoMode に設定される
- CameraParameters の hologramOpacity は 1 に設定される
- 他は NRPhotoCapture と同様だが、camMode が異なるため以下が異なる
- NRCaptureBehaviour は NRRecordBehaviour (Record/Prefabs/NRRecorderBehaviour) になる
- IEncoder は VideoEncoder になる
- FrameCaptureContext.StartCaptureMode(CameraParameters)
- StartRecordingAsync
- IsRecording が true の場合は UnknownError とする (Unknown なんだ...)
- NRRecordBehaviour.SetOutPutPath を行う
- FrameCaptureContext.StartCapture()
- NRPhotoCapture と同様
- IsRecording を true にする
- StopRecordingAsync
- IsRecording が false の場合は UnknownError とする (Unknown なんだ...)
- FrameCaptureContext.StopCapture()
- IEncoder.Stop()
- FrameProvider.Stop()
- IsRecording を false にする
- StopVideoModeAsync
- NRPhotoCapture と同様
- Dispose
- NRPhotoCapture と同様
- CreateAsync
要点の整理
NRPhotoCapture, NRVideoCapture の基本的な使い方は似ています。以下の 5 つに分類して理解します。
- CreateAsync
- Start[Photo/Video]ModeAsync
- TakePhotoAsync or [Start/Stop]RecordingAsync
- Stop[Photo/Video]ModeAsync
- Dispose
CreateAsync
引数は showHolograms と callback の 2 つです。 showHolograms は使用されていません。 callback は処理が終了するときに呼び出されます。 非同期処理ではありません。 callback が呼び出されてから CreateAsync が終了します。
CreateAsync は NRPhotoCapture, NRVideoCapture インスタンスを生成します。 この時、FrameCaptureContext インスタンスを生成して設定します。 更に、FrameProvider (AbstractFrameProvider のサブクラス) インスタンスを生成して FrameCaptureContext インスタンスに設定します。 つまり、下記の階層構造になります。
- NR[Photo/Video]Capture
- FrameCaptureContext
- FrameProvider
- FrameCaptureContext
FrameProvider は実行環境により異なります。 Unity Editor では EditorFrameProvider、それ以外では RGBCameraFrameProvider が使われます。
Start[Photo/Video]ModeAsync
引数には CameraParameters, callback が渡されます。 Video では AudioState が追加で渡されます。 CameraParameters は諸々の場面で使用されます。 callback は処理が終了するときに呼び出されます。 非同期処理ではありません。 callback が呼び出されてから Start[Photo/Video]ModeAsync が終了します。 AudioState は使用されていません。
Start[Photo/Video]ModeAsync では PhotoMode/VideoMode で FrameCaptureContext を初期化します。 FrameCaptureContext.StartCaptureMode(CameraParameters) を呼び出し、以下に続くいくつかの処理を行います。
CameraParameters の値をいくつか上書きします。 Photo/Video に応じて、CameraParameters.camMode の値を PhotoMode/VideoMode で上書きします。 Video の時のみ、CameraParameters.hologramOpacity を 1 に上書きします。
Behaviour (CaptureBehaviourBase のサブクラス) インスタンス、Encoder (IEncoder を実装) インスタンス、FrameBlender インスタンスを生成します。 Behaviour と Encoder は Photo/Video により生成するインスタンスが異なります。
- Photo
- NRCaptureBehaviour (Record/Prefabs/NRCaptureBehaviour)
- ImageEncoder
- Video
- NRRecordBehaviour (Record/Prefabs/NRRecorderBehaviour)
- VideoEncoder
FrameProvider.OnUpdate に CaptureBehaviourBase.OnFrame を登録します。 FrameProvider.OnUpdate が呼び出されたときに Behaviour の OnFrame が呼び出される様になります。
終了時点では、階層構造は下記に更新されています。
- NR[Photo/Video]Capture
- FrameCaptureContext
- FrameProvider
- Behaviour
- Encoder
- FrameBlender
- FrameCaptureContext
Photo の時のみ、FrameCaptureContext.StartCaptureMode() に続いて FrameCaptureContext.StartCapture() を呼び出します。 FrameCaptureContext.StartCapture の説明は StartRecordingAsync を参照してください。
Stop[Photo/Video]ModeAsync
引数には callback が渡されます。 callback は処理が終了するときに呼び出されます。 非同期処理ではありません。 callback が呼び出されてから Stop[Photo/Video]ModeAsync が終了します。
Stop[Photo/Video]ModeAsync では FrameCaptureContext を無効化します。 FrameCaptureContext.StopCaptureMode() を呼び出すことで行われます。
StopCaptureMode では FrameCaptureContext.Release() を呼び出します。 Release では以下を無効化します。
- FrameProvider
- Behaviour
- Encoder
- FrameBlender
また、FrameProvider.OnUpdate から CaptureBehaviourBase.OnFrame を削除します。
(Photo の時は FrameCaptureContext.StopCapture() を呼び出した方が良いと思うけどやっていない。)
Dispose
引数はありません。他とは違い Async が付かないため明確に同期呼び出しでの使用を想定していると思われます。
Dispose では FrameCaptureContext.Release() を呼び出した後に、FrameCaptureContext インスタンスを破棄します。 Release の処理内容は Stop[Photo/Video]ModeAsync を参照してください。
撮影/録画
StartRecordingAsync
引数には filename と callback が渡されます。 callback は処理が終了するときに呼び出されます。 非同期処理ではありません。 callback が呼び出されてから StartRecordingAsync が終了します。 録画中に呼び出したり、処理中に例外が発生すると callback に UnknownError が渡されます。
StartRecordingAsync では FrameCaptureContext.StartCapture() を呼び出して、録画を開始します。 StartCapture では、Encoder を開始(Start)し、FrameProvider を再生状態に(Play)します。
StartCapture の呼び出し前には NRRecordBehaviour に filename を設定します。 NRRecordBehaviour では設定された filename を VideoEncoder.EncodeConfig に設定します。
StopRecordingAsync
引数には callback が渡されます。 callback は処理が終了するときに呼び出されます。 非同期処理ではありません。 callback が呼び出されてから StopRecordingAsync が終了します。 未録画時に呼び出したり、処理中に例外が発生すると callback に UnknownError が渡されます。
StopRecordingAsync では FrameCaptureContext.StopCapture() を呼び出して、録画を停止します。 StopCapture では、Encoder を停止(Stop)し、FrameProvider を停止状態に(Stop)します。
TakePhotoAsync
引数のパターンが 2 種類あります。 filename と PhotoCaptureFileOutputFormat を指定するパターンと、指定しないパターンです。 いずれの場合も callback が渡されます。 filename を指定するパターンでは callback が使用されません。 filename を指定しないパターンでは画像が取得できたときに callback が呼ばれます。 SystemInfo.supportsAsyncGPUReadback が true の場合は非同期処理、false の場合は同期処理になります。
TakePhotoAsync は引数のパターンにより処理方法が異なります。
filename を指定するパターンでは、NRCaptureBehaviour.Do を呼び出します。 Do では ImageEncoder.Encode(width, height, captureFormat) を呼び出します。 Encode により画像データが取得し、ファイルに書き出します。
filename を指定しないパターンでは、NRCaptureBehaviour.DoAsyn を呼び出します。 DoAsyn では CaptureTask インスタンスを生成し、CaptureTask に応じた画像データ取得の処理を行います。 CaptureTask には OnReceive 関数を設定して、画像データの取得が完了した場合の処理を指定します。 OnReceive 関数は画像データが得られたときに PhotoCaptureFrame インスタンスを生成し、callback を呼び出します。 PhotoCaptureFrame は callback の引数として渡されます。
CaptureTask は SystemInfo.supportsAsyncGPUReadback の値により処理方法が異なります。
supportsAsyncGPUReadback が true の場合、CaptureTask をキューに積んで非同期に処理を行います。 CaptureTask は Update のタイミングで処理されます。 UnityEngine.Rendering.AsyncGPUReadbackRequest を使用して画像データを取得します。 画像データ (Texture2D) が取得されると OnReceive を呼び出します。 呼び出す際に CaptureTask.CaptureFormat に従って JPG/PNG にエンコードしたデータが渡されます。
supportsAsyncGPUReadback が false の場合、ImageEncoder.Encode(width, height, captureFormat) を呼び出します。 width, height, captureFormat は CaptureTask から取得します。 Encode により画像データが取得されたら OnReceive を呼び出します。 呼び出す際に Encode で得たデータが渡されます。
ImageEncoder.Encode は filename を指定するパターンと filename を指定せずに supportsAsyncGPUReadback が false の場合に呼び出されます。 Encode では FrameBlender.OnFrame で生成された RenderTexture から得られた画像データ (Texture2D) を CaptureTask.CaptureFormat に従って JPG/PNG にエンコードします。 FrameBlender.OnFrame については次に説明します。
FrameBlender
FrameBlender.OnFrame では BlendMode (RGBOnly/VirtualOnly/Blend/WidescreenBlend) に応じて Texture を Blit します。 OnFrame は引数に RGBTextureFrame を受け取ります。 Blit した結果の RenderTexture は Encoder に設定(Commit)されます。
FrameBlender.OnFrame は CaptureBehaviourBase.OnFrame から呼ばれます。 CaptureBehaviourBase.OnFrame は FrameProvider.OnUpdate に登録されるため、FrameProvider.OnUpdate が呼び出されたときに FrameBlender.OnFrame が呼ばれることになります。
FrameProvider.OnUpdate は引数に RGBTextureFrame を受け取っており、これが FrameBlender.OnFrame の引数になります。
RGBCameraFrameProvider
Unity Editor 以外で動かした場合に使われる FrameProvider です。 NRRGBCamTexture を使って RGBTextureFrame を取得します。
FrameCaptureContext の操作に応じて以下の様に NRRGBCamTexture の操作が使われます。
FrameCaptureContext | RGBCameraFrameProvider | NRRGBCamTexture |
---|---|---|
StartCapture | Play | Play |
StopCapture | Stop | Pause |
Release | Release | Stop |
NRRGBCamTexture では NRRgbCamera.GetRGBFrame を呼び出して RGBTextureFrame を取得しています。 この RGBTextureFrame が FrameProvider.OnUpdate を呼ぶときに渡されます。
FrameProvider.OnUpdate は NRKernalUpdater.OnUpdate が呼ばれて NRRGBCamTexture.OnUpdate が呼ばれたときに呼ばれます。 OnUpdate, OnFrame の呼び出し関係を整理すると下記の様になります。
NRKernalUpdater.OnUpdate NRRGBCamTexture.OnUpdate RGBCameraFrameProvider.OnUpdate Behaviour.OnFrame FrameBlender.OnFrame Encoder.Commit
EditorFrameProvider
Unity Editor で動かした場合に使われる FrameProvider です。 RGBTextureFrame インスタンスを生成し、インスタンスに Texture2D (Record/Textures/captureDefault) を設定します。
コルーチンで FrameProvider.OnUpdate を呼んでいます。その際に生成した RGBTextureFrame を渡します。 但し、isPlay が true のときのみ呼ばれます。isPlay は FrameCaptureContext の操作に応じて以下の様に設定されます。
FrameCaptureContext | EditorFrameProvider | isPlay |
---|---|---|
StartCapture | Play | true |
StopCapture | Stop | false |
Release | Release | false |
NRFrame.GetTrackables を読み解いてみる
概要
ImageTracking の仕組みを理解するために NRFrame.GetTrackables を読み解いてみます。 個人的な読解結果であるため誤りが含まれている可能性があることをご了承ください。
目次
確認環境
- NRSDK 1.2.1
解説
拾い読み
NRFrame.GetTrackables から続く処理を拾い読みしました。以下が要点かと思います。
- NRFrame.GetTrackables
- NRTrackableManager.GetTrackables
- UpdateTrackables を呼び出す
- m_AllTrackables を m_NewTrackables と m_OldTrackables に振り分ける
- NRTrackableManager.UpdateTrackables
- trackablelist_handle = NativeTrackable.CreateTrackableList()
- NativeTracking.UpdateTrackables(trackablelist_handle, trackable_type)
- trackable_type : TrackableType (BASE/PLANE/IMAGE)
- count = NativeTrackable.GetSize(trackablelist_handle)
- count 回繰り返す
- trackable_handle = NativeTrackable.AcquireItem(trackablelist_handle, index)
- Create(trackable_handle, m_NativeInterface) を呼び出す
- NativeTrackable.DestroyTrackableList
- NRTrackableManager.Create
- NRTrackableManager.GetTrackables
ここまで読んで、TrackingImageDatabase (ScriptableObject) は何に使われているのか疑問に思いました。 TrackingImageDatabase が関連している処理を探して読んでいくと、NRSessionBehaviour から始まる処理で使用されていることがわかりました。 一連の処理では以下が要点になると思います。
- NRSessionBehaviour
- NRSessionManager.CreateSession
- NRDevice.Init
- NativeInterface.NativeTracking.Create
- NativeApi.NRTrackingCreate(ref m_TrackingHandle)
- NativeInterface.TrackingHandle = m_TrackingHandle
- NativeAPI.NativeTracking.SetTrackingMode(TrackingMode)
- DeployData
- database = SessionConfig.TrackingImageDatabase
- ZipUtility.UnzipFile(database.RawData, database.TrackingImageDataOutPutPath, NativeConstants.ZipKey)
- TrackingImageDataOutPutPath は [Application.persistentDataPath]/TrackingImageData/
- NRSessionManager.StartSession
- SetAppSettings(SessionConfig.OptimizedRendering)
- NativeAPI.NativeTracking.Start()
- NativeApi.NRTrackingStart
- NativeAPI.NativeHeadTracking.Create()
- NativeApi.NRHeadTrackingCreate
- NRSessionManager.SetConfiguration
- NativeConfigration.UpdateConfig
- NativeApi.NRConfigCreate(m_NativeInterface.TrackingHandle, ref m_ConfigHandle)
- m_ConfigHandle が 0 の場合のみ行う
- NRSessionConfig.ImageTrackingMode が DISABLE の場合
- NativeTrackableImage.DestroyDataBase(m_DatabaseHandle)
- m_DatabaseHandle が 0 以外の場合のみ行う
- NativeTrackableImage.DestroyDataBase(m_DatabaseHandle)
- NRSessionConfig.ImageTrackingMode が ENABLE の場合
- m_DatabaseHandle = NativeTrackableImage.CreateDataBase()
- NativeTrackableImage.LoadDataBase(m_DatabaseHandle, TrackingImageDataPath)
- TrackingImageDataPath : NRSessionConfig.TrackingImageDatabase.TrackingImageDataPath
- [Application.persistentDataPath]/TrackingImageData/[DB_GUID] ディレクトリ
- TrackingImageDataPath : NRSessionConfig.TrackingImageDatabase.TrackingImageDataPath
- NativeApi.NRConfigSetTrackableImageDatabase(m_NativeInterface.TrackingHandle, m_ConfigHandle, m_DatabaseHandle)
- NativeApi.NRConfigCreate(m_NativeInterface.TrackingHandle, ref m_ConfigHandle)
- NativeConfigration.UpdateConfig
- NRSessionManager.CreateSession
要点を整理する
NRFrame.GetTrackable
NRFrame.GetTrackable の API は以下の様になっており、引数 trackables には検出した結果を格納する List、引数 filter には結果を絞り込む条件を指定します。
void NRFrame.GetTrackable<T>(List<T> trackables, NRTrackableQueryFilter filter) where T : NRTrackable
NRTrackableQueryFilter は All と New の 2 種類です。 NRTrackable のサブクラスは NRTrackableImage と NRTrackablePlane があり、T にはいずれかを指定します。
NRFrame.GetTrackable では、NRTrackableManager.GetTrackables を呼び出しています。引数は NRFrame.GetTrackables と同じです。 NRTrackableManager.GetTrackables では以下を行います。
- NRTrackableManager.UpdateTrackables を呼び出して AllTrackables (List<NRTrackable>) を更新する
- AllTrackables に新しく追加された NRTrackable を NewTrackables (List<NRTrackable>) に格納する
- filter が All であれば AllTrackables の NRTrackable を trackables に格納する
- filter が New であれば NewTrackables の NRTrackable を trackables に格納する
NRTrackableManager.UpdateTrackables では、Native API を呼び出して AllTrackables を更新します。 UpdateTrackables の引数には TrackableType が渡されます。 TrackableType は型パラメータの T から決定されます。 T が NRTrackableImage の場合は TrackableType は TRACKABLE_IMAGE、T が NRTrackablePlane の場合は TrackableType は TRACKABLE_PLANE になります。
NRTrackableManager.UpdateTrackables は以下の疑似コードの様に Native API を呼び出します。
void UpdateTrackables(TrackableType trackable_type) { UInt64 trackable_list_handle; int list_size; NRTrackableListCreate(session_handle, ref trackable_list_handle); NRTrackingUpdateTrackables(session_handle, trackable_type, trackable_list_handle); NRTrackableListGetSize(session_handle, trackable_list_handle, ref list_size); for (int index = 0; index < list_size; index++) { UInt64 trackable_handle; TrackableType out_trackable_type; NRTrackableListAcquireItem(session_handle, trackable_list_handle, index, ref trackable_handle); NRTrackableGetType(session_handle, trackable_handle, ref out_trackable_type); if (out_trackable_type == TRACKABLE_PLANE) AllTrackables.Add(NRTrackablePlane(trackable_handle)); if (out_trackable_type == TRACKABLE_IMAGE) AllTrackables.Add(NRTrackableImage(trackable_handle)); } NRTrackableListDestroy(session_handle, trackable_list_handle); }
上記のコードにおいて、session_handle は NativeInterface.TrackingHandle です。 NativeInterface.TrackingHandle は NRSessionManager.CreateSession の過程で設定されます。 これについては後ほど説明します。
以上で、NRFrame.GetTrackable から始まる一連の処理は終了です。 Native API を使用して ImageTracking を行っていることが分かります。
ImageTracking に関連する初期化処理
ここまで TrackingImageDatabase (ScriptableObject) は全く登場しませんでした。 ImageTracking には不要なのでしょうか? これについては NRSessionManager の Awake, Start の処理を見ていくことで理解が深まります。
NRSessionManager の Awake, Start では NRSessionManager の CreateSession, StartSession, SetConfiguration を呼び出しています。 これらの過程では以下に示す ImageTracking に関連する初期化処理が行われています。
- NativeInterface.TrackingHandle の初期化
TrackingHandle の値は Native API から取得されます。 疑似コードで示すと以下の様になります。
UInt64 tracking_handle; NRTrackingCreate(ref tracking_handle); NativeInterface.TrackingHandle = tracking_handle;
- TrackingImageDatabase データの展開
TrackingImageDatabase データは SessionConfig.TrackingImageDatabase.RawData に byte[] として保持しています。 この RawData は zip アーカイブのデータになっており、unzip して [Application.persistentDataPath]/TrackingImageData/ に展開されます。
- ImageTracking の有効化/無効化
SessionConfig.ImageTrackingMode が ENABLE の場合には ImageTracking を以下の疑似コードの処理で有効化します。 session_handle は NativeInterface.TrackingHandle で、trackable_image_database_directory は SessionConfig.TrackingImageDatabase.TrackingImageDataPath です。 TrackingImageDataPath は [Application.persistentDataPath]/TrackingImageData/[DB_GUID]/ です。
UInt64 config_handle; UInt64 trackable_image_database_handle; NRConfigCreate(session_handle, ref config_handle); NRTrackableImageDatabaseCreate(session_handle, ref trackable_image_database_handle); NRTrackableImageDatabaseLoadDirectory(session_handle, trackable_image_database_handle, trackable_image_database_directory); NRConfigSetTrackableImageDatabase(session_handle, config_handle, trackable_image_database_handle);
SessionConfig.ImageTrackingMode が DISABLE の場合には ImageTracking を以下の疑似コードの処理で無効化します。 session_handle は NativeInterface.TrackingHandle で、trackable_image_database_handle は有効化した時の値、trackable_image_database_handle は 0 です。
UInt64 config_handle; NRConfigCreate(session_handle, ref config_handle); NRTrackableImageDatabaseDestroy(session_handle, trackable_image_database_handle); NRConfigSetTrackableImageDatabase(session_handle, config_handle, trackable_image_database_handle);
以上で、初期化処理は終了です。TrackingImageDatabase のデータは RawData に保持しており、unzip した上で Load しています。
TrackingImageDatabaseInspector を読み解いてみる
概要
TrackingImageDatabase の生成を理解するために TrackingImageDatabaseInspector を読み解いてみます。 個人的な読解結果であるため誤りが含まれている可能性があることをご了承ください。
目次
確認環境
- NRSDK 1.2.1
- macOS 10.15
解説
拾い読み
ざっくりと TrackingImageDatabaseInspector を拾い読みすると、以下が要点になるのかなと思います。
- TrackingImageDatabaseInspector
- NRTrackingImageDatabase のインスペクターを表示する際
- UpdateDatabaseQuality を呼び出す
- BuildImage を呼び出す
- 出力先は [Application.persistentDataPath]/TrackingImageData/[DB_GUID]/markers.json
- TrackingImageDatabasePreprocessBuild の OnPreprocessBuild が実行される際
- BuildDataBase を呼び出す
- BuildImage を呼び出す
- 出力先は [Application.persistentDataPath]/TrackingImageData/[DB_GUID]/markers.json
- m_UpdatedQualityScores (JsonData) を更新する
- UpdateDatabaseQuality を呼び出す
- 呼び出した次に NRTrackingImageDatabase の BuildIfNeeded を呼び出す
- BuildIfNeeded では
- [Application.persistentDataPath]/TrackingImageData/[DB_GUID]/markers.json を読み込み
- [Application.persistentDataPath]/TrackingImageData/[DB_GUID]/markers.dat に書き込み
- [Application.persistentDataPath]/TrackingImageData/[DB_GUID] を [Application.persistentDataPath]/TrackingImageData/[DB_GUID]_zipFile にアーカイブ
- zip のバイナリを NRTrackingImageDatabase の m_RawData に保持する
- BuildIfNeeded では
- BuildImage を呼び出す
- BuildDataBase を呼び出す
- UpdateDatabaseQuality
- m_UpdatedQualityScores (JsonData) の内容を NRTrackingImageDatabaseEntry に設定する
- NRTrackingImageDatabaseEntry の Quality, Width, Height を設定する
- m_UpdatedQualityScores (JsonData) の内容を NRTrackingImageDatabaseEntry に設定する
- BuildImage
trackableImageTools_[os] -image_path="[imagePath]" -save_dir="[outPath]" -width="[imageWidth]"
を ShellHelper.RunCommand で実行する
- NRTrackingImageDatabase のインスペクターを表示する際
要点の整理
拾い読んだ内容を整理すると、インスペクター表示やビルドを行う際に TrackingImageData ディレクトリに追跡対象画像の学習結果を保存していると思われます。 macOS では /Users/[Account]/Library/Application Support/DefaultCompany/[Project]/TrackingImageData/[DB_GUID]/ ディレクトリに保存されます。
TrackingImageData/[DB_GUID] ディレクトリの内容は以下の様になっています。
. ├── Data │ ├── nreal_city.fset │ ├── nreal_city.fset3 │ ├── nreal_city.iset │ ├── nreal_snake.fset │ ├── nreal_snake.fset3 │ ├── nreal_snake.iset │ ├── nreal_trib.fset │ ├── nreal_trib.fset3 │ └── nreal_trib.iset ├── markers.dat └── markers.json
さらに、markers.json と makers.dat の内容は以下の様になっています。
{ "is_use_world_cam" : 0, "list" : [ "nreal_city", "nreal_snake", "nreal_trib" ], "nreal_city" : { "filter" : 50, "info" : "20200422212420_30.72_1.79_2_2", "physical_height" : 396.87496948242188, "physical_width" : 396.87496948242188, "train_score" : 70.502113342285156, "type" : "NFT" }, "nreal_snake" : { "filter" : 50, "info" : "20200422212420_30.72_1.79_2_2", "physical_height" : 396.87496948242188, "physical_width" : 396.87496948242188, "train_score" : 64.339988708496094, "type" : "NFT" }, "nreal_trib" : { "filter" : 50, "info" : "20200422212420_30.72_1.79_2_2", "physical_height" : 396.87496948242188, "physical_width" : 396.87496948242188, "train_score" : 68.515426635742188, "type" : "NFT" }, "pixel_format" : "MONO8", "track_thread_num" : 8 }
# Number of markers 3 ./Data/nreal_city NFT FILTER 50 MARKER_WIDTH 396.874969482422 MARKER_HEIGHT 396.874969482422 ./Data/nreal_snake NFT FILTER 50 MARKER_WIDTH 396.874969482422 MARKER_HEIGHT 396.874969482422 ./Data/nreal_trib NFT FILTER 50 MARKER_WIDTH 396.874969482422 MARKER_HEIGHT 396.874969482422
そして、markers.json を生成するために以下のコマンドを実行しています。
trackableImageTools_[os] -image_path="[imagePath]" -save_dir="[outPath]" -width="[imageWidth]"
- os : osx/win/linux
- imagePath : Texture のパス
- outPath : TrackingImageData/[DB_GUID] ディレクトリのパス
- imageWidth : 画像の幅 [m]
ビルド時には、NRTrackingImageDatabase の BuildIfNeeded にて markers.json を markers.dat に変換し、TrackingImageData/[DB_GUID] ディレクトリ zip ファイル ([DB_GUID]_zipFile) にアーカイブしています。
また、NRTrackingImageDatabase は ScriptableObject であり以下の情報を保持しています。
- RawData : byte[]
- zip ファイルのバイナリデータ
- Images : List
- Name : string
- Width : float
- Height : float
- Quality : string
- TextureGUID : string